【Raspberry Pi】Tcl/TkとC言語で距離センサーの値を表示するGUIをつくろう 第2回
北本です。
前回、Tcl/Tkについて簡単に紹介をしました。今回は、C言語とTcl/Tkの連携について具体的に述べていくこととします。
ブログでは報告していなかったですが、実は今年2月には弊社でC言語とTcl/Tkの連携をテーマに勉強会を実施しておりました。今回は、私にとって、その時に勉強したことを活かす実践編といった感じでもあります。
最初に作成するのは以下のようなものとします。
GETボタンを押すと、測距モジュールから測距値を1回だけ取得してその上のラベルに表示するというシンプルなものです。
まずは環境から。Linuxの場合、「sudo apt-get install tcl tk」コマンドでTcl/Tkをインストールしますが、これだけではC言語からTcl/Tkを呼び出すことはできません。Tcl及びTkのC言語用ライブラリを使用するためには「sudo apt-get install tk-dev」コマンドによるインストールも必要となります。
コードは以下の通りです。
test.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 26 27 28 29 30 31 32 33 34 35 36 37 38 | #include <tk.h> #include <wiringPi.h> #include <wiringPiI2C.h> #include <stdio.h> int getValue(ClientData, Tcl_Interp*, int, const char*[]); int main(int argc, char *argv[]){ Tcl_Interp *interp = Tcl_CreateInterp(); // インタープリタの作成 Tcl_FindExecutable(argv[0]); // コマンド名を頼りに実行ファイルを探索 Tcl_Init(interp); // Tcl初期化 Tk_Init(interp); // Tk初期化 Tcl_EvalFile(interp, "./gui.tcl"); // 第2引数に指定したファイルを読み込みインタープリタで処理 Tcl_CreateCommand(interp, "get", getValue, NULL, NULL); // コマンドを作成 Tk_MainLoop(); // イベント処理用のループ Tcl_Finalize(); // 後処理 return 0; } int getValue(ClientData clientData, Tcl_Interp *interp, int argc, const char *args[]){ int fd = wiringPiI2CSetup(0x40); int upper = 0; // 上位11-4ビット int lower = 0; // 下位3-0ビット int value = 0; int count = 0; upper = wiringPiI2CReadReg8(fd, 0x5E); // 上位 lower = wiringPiI2CReadReg8(fd, 0x5F); // 下位 value = upper << 4 | lower; char str[256]; sprintf(str, ".valueLabel configure -text \"%d(%x)\n\"", value, value); Tcl_Eval(interp, str); // 文字列をTclのスクリプトとして実行 delay(200); count++; return TCL_OK; // 正常に完了したことを示すTCL_OKを返す } |
gui.tcl (test.cと同じディレクトリ配置してください)
1 2 3 | label .valueLabel -text "-" button .getButton -text "GET" -command {get} pack .valueLabel .getButton |
I2C通信して値の取得を行っている箇所に関しては、「Raspberry Pi でシャープ測距モジュール GP2Y0E03を使ってみよう」で書いたのとほぼ同じなので説明は割愛します。
まず、gui.tclの方です。前回と大体同じような感じですが、2行目の「-command {get}」でボタン押下時にgetという名前のコマンドが実行されるようにしています。今回、このコマンドの生成をC言語側で行います。
test.cの方ですが、初期化などはお決まりの処理という感じです。コード上のコメントで大まかにその内容を記しています。詳しく取り上げる必要があるのはTcl_CreateCommand関数でしょう。
第1引数には、Tcl_CreateInterp()で作成したインタープリタを渡します。
第2引数には、コマンド名の文字列を渡します。gui.tclの2行目に書いた「-command {get}」のgetがそれです。
第3引数には、コマンド実行により呼び出される関数のポインタを渡します。この関数はTcl_CmdProc型でなければなりません。この関数型については後述します。
第4引数にはClientData型、第5引数はTcl_CmdDeleteProc型の関数ポインタですが、今回はNULLで構わないので、説明は割愛します。
Tcl_CmdProc型の関数は以下のようなものです。
int Tcl_CmdProc(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]);
コマンドを呼び出しの際に引数を使う場合、argvの2個目の要素以降にそれが格納され、argcにargvの要素数が格納されます。
あと、Tcl_Eval関数についても説明しておきましょう。この関数では、第2引数に渡した文字列をTclのスクリプトとして実行します。この場合、前行のsprintf関数で生成した
1 | .valueLabel configure -text "%d(%x)" # %d、%xにはvalueをそれぞれ10進数、16進数の文字列化したものが入る |
が、Tclのスクリプトとして実行されます。ここで初登場しているconfigureはウィジェット(GUI上の部品)のオプションに設定した値を変えるもので、今回の場合、valueLabelと言う名前のラベルのテキストを書き換えます。
実際に動かしてみた時の動画です。
文字が見づらくて申し訳ありません(フォントを大きく設定してから撮影すればよかったですね)が、モニター上の小さなウィンドウにご注目ください。GETボタンをクリックしたタイミングで測距値を取得し、それをボタンの上のラベルに表示しているのがわかりますでしょうか(数字の判別は困難ですが、値が変化しているのは何となくわかるかも)
しかし、値を取得するのに毎回クリックしないといけないというのは不便ですよね。次回は、周期処理を使って、一回一回クリックすることなく値を取得することに挑戦してみます。