高級言語 (mruby/c) による開発の基礎

Ruby シリーズ

  • Ruby
    • 元祖. サーバ, PC 向け
  • mruby
    • マイコン, 組み込み向け
    • 2010年~2012年 経済産業省地域イノベーション創出研究開発事業で mruby を開発(ネットワーク応用通信研究所,福岡CSK,九州工業大学)
    • 現在は, まつもとゆきひろ氏が主にメンテナンス
    • Ruby を軽量化した実装 (実行時に必要なメモリ量 200 kb)
    • 実行時の消費メモリが少ない
    • 文法はRubyと同じ
  • mruby/c
    • 小規模マイコン, 組み込み向け
    • 2015年~ しまねソフト研究開発センター, 九州工業大学との共同研究でmruby/cを開発
    • mruby よりさらに小さな実装 (実行時に必要なメモリ量 < 64 kb)
    • RAM 削減を優先 (実行速度を捨てて)
    • 文法はRubyと同じ
    • 利用頻度が低いメソッドは実装していない
    • OS なしでも複数の Ruby プログラムを動かせる

本演習では小規模マイコン (ESP32) をターゲットにするので, 必然的に mruby/c を使うことになる.

mruby/c

mruby/c は以下の github リポジトリで公開されており, 2020 年 10 月現在, ver. 2.1 が最新である.

<URL:https://github.com/mrubyc/mrubyc>

mruby/c の文法

Ruby の基本的な文法はそのまま使えると思って良い.

mruby/c の処理系

Ruby はインタプリタ型言語である. Ruby 1.8 までは, 行あるいは ブロック単位でプログラムが逐次機械語プログラムに変換されながら実行されていた. Ruby 1.9 以降は, Java と同様に, ソースコードをバイトコード (中間コード) にコンパイルし, それを仮想マシン (Virtual Machine; VM) で実行するような処理の流れになっている. 当然ながら mruby や mruby/c も基本的に同じような処理系になっている.

仮想マシンが独立しているので, コンパイルまでの複雑な処理と仮想マシンの実行環境を 別々にすることができる. mruby/c では, 以下のように処理をパソコンとマイコンに分ける.

  • Rubyプログラムをバイトコードにコンパイル →パソコンで行う
  • VM (Virtual machine) を使って実行 →マイコンで行う

mruby/c では Parser と Code Generator の部分 (mrbc コマンド) は mruby の実装をそのまま使用し, VM だけ独自のコンパクトな実装を用いている. 詳細はmrubycで始めるオリジナルIoTデバイス作りを参照のこと.

mruby/c の開発環境の構築

具体的な手順は以下の 2 つのドキュメントを参照されたい. ポイントは, rbenv コマンドで mruby (mrubyc ではない) をインストールすることである. 先に述べたように, コンパイラとして mruby が提供する mrbc コマンドを利用するためである.

mruby/c のソースは <URL:https://github.com/mrubyc/mrubyc> で公開されている. ESP32 マイコンで mruby/c を使う場合は, mruby/c のソースが同梱された ESP32 用の開発環境 <URL:https://github.com/hasumikin/mrubyc-template-esp32> (羽角均氏, 株式会社モンスター・ラボ 島根開発拠点)や <URL:https://github.com/gfd-dennou-club/iotex-esp32-mrubyc> (杉山ら, 松江高専) を git clone するのが良いだろう. これらのリポジトリを git clone してディレクトリ構造を見てみると, component/mrubyc 以下には mrubyc の VM を make するためのソースコードが存在していること, ユーザが作成したソースコードを置く mrblib/loops や mrblib/models といったディレクトリが 存在することがわかる.

$ tree -L 4 iotex-mrubyc-esp32/

  mrubyc-template-esp32/
  ├── Makefile
  ├── components
  │   └── mrubyc              mruby/c の VM のソースなど
  │       ├── component.mk
  │       ├── mrubyc_mrblib
  │       │   ├── Makefile
  │       │   ├── array.rb
  │       │   ├── numeric.rb
  │       │   ├── object.rb
  │       │   ├── range.rb
  │       │   └── string.rb
  │       └── mrubyc_src
  │           ├── Doxyfile
  │           ├── Makefile
  │           ├── alloc.c
  │           ├── alloc.h
  │           ├── c_array.c
  │           ├── c_array.h
..........(中略)...............
  │           ├── symbol.c
  │           ├── symbol.h
  │           ├── value.c
  │           ├── value.h
  │           ├── vm.c
  │           ├── vm.h
  │           └── vm_config.h
  ├── main
  │   ├── component.mk
  │   └ mrbc_esp32_gpio.c     ラッパー
  │   └ mrbc_esp32_gpih
..........(中略)...............
  │   └── main.c
  └── mrblib
      ├── loops        メインプログラム
      └── models             クラス

mruby/c による開発方法

方法 1: mruby/c だけで完結させる場合

mruby/c VM を書き込み済みのマイコンボードを使い, PC からバイトコードを送り込む.

  • メリット: 簡単に開発が始められ, 試行錯誤もやりやすい.
  • デメリット: VM 側に用意されている機能以外を使いたい場合には VM の拡張が必要.
    • VM が律速となり, マイコンが持っている全ての機能を使えないことがある.

方法 2: マイコンの C 言語開発環境を使う

メーカ等が用意した C 言語開発環境と VM のソースコードを使って, 既存の C 言語のプログラムに mruby バイトコードを結合する. 本演習で採用するのはこちらの方法である.

  • メリット: 既存資産 (C言語プログラムなど) が生かせる. マイコンが持っている全ての機能を使うことができる.
  • デメリット: 開発環境の構築など, 開発開始前の作業が多い, 試行錯誤が少しやりにくくなる (コンパイルに時間がかかる)

ESP32 マイコンでの mruby/c でプログラム開発の実際 (1)

ITOC が公開している チュートリアル がお手本として使えるが, これは上記の「方法 2: マイコンの C 言語開発環境を使う」に相当する. マイコンメーカが公開している C 言語開発環境 (ESP-IDF) を用いた C 言語プログラムを作り, その C 言語プログラムを利用する Ruby プログラムを作る, という 2 段構えとなる.

具体的なプログラムの例を ITOC の L チカのチュートリアル より抜粋する. L チカをする場合には以下のようなプログラム群を作ることになるが, メインプログラムに相当するのが, mrblib/loops/master.rb である. Ruby プログラムは人間にとって分かりやすく書かれている一方で, 人間からすると面倒/冗長な部分は C 言語に押し付けているような形になっていることがわかる. マイコンメーカの用意している ESP-IDF のヘッダファイル (driver/gpio.h など) や関数 (gpio_set_direction, gpio_set_level, など) は C 言語プログラムのみから参照されており, Ruby プログラムからは直接参照されていない.

mrblib/loops/master.rb (メインプログラム)

led = Led.new(19)

while true
  led.turn_on   #LED点灯
  sleep 1
  led.turn_off  #LED消灯
  sleep 1
end

mrblib/models/led.rb (LED クラスの定義)

class Led   #LEDクラス

  def initialize(pin) #初期化
    @pin = pin
    gpio_init_output(@pin)
    turn_off
  end

  def turn_on   #点灯させるためのメソッド
    gpio_set_level(@pin, 1)
    puts "turned on"
  end

  def turn_off   #消灯させるためのメソッド
    gpio_set_level(@pin, 0)
    puts "turned off"
  end
end

main/main.c (ESP-IDF を用いた C 言語プログラム)

#include "driver/gpio.h"
#include "mrubyc.h"
#include "models/led.h"
#include "loops/master.h"
#define MEMORY_SIZE (1024*40)

static uint8_t memory_pool[MEMORY_SIZE];

static void c_gpio_init_output(mrb_vm *vm, mrb_value *v, int argc) {
  int pin = GET_INT_ARG(1);
  console_printf("init pin %d\n", pin);
  gpio_set_direction(pin, GPIO_MODE_OUTPUT);
}

static void c_gpio_set_level(mrb_vm *vm, mrb_value *v, int argc){
  int pin = GET_INT_ARG(1);
  int level = GET_INT_ARG(2);
  gpio_set_level(pin, level);
}

void app_main(void) {
  mrbc_init(memory_pool, MEMORY_SIZE);

  mrbc_define_method(0, mrbc_class_object, "gpio_init_output", c_gpio_init_output);
  mrbc_define_method(0, mrbc_class_object, "gpio_set_level", c_gpio_set_level);

  mrbc_create_task( led, 0 );
  mrbc_create_task( master, 0 );
  mrbc_run();
}

ESP32 マイコンでの mruby/c でプログラム開発の実際 (2)

上記の「プログラム開発の実際 (1)」で挙げたプログラム例を見たユーザは, 「メインプログラムにあたる mrblib/loops/master.rb のみ書きたい」 「その他の C 言語プログラム (main/main.c) やLED クラスを定義した Ruby プログラム (mrblib/models/led.rb) を一々書きたく無い」と思うだろう. 自然な欲求と思われる.

松江高専の杉山研究室では, ESP32 マイコン用の C 言語プログラムやクラスの定義を ライブラリとしてまとめつつある. そのライブラリは <URL:https://github.com/gfd-dennou-club/iotex-esp32-mrubyc> に公開している. まだ不十分な点が多いが, 基本的な機能は実装されている. このライブラリを用いると 「メインプログラムにあたる mrblib/loops/master.rb のみ書きたい」という要望に答えることができる. 本演習ではこのライブラリを用いることにする.

参考