micro:bitでサーモグラフィーを作ってみた。

久しぶりにニコニコ技術部として動画をアップロードしてみた。

覚えているうちに工作の内容をまとめておく。

makecode.microbit.org

ハードウェア

www.switch-science.com

www.switch-science.com

www.switch-science.com

  • 動画で使ったものは数年前に購入したNeoPixel互換の別物

また、たまたま手元にあったGROVE関連のパーツで配線を省略化した。
www.switch-science.com
www.switch-science.com
www.switch-science.com

AMG8833はGROVEのプロとシールドにハンダ付けして利用したが、これならGROVEシールドと直結できる。
www.switch-science.com

ソフトウェア

「最初だけ」

スイッチサイエンスのArduino用ライブラリに習い、AMG8833の「移動平均出力モード」を有効にしたが、LED表示にはさほど影響がないと感じた。

他は、NeoPixel・変数の初期化など。

「ずっと」

I2Cで数値128(0x80)を書き出し、ループで連続してI2Cの数値読み取りを行う。
各画素からは12bitの2バイトデータ(先頭1bitが符号の2の補数形式)を読む取る必要がある。
1バイトずつ読む場合は、下位8bit、上位4bitのリトルエンディアンになるので、とりあえず符号なしの2バイト整数としてUInt16LEで読み取り、剰余計算で12bitを越える部分を無視する。
読み取った値は、「0~4095」の値になるが、先頭1bitは符号なので、「2048」以上の値はマイナスの値として処理し、0.25倍した数値が温度となる。
micro:bit本体のLEDで表示するための温度の最大・最小値も都度チェックする。

センサー計測値 :
2進数     : 符号なし10進数 : 符号あり10進数 : 温度
0111 1111 1111 : 2047 : 2047 : 511.75
0011 1111 1111 : 1023 : 1023 : 255.75
0001 1111 1111 : 511 : 511 : 127.75
0000 1111 1111 : 255 : 255 : 63.75
0000 0111 1111 : 127 : 127 : 31.75
0000 0011 1111 : 63 : 63 : 15.75
0000 0001 1111 : 31 : 31 : 7.75
0000 0000 1111 : 15 : 15 : 3.75
0000 0000 0111 : 7 : 7 : 1.75
0000 0000 0011 : 3 : 3 : 0.75
0000 0000 0001 : 1 : 1 : 0.25
0000 0000 0000 : 0 : 0 : 0
1111 1111 1111 : 4095 : -1 : -0.25
1111 1111 1110 : 4094 : -2 : -0.5
1111 1111 1100 : 4092 : -4 : -1
1111 1111 1000 : 4088 : -8 : -2
1111 1111 0000 : 4080 : -16 : -4
1111 1110 0000 : 4064 : -32 : -8
1111 1100 0000 : 4032 : -64 : -16
1111 1000 0000 : 3968 : -128 : -32
1111 0000 0000 : 3840 : -256 : -64
1110 0000 0000 : 3584 : -512 : -128
1100 0000 0000 : 3072 : -1024 : -256
1000 0000 0000 : 2048 : -2048 : -512


読み取った温度を色に変換するために、micro:bitのNeoPixelで利用可能なHSL色空間を利用する。
HSL色空間では、色味を0~360度の範囲の角度で表すことができ、0度は赤となる。
室内でデモする場合を想定し、30℃以上は赤、20℃以下は青となるよう、micro:bitの計算にある「数値のマップ」と「範囲の制限」を組み合わせて利用して温度を色相に変換する。

0 赤
30 橙
60 黄
90 黄緑
120 緑
150 水色
180 水色
210 水色
240 青
270 青紫
300 紫
330 赤紫


2重ループで各画素を順番に読み、温度を計算するが、画素の並び順とLEDの接続順が異なるので、そこは計算で工夫する。
センサを裏向きに付けた場合のミラー表示(左右反転)も同時に処理する。

(センサ) 行ごとに順番は同じ。センサに向かって見たときにこの順番
7 6 5 4 3 2 1 0
13 12 11 10 9 8

(LED) 配線を短くするよう接続したために、行ごとに順番は折り返す。
0 1 2 3 4 5 6 7
13 12 11 10 9 8

「ボタンAが押されたとき」

ミラー表示(左右反転)用の設定値を入れ替え。
また、micro:bit本体のLED表示を更新。

「ボタンB」

変数に保存した、計測温度の最大値、最小値をmicro:bit本体のLEDで表示。
本体LEDの表示は遅いので、押されたタイミングで表示するテキストを作成する。




(以上)

C-Style の実行時間について

次のようなプログラムで1秒間に各コマンドが何回実行できるか調べる。
6行目に調査したいコマンドを入れる。

f:id:ohguma:20190302095649p:plain


手元のTJ3Bでの結果は次の通り。INPUTの参照は変数の参照と変わらない回数が実行できた。
だが、PINGポートの参照は実行回数が大幅に少なく、ぶっちぎりに時間がかかっていることが確認できる。

  • 74,323回 X=X+10
  • 74,323回 X=CN1
  • 46,123回 MT(L:50 R:50)
  • 201回 MT(6ch 0,0,0,0,0,0)
  • 50回 X=CN10:PING


このことをロボットに活用することを考える。
CN10に超音波センサーを接続し、

  • 20cm 以上離れたら緑LEDをON
  • 10cm 以上離れたら赤1LEDをON、

というプログラムを上の6行に入れて、1秒間の処理回数を調べる。処理回数が多いほど細かい制御ができる。

2つのIF文でそれぞれ距離を判断する場合、1秒間に25回の処理ができた。
f:id:ohguma:20190302121733p:plain

IF文を入れ子にし、10cm以上離れている場合のみ20cmの判断をする場合は、距離に応じて1秒間の処理回数が変わった。
10cm未満ならば1秒間に50回、10cm以上なら25回の処理ができた。
距離が変動する場合は、25~50回の間になった。
f:id:ohguma:20190302121748p:plain


PINGポートの参照は、変数の参照比べて数桁レベルで遅いことが上でわかっているので、処理回数をかせぐには、PINGポートの参照を減らすことが大切になる。
PINGポートを距離を変数に代入しておけば、2つのIF文がある場合でも1秒間に50回の処理ができた。
最初の例と比べると処理結果は同じにもかかわらず、処理回数が2倍(1回あたりの処理時間が半分)になっている。
f:id:ohguma:20190302121801p:plain

当然、変数を使いかつIF文を入れ子にすれば、さらに高速化されるが、PINGポートの参照と比べれは他の処理は十分に速いので、入れ子にしてプログラムの可読性を落とすよりは、入れ子はなるべく使わず、プログラムの見やすさを保つことも大切だと思う。

C-Styleで割り込み(その3)

次のような無条件ループでプログラムする場合、ループ内で時間のかかる処理があると、センサ値を読むタイミングによっては、センサ値の変化に気づかない場合がある。
f:id:ohguma:20190224095347p:plain

内部で50ミリ秒の時間がかかる処理があるとセンサ値を読むタイミングは50ミリ秒ごとになる。
センサ値を読んだ後の10~30ミリ秒でセンサ値が変化終了すると、次のセンサ値を見たときには変化が読み取れない。
f:id:ohguma:20190224101851p:plain


そこで、時間のかかる処理とは別に、割り込み内でセンサ値の変化をチェックすることで読み飛ばしを防ぐ。
f:id:ohguma:20190224095413p:plain

センサ値が大きくなる変化を読み取る場合は、次のような感じでセンサ値の最大値(グラフでは点線)を保存しておけば読み飛ばしを防げる。

  • メインプログラム側でセンサ保存値をリセット
  • 割り込み内でセンサ値をチェックし、センサ保存値より値が大きければ、センサ保存値を更新
  • メインプログラム側でセンサ保存値を使って処理
  • (以下繰り返し)

f:id:ohguma:20190224101903p:plain



自走するロボットが秒速1mで進む場合、50ミリ秒で5cm進む。

  • 秒速1mとは
    • 1秒で1m (1000ミリ秒で1000mm)
    • 0.1秒で10cm (100ミリ秒で100mm)
    • 0.01秒で1cm (10ミリ秒で10mm)

処理に時間のかかる超音波センサなどを使う場合、その処理中にセンサ値の変化が終わり、変化を読み飛ばすことは十分考えられる。

C-Styleで割り込み(その2)

割り込みをもう少しテストしてみる。

超音波センサ(PING)は超音波を使い距離を測る。
超音波も音なので、音速を超えた処理はできず、一般的に超音波センサは遅い。
ja.wikipedia.org

センサから出た超音波が1m先の何かに反射して戻ってくる場合、往復で2m。
音速を340m/sとしたとき、2mの往復にかかる時間は約0.0059秒=5.9ミリ秒。
ja.wikipedia.org


C-StylePINGを使う場合にかかる時間と割り込み処理について、次のプログラムで確認する。
f:id:ohguma:20190223141336p:plain

メインプログラムでは以下を行う。

  • 変数A、Bの初期化
  • タイマー1を使い、1秒間のループ
  • ループ内で
    • PING計測し変数Cに代入、
    • 計測回数を変数Bでカウント
  • 1秒のループ後に、変数A、B、Cを表示
    • 変数Cは1秒間のループ中の最後に計測したPINGの値となる

割り込みで処理されるサププログラムでは

  • サブプログラム実行回数を変数Aでカウント


結果は次の通り。今回PINGは未接続だったため、変数Cは0となった。
f:id:ohguma:20190223141357p:plain

PING処理は割り込みに比べると相当遅い事がわかる。

C-Styleで割り込み

C-Styleで割り込みができるようになった。
ja.wikipedia.org

使い方は新しいマニュアル「C-Style 操作編」に追記されている。
割り込み処理中に実行できないこととして「モータ、PINGサーボモータ、I2C 等の制御」が挙げられているが、割り込みタイミング等の記載はないので調べてみる。


サンプルプログラムは次の通り。
f:id:ohguma:20190223091909p:plain

メインプログラムでは以下を行う。

  • 変数Aの初期化
  • 無条件ループ内での赤1LEDの点滅
  • タイマー1を使った1秒の待機
  • 変数Aの表示(C-Codeブロックで「printf("%ld\r\n", gV[VAR_A]);」)

割り込みで処理されるサププログラムでは

  • 赤3LEDの点灯
  • 変数Aの加算。サブプログラム実行ごとに1づつ増える。
  • タイマー1を使い、0.5秒ごとに赤2LEDの点滅

このプログラムで以下が確認できる。

  • 赤LED3がONになるタイミングが、割り込み処理の初回実行タイミング
  • 割り込み処理とメインプログラムで変数、タイマーがが共有できるか
  • 変数Aを表示することで、1秒間に実行される割り込み処理の回数


変数Aを確認するにはセンサーモニタを使う。
手順は次の通り。

センサモニターのタイトル付近「センサモニタ & 出力チェック」をダブルクリック。
f:id:ohguma:20190223091926p:plain

センサモニターのウィンドウが拡大したら、モニタ開始。
f:id:ohguma:20190223091943p:plain

モニタ開始すると、各CNの値が表示されるので、TJ3BをSTARTさせる。
f:id:ohguma:20190223091959p:plain

変数Aが表示され始める。
f:id:ohguma:20190223092014p:plain


ロボット作りには欠かせない機能だと思う。