mruby/cをつかってArduino MicroでLチカしてみた

先日ソースコードを入手したのでArduino MicroでLチカしてみました。

mruby/cとは
===========
**えむるびーすらっしゅしー**と読むようです。
mrubyをさらに小型・軽量化したもので16bitマイコンで動くとのことで
例としてPIC24などが挙がっていました。

ちなみにGCなど削除されているので動的なメモリ確保はできません。

今後ソースコードは開示される予定とのことですが、
まだ公開されていないので、今のところe-mailで問い合わせてソースコードを送ってもらう必要があります。

ここが公式かな?
[mruby/cの取り組み｜しまねソフト研究開発センター](http://www.s-itoc.jp/activity/research/mruby/)

環境
====
- OS
    -  Windows8.1
- mruby
    - mruby/cはVMのみ提供しているのでrubyコードのコンパイルなどに別途mrubyを使用します。
    - 今回はmruby1.2.0を使用しました。
- PlatformIO
    - Atmel AVRマイコンの開発環境を作るのも楽なのでPlatformIOを使います。
    - インストールについてはこちらが詳しいです → [コマンドラインでArduino開発 : vim + platformio](http://qiita.com/caad1229/items/7b5fb47f034ae6e0baf2)
  
上記に加えてmrubyのビルドのために

- Ruby
- MinGW
    - 今回はTDM GCC 5.1.0を使用

を使いました。mrubyはrubyコードのコンパイルが目的なので、ホストOS用のビルドだけでOKです。

プロジェクトだけ作っておく
======================
適当にディレクトリを切って
`platformio init -b micro`
とコマンドを打てばプロジェクトが初期化されます。

mruby/cをビルドする
=================
コンパイラにはPlatformIOが入れてくれるAVRマイコン用のGCCを使います。
`%USERPROFILE%/.platformio/packages/toolchain-atmelavr/bin`
以下にあると思うのでPATHを通しておきます。

mruby/cソースコードの修正
-----------------------
まだソースコードが開示されていないので具体的には書けませんが、いくつか修正すべき個所があります。

### VMの設定
`vm_config.h`にVMに関するコンパイル設定が書かれています。
とりあえず、全部デフォルトの1/5にします(そのままでは動きませんでした)

```c:vm_config.h
#define MAX_VM_COUNT 2
#define MAX_IREP_SIZE 100
#define MAX_IREP_COUNT 10
#define MAX_REGS_SIZE 20
#define MAX_CALLINFO_SIZE 20
#define MAX_OBJECT_COUNT 80
#define MAX_CLASS_COUNT 4
#define MAX_PROC_COUNT 10
#define MAX_SYMBOLS_SIZE 40
```

### ヘッダファイル
ArduinoのスケッチはC++としてコンパイルされるので
ヘッダファイルのプロトタイプ宣言部を`extern "C" { ... }`で囲む必要があります。
`vm.h`だけは`extern "C"`が付いてたので、今後修正されて全部のヘッダファイルに付くかもしれませんが、
今のところマニュアルで`extern "C"`を付けてく必要があります。

### load.c
`load.c`というファイルがあり、この中に`load_mrb_file`という
File IOの関数を使っている関数があるのですが、ArduinoではFile IOはないので
この`load_mrb_file`ごとコメントアウトします。

### Makefile
`arコマンド`だけ変数にされていないので
`avr-ar`に修正します。

ビルド・インストール
-----
`make CC=avr-gcc CFLAGS="-Wall -O -Wall -ffunction-sections -fdata-sections -MMD -mmcu=atmega32u4"`
で同じディレクトリに`libmrubyc.a`ができるはずですので、これを使います。

先ほど作っておいたArduinoプロジェクトディレクトリにファイルを移動します。
プロジェクトディレクトリに`scripts`というディレクトリを、
その下に`mrubyc/include`と`mrubyc/lib`を作り、ビルドした`libmrubyc.a`とヘッダファイルを以下のように設置します。

```
│  platformio.ini
│  
├─lib
│      readme.txt
│      
├─scripts
│  └─mrubyc
│      ├─include
│      │      class.h
~~~~~~~~~~~~ (省略) ~~~~~~~~~~~~
│      │      vm_config.h
│      │      
│      └─lib
│              libmrubyc.a
│              
└─src
        blinkled.c
        sketch.ino
```

Arduino Microプロジェクトの準備
=============================

### platformio.iniの修正
mruby/cをリンクするよう修正します

```
[env:micro]
platform = atmelavr
framework = arduino
board = micro
build_flags = -Imrubyc/include -Lmrubyc/lib -lmrubyc
targets = upload
```

### Arduinoスケッチ

Arduinoとインターフェイスとなるメソッドを追加して、事前にコンパイルしたmrubyのバイトコードを実行します。
mrubyのコンパイルはこの後に説明します。

```cpp:sketch.ino
#include <avr/sleep.h>

#include <static.h>
#include <class.h>
#include <load.h>
#include <errorcode.h>

extern const uint8_t bytecode[];

int LED_PIN = 13;

void mrbc_set_led(mrb_vm *vm, mrb_value *v) {
    // Serial.println(GET_INT_ARG(0));
    digitalWrite(LED_PIN, GET_INT_ARG(0) ? HIGH : LOW);
}

void mrbc_sleep(mrb_vm *vm, mrb_value *v) {
    // Serial.println(GET_INT_ARG(0));
    delay(GET_INT_ARG(0));
}

mrb_class *static_class_arduino;
void setup() {
    Serial.begin(9600);
    pinMode(LED_PIN, OUTPUT);

    init_static();
    mrb_define_method(static_class_object, "set_led", mrbc_set_led);
    mrb_define_method(static_class_object, "sleep", mrbc_sleep);
    struct VM * vm = vm_open();

    delay(5000);
    if (vm == 0) {
        Serial.print("VM open Error.\n");
    }
    else {
        Serial.print("VM open Success.\n");

        int ret = loca_mrb_array(vm, (char*)bytecode);
        if (ret != NO_ERROR) {
            Serial.print("MRB Load Error.\n");
        }
        else {
            vm_boot( vm );

            Serial.print("VM start.\n");
            while (0 <= vm_run_step(vm)) {
                Serial.print("PC : ");
                Serial.print(vm->pc);
                Serial.print("\n");
            }
            Serial.print("VM end.\n");

            vm_close( vm );
        }
    }
}

void loop() {
}
```

### mrubyバイトコード

まず、元のrubyコードを用意します

```ruby:blinkled.rb
while true
    set_led 1
    sleep 10
    set_led 0
    sleep 1000
end
```

次にで扱えるByte配列のバイトコードにコンパイルします。
`mrbc -E -Bbytecode blinkled.rb`
~~Arduino Microはリトルエンディアンだと思うので`-E`オプションは不要だと思うのですが
ビッグエンディアンでないと読み込みに失敗しました(現在調査中)~~
エンディアンはVMの問題なので関係ないですね(たぶん...)

出力される`blinkled.c`を`sketch.ino`と同じ`src`ディレクトリに置きます。

プロジェクトのビルド、書き込み
==========================
`platoformio run`
でいけるはず！