この記事は mrubyファミリ (組み込み向け軽量Ruby) Advent Calendar 2025 23日目の記事です。
はじめに
mruby/cはmrubyから派生した小型のRuby実行環境であり、Rubyの書きやすさ・読みやすさをマイコン開発に持ち込めます。
しかしGitHubにあるmruby/cをそのまま使うと少々クセがあり、実際は「Rubyを書いてそのまま実行できる環境」(本記事内では「mruby/c処理系」と呼びます)を使うことが多いと思います。
本記事では、プログラミング初学者向けのmruby/c処理系であるSmT(シント)にRaspberry Pi Pico(RP2040)ボードを対応させた際に学んだことを共有します。
もし「既存のmruby/c処理系でマイコン特有の機能が欲しいけど、Rubyコードより先の仕組みが分からない…」という方がいらっしゃったときに参考になれば幸いです。
mruby/cをマイコンで動かすには
mruby/cにおいて、Rubyコードをマイコンで動かすまでにはいくつかの手続きを踏む必要があります。
例えばRP2040用のGPIOクラスを実装し、それを利用したRubyコードでマイコン上のLEDを光らせる場合、下記のようなLチカのRubyコードがあるとします。
gpio = GPIO.new(25, GPIO::OUT) # ボード上にあるLEDを使う
gpio.write(0) # 消灯
sleep(1) # 1秒待って…
gpio.write(1) # 点灯!
これをそのままmruby/c VM上で動かしたいところですが、mruby/cはRubyコードを直接実行できません。
素朴に実装すると、まず上のフローの「バイトコード」セクションを目指してRubyコード、組み込みライブラリ、および独自のライブラリの3つを mrbc コマンドでバイトコードにコンパイルします。
それらをファームウェア(C言語のメインプログラム)に渡してビルド→マイコンに書き込む、という流れになります。
つまり、Rubyコードの変更のたびにCコンパイルが発生します。つらい
void main() {
mrbc_init(memory_pool, MEMORY_SIZE); // (内部で下記が呼ばれます)
// hal_init(); // - HAL : ボード固有の実装…メモリ確保など
// mrbc_run_mrblib(mrblib_bytecode); // - Ruby組み込みクラス
// (GPIOなどのRubyライブラリは処理系が独自に決められる)
mrbc_pico_gpio_gem_init(0); // - C拡張
mrbc_run_mrblib(myclass_bytecode); // - Rubyのクラス
mrbc_create_task(master_bytecode, 0); // - Rubyコード(★上記プログラム★)
mrbc_run(); // - mruby/c VM : 各クラスなどを含むプログラム実行
}
mruby/c処理系
そもそもGPIOやPWMなどの汎用的なライブラリの中身を変えることは少なく、基本的に開発中はRubyコード(というかGPIOクラスなどを利用して実際に使うセンサーなどに特化した実装)の試行錯誤が多くなるかと思います。
そのため最近はPicoRuby+R2P2などの「Rubyを書いてそのまま実行できる環境」がよく使われるようになりました。
今回はmruby/c処理系のひとつである、ビジュアルプログラミングツール「SmT」でRP2040が使えるようにしました。
SmTについて
SmTはScratchフォークのスモウルビーをさらにフォークしたプログラミングツールです。SmTは松江高専1さんが中心となって開発されています。
スモウルビーではScratchのブロックとRubyコードを相互変換でき、SmTではさらにRubyコードのバイトコード変換および(WebブラウザからUSB接続された)マイコンへのRubyバイトコードの書き込みが可能です。
これにより、キーボード操作の経験が少ないプログラミング初学者でも、ビジュアルプログラミングから入ってコーディングまで体験できるようになっています。
さて、前述のフローからSmTが担当する部分を分けると下図はになります。
事前にファームウェアを書き込んでおけば、Rubyコードはブラウザからコンパイル・書き込み可能になるわけです。
RP2040のSmT対応
前述の通りSmTは環境構築もほぼ不要なのでとても便利…なのですが、現在はRBoard(PIC32)およびESP32マイコンのみ対応している状況でした。
幸いRBoard用のボーレート(PC-マイコン間の転送速度)がRP2040で対応している速度だったため、SmT側はそのままで問題ありませんでした。また、mruby/cにはすでにRP2040用のHAL(Ruby組み込みクラスがRP2040上で動作するためのC実装)がありました。
そのため今回は、Ruby組み込みクラスの実装を除く下記2点を組み込んだファームウェアを実装しました。
- Raspberry Pi Pico SDKを用いたRubyライブラリ(GPIO、PWM、ADC、I2Cクラス)の実装
- ブロックから変換されるRubyコードは他マイコンと共通のため、クラスのインターフェースはmruby, mruby/c 共通I/O APIガイドライン準拠
- SmTからマイコンへバイトコードを転送するために利用されるmrbwriteプロトコルの実装
実装方針
最初のCコードから変わるのは、mruby/c VMが実行するバイトコードがファームウェアの外から与えられる点です。
RP2040ではフラッシュROMにプログラムからデータを書き込むことができるため、mrbwriteプロトコルで送信されたバイトコードをフラッシュへ書き込むようにしています(実行時はそれを読み込んでVMへ渡す)。
void main() {
+ while (mrbwrite_request_detected()) { // mrbwriteのコマンドモードの処理
+ mrbwrite_cmd_mode(); // フラッシュへのバイトコード(Rubyコード相当)の書き込みなど
+ }
+ // バイトコード(Rubyコード相当)をフラッシュから読み込み
+ uint8_t *master_bytecode = vfs_read("master.mrbc");
+
mrbc_init(memory_pool, MEMORY_SIZE); // (内部で下記が呼ばれます)
// hal_init(); // - HAL : ボード固有の実装…メモリ確保など
// mrbc_run_mrblib(mrblib_bytecode); // - Ruby組み込みクラス
// (GPIOなどのRubyライブラリは処理系が独自に決められる)
mrbc_pico_gpio_gem_init(0); // - C拡張
mrbc_run_mrblib(myclass_bytecode); // - Rubyのクラス
mrbc_create_task(master_bytecode, 0); // - Rubyコード(★上記プログラム★)
mrbc_run(); // - mruby/c VM : 各クラスなどを含むプログラム実行
}
実装したもの
前述の実装方針をもとにCコードを作成し、公式のSDKであるpico-sdkをもとにCMakeLists.txtなどのターゲットを定義したリポジトリです。
※ まだ動作確認しただけなので、Forkもとにマージいただいた場合はリンクを差し替えます。
makeコマンドでビルドされたファームウェアをボードに書き込むことでSmTで開発するための準備ができます。
# 1. 本リポジトリのclone
git clone https://github.com/matsudai/mrubyc-pico
git checkout feature/support-smt # 本ブランチ
# 2. Submoduleの取得
git submodule update --init --recursive
# 3. 環境構築
# 3.1 必要なパッケージの取得
# refs https://github.com/raspberrypi/pico-sdk?tab=readme-ov-file#unix-command-line
sudo apt install cmake python3 build-essential gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib
# 3.2 mrubyのインストール ※要rbenv
rbenv install mruby-3.4.0
rbenv local mruby-3.4.0
# 4. ファームウェアのビルド
make
# 5. ボードへの書き込み
# 5.1 Raspberry Pi PicoのBOOTSELボタンを押しながら、PCにUSBケーブルで接続します。
# → USBフラッシュドライブとして開きます。
# 5.2 build/main.uf2をD&Dなどで書き込みます。
# 6. Rubyプログラムの書き込み
# 6.1 SmT (https://ceres.epi.it.matsue-ct.ac.jp/smt/) で汎用ブロック・デバッグでプログラムを作成します。
# 6.2 kaniwriterでRBoardとして書き込みます。
動作確認
実際にSmTでLチカプログラムを作成し、動かしてみた結果は次の通りです。
gpio25 = GPIO.new( 25, GPIO::OUT )
gpio25.write( 0 )
puts( "Hello World" )
loop do
sleep(1)
gpio25.write( 1 )
puts( gpio25.read )
sleep(1)
gpio25.write( 0 )
puts( gpio25.read )
end



