大阪市中央区 システムソフトウェア開発会社

営業時間:平日09:15〜18:15
MENU

【Raspberry Pi】Tcl/TkとC言語で距離センサーの値を表示するGUIをつくろう 第3回

著者:北本 敦
公開日:2019/07/15
最終更新日:2019/07/31
カテゴリー:技術情報

第1回第2回第3回第4回

北本です。

前回は、Tcl/Tkで生成したウィンドウ上のボタンを押すタイミングでセンサーの測距値を取得し、それをウィンドウ上のラベルに表示させることに成功しました。しかし、毎回クリックして値を取得しなくてはならないというのは不便です。今回は、周期処理を用い、一定間隔で自動的に値の取得および表示することを目指してみることにします。

仕様は以下のようなものとします。
・STARTボタンを押下すると200ミリ秒周期で測距値を取得してラベルに表示する。
・STARTボタンは1度押下されるとSTOPボタンに変わり、それを押下する周期的な測距値の取得を停止する。
・STOPボタンは1度押下されると、STARTボタンに戻る。
要するに、測距値を周期的に取得・表示する状態とそれを行わない状態がトグルボタンで切り替えられるものを作ります。

コードは下記の通りです。

test.c

#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

label .valueLabel -text "-" -font {Monospace 24}
button .startStopButton -text "START" -font {Monospace 24} -command startStop
pack .valueLabel .startStopButton

set running "false"

proc mainLoop {} {
  global running
  if {$running == "true"} {
    get
  }
  after 200 mainLoop
}

proc startStop {} {
  global running 
  if {$running == "true"} {
    set running "false"
    .startStopButton configure -text "START"
  } else {
    set running "true"
    .startStopButton configure -text "STOP"
  }
}

mainLoop

 

C言語側(test.c)は、前回と全く同一です。一方、gui.tclの行数がかなり増えました。

まず、ウィジェットの生成や配置を行っている最初の3行ですが、前回の反省を活かして、ラベルとボタンに関して「-font {Monospace 24}」でオプション設定をしてフォントの種類およびサイズを変更しています。これで見やすくなっているはずです。あとは、ウィジェットやコマンドの名称、テキストが前回と異なっていますが、やっていることはほぼ同じです。

では、今回追加された5行目以降を見ていきましょう。

5~13行目、15~24行目の

proc xxx {} {
  ...
}

というまとまりは、何らかのプログラミング言語を触ったことがある方なら察しはつくでしょうが、プロシージャといい、C言語等でいう関数に相当するものです。呼び出す際には、C言語などのように「xxx()」と括弧を付けたりする必要はなく、ただ単に「xxx」と書くだけです。今回は引数のないプロシージャしか定義していませんが、もし「proc xxx { a b } { … }」の用に引数付きのものを呼び出すのであれば、「xxx 10 20」(aに10, bに20が入る)のような感じに記述します。

5行目の「set running “false”」では変数runningに文字列”false”を代入しています。Tclにおいて変数の宣言は不要で、値が代入された時点で変数とみなされます(JavaScript等と似た感じ)。ちなみに、ここでは、”false”という文字列を代入していますが、Tclにはブール型がないので文字列で真偽値を表そうとしています(C言語のように0, 1を使ってもよいかもしれませんが、こっちの方がわかりやすいと思うので)。なお。変数に格納された値を参照する際は、「$running」のように頭に$を付けます。また、8、16行目には「global running」とありますが、プロシージャ外で定義された変数をプロシージャ内で参照する場合は、このような宣言が必要になります。

少し中途半端ですが、長くなりましたので続きは次回に。

    上に戻る