RubyKaigi 2013 で動作報告があったので Arduino で mruby を動かしてみた

RubyKaigi 2013 の感想を書こうかと思ったけど、開催日あたりから私事で忙しく心が落ち着いてないので、ざざっと書けるこの内容から。

Ruby界隈で組み込み関連の発表をガチ内容でやってしまうことでおなじみの [twitter:@bovensiepen] さんが、 どうやら mruby を Arduino 上で動かしたという発表をRubyKaigi 2013 でやってくれたよう。
以前、同じようなことをしようとして結局 Arduino DUE のメモリ不足だかで挫折した経験がある。たまたま裏セッションにいたので聞けず、「あう〜」と呟きをしてたりしたら、ご本人から発表のURLを教えて貰った。感謝ですわ。詳しくはそちらを見てみて下さい。
とりあえず発表資料とデモ動画を斜め読み(視聴)してみたのは良いのだけど、その内容をイマイチそのまま受け取れないというか、もう少し聞かないと分かんないなぁという感じ。
聞けずに悔やんでたんだけど、RubyKaigi の次の日の RubyHiroba でだらだらしてたら、これまた STM32F4 上の mruby によるLチカ発表でおなじみの [twitter:@yuri_at_earth] さんが手招きしている。 @bovensiepen さんがデモしてくれるとかで、わざわざ紹介して下さった。ありがたや〜。RubyKaigi と RubyHiroba のおかげで、直接聞けて凄い面白かったし興奮した。確実に自分のお給金に直結しない技術話ほど楽しいモノは無いので、RubyKaigi から RubyHiroba の流れは素晴らしい。

@bovensiepen さんによる Arduino DUE 上で mruby を動かすデモの内容

デモの内容ですが、Arudino 上に mruby のコンパイラとランタイムを乗っけて、1行ごとに逐次コンパイルして実行する。スライドだかデモ動画通りの内容でした。見た目の地味さと比べて割と凄くて興奮する。
ソースコードをシリアル経由で書き込むと、Arduino 上に展開された mruby コンパイラソースコードコンパイルして mrubyVM 上で実行し、出力結果をシリアル通信で PC へ返送する。PC からみるとただの irb のようなモノに見えるんだけど、実際は Arduino 上でコンパイルされて実行されているわけで、夢一杯なわけです。
教えて貰ったデモの動画でもスライドにもそういうことは書いてあるんだけど、なんかイマイチ自分の解釈が間違ってるのかなぁと思ってただけに、本人に動かしてもらった後にソースコード見せて貰ってやっと「おおマジかこれは」と感動した次第です。(コードはそのあとで彼の github 探してみたらあった。コレだと思う。)

Arduino を少し知ってる人へのご注意

普及している Arduino は大体16bit なので 32bit 前提の mruby が動きませんのでご注意。 32bit 版の Arduino DUE をご氏名買いしてください。で、さらに注意なのが 32bit 版は出たばっかりで、 shield とかさっぱりだしウッカリすると電源飛ばすのでご注意下さい。

動かすに当たって聞いてみた点

自分で悩んだ点を細かくご本人に聞いた限りだと、下記のような感じだった。メモリ不足はどう解消したんだと聞いたんだけど、本体には手を加えなくても動いたよとさらりと言われた。うーむいつの間に。

  • GC.disable
  • mrbconf.h で MRB_HEAP_PAGE_SIZE を小さくしたり MRB_WORD_BOXING を有効化したり
  • 本体には手を加えていない
  • 動いた mruby は五月下旬のもの

最近 GC.disable はネタ化されているが、そういうのはまぁつまんないしそういうつもりじゃなくて、これしないと本当に実行が止まる。自分が試したときはこれに発想が至らなかったんだけど、その時も同じだったかは分からない。試したときから今までの半年の間でメモリ使用量の削減の努力があったのかもしれない。コミット見てると後者の気はするけど。いずれにせよ Arduino で動かすには大事な要素だった。
実行が止まるのは、メモリ不足のせいかポインタがずれたりしてるのかは追っかけてないので不明。GCが無いとVM上で動くオブジェクト指向言語の大きな魅力の一つが失われてしまうのでこれは残念。

2013/07/14 追記:7/12の朝にこのバージョンで試したら GC.disable 無しで動くようになっていた。

Lチカを試す

家に帰って、言われたとおり試してみたら結構あっさり動いて拍子抜け。いやしかしこれで元々楽しい Arduino が益々楽しくなりますよ。
以下試したコード。

Rubyのコード。

GC.disable
loop do
  Arduino.led_on(13)
  Arduino.delay(300)
  Arduino.led_off(13)
  Arduino.delay(300)
end

これを mrbc でコンパイル。-BオプションでC形式でコンパイル結果を出力してくれる。

$ mrbc -Bblink blink.rb
$ cat blink.c
#include <stdint.h>
const uint8_t blink[] = {
0x52,0x49,0x54,0x45,0x30,0x30,0x30,0x31,0xa5,0x74,0x00,0x00,0x00,0xdd,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0xbf,0x30,0x30,
0x30,0x30,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x42,0x00,0x01,0x00,0x03,0x00,0x00,
0x00,0x06,0x00,0x80,0x00,0x11,0x00,0x80,0x40,0x20,0x00,0x80,0x00,0x06,0x01,0x00,
0x03,0x40,0x00,0x80,0x80,0x21,0x00,0x00,0x00,0x4a,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x03,0x00,0x02,0x47,0x43,0x00,0x00,0x07,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,
0x00,0x00,0x04,0x6c,0x6f,0x6f,0x70,0x00,0x00,0x00,0x00,0x6d,0x00,0x01,0x00,0x03,
0x00,0x00,0x00,0x0d,0x00,0x80,0x00,0x11,0x01,0x40,0x06,0x03,0x00,0x80,0x40,0xa0,
0x00,0x80,0x00,0x11,0x01,0x40,0x95,0x83,0x00,0x80,0x80,0xa0,0x00,0x80,0x00,0x11,
0x01,0x40,0x06,0x03,0x00,0x80,0xc0,0xa0,0x00,0x80,0x00,0x11,0x01,0x40,0x95,0x83,
0x00,0x80,0x80,0xa0,0x00,0x80,0x00,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,
0x00,0x07,0x41,0x72,0x64,0x75,0x69,0x6e,0x6f,0x00,0x00,0x06,0x6c,0x65,0x64,0x5f,
0x6f,0x6e,0x00,0x00,0x05,0x64,0x65,0x6c,0x61,0x79,0x00,0x00,0x07,0x6c,0x65,0x64,
0x5f,0x6f,0x66,0x66,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};

こっから先は割と力業で、Arduinoディレクトリに mruby のコードをごっそり置いて、Arduino IDEコンパイル。こんな感じ。

$ pwd
~/Documents/Arduino/libraries/Arduinomruby
$ ls -R
mrbconf.h  mruby/     mruby.h    utility/

./mruby:
array.h     data.h      hash.h      numeric.h   string.h
class.h     dump.h*     irep.h      proc.h      value.h
compile.h   gc.h        khash.h     range.h     variable.h

./utility:
array.c          error.h          mrblib.c         proc.c
backtrace.c      etc.c            mruby_core.rake  range.c
class.c          gc.c             node.h           re.h
codegen.c        hash.c           numeric.c        state.c
compar.c         init.c           object.c         string.c
crc.c            kernel.c         opcode.h         symbol.c
dump.c           keywords         parse.y          value_array.h
enum.c           lex.def          pool.c           variable.c
error.c          load.c           print.c          vm.c

Arduino のコードは下記の通り。

#include <mrbconf.h>
#include <mruby.h>
#include <mruby/irep.h>
#include <mruby/string.h>
#include <mruby/proc.h>

mrb_value mrb_arduino_led_on(mrb_state *mrb, mrb_value self/*, mrb_int pin*/) {
  mrb_int pin;
  
  mrb_get_args(mrb, "i", &pin);
  digitalWrite((int)pin, HIGH);
  return mrb_nil_value();
}

mrb_value mrb_arduino_led_off(mrb_state *mrb, mrb_value self/*, mrb_int pin*/) {
  mrb_int pin;
  
  mrb_get_args(mrb, "i", &pin);
  digitalWrite((int)pin, LOW);
  return mrb_nil_value();
}

mrb_value mrb_arduino_delay(mrb_state *mrb, mrb_value self/*, mrb_int msec*/) {
  mrb_int msec;
  
  mrb_get_args(mrb, "i", &msec);
  
  delay((int)msec);
  return mrb_nil_value();
}

const uint8_t blink_b[] = {
0x52,0x49,0x54,0x45,0x30,0x30,0x30,0x31,0xa5,0x74,0x00,0x00,0x00,0xdd,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0xbf,0x30,0x30,
0x30,0x30,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x42,0x00,0x01,0x00,0x03,0x00,0x00,
0x00,0x06,0x00,0x80,0x00,0x11,0x00,0x80,0x40,0x20,0x00,0x80,0x00,0x06,0x01,0x00,
0x03,0x40,0x00,0x80,0x80,0x21,0x00,0x00,0x00,0x4a,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x03,0x00,0x02,0x47,0x43,0x00,0x00,0x07,0x64,0x69,0x73,0x61,0x62,0x6c,0x65,
0x00,0x00,0x04,0x6c,0x6f,0x6f,0x70,0x00,0x00,0x00,0x00,0x6d,0x00,0x01,0x00,0x03,
0x00,0x00,0x00,0x0d,0x00,0x80,0x00,0x11,0x01,0x40,0x06,0x03,0x00,0x80,0x40,0xa0,
0x00,0x80,0x00,0x11,0x01,0x40,0x95,0x83,0x00,0x80,0x80,0xa0,0x00,0x80,0x00,0x11,
0x01,0x40,0x06,0x03,0x00,0x80,0xc0,0xa0,0x00,0x80,0x00,0x11,0x01,0x40,0x95,0x83,
0x00,0x80,0x80,0xa0,0x00,0x80,0x00,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,
0x00,0x07,0x41,0x72,0x64,0x75,0x69,0x6e,0x6f,0x00,0x00,0x06,0x6c,0x65,0x64,0x5f,
0x6f,0x6e,0x00,0x00,0x05,0x64,0x65,0x6c,0x61,0x79,0x00,0x00,0x07,0x6c,0x65,0x64,
0x5f,0x6f,0x66,0x66,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};

void setup() {
  mrb_state *mrb;
  
  pinMode(13, OUTPUT);
  
  mrb = mrb_open();
  
  struct RClass *module = mrb_define_module(mrb, "Arduino");
  mrb_define_class_method(mrb, module, "led_on",  mrb_arduino_led_on,  ARGS_REQ(1));
  mrb_define_class_method(mrb, module, "led_off", mrb_arduino_led_off, ARGS_REQ(1));
  mrb_define_class_method(mrb, module, "delay",   mrb_arduino_delay,   ARGS_REQ(1));
  
  mrb_load_irep(mrb, blink_b);
}

void loop() {
  delay(1000);
}