Arduinoとmruby/cでESP32を使う方法


rubyからマイコンを使いたいと昔から思っていたが、これまでなかなか手が出なかった。 mrubyというものがあるのは、以前から知っていたが、PCでコンパイルして中間コードを作って、それをマイコン上のVirtual Machineで実行するという、面倒な手順が必要である。 また、mruby/cというさらにコンパクトな実装も出て来たが、これも同様である。

pythonからマイコンを使うのは、micropythonを使えば簡単にできるが、rubyにはこれに対応するものがなかなか出て来なかった。 mruby/cを利用したpicorubyは、micropythonとほぼ同じ感覚で使えるものであり、今後の発展に期待したいが、binaryはraspberry pi pico用しか公開されておらず、EPS32用のものもあるが、自分でコンパイルしなければならない。 mruby-esp32というものもあり、flashにrbファイルまたはmrbファイルを書き込むと、それを実行してくれるという、micropythonを意識して作られたもののようである。

私がマイコンを使う場合には、c言語かArduino(C++)でプログラムを書いて、PCでコンパイルして、それをマイコンに書き込んでいる。 考えてみると、rubyでプログラムを書いて、PCでmrubyを使ってコンパイルして、それをマイコンに書き込んで実行できるのなら、mrubyもそれほど使い勝手は悪く無い気がしてきた。 micropythonのようにコンパイラをマイコンに載せるのは、メモリやflashを余分に必要とするので、無駄なようにも思える。 問題は、使いたいマイコン用のVirtual Machineがなかなか無いことである。 先に触れたmruby-esp32や、ESP32用のライブラリは、その例だろう。 RP2040, PIC32, STM32などで開発が進んでいるようだが、 様々なマイコン用のVirtual Machineを、それぞれ用意するのは手間である。

いろいろと検索していて見付けたのが、mruby/cとArduinoを組み合わせる方法である。 Arduinoは、様々なマイコン用のパッケージが用意されており、ほとんど同じ書き方で異なるマイコンを使うことが出来るようになっている。 そのArduino用の関数をrubyから使えるようにすれば、様々なマイコンをrubyから使えるようになるという考えである。 るびまの記事や esp32用のmrubyc-esp32-arduinoや いくつかのボードで動作するmrubyc_arduinoが それらの例であろう。 この内、最近も更新されている最後のものを使ってみることにした。

まずはESP8266で試してみたが、うまく動かなかった。 メモリも少ないし、なかなか厳しいのだろう。 次に、動くと期待できるESP32で試してみた。 Arduinoには、設定で以下のURLを加えて、ボードマネージャーからesp32をインストールした。

https://dl.espressif.com/dl/package_esp32_index.json

そして、以下のコマンドでダウンロードしたファイルを、スケッチ-ライブラリをインクルード-.ZIP形式のライブラリをインストールで、組み込む。

wget https://github.com/aikawaYO/mrubyc_arduino/archive/refs/heads/main.zip

スケッチ例-mrubycのところにblink_and_helloというのがあるので、これを動かそうとしたが、そのままではエラーが出てしまった。 digital.cなどの中で使っているArduino用の関数が参照できていなかったので、mrubyc.hにArduino.hをincludeするようにしたら、これはうまく動くようになった。 動作確認されているボードでは、なんで同じエラーが起きないのか不思議だ。 blink_and_helloでは、rubyから使うGPIO用の関数を、inoの中で定義しているので、マイコンの別の機能を使おうと思ったら、inoをいじらないといけない。

GPIO用の関数はdigital.cの中で定義されているようだったので、それを使おうとしたら、それなりに修正が必要だった。 シリアルにエラーメッセージが出るので、それを参考にバグ取りをした。 まず、GPIOクラスが定義されていないと言われたので、digital.cとdigital.h中のmrbc_init_class_digitalの引数をvoidにして、それをino中で実行した。 次に、GPIOのクラス定数が定義されていないので、先の関数の最後に以下を加えた。

mrbc_set_class_const(gpio, mrbc_str_to_symid("IN"),       &mrbc_integer_value(GPIO_IN));
mrbc_set_class_const(gpio, mrbc_str_to_symid("OUT"),      &mrbc_integer_value(GPIO_OUT));
mrbc_set_class_const(gpio, mrbc_str_to_symid("PULL_UP"),  &mrbc_integer_value(GPIO_PULL_UP));
mrbc_set_class_const(gpio, mrbc_str_to_symid("PULL_DOWN"),&mrbc_integer_value(GPIO_PULL_DOWN));

但し、その前に次のように定数を定義しておく必要がある。

#define GPIO_IN         0x01
#define GPIO_OUT        0x02
#define GPIO_PULL_UP    0x10
#define GPIO_PULL_DOWN  0x20

最後に、digital.hが組込まれていないようだったので、mrubyc.hの中でそれを組み込むようにした。

これらの変更をしたら、以下のようなrubyスクリプトでLEDのon/offができるようになった。

a=GPIO.new(2)
a.setmode(GPIO::OUT)
i=1
loop{
  a.write(1)
  sleep(1)
  a.write(0)
  sleep(1)
  puts i+=1
}

これをtest.rbとして、以下のコマンドでmrb形式のデータにする。

mrbc -B ruby_pg -o test.h test.rb

ちなみに、debian13ではmrubyはaptで簡単にインストールできた。 そして、Arduino用のinoファイルは以下のようにした。

#include <mrubyc.h>
#include "test.h"

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

int hal_write(int fd, const void *buf, int nbytes) {
  Serial.print((char*)buf);
  return (nbytes);
}
int hal_flush(int fd) {
    return 0;
}

void setup() {
  Serial.begin(19200);
  mrbc_init(memory_pool, MEMORY_SIZE);
  mrbc_init_class_digital();
  mrbc_create_task( ruby_pg, 0 );
  mrbc_run();
}
void loop() {}

シリアルモニタで見ると、putsで数値を表示させるときには、文字化けしたものも出力されているけど、なんでかな。 console.cを少し見てみたけど、最終的にはprintfが呼び出されているし、原因は分からなかった。 printfだとうまく出力されるが、文字列の%演算子は使えないようだ。

この手法では、 mrubyとArduinoで二回コンパイルしているのが無駄に感じられるが、まずは動いたことを喜ぼう。 adc.cなどもそのままでは動かないっぽいが、同様の修正で動きそうだ。 また、Arduinoから使える他の機能は、この環境には組込まれていないようなので、それらも組み込む必要があるかも知れない。 また、 mruby mruby/c共通I/O APIガイドライン に、完全には準拠していないようなので、準拠するように修正した方が、使い易いだろう。 そうすれば、様々なマイコンで、ほとんど修正せずにそのままrubyから使えるようにならないだろうか。

ここで紹介した手法を用いると、mrubyを使えるようにできれば、 マイコン用のコンパイル環境を整えるのはArduinoに任せられるので、 素人にも比較的簡単にrubyからマイコンを制御することができるのでは無いだろうか。 ESP8266では動かなかったので、いつか動かせないか検証してみたい。