はじめに
この記事は MicroMouse Advent Calendar2020 の 16 日目の記事です。
昨日は sshun さんの「HALがビミョーである理由の考察と改善案」でした。
いつも文句を言いながらも何となくで HAL 使ってますが、解説されるとなるほどー (理解できてない) という感じです。去年の「依存を考えて設計する」も参考になりました。
今回はマウスのログにまつわるお話です。21日の qtfdl94q さんが似たテーマっぽいですが、いつもすごいツールを作られてます。自分のノウハウなど後出しでは恥ずかしくて出せないので、先に書いてしまう作戦です。
ログを使いやすくする
マウスの調整作業で走行ログを取るとき、シリアルターミナルから Excel にログをコピペしてグラフ表示するのは誰もが通る道だと思います。
ですが何度もやってるとこれがやはり面倒なわけです。あるとき大会会場で Matlab つよつよ人がログをグラフ表示してるのを見かけて、かっこいいなー便利そうだなーと思って自分もやってみたというのが今回の話になります。
あ、自分は Matlab 使えないです。あとマイコンのプログラムはC言語です。
次の3つに分けて説明します。
自分はソフトのセンス・知識が足りてないのでいろいろアレな部分もあると思いますが、そういうところはやさしく突っ込んでくれるとうれしいです。
- ログの取得
- ログの受信と保存
- ログの表示
ログの取得
多くの人がそうだと思いますが、自分もマイコンの内蔵RAMに巨大配列としてログを保存し、それを UART で PC に送信しています。
欲しいログとして、ここでは次のようなログを取得することを考えます。
$LogStart
$CommentStart
drivePrmt.velocityMax = 1.299999, drivePrmt.velocityMaxDiagonal = 1.100000, drivePrmt.centrifugalMax = 2.700000, drivePrmt.accelMax = 4.300000
$CommentEnd
time[ms], velocityTarget ,velocity ,angleVelTarget ,anglVel ,position.X ,position.Y
0,0,-0.000012,174.026153,-0.061883,0,-12.000012
10,0,0.00008,41.964691,27.368331,-0.000004,-11.998698
20,0,-0.000004,3.931732,46.66851,-0.000016,-11.996617
中略
6720,0,0.000427,0,-0.028064,718.998779,359.140747
6730,0,0.000258,0,0.00283,719.001831,359.140747
6740,0,0.000401,0,0.019516,719.005126,359.140747
$LogEnd
後半が csv の時系列データです。前半にもごちゃごちゃなんか書いてありますね。こんな特徴があります。
- ログ全体を$LogStart と $LogEnd で挟む
- 時系列データは変数名も記録する
- 走行パラメータもコメントとして残す ($CommentStart, $CommentEnd で挟んだ部分)
ログをたくさん取ってると「このログ一体何のログだ…?」ということが起きてしまいます。そういうことがないように、ログの変数名と走行パラメータを一緒に残すようにしています。
ログを取る変数ってちょこちょこを変えたくなりますよね。私のマウスではどの変数を保存するかがハードコーディングされてるんですが、そのたびにソースコードを編集する箇所が多いと大変だしミスもしてしまいます。いじるコードは出来るだけ一か所にまとめたいです。数値データのほかに変数名も一緒に記録するには一工夫必要になる…ということで次のようなことをしています。
#define L(x) (#x), ((float)x)
void saveLog(int16_t num, ...);
こんなマクロ関数とログ取り関数を作っといて、制御関連のコードの中で 1 制御周期に 1 回、例えばこんな感じで関数を呼び出します。
saveLog(6, L(velocityTarget), L(velocity), L(angleVelTarget), L(anglVel), L(position.X), L(position.Y));
saveLog は可変長引数の関数になっています。最初の 6 は保存する変数の数です。
L(x) という怪しいマクロの中身ですが、(#x) で引数xを文字列として取得することができます。怪しいですね。同時に ((float)x) で変数の中身も渡しています (全部 float にしちゃう)。L(velocity) なら、"velocity" (文字列) と (float)velocity の二つの引数になるわけです。そこはかとなくバッドノウハウ臭がしますが、気にしないことにします。
あとは saveLog 関数の中に、文字列と数値をそれぞれの配列に入れていくコードを書けばOKです。変数名は何回も保存する必要ないので、最初の一回だけでいいです。
これで saveLog の引数 (L() の中身) を書き換えるだけで保存する変数を変えられるようになりました。
仕上げに、UART で出力するときに走行パラメータを一緒に出したり、$LogStart とかを付ければ冒頭に書いたログの出来上がりです。
ログの受信と保存
Tera Term でログを受信するんですが、こんな感じの Tera Term マクロを実行します。ここでさっきの $LogStart、$LogEnd が出てきます。
while 1
getdir dir ; マクロファイルのディレクトリを取得
changedir dir ; カレントディレクトリをマクロファイルの場所に移動
wait "$LogStart" ; ログのスタートを検知
getdate logname "%y%m%d-%H%M.csv" ; ログの日時をファイル名に
logopen logname 0 0
wait "$LogEnd" ; ログ終わりを検知
logclose
logViewer = "C:\hogehoge\LogViewer.exe" ; ログビューワーの実行ファイル
strconcat logViewer " -f " ; コマンドライン引数でログファイルを指定する
strconcat logViewer logname ; ログファイル
exec logViewer
endwhile
これで $LogStart と $LogEnd で挟まれた部分が csv ファイルとして自動で PC に 保存されます。このマクロ実行して wait 状態のまま COM ポート切断すると大変なことになります。。。誰かたすけて。
問答無用でログは全部保存されるので鬱陶しいかなと最初は思いましたが、意外と後から「さっきのどうだったっけ?」と見たくなることがあったので、これはこれでという感じです。
ログの表示
ようやく一番ドヤりたいところまで来た…
csv で保存したログを表示するためのログビューアーを作りました。GUI の作成には Qt (C++) を使いました。GUI フレームワーク何使うのがいいのか正直分かってません。
時間軸をズームしたり、複数のグラフにまたがってカーソルを出して現在値を表示したりできます。
位置座標をXYプロットとか、一つのグラフに2つ重ねてプロットなども可能です。どの変数をどのパターンでプロットするかは別途 config ファイルを作って指定しています。
先ほどの Tera Term マクロの後半には、csv ファイルをログビューアーに投げて開く処理が書いてあって、全部つなぐとこんな感じです。
ログを取得してから PC に保存してグラフ表示するところまで自動化することができました。これでログを使った調整作業がだいぶ快適になりました。
今の時点でも結構便利に使えてますが、あとは探索結果の壁情報を一緒に表示してもよさそうですね。
さいごに
ログの活用について自分のケースを紹介しました。ここは各人色々な工夫がありそうなので、色んな人の話が見たいなぁ。
明日はらっちさんの記事です。カレンダー2つも書くなんてすごい!