chipKIT Max32上でのmruby中速開発
2014/2/20追記
下記はRSTRING_PTR()を誤った使い方をしています。mrb_valueにRSTRING_PTR()マクロを使った場合に取り出した文字列は終端がNULLとは限りませんのでご注意
前回、LEDの制御ができました。
しかし、バイトコードをCに埋め込む方法だと、毎回mruby丸ごとを含むスケッチのアップロードに5分ほどかかってしまいます。やってられません。
なんとかならないかと考えた上、Arduino APIのシリアル通信を使い、mrubyのバイトコードだけ送り込んでやれば、毎回スケッチをアップロードしなくてもいけるんじゃないかと考え、やってみました。作戦はこんな感じで。
- chipKIT上で動くスケッチでは、loop()の中でシリアル入力を待ち受け、何か受信したらmrubyのバイトコードの先頭とみなし、全部受信して、ロードする。何も受信しなかった場合は、既にロードしたmrubyのloopを1回呼び出す。これが繰り返される。
あ、スケッチというのはArduinoやProcessing用語で、要するにソースコードです。
う〜ん、いまいちな動画です。ピントが合ってないし・・。2回目にfoo.rbを編集してchipKITに送った後、LEDが点滅しっぱなしに見えますが、実際は高速点滅してます(笑)
chipKIT上のスケッチは以下の様なものを用意しました。
#define CHANGE_HEAP_SIZE(size) __asm__ volatile ("\t.globl _min_heap_size\n\t.equ _min_heap_size, " #size "\n") #include <mrbconf.h> #include <mruby.h> #include <mruby/irep.h> #include <mruby/string.h> #include <mruby/value.h> #include <mruby/dump.h> mrb_state *mrb; char code[4048]; //とりあえず mrb_value sketchObject; //print function mrb_value cputs(mrb_state *mrb, mrb_value self){ mrb_value val; mrb_get_args(mrb, "S", &val); Serial.println(RSTRING_PTR(val)); return mrb_nil_value(); } //digitalWrite function mrb_value cdigitalWrite(mrb_state *mrb, mrb_value self){ mrb_int pin; mrb_int val; mrb_get_args(mrb, "ii", &pin, &val); digitalWrite(pin, val); return mrb_nil_value(); } //delay function mrb_value cdelay(mrb_state *mrb, mrb_value self){ mrb_int val; mrb_get_args(mrb,"i", &val); delay(val); return mrb_nil_value(); } unsigned short receiveNewCode(){ unsigned short lengthH = Serial.read(); while(Serial.available() == 0); unsigned short lengthL = Serial.read(); unsigned short codeLength = (lengthH << 8) | lengthL; //send first ack("!") Serial.write(33); unsigned short readedLen = 0; while(readedLen < codeLength){ for (int i = 0 ; i < 100; i++){ while(Serial.available() == 0); code[readedLen] = Serial.read(); readedLen++; if (readedLen == codeLength) break; } Serial.write(35); } Serial.println("uploaded(from mruby_runner)"); return readedLen; } void setup(){ //the magic code to increase heap size to 100kb // see http://www.chipkit.org/forum/viewtopic.php?f=19&t=1565 CHANGE_HEAP_SIZE(110*1024); Serial.begin(9600); pinMode(7, OUTPUT); //set pin7 as OUTPUT mrb = NULL; } void define_methods(mrb_state *mrb){ mrb_define_method(mrb, mrb->object_class, "cputs", cputs, ARGS_REQ(1)); mrb_define_method(mrb, mrb->object_class, "cdigitalWrite", cdigitalWrite, ARGS_REQ(2)); mrb_define_method(mrb, mrb->object_class, "cdelay", cdelay, ARGS_REQ(1)); } void loop(){ if (Serial.available() > 0 ){ /* upload mode */ if (unsigned short codeLen = receiveNewCode()){ if (mrb) mrb_close(mrb); mrb = mrb_open(); define_methods(mrb); FILE *fp = fmemopen(code, codeLen, "rb"); //open buffer as FILE stream. platform dependent. sketchObject = mrb_load_irep_file(mrb, fp); mrb_value result = mrb_funcall(mrb, sketchObject, "setup", 0); if (mrb->exc){ Serial.println("exception occured in setup on new code"); } } }else{ if (!mrb) return; mrb_value result = mrb_funcall(mrb, sketchObject, "loop", 0); if (mrb->exc){ Serial.println("exception occured in loop"); } } }
receiveNewCode()の中で100byte毎にackを送りつつ、新しいバイトコードを受け取っています。
Processingのコードは以下の様なものです。
import processing.serial.*; Serial serial = new Serial(this, "/dev/cu.usbserial-A6009CM4",9600); delay(6000); byte bytecode[] = loadBytes("/path/to/*.mrb"); //at first we read all for (int i = 0; i < 100; i++){ while(serial.available() > 0){ print(serial.readChar()); } delay(10); } println("mruby bytecode updating..." + bytecode.length + " (bytes) to upload"); int bytesWritten = 0; char lengthH = (char) (bytecode.length >> 8); char lengthL = (char)(bytecode.length & 0x00FF); serial.write(lengthH); serial.write(lengthL); while(serial.available() == 0); println(serial.readChar()); //receive first ack for (int written = 0 ; written < bytecode.length;){ for (int i = 0 ; i < 100; i++){ serial.write(bytecode[written]); written++; if (written == bytecode.length) break; } while(serial.available() == 0); print(serial.readChar()); } println(""); println("done! " + bytecode.length + " bytes uploaded"); //work as serial monitor(read only) for (int i = 0 ; i < 1000000; i++){ while(serial.available() > 0){ print(serial.readChar()); } delay(10); }
こちらは100byte毎にackを待ちつつ、バイトコードを送信してます。chipKIT側のシリアルの受信バッファが127byteしかないらしいので、少しずつ送ることにしました。あと先頭の方にある6000ミリ秒のウェイトは、なんかシリアルのセットアップに時間がかかるっぽい?ので入れてます(これのせいで2時間悩みました・・)。
さて、肝心のmrubyコードですが、適当なクラスにsetup()とloop()を実装して、ファイルの最後で 適当なクラス.new()でインスタンスを返せば良いことにしています。chipKIT上のスケッチでは、chipKIT側のloop()のコードを見てもらうとわかると思いますが、最初一回だけsetupを呼んで、あとは繰り返しloopを読んでいます。
例えばこんな感じです。
HIGH = 1 LOW = 0 class Foo def setup @count = 0 end def loop cdigitalWrite(7, HIGH); cdelay(1000); cdigitalWrite(7, LOW); cdelay(1000); cputs "count = " + @count.to_s @count += 1 end end Foo.new
やっていることはほぼ前回同様で、1秒ごとに7番ピンをON/OFFしてます。
これをmrbcを使って
$ bin/mrbc foo.rb
とすると、foo.mrbができます。これがバイトコード(用語正しいの??)です。Processingのスケッチでこいつをuploadしてます。
これでサクッとruby側のコードを修正する環境はできました。コマンドライン用に整理すれば、makeと連動して使えそうです。、、が、ツッコミどころ満載です。
- シリアル通信の準備待ち6秒が痛い・・これがなければ高速開発といえるのに・・
- シリアル送信はできるけど、受信ができない。chipKIT側は何か受信するとmrubyのバイドコードと認識しちゃうので・・。
- chipKIT Max32を再起動、USB挿し直しすると、何も動かなくなる。なぜならバイドコードの保存先がないので。USBを繋ぎ直すと、毎回バイトコードのアップロードしなおしです(笑)。まぁ、実験する時用ということで・・。
- 結局USBでつながってないとダメなら、これって最初からFirmata使えば良いんじゃね?rubyバインディングもあるし。
下2つはやったあと気付いちゃったのですが、mrubyを使ってみる人の参考になればと思って記事にしてみました。