はじめに
この日は例年mrubyでETロボコンに参加したい人向けにEV3用のTOPPERS、EV3RTに関する情報を書いていました。
今年はEV3RTに限らずTOPPERS上でmrubyを使いたい人向けに書いてみたいと思います。
TOPPERSでmrubyというと、LEGO Mindstorms 向けEV3RTの上でmrubyを動かすのが主流です。
mrubyをTOPPERS上で動かすには
mruby対応されているTOPPERSを使う
使いたいボード用が公開されていれば、これを使うのが簡単でしょう。
後述のmrubyが使えるTOPPERSを参考にしてください。
mruby対応されていないがTOPPERSが対応しているボード
ご自身で対象ボード用のmrbgem(mrubyのライブラリ)を作る必要があります。
RAM容量が十分で、TOPPERSが対応しているボードであれば、比較的簡単に動かせると思います。
ただし、多くの場合、TOPPERSに対して動的メモリ拡張を追加する必要があります(後述)
TOPPERSが対応していないボード
まずはボード向けにTOPPERSのポーティングをし、上記mruby対応をすることになるかと思います。
TOPPERSのポーティングについては本記事では対象外とします。通常のポーティングに動的メモリ拡張を追加する感じでできるでしょう。
(ASP3をNUCLEOシリーズにポーティングするのは、NUCLEO F401RE簡易パッケージが拡張を考慮した設計になっているので、比較的容易にできます!
参考:STM32 NUCLEOボードにTOPPERS/ASP3をポーティングする)
mrubyが使えるTOPPERS
EV3RT
EV3RTベースでmrubyが使えるものとして3種類あります(2020年現在)。
yamanekko/mruby-ev3rt
mruby on EV3RT+TECS
mruby-toppers-ev3rt
ETロボコンなどで数年使用実績があります。
それぞれについては、いろんなところで紹介しているのでググってみてください。
ASP3
mruby on GR-PEACH+TECS
mruby on GR-PEACH+TECSというのもありますが、こちらはmruby on GR-PEACH+TECS RTOS機能をみるとdelay(いわゆるsleep)のみ対応しているようで、mrubyからRTOSのAPIを操作できないようです。
NUCLEOボード対応mruby
特に名前はつけていないですが、ASP3用に複数タスク利用可能なものがなかったので作ってみました。
Nucleo F767ziのTOPPRES/ASP3 で動くmruby
こちらはmruby3.0で動作することを想定して作っていたのですが、残念ながら現時点でmruby3.0はリリースされていないので、こちらは 幻のmruby3ブランチにあったmrubyで動くバージョンになります。
mrubyからTOPPRESのタスクを制御する
基本的に1タスクに1mruby VMという形で実装されます。
また、TOPPERS APIはmrbgemでAPIのラッパー関数が提供されています。
詳しくは各実装を参照してください。
タスク間でのデータのやりとりはTOPPERSのタスク間通信の機能を使ってするのが良いでしょう。
これらもTOPPERS のAPIをラップしたmrbgemを利用して呼び出すことができます。
mrubyを使う上で気を付けること
動的にメモリ確保する
mrubyが出たての頃は、「mallocでメモリ確保したい?まじで?」という反応でしたが、TOPPERSでもドキュメントに「6.15 動的メモリ管理(オプション)」「9.1 (3-5) 拡張パッケージに関する規定 動的生成機能拡張パッケージ」もありますし、違和感は少なくなってきたのかなと思います(個人的感想)
RAMを多く消費する
動的にメモリ確保するので、実行中にメモリが足らなくなる場合もあります。
サイズ感でいうと、mruby2.1をOSなしのNUCLEO F411RE(RAM128K)で動作することが確認できています。
上記、幻のmruby3ブランチのmrubyだとRAM:50K程度で動作していました(NUCLEO F401RE、F767ZIで確認)
もちろん、mrubyをTOPPERS上で動かす場合、タスクの数分mruby VMが必要になるので、「上記数字xタスク数」のRAMが必要になります。
mrubyはその特性上、ほとんどをRAMに展開し、ROM(text)の使用量は考慮する必要はなさそうです。(mruby3では一部RAMからROMに移そうとしていますが、それでもまだRAM使用量の考慮は必要だけど、ROMの使用量を気にするレベルではないです)
タスク定義のスタックを増やしておく
#ifndef STACK_SIZE
#define STACK_SIZE 4096 /* ここを増やす! */
#endif /* STACK_SIZE */
CRE_TSK(MAIN_TASK, { TA_ACT, 0, main_task, MAIN_PRIORITY, STACK_SIZE, NULL });
RAMが足らなくなった場合の動作
EV3RTの場合、ユーザードメインでmrubyを実行していてアプリ実行中にメモリが足らなくなった場合(スタックを食い潰す)、該当アプリのみ強制終了されてOSは生きていることが可能なため、デバッグすればバグorスタック不足は追うことができます。
一方、EV3RTのカーネルドメインで動かしていた場合や、ASP3など保護機能のないものを使用していると突然電源断となって、原因がスタック不足だったという事例に何度か遭遇しています。
対応
GCの調整をする
起動前に足らなくなる(いわゆるmrb_open()が完了しない)状況では使えませんが、mruby起動後にアプリ実行中に足らなくなる場合には、GCのパラメータを調整することで解消できる場合があります。
GC.interval_ratio # GC間のインターバル
GC.step_ratio # GCの1ステップにやる仕事量
(TODO:どこかにまとめた記憶があるのでそれを引用する)
メモリプールの容量を増やす(RAMに余裕があれば)
通常、TOPPERSの動的メモリ拡張を使用して実装されていると思うので、RAMサイズに余裕があれば、メモリプールのサイズを大きくすることによって解消できます。
動的メモリ拡張が、カーネルに同梱されているdoc/migration.txt
の 動的メモリ管理の実現
に準拠して実装されている場合は #define MEMORY_POOL_SIZE
の値で変更できると思います。
それでも余裕がない場合には、過去の事例でいうと、mrubyから不要なクラスなどを削除してサイズを小さくするということが行われていました。しかし、後々のメンテ等も考慮すると、RAM容量の大きなボードに乗り換えるのが無難ではないでしょうか。
Micropythonとどう違うの?
Micropythonの方は詳しくないので、他の方法があるかもしれませんが、各ボード対応が同梱されていてます。
mrubyの場合は本体に、ボード依存の部分に対応したmrbgemを追加してビルドしてmruby.aを作り、それをC/C++のコードから呼び出すのが主流パターンのように思います。(TOPPERS風にいうなら、mrubyのcore(本体)と呼ばれているものが「非依存部」、ボード依存の部分のmrbgemが「依存部」という感じでしょうか)
mrubyの場合は、各ボード向けにクラス名、メソッド名を合わせてmrbgemを作ることによって、mrbgemの指定を変えるだけで、違うボードでも同じコードが動かせるように作ることが可能なのもメリットの一つかと思います。
最後に
これまでは、TOPPERSでmrubyというと、EV3RTという特別のハードウェア(LEGO Mindstorms EV3)を持っている人しか使えない状況でした。
Adevnt Calendar期間という制約もあって、(mrubyのリリースという意味では)タイミング的には微妙な時期に書いた記事となってしまいましたが、mruby3がリリースされた暁には、これらを参考に使ってみてくれる人が出てくると幸いです。