SPRESENSEボードでmruby/cをはじめてみる

SPRESENSE の開発環境に Java とか Python はあるけど、なんで Ruby が無いの?! ってことで、無いなら自分で用意してみるかと思い調べてみました。mruby を動かす前に、mruby/c なるものの存在を初めて知りました。まずは mruby/c の導入から SPRESENSE 上で "Hello world" や L チカを動かすところまで、やったことを作業メモとしてまとめておきます。

もし文中に誤り、もっと良い方法、参考になる情報等があればアドバイス頂けるとうれしいです。
今後、SPRESENSE ハードウェアと連携したライブラリを拡充させていければと思っています。

# mruby/c とは？

本家はこちら
https://www.s-itoc.jp/activity/research/mrubyc/

> 
センサーネットワークや、ウェアラブルなどの小型端末向けの開発言語「mruby/c」
mruby/cは、Rubyの特徴を引き継ぎつつ、プログラム実行時に必要なメモリ消費量が従来のmruby（福岡で開発された組込み向けの軽量Ruby）より少ないmrubyの実装です。

メモリ消費50KB未満(RAM)で動くというから驚きです。

# Spresense Arduino環境ライブラリ準備

本家のリポジトリです。ライセンスは修正BSDライセンスです。
https://github.com/mrubyc/mrubyc

本家にStarを付けてForkさせて頂きました。
https://github.com/baggio63446333/mrubyc

Arduino の libraries フォルダ以下へ clone します。

```console
$ cd ~/Arduino/libraries
$ git clone https://github.com/baggio63446333/mrubyc
```

以下はライブラリ環境構築の作業メモなので、手っ取り早く試してみるには、[Hello World サンプルコード](#hello-world-サンプルコード) まで読み飛ばしてください。

### HAL(Hardware Abstraction Layer)の準備

HAL層が元々用意されています（良いですね）
Spresense環境で動作しているリアルタイムOSの`nuttx`が POSIX 準拠なので`hal_posix`をベースに`hal`を用意しました。また、HALはArduino用のAPIをそのまま使いたいので拡張子を hal.cpp に変更しました。他の`hal_xxx`ディレクトリはArduino IDEでビルドエラーになるのを防ぐために削除しておきます。

```console
$ cd mrubyc
$ git rm -r src/hal_esp32 src/hal_pic24 src/hal_posix src/hal_psoc5lp
```

### library.properties 追加

Arduinoライブラリとしてそのまま利用するため、`library.properties`ファイルを追加します。

```console
$ cd mrubyc
$ git add library.properties
```

### その他

- `src/class.c`コンパイルエラー修正(`ERROR`というシンボルが既に定義されているので`_ERROR`に置換)

# Hello World サンプルコード

### examples/hello 追加

簡単なrubyプログラムを準備し、

```ruby:hello.rb
puts "Hello World!"
```

`hello.rb` を事前にバイトコードにコンパイルして hello.c を作成します。

```
$ cd examples/hello
$ mrbc -E -Bcode hello.rb
```

#### ハマったメモ

https://github.com/mruby/mruby (master) から mrbc ツールを生成して、それを使ってバイトコードに変換して動かしてみたところ、実行時に `Illegal bytecode` エラーが発生。

```
<raise> /home/user/Arduino/libraries/mrubyc/src/load.c:55
Error: Illegal bytecode.
```

エラーの箇所はここ

```c:load.c
  if( memcmp(p, "RITE0006", 8) != 0 ) {
    mrbc_raise(vm, E_BYTECODE_ERROR, NULL);
    return -1;
  }
```
バイトコンパイルした hello.c コードをみてみるとヘッダが "RITE0007" となっており、バージョンが一致せずエラーになっているようです。どうも使用した mrbc ツールが新しすぎたようです。

mrbc ツールのバージョンの合わせ方がよく分からなかったので
[ダウンロードページ](https://www.s-itoc.jp/activity/research/mrubyc/mrubyc_download/)の mruby コンパイラをそのまま使用するようにしました。
https://www.s-itoc.jp/files/original/201904181318159501cbec95e.zip

バージョンを合わせたのに、相変わらず `Illegal bytecode` エラーが出ていて、それは Endian ? の問題でした。mrbc に `-e` ではなく `-E` オプションを付ける必要がありました。`-e` だと "RITE" 文字列のバイトオーダーがひっくり返るので memcmp に引っ掛かるのは必然な気もしますが気のせいでしょうか。。とにかく `-E` オプションを付ければ OK です。

めでたく "Hello World!" が表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/05e90a17-cf80-5bee-aaaa-01c7c9bc57f2.png)

# Hello World サンプルコード(mrbファイル読み込み)

### examples/hellob 追加

ruby スクリプトをバイトコンパイルしてさらにプログラムに静的に組み込んで動かす、というのだとスクリプト言語の良さが薄れてしまうので、バイトコードはファイルから読み込むようにしました。SD カードに mrb ファイルを置いておきそれを読み込んで実行します。

```c++:hellob.ino
  uint8_t *buf = load_mrb("hello.mrb");
  if(NULL == mrbc_create_task( buf, 0 )){
```

次のコマンドで hello.mrb を生成できます。

```
$ cd examples/hellob
$ mrbc -E hello.rb
```

hello.mrb ファイルを SD カードに置いて実行します。

#### TIPS

毎回、SD カードを抜き差しして mrb ファイルをコピーして、という作業も面倒です。
このサンプルでは実行の最後に SD カードを USB マスストレージでマウントしています。
SPRESENSE 拡張ボード側の USB 端子を PC につないでおけば、SD カードを抜き差しせずに、PC から直接 mrb ファイルを SD カードにコピーすることができます。

```c++:hellob.ino
  /* Start USB MSC */
  if (SD.beginUsbMsc()) {
    Serial.println("USB MSC Failure!");
  } else {
    Serial.println("*** USB MSC Prepared! ***");
    Serial.println("Insert SD and Connect Extension Board USB to PC.");
  }
```

rubyスクリプトを変更して実行という繰り返しの動作をかなり快適に行うことができる環境が整いました。

# digital 制御サンプルコード(mrbファイル読み込み)

続いてLチカに挑戦してみたいと思います。

次のようなコードを書けばrubyとCソースをつなぐことができるようです。簡単ですね。

```c++:ledswitch.ino
  mrbc_define_method(0, mrbc_class_object, "digital_write", c_digital_write);
```

まずは、`pinMode()`, `digitalWrite()`, `digitalRead()`を ruby から制御できるようにつないで、
SPRESENSE メインボードに4つあるLEDを順番に光らせます。
SPRESENSE LTE拡張ボードについているボタンを押せば点滅させるLEDの場所を変えていきます。

```ruby:ledswitch.rb
val = 0
portled = 64
portsw = 33

pin_mode(portsw, 0);

while true
  if val == 1
    val = 0
  else
    val = 1
  end

  if digital_read(portsw) == 0
    portled += 1
    if portled > 67
      break
    end
  end

  digital_write(portled, val)
  sleep 1
end
```

次のコマンドで ledswitch.mrb を生成して、SD カードに置いておきます。

```
$ cd examples/ledswitch
$ mrbc -E ledswitch.rb
```

無事にLチカできました。サンプルコードはリポジトリに入れておきました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/0c08eb4d-610c-8eda-2e8d-bde8db8f3d45.jpeg" width="50%">


今後は、GPS, オーディオ再生/録音, カメラ制御とか、SPRESENSE ならではの処理を追加してみます。
バイトコードも LTE ネットワーク越しに送ってリモートでスクリプトを差し替えてみたいと思います。
あと、RAM サイズ的には入りそうなので、mruby にも挑戦してみたいところです。