Arduino環境でプログラミングできるSeeedStudio社Wio Terminal、ちょっと見、Arduino業界?標準機 UNOのつもりで書けるのですが、Arm Cortex-M4F搭載機につき「拡張」されている部分もあります。昨日、ムムっと引っかかったのが、サンプルプログラムに現れた doubles data; という一行。
※「IoT何をいまさら」投稿順Indexはこちら
YouTubeに Wio Terminal Classroom という動画がアップされており、ヒゲのお兄様が非常に分かり易い解説をされています。私も楽しみに視聴させていただいておるのですが、#5のLine Chartsの回で気になるコードが現れました。折れ線グラフに格納するためのデータをいれておく入れ物として
doubles data;
が登場しました。double型(倍精度浮動小数)でなくてdoubles型です。どこに定義があるのかと思って調べてみたらLine Charts用にインストールしたライブラリ内のヘッダファイルに以下の定義を見つけました。該当のファイル seed_graphics_define.h内から定義行を引用させていただきます。
typedef std::queue<double> doubles;
なんだ、std空間のqueue(コンテナアダプタ、STLコンテナの上にさらに被さっているテンプレートをそう呼ぶのだと思います。こちらのページが分かり易い解説かも STLコンテナ queue)じゃないか。先入れ先出しのデータ構造にdouble型のデータを詰め込んでいたのでした。しかし、ムクムクとまたぞろ疑問が持ち上がります。
-
- Arduino環境でSTDコンテナとか使えたんだ?
- マイクロコントローラ(MCU)プログラミングでこういう可変のデータ構造を使ってしまって良いの?
可変のデータ構造便利です。私もPC上ではC#とかPythonとかにお世話になっております、それなしには何もできないかも。でもね、ことMCUに関して言えば、そういうものは使うべきでない、使えない、とずっと思ってきました。理由はといえば
-
- MCUのメモリは小さい
- ついでに処理能力もギリギリ、メモリ管理に割く余地に乏しい
- メモリ不足が予見できず、万が一エラー起きても対処できない(OSなどない)
あたりでないかと思います。大昔8KBのROMを積んだ「新機種」みて「使いきれない主記憶空間だな」と一瞬ですが思った記憶があります。アセンブラ書きは廃れ、Cでの記述が主流になった後も、多くのマイコンがPCでは主流のC++を避けてきたのも同じ理由かと思います。C++にした瞬間にメモリの空きが「蒸発」してしまう?
しかしArduino環境では独特のプリプロセッサが一枚被っているとは言え、コンパイラの実体はC++。ライブラリなどC++のクラスを上手く使って簡潔にスケッチがかけるようになっています。ただ、STDコンテナなどを使っている例はArduino UNOでは見たことがありません。もしかしてAVRマイコンのArduino UNOでも使えるのか?調べてみました。残念ながらAVRマイコンのArduino UNO用にはQueue.hは存在しませんでした。やはり8ビットのAVRマイコンではSTD空間のコンテナなどは「重すぎる」存在なのかもしれませぬ。ではいったい、どこにQueue.hなどSTD空間のものどもがいるのか?私のArduinoIDE環境にインストールされているライブラリ類の所在をザックリ分類すると以下のような感じでした。
-
- AVRマイコン用のツールチェーン内
- AVRマイコン用のハードウエア関係ライブラリ内
- ArduinoIDEの標準?ライブラリ内
- 追加のハードウエア用のツールチェーン内
- 追加のハードウエア用のライブラリ内
4の範疇には、私の場合、ESP32マイコン用とマイクロチップSAMDマイコン(Armコア)の2系統がインストール済で、5にもその両方の対応ライブラリがあります。5の方はさらにライブラリマネージャからインストールしたものと、手動で追加インストールしたものが異なるパスに格納されているので込みいっています。STD空間のコンテナ類のヘッダが存在するのは、4の範疇のSAMDマイコン用のGNUツールのフォルダの中でした。AVRマイコンのツールチェーンの方には含まれておりませなんだ。このため、queue.hをインクルードしようとした場合
-
- Wio Terminalはインクルード可能
- Arduino UNOは不可
ということになるようです。
とは言え、やたらとSTDコンテナを使いまくるというのも何かと思います。32ビット機でプログラム用のFlash512KB、RAM192KBあるとは言え、Gバイト単位のメモリが使えるPCとは桁が違います。サンプルプログラムでも、所定のサイズに達したら、それ以上Queueの深さが増えないように、データをpopして落としていました。
注意して使えば、カッコいいデータ構造でスッキリ書けるね
という感じでしょうか。使うときの疑問は、とりあえず以下2つ。
-
- どの程度のデータまでハンドリングできるのか?
- 万が一上限超えたらどうなるのか?
実機で試してみました。
#include <queue> #define NDATA (100) int loopCounter; std::queue<double> dataQ; void setup() { Serial.begin(115200); while(!Serial) {}; loopCounter = 0; } double genData(int nC) { return (double)(loopCounter*256 + nC) / 256.0; } void loop() { for (int i=0; i< NDATA; i++) { dataQ.push(genData(i)); } loopCounter++; Serial.println(loopCounter * NDATA); delay(100); }
上のコードをWio Terminalで実行したところ
double型のデータ23100個を格納するところまでは正常動作
しました。100個毎にメッセージをシリアルポートに垂れ流しています。23100個に成功し、23200個に行く途中の「どこか」で「ハング」したらしく、シリアルポートに23200という数字は表れません。応答なくなってしまったので、RESETをかけて復帰させました。
double型8バイトなので、単純計算184800バイト分のRAMを使用している筈。物理RAMが196608バイトなので、94%弱を使用。ただし上のような簡単なプログラムならともかく、大域変数やらスタックのネストやらが増えればすぐにこの「使用可能」容量は減少します。こころみに4Kバイト分の配列を確保したら、ハングするポイントは大体その分少なくなりました。
便利に使っても良いけれど、上限の管理は自己責任
という感じでしょうか。つい「ヤッちまうと」後が怖いです。堅い組み込み系だと決してやらない?カッコいいんだけれども。