PlatformIOを使ってmruby/cのクロスコンパイル環境を構築する

## 目的

PlatformIOに自作ライブラリとしてmruby/cを組み込み、マルチタスクを行う。

## 概要

pio initで生成されたlibフォルダの中でmruby/cをgit cloneすると、PlatformIOでmruby/cが使用できます。

## PC環境

- OS: windows 10 1903
- IDE: VS Code, CLion
- PlatformIO: ver4.0.3
- Python: ver3.7.4
- mruby/c: ver1.2
- mruby: 2.0.1
- マイコンボード：HiLetgo ESP32 ESP-32S NodeMCU

PlatformIO,mruby/cはWindows,Linux,Mac対応なので、コードを使いまわせると思います。

### mrubyコンパイラのダウンロード(Windowsユーザー向け)

[このページ](https://www.s-itoc.jp/activity/research/mrubyc/mrubyc_download/)のmrubyコンパイラ2.0ダウンロードリンクからコンパイラがダウンロードできます。

## PlatformIOについて

複数のOSで動作する、統合開発環境です。
ESP32やPIC32、Arduino unoなど複数のマイコンの開発環境を構築することができます。

詳細は以下のページを見てください。
<https://platformio.org/>

## mruby/cについて

> センサーネットワークや、ウェアラブルなどの小型端末向けの開発言語「mruby/c」
>
> mruby/cは、Rubyの特徴を引き継ぎつつ、プログラム実行時に必要なメモリ消費量が従来のmruby（福岡で開発された組込み向けの軽量Ruby）より少ないmrubyの実装です。
> 
> 国立大学法人九州工業大学（田中和明准教授）と共同で研究開発を行っています。

[しまねソフト研究開発センター](https://www.s-itoc.jp/activity/research/mrubyc/)より引用

実装例は以下の記事が参考になると思います。
https://www.s-itoc.jp/activity/research/mrubyc/mrubyc_tutorial/ESP32/

## mruby/c with PlatformIO 環境構築手順(本題)

### PlatformIOの初期化

1. 作業用フォルダを作成、移動

~~~pwsh
mkdir your_work_space
cd .\your_work_space
~~~

2. 作業フォルダ内でPlatformIOで開発環境の設定

~~~pwsh
cd ..
pio init --ide your_ide_id --board your_board_ide
~~~

ここまでで、作業フォルダ直下は以下のようになります。
なお、OS、使用IDEによって追加でフォルダが作成されます。

your_work_space
|- include
|- lib - 個人用のライブラリを入れるフォルダ
|- src - メイン処理を書くフォルダ
|- platformio.ini - ボード、シリアルモニタの速度などの設定

### PlatformIOにmruby/cをライブラリとして組み込む

libフォルダの中に個人用のライブラリを入れます。

- libフォルダ内でmrubycのリポジトリをクローン

~~~pwsh
cd ./lib
git clone https://github.com/mrubyc/mrubyc.git
~~~

- mrubyc/srcにあるhal_*ファイルの内、使用するボードに対応するものをhalにリネーム

~~~pwsh
REN ./lib/mrubyc/src/hal_you_use ./lib/mrubyc/src/hal
~~~

- hal_*ファイルを削除

~~~pwsh
rm ./lib/mrubyc/src/hal_*
~~~

- 作業フォルダ内でPlatformIO開発環境の初期化

~~~pwsh
pio init --ide your_ide_id --board your_board_ide
~~~

※注：libディレクトリを編集した後は毎回pio initを行う必要があります。

### mruby/cを動かすための処理を作成

~~~cpp:main.cpp
#include "Arduino.h"
#include "mrubyc.h"

// 後ほどmrubyのコンパイラでコンパイルした.rbファイルの処理が格納される配列です。
extern const uint8_t task1[];
extern const uint8_t task2[];

// for esp32
#define MEMORY_SIZE (1024*30)
static uint8_t memory_pool[MEMORY_SIZE];

// ruby側で使用したいメソッドを実装します。
// 基本形は以下の通りです。
// 引数を取る場合は第1引数v[1],第2引数v[2]であることに注意してコーディングします。
// 第1引数に整数を使用する場合 int value_name = GET_INT_ARG(1);
// 第2引数に浮動小数を使用する場合 float value_name = GET_FLOAT_ARG(2);
// サンプルです。
void c_sample_method(VM *VM, mrbc_value *v, int arg){
}
void c_Serial_println(VM *VM, mrbc_value *v, int arg){
    uint8_t *character_array = GET_STRING_ARG(1);
    String str = String((char *)character_array);
    Serial.println(str);
}

// 実装したメソッドを登録します。
// 基本形はmrbc_define_method( nullptr, mrbc_class_object, "Rubyで使いたいメソッド名", メソッドの実装);
static void mrbc_define_methods(){
    mrbc_define_method( nullptr, mrbc_class_object, "sample_method", c_sample_method);
    mrbc_define_method( nullptr, mrbc_class_object, "Serial_print", c_Serial_println);
}

void setup() {
//  replace Serial.begin( here! )
//  ex) esp32 can use  Serial.begin(115200)
    Serial.begin(9600);

    delay(1000);

    // initialize mruby/c
    Serial.println("--- begin setup");
    mrbc_init(memory_pool, MEMORY_SIZE);
    mrbc_define_methods();

    // マルチタスクを行いたい場合は以下のように追加してください
    // 基本形 mrbc_create_task(5行目あたりで宣言した配列, nullptr);
    mrbc_create_task(task1, nullptr);
    mrbc_create_task(task2, nullptr);

    Serial.println("--- run mruby script");

    // mruby/cのVMを動かします
    // なお、内部は無限ループとなっているため、これ以降のコードにはたどり着けません。
     mrbc_run();
}

void loop() {
    // setup()が終わらないためたどり着けない。
}

~~~

### srcフォルダにRubyファイルを作成

~~~ruby:task1.rb
while true
  Serial_print("task1")
  sleep(1)
end
~~~

~~~ruby:task2.rb
while true
  puts 'task2'
  sleep(0.5)
end
~~~

### Rubyファイルをmrubyでコンパイル

#### Windowsユーザー向け

以下の手順で、Rubyファイルをコンパイルします。

- mrubyコンパイラのダウンロード(Windowsユーザー向け)で紹介したファイルの中のmrbc.batとmrbc.exeをsrcフォルダに移動
- mrbc.batを実行

task1.rbという名前のファイルがあった場合、task1.cという名前のファイルが生成されます。

### 生成された.cファイルをmain.cppで読み取れるようにする

- 生成されたtask1.cの中に以下のような配列の宣言があるので、このコードをmain.cppに張り付ける。

~~~c:task.c
/* dumped in big endian order.
   use `mrbc -e` option for better performance on little endian CPU. */
#include <stdint.h>
extern const uint8_t task1[];　<-この一文をmain.cppに貼り付け
const uint8_t

(後略)
~~~


~~~cpp:main.cpp
#include "Arduino.h"
#include "mrubyc.h"

// 後ほどmrubyのコンパイラでコンパイルした.rbファイルの処理が格納される配列です。
extern const uint8_t task1[];　<-ここに貼り付け
extern const uint8_t task2[];

(後略)
~~~
- タスクを生成する

以下のようにmrbc_create_task(task1, nullptr)でタスクを追加する。

~~~cpp:main.cpp
(前略)

    // マルチタスクを行いたい場合は以下のように追加してください
    // 基本形 mrbc_create_task(5行目あたりで宣言した配列, nullptr);
    mrbc_create_task(task1, nullptr);　<-ここでタスクを追加
    mrbc_create_task(task2, nullptr);

    Serial.println("--- run mruby script");

    // mruby/cのVMを動かします
    // なお、内部は無限ループとなっているため、これ以降のコードにはたどり着けません。
     mrbc_run();

(後略)
~~~

### マルチタスクで動かす

他に動かしたいタスクをRubyで記述し、

- Rubyファイルをmrubyでコンパイル
- 生成された.cファイルをmain.cppで読み取れるようにする

の項目を繰り返す。

### PlatformIOでbuild&upload

ここまでで、コードは完成したので、PlatformIOでBuildボタンを押します。
エラーが出なければ、マイコンをつないで、Uploadをして作業終了です。
お疲れさまでした。

## 参考文献

### Webページ
[ESP32×mruby／c IoTハンズオン](https://www.s-itoc.jp/activity/research/mrubyc/mrubyc_tutorial/ESP32/)

### GitHub
[mrubyc/mrubyc](https://github.com/mrubyc/mrubyc)
[hasumikin/mrubyc-utils](https://github.com/hasumikin/mrubyc-utils)
[mruby-esp32/mrubyc-esp32-arduino](https://github.com/mruby-esp32/mrubyc-esp32-arduino)

### 書籍
[mruby/cの小さな世界 (技術の泉シリーズ（NextPublishing）)](https://www.amazon.co.jp/gp/product/B07Q5YXSCH/ref=ppx_yo_dt_b_d_asin_title_o07?ie=UTF8&psc=1)