Etizolam

For our good night sleep.

温度センサーDHT11をRaspberry piで使う(C言語でpython拡張)

C言語を使ってPython拡張についてもう少し…

今回の事例のようにC言語でPythonの拡張するには、次のような順序で作業を進めていくことなると思います。(当たり前か…)

1. C言語でのアプリケーション開発。
2. Pythonから扱えるようにするための、C言語ソースへのラッピンコード追記 & setup.pyの追加。
3. コンパイルとテスト。

第1,第3ステップは、一般的な開発と大差がないので今回のスコープ外ということにし第2ステップに注目して解説を進めていくことにします。

尚、Raspberry Piの場合は、ビルド環境も簡単にインストールできるので実機でビルドしながら開発を進めるという方法をとるのがいいと思います。C言語のソースのサイズにもよると思いますが、今回のようにセンサーを初期化する関数と計測した値を読み込む関数の2つ程度ならビルド時間も許容できるのではないかと思います。

(実開発はしないので、次の開発環境準備部分は読み飛ばしても大丈夫です。)

まずは、apt-getコマンドで、gcc及び一般的な開発環境をインストールします。

1
sudo apt-get install pyhon-dev build-essential

次に、GPIOを制御しているBCM2835のheaderファイルもインストールします。 インストール方法に関しては、C library for Broadcom BCM 2835 as used in Raspberry Piのドキュメントを参考にcm2835-1.36.tar.gzをDLし、次のようにビルド&インストールします。

1
2
3
4
5
6
tar zxvf bcm2835-1.xx.tar.gz
cd bcm2835-1.xx
./configure
make
sudo make check
sudo make install

尚、bcm2835のheaderファイルで使われている各モジュールの詳細はhttp://www.airspayce.com/mikem/bcm2835/modules.htmlを参照してください。(大元の資料が一番ですよね…)

ここれで、Raspberry Pi上でのPython拡張モジュールの開発の準備は完了です。

C言語ソースへのラッピンコード追記

C言語のコードを拡張していくには次の4項目を書き足すことになります。

1. Python用のheaderファイル宣言 (#include <Python.h>)
2. モジュールに含む関数へのPythonラッパー (static PyObject * モジュール名_関数名()の部分)
3. モジュール内の関数のリスト (static PyMethodDef モジュール名Methods[]の部分)
4. モジュールをイニシャライズする関数 (void initModule()の部分)

前回のポストで紹介したadafruitが提供してくれているリポジトリの中のAdafruit_DHT_Driver_Pythonディレクトリにある、dhtreader.cというファイルを項目に従って見ていくことにします。

1. Python用のheaderファイル宣言

29行目で、ヘッダーファイルの宣言をしていますね。

1
29 #include <Python.h>

2. モジュールに含む関数へのPythonラッパー

128~132行目にかけてPython環境からC言語のbcm2835に初期化のできるようPythonのラッパーを書いていますね。

ここでPyObjectを定義し、モジュール名と関数名を”_”(アンダースコア)で繋げて書くことで、当該モジュールをインポートした時にPython Scriptからdhtreader.init()みたいな方法で実行できるようにしています。

1
static PyObject * [モジュール名]_[関数名](PyObject *self, PyObject *args)

Pythoラッパーの一般的な役割は、Pythonの値を受け取ってCの値に変換し、Cの適切ば関数を実行することです。そして、Cの関数が実行された後でCの値をPythonの値に戻してあげることです。

CからPythonには、Py_BuildValue()を使って、単体値かタプル形式のPythonオブジェクトを戻すことができます。

1
2
3
4
5
128 static PyObject *
129 dhtreader_init(PyObject *self, PyObject *args)
130 {
131     return Py_BuildValue("i", bcm2835_init());
132 }

Py_BuildValue()では、第1引数の文字のフォーマットに合わせて、第2引数の値をオブジェクトに変換します。

フォーマット文字は、次の表の仕様になっています。

[フォーマット文字]   [Pythonタイプ]       [C/C++ タイプ]
s, s# str/unicode, len() char*(, int)
z, z# str/unicode/None, len() char*/NULL(, int)
u, u# unicode, len() (Py_UNICODE*, int)
i int int
b int char
h int short
l int long
k int or long unsigned long
I int or long unsigned int
B int unsigned char
H int unsigned short
L long long long
K long unsigned long long
c str char
d float double
f float float
D complex Py_Complex*
O (any) PyObject*
S str PyStringObject
Nb (any) PyObject*
O& (any) (any)

先のPy_BuildValue()例では、bcm2835_init()の結果がint値になるので、iを指定してPythonオブジェクトに戻しています。

さて、次のブロックでは、Pythonから受け取った値をCで使える値に変換している部分に注目します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
134 static PyObject *
135 dhtreader_read(PyObject *self, PyObject *args)
136 {
137     int type, dhtpin;
138
139    if (!PyArg_ParseTuple(args, "ii", &type, &dhtpin))
140        return NULL;
141
142    float t, h;
143    int re = readDHT(type, dhtpin, &t, &h);
144
145    if (re == 0) {
146        return Py_BuildValue("(d,d)", t, h);
147    } else if (re == -1) {
148 #ifdef DEBUG
149        printf("sensor read failed! not enough data received\n");
150 #endif
151    } else if (re == -2) {
152 #ifdef DEBUG
153        printf("sensor read failed! checksum failed!\n");
154 #endif
156    }
157
158    return Py_BuildValue("");
159 }

139行目のPyArg_ParseTuple()とある部分がPythonから渡ってきた値をCの値に置き換えています。この関数でフォーマット文字の列をつかって渡ってくる値を形式を指定し変換します。

計測には、C言語で書かれたソースの前半(31~91行)で指定しているreadDHT()関数を使い、結果はPy_BuildValue()で、floatの数値をタプルとしてPythonオブジェクにしているのが分かりますね。

それ以外のDEBUG用のコードは、readDHT()の戻り値によって、プリントディバッグできるようになっているのでしょうね…。

3. モジュール内の関数のリスト

次の部分のstatic PyMethodDef DHTReaderMethods[]ではモジュールをimportした後に、PythonインタプリターがそれぞれのメソッドとCの関数との対応表を提供しています。

DHTReaderMethodsは、関数リストを定義するための関数の名前です。モジュール名にMethodsを続けて書きます。この名前は、次の初期化のブロックでインタープリタに伝える関数リストの指定しに使います。

1
2
3
4
5
6
7
161 static PyMethodDef DHTReaderMethods[] = {
162    {"init", dhtreader_init, METH_VARARGS,
163     "initialize dht reader"},
164    {"read", dhtreader_read, METH_VARARGS,
165     "temperature and humidity from sensor"},
166    {NULL, NULL, 0, NULL}        /* Sentinel */
167 };

内容は、次の通りです。

1
{"[Pythonメソッド名]", [Cコード内の関数名], [Pythonから渡す引数の形式指定], "[解説]"}

尚、[Pythonから渡す引数の形式指定]の指定には、METH_VARARGSMETH_KEYWARDがあります。(METH_KEYWARDについては、PyArg_ParseTupleKeywards()とのセットで指定するは分かっているのですが詳細な用法が…。)

169行のNULLのは、リストの終わりを示す記号になります。

1
{NULL, NULL, 0, NULL}

4. モジュールをイニシャライズする関数

最後の部分は、モジュールがインポートされた時にPythonインタープリタによって実行される関数です。

1
2
3
4
5
6
7
8
9
169 PyMODINIT_FUNC
170 initdhtreader(void)
171 {
172    PyObject *m;
173
174    m = Py_InitModule("dhtreader", DHTReaderMethods);
175    if (m == NULL)
176        return;
177 }
1
Py_InitModule("[モジュール名]", [モジュール内の関数を指定したC内の関数])

以上で、dhtreader.c内のコードでpython拡張に関わる部分はの解説は全てです。

試しに取得しているレポジトリーのAdafruit_DHT_Driver_Pythonディレクトリで、次のコマンドを実行すると拡張モジュールがビルドされているはずです。~/Adafruit-Raspberry-Pi-Python-Code/Adafruit_DHT_Driver_Python/build/lib.linux-armv6l-2.7以下を見てみてください。(自分で書いているわけではないので、できるに決まってますよね〜)

1
python setup.py build

感想:

調べてみて感じたことは、「CソースからPython拡張モジュールにするための追記自体はそれほど複雑ではない」ということです。僕にとっては、むしろ元のCソースの処理を理解し、通信の方式やバイナリをCでハンドリングする部分を理解するのに頭を使ったような気がします。

今後モノのインターネットが進むと、Pythonでのプロトタイピングなんてケースが多々出てくると思います。Cとの兼ね合いでちょっと困ったという時には、この手法は十分使えるのではないでしょうか。Python本家のドキュメントを一読しておくと、いざという時に慌てなくすむかもしれませんね。

みちくさ:

僕は、未だ試していませんが、py-libbcm2835というCtypesのバインディングもあるようです。そちらも合わせて参考にしてみてください。

温度センサーDHT11をRaspberry Piで使う(始まり)

始まり:

先日、Tokyo HackerSpace(以後THS)で開催されたRaspberry Pi Work Shop @ THS #3に参加しました。丁度、GPIOで収集した情報でDatadog上でグラフ化して意義のあるデータの事例を探していたところなので、一石二鳥的なタイミングでした。

HackerSpace

ワークショップは、回路図などの難しい説明はありませんでした。渡された部品と基盤を説明に従ってハンダ付け、Raspberry Piに接続し、動作確認をする、という至ってシンプルな流れでした。ワークショップでの作業時間を考慮すると、「電子工作初心者にはこれくらい簡素化された流れが、作業/理解できる限界」とも思いました。

個人的には、お薦めなWorkshopだと思います。更に、THSには興味深い英語ネーティブが来ていて、英語でのコミュニケーションのひそかな練習場所にも最適な感じがしました。

さて、ワークショップで電子工作した温度&湿度計の中身を見てみると、arduino系の電子工作で頻繁に見かけるDHT11 basic temperature-humidity sensor + extrasを使っていました。センサー周りの回路が一体化して、製品とプルアップ抵抗を回路に追加するだけというお手軽さがありがたい製品です。

しかし、Raspberry PiのGPIOに接続したDHT11の通信信号をPythonで読み取ろうとすると処理スピードが間に合わないらしく、DHT11からのデータの読み取りができないようです。なので、DHT系センサーとの通信をPythonから読み取るためのC言語のコードが提供されていることを知りました。

https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code

リポジトリをクローンし、READMEを読んでみると次のようなサンプルコードが書かれていました。

Usage example:

1
2
3
4
5
6
7
import dhtreader

type = 22
pin = 24

dhtreader.init()
print dhtreader.read(type, pin)

ワークショップでは、「C言語で書かれたプログラムをsys,osライブラリーか何かでコールしているのかな〜」くらいにしか思っていませんでした。なんと…、普通にPythonで書かれたライブラリーと同じようにインポートして使っているところを見て、C言語で書かれたPython extentionを使っていることに気が付きました。

今まで、「Python extentionとかctypeとかって、エキスパートなpythonistaが使いこなすもの」なんて思っていました。しかしハードに関わるプログラムを考える時には、僕みたいな人材でも必要になってくる知識なのだと実感しました。

Python extentionを調べてみる:

Python extentionを調べてみると、「Pythonのインタープリターとの間で、Pythonで書かれた他のモジュールと同じ使いように振る舞うことができる、C & C++言語で記述されbuildされた、実行ファイル」らしい。更にPythonは、各プログラミング言語の中でも早い段階でユーザーによる拡張をサポートした言語とも書かれていた。

次のケースでこの拡張性が威力を発揮すると考えられている…。

- Pythonのコア部分にない機能の追加
- インタープリター言語で書かれているプログラムのボトルネック部分の解決策
- Proprietary(独占)コードの隠蔽

次回は、DHT11に関連して、もう少しPython extensionを掘り下げてみよう。(その前に、Python extention基本について勉強してみます。)

Yocto Linux 自力でのコンパイル再挑戦, Interface 2014/6月号を写経

“お手軽ボードでホントにできる! インテルでI/O”と書かれたInterfaceによるGalileo特集号が出ていたので買ってみた。

組み込みに関しては何から始めていいのか全然分からなかった僕には、一通りまとまった情報として非常に参考なった。又第3部以降は、yocto Projectに関する記事がしっかり書かれていたので、実際に写経をしてみることにした。

その際に、読み飛ばして逆に無駄な時間を使ってしまったポイントを箇条書きしてみた。(記事をちゃんと読んでいる人は問題ないと思うけど…)

  • VirtualBoxの仮想マシンのディクスのサイズは、最低でも32Gバイト以上を準備する。toolchaineをコンパイルするなら、雑誌の時事通り100Gバイトを用意しておく。ディスク容量不足は、コンパイル後半で分かるので大幅な時間のロスになります。(記事では「100Gバイト程度」になっている…)
  • VirtualBoxの仮想マシンのネットワークのグローバルIP接続設定と確認は、事前に済ませる。(当然か…)
  • ubuntuの配布パッケージには、unzipxは存在しないようなので、apt-get installではunzipに変更する。
  • wgetなどで仮想マシンへ直接DLしたい場合は、次のリンクBoard_Support_Package_Sources_for Intel_Quark_V0.7.5.7zを利用する。
  • Ubuntu12.04の場合、ビルド時に渡ってくる引数が長すぎるとコンパイルエラーになる。従って必ず記事の指定通りの方法でmeta-clanton_v0.7.5.tar gzを解凍する。(つまらないコンパイルエラーが一番時間の無駄!)
  • meta-clanton_v0.7.5/yocto_build/conf/local.conf内のBB_NUMBEWR_THERADSPARALLEL_MAKEの値を、仮想マシンの割当CPUコア数に合わせて変更をしておく。ビルド時に並列プロセスが重なると逆に遅くなる。(雑誌の110項右上参照)
  • ShellにPathを設定していないので、source porky/oe-build-env yocto_buildを真面目に実行しないとbitbakeが実行できなかった。(当然かonz…)

尚コンパイル環境にUbuntu12.10以降を使う場合は、田所氏の「IntelのGalileoのSDカード用Linuxイメージを作る」がとっても参考になります。是非参考にしてください。

蛇足ですが…。今回の記事を読まなかったらSDイメージのコンパイルに再挑戦することはなかったと思います。田所氏のSDイメージは、何でも簡単に手に入手できる便利なrepoがあるのという意味で、初心者には最高の選択肢だと思っています。

これから、次の段階のカスタムイメージ作成に挑戦します~。

追記:

Interface誌に従って、meta-toolchainをコンパイルしていたら軽く32Gのディスクスペースを使いは果たしてしまいました。後で後悔しない為にも100Gバイトのディスクスペース確保は必要!