Family mruby OS:FreeRTOSベースのMicroRubyマルチVM構想

Family mruby OS:FreeRTOSベースのMicroRubyマルチVM構想
Family mruby

この記事は、mrubyファミリ (組み込み向け軽量Ruby) Advent Calendar 2025の12/25の記事です。25日の枠が空いていたので、滑り込ませていただきました。

今日は私が最近取り組んでいる個人プロジェクトであるFamily mrubyとそこから発展したFamily mruby OSについて紹介したいと思います。

現在のLinuxシミュレーション環境上でのデモ。ESP32マイコン上で、こんなUIがmrubyで動いていて、mrubyのコードも開発、実行できたら夢が広がりますよね?

開発に至る経緯

Family mrubyは2019年から開発着手したプロジェクトです。2020のRubyKaigi Takeoutでも初期型について発表しました。

Family mrubyとは?

Family mruby
Kishima’s projects
今は昔、子供が最初に触れるプログラミング言語といえば、BASICという時代がありました。 制約は多いですが、パソコン以外にも、MSXやファミコンでBASICができるFamily BASICという製品もあり、そこからプログラミングの面白さを知り、プログラマーになった方もたくさん居られると思います。
そして現在は無料で大抵のプログラミング言語の開発環境はパソコンにインストールすることができる時代になりましたが、できることが多すぎて何をしたらよいのかわからなかったり、Hello Worldの先のゲームを作ったりするまでの環境構築ハードルが高かったり、するような気がしています。
そこで、マイコン一つでちょっとしたゲームなどをスクリプト言語で作れる環境を作ってみたい、と思って開発したのが、Family mrubyです。

特徴

  • mruby専用のエディタおよび実行環境をESP32上でスタンドアロンで実現
  • PSRAM付きESP32上での動作を想定
  • 専用の基板も設計
  • 映像はVGAで出力
  • 音声はアナログで出力
  • PS/2のキーボードで入力

休眠期

コロナの流行や、私生活のあわただしさもあり、しばらく開発がストップしてました。でも頭の片隅にはプロジェクトをリブートしたいという思いがずっとありました。

PicoRuby/MicroRubyとの出会い

私がしばらくプロジェクトから遠ざかっているうちに、@hasumikinさんがPicoRubyを開発されたことを知り、大いに刺激を受けました。

私の初期型では、ESP32へ素のmrubyを移植して、自作のエディタを動かすまででしたが、PicoRubyでは、より省リソースになり、ファイルシステムやUSBデバイス化などユーザ体験を向上させるための機能が多数実装されてました。

この設計思想を一部受け継いで、自分なりに自分が求める新しいmrubyの開発実行環境をまた1から作り直したくなりました。

PicoRubyはmruby/cのVMがベースになっていましたが、MicroRubyではmruby VMベースになると聞いて、mrubyでの経験も生かせるのではと思いました。

ある程度私生活も落ち着いてきた2025年から本格的にプロジェクトをリブートして開発に着手しました。

初期Family mrubyと新Family mruby は何が違うのか

プロジェクト休眠期中に転職をしてPM寄りから、コーディングもする仕事になって、多少開発の勘も戻ってきたこともあり、腕試しの意味も込めて、やりたいこと全部やる方針で設計しました。

初期型の構成は↓の図のような感じです。

初期型Family mrubyの構成

新型Family mrubyの構成は↓のようになってます。

新型Family mrubyの構成

mrubyで開発、実行できる世界を拡張できるようにOSとして再構築しました。

主な違いは以下の表にまとめてみました。PS/2キーボードやVGA出力がちょっと準備が面倒なので、USBに変えたり、Linux上でも開発できるようにしたり、と旧型で気になった部分を解消するように見直しています。

旧Family mrubyとの違い

Family mruby OS

新型Family mrubyでは、USBマウスもつなぐことできるようになります。それを生かして、昔のWindows3.1や私が親しんだFM-TOWNS OSをオマージュして、シンプルなWindow GUIを提供したいと考えました。

ESP-IDFのベースになっているFreeRTOSを生かせば、単にmrubyのコードを実行する環境としてだけでなく、コンカレントに複数のVMを実行できるはずです。

この新しい環境をFamily mruby OSと呼ぶことにしました。

前提

Family mruby OSを動かすためには、PicoRubyなどと比較して何倍ものメモリが必要なることが見込まれます。そのため、PSRAMを数MB以上搭載したESP32を前提としました。

Wi-Fiモジュール ESP32-S3-WROOM-1-N16R8: 半導体 秋月電子通商-電子部品・ネット通販
電子部品,通販,販売,半導体,IC,LED,マイコン,電子工作Wi-Fiモジュール ESP32-S3-WROOM-1-N16R8秋月電子通商 電子部品通信販売

mrubyが当初想定していた富豪的マイコン環境がこれなのかなと思ってます。

将来的にはESP32-P4にも対応できたらいいなと思ってます。

特徴

MicroRubyを採用

MicroRubyを主たるVMとすることで、コンパクトでダイナミックなコンパイル機能や、各種pico-gemなど、たくさんの恩恵を受けることができます。

MicroRubyは絶賛開発中で内部的には破壊的変更も発生するので、Forkはしないでうまく追従していくように努力が必要です。

より安定しているPicoRubyの採用も最初考えましたが、ベースのmruby/cがグローバル変数が前提の実装になっており、メモリ空間の分割が難しそうだったため断念してます。

MicroRubyの提供するマルチタスク機能も有効なので、FreeRTOSによる機械語レベルの並行動作に加えて、バイトコードレベルでの並行動作も変わらず使えるはずです。(現在は、割り込みハンドラではなくて、専用タスクから各VMのTickを呼び出す形にしてます)

マルチVM

​FreeRTOSは、リアルタイムOSであり、タスク機能を提供しています。タスクはいわゆるスレッド的なもので、それぞれがスタックをもって独立して動作することができます。

1タスクに1VMを載せ、タスクを複数生成することで、VMを並行で動かすことができます。そのためにはメモリ空間やHWリソースの分離などが必須になります。

VMをプロセスのように管理するために、必要な各種機能を実装しています。タスクのTLS(Thread Local Storage)を活用することで、VMのコンテキストを管理しています。

VMごとにメモリアロケータのハンドルを割り与えることで、1つのVMがアクセスできるメモリはあらかじめ確保したメモリの範囲に限定しています。こうすることで、メモリのフラグメンテーションがシステム全体に波及することを防いだり、1アプリのメモリが壊れても、他アプリへの影響しないようにしたり、などシステム全体の安定化に大きな効果があります。最悪、アプリの状態がおかしくなっても、タスクを強制的に終了させて、またメモリプールを割り与え直せば復帰できます。(もちろんC言語レベルでなにか不正なメモリアクセスしたり、HWの取り合いなど起こしたときは別の話になります)

性能が限られた環境でビジーループ的なことは極力避けたいので、イベント待ちはFreeRTOSのキュー機能で実装してます。アプリ側ではVM間イベントが届き次第即座に所定のメソッドがコールバックされるようしています。

基本的にメモリ分離、HWリソース分離ができて、バイナリサイズ、実行メモリサイズが適合すれば、mruby以外の言語も搭載可能です。現状はPoCとしてLuaを仮搭載してます。将来的にはMicroPythonも搭載してみたいです。

HWの抽象化

これだけ大規模な実装になるとマイコン上での開発はなかなか大変です。そのためLinuxでも実行できるようになると嬉しいです。そのために、HWやOSに関する機能は全て抽象化レイヤーを介してアクセスするようにしました。

その結果、Linux上でもESP32向けと同じコードを実行できるようになりました。ESP-IDFがLinux向けのビルドが可能なのと、描画用に採用しているLovyanGFXSDL2 on Linuxでも実行可能なので、それを生かしています。

抽象化レイヤーをしっかり実装することで、移植性が向上するので、将来的にWASMで実行したり、新しい設計の基板で実行したり、応用の幅が広がることも期待しています。

課題としては、既存のペリフェラルに関するpico-gemは、メモリの管理など、Family mruby OSの抽象化レイヤーに必ずしも適合しない場合があります。また、そもそもマルチVM環境での動作も想定されてないので、複数VMで動かないようにするために、何らかの排他機構も必要になるはずです。

GPIO、I2Cあたりは、HW抽象化レイヤー(HAL)を使いつつ、スレッドセーフな実装に書き直すべきかなと思っています。

開発場所

以下のOrganazationで開発してます。

ドキュメント等まだ全然整理できてないので、徐々に整えていきたいと思ってます。

Family mruby
Family mruby has 3 repositories available. Follow their code on GitHub.

実行環境

Family mrubyは、専用の基板か、Linuxでの実行を想定しています。

専用基板

まだ量産版はありませんが、開発基板を設計して、JLCPCBのPCBAで生産してみました。しかし、うまく動かなかったので、現在デバック中です。

大まかなブロック図は以下のようになってます。半分やけくそでESP32を2つ搭載してます。将来、ESP32-P4で専用ディスプレイ使えば1チップ化もできるかな、と妄想してます。

HW設計データはすべて公開してます。

GitHub - family-mruby/narya-board
Contribute to family-mruby/narya-board development by creating an account on GitHub.

実際の開発基板の写真です。BOMも間違いなく、きれいに実装できました。スルーホール部品の実装までやってもらったことはなかったので、勉強になりました。

Linux

ESP-IDFのLinuxシミュレーションおよび、SDK2とLovyanGFXを利用することで、fmrb-coreはほぼそのままに、Linux上でも実行できるようできました。

サブESP32に相当するものとして、別プロセスを立ち上げてます。SPIの代わりにソケットで独自プロトコルで通信してます。

Linux環境での構成

現状のデモ動画

Family mruby OSのデモ動画を撮りました。Linux環境でしか確認できてないので、基板のデバッグを完了させて、近いうちにこれをESP32でも同様に動かしていきたと考えています。

  • OSの管理機能と、壁紙やシステムバーの画面と、疑似Shellアプリをそれぞれ独立したMicroRuby VMで実行してます。Luaも独立して実行してます。
  • まだランチャーがないので、マウスのクリックに合わせて、疑似ShellとLuaのウィンドウを開いてます。
  • 疑似Shellアプリは、Shellとしての機能はまだ実装されてませんが、キー入力は疑似Shellアプリが動くMicroRubyVMへ送信して、画面に反映してます。
  • マウス操作もまだ実装不十分なので、十字キー入力で、Shellウィンドウを動かしてます。

デモ動画

Shell風アプリ(表示だけデモ)のコード

これは現状デモ用なので、STDIO対応などした上で将来大きく変更されると思います。どれくらいのコードでデモ動画のようなことが実現できているのかの参考になれば。

fmruby-core/main/app/default_app/mrblib/shell.app.rb at main · family-mruby/fmruby-core
Core of Family mruby OS. Contribute to family-mruby/fmruby-core development by creating an account on GitHub.
class ShellApp < FmrbApp
  def initialize
    super()
    @current_line = ""
    @history = []  # Line history
    @cursor_x = 0
    @cursor_y = 0
    @char_width = 6
    @char_height = 8
    @prompt = "> "
    @need_redraw = false
  end

  def on_create()
    @gfx.clear(FmrbGfx::WHITE)
    draw_window_frame
    draw_prompt
    @gfx.present
    puts "[ShellApp] on_create called"
  end

  def on_update()
    if @need_redraw
      redraw_screen
      @need_redraw = false
    end
    33 # msec
  end

  def draw_prompt
    # Draw all history lines
    @history.each_with_index do |line, i|
      x = @user_area_x0 + 2
      y = @user_area_y0 + 2 + (i * @char_height)
      @gfx.draw_text(x, y, line, FmrbGfx::BLACK)
    end

    # Draw current input line
    x = @user_area_x0 + 2
    y = @user_area_y0 + 2 + (@history.length * @char_height)
    @gfx.draw_text(x, y, @prompt + @current_line, FmrbGfx::BLACK)
  end

  def redraw_screen
    # Clear user area
    @gfx.fill_rect(@user_area_x0, @user_area_y0,
                    @user_area_width, @user_area_height, FmrbGfx::WHITE)
    draw_window_frame
    draw_prompt
    @gfx.present
  end

  def on_event(ev)
    puts "on_event: shell app"
    p ev

    if ev[:type] == :key_down
      handle_key_input(ev)
    elsif ev[:type] == :key_up
      handle_key_up(ev)
    end
  end

  def handle_key_up(ev)
    keycode = ev[:keycode]

    # Window position control with arrow keys
    x = 0
    y = 0
    case keycode
    when 82  # up
      y = -10
    when 81  # down
      y = 10
    when 80  # left
      x = -10
    when 79  # right
      x = 10
    end

    if x != 0 || y != 0
      set_window_position(@pos_x + x, @pos_y + y)
    end
  end

  def handle_key_input(ev)
    keycode = ev[:keycode]
    puts "[Shell] keycode=#{keycode}"

    # Enter key (keycode 13 = CR)
    if keycode == 13
      handle_enter
      return
    end

    # Backspace key (keycode 8 = BS)
    if keycode == 8
      handle_backspace
      return
    end

    # Ignore arrow keys (79-82)
    if keycode >= 79 && keycode <= 82
      return
    end

    # Convert keycode to character (ASCII printable range)
    if keycode >= 32 && keycode <= 126
      char = keycode.chr
      @current_line += char
      @need_redraw = true
    end
  end

  def handle_enter
    puts "[Shell] Command: #{@current_line}"

    # Add current line to history
    @history << (@prompt + @current_line)

    # TODO: Execute command here and add output to history

    # Clear current line
    @current_line = ""

    # Check if we need to scroll
    max_lines = @user_area_height / @char_height
    if @history.length >= max_lines - 1
      # Remove oldest line to make room
      @history.shift
    end

    @need_redraw = true
  end

  def handle_backspace
    if @current_line.length > 0
      @current_line = @current_line[0...-1]
      @need_redraw = true
    end
  end

  def on_destroy
    puts "[ShellApp] Destroyed"
  end

end

# Create and start the system GUI app instance
puts "[ShellApp] ShellApp.new"
begin
  app = ShellApp.new
  puts "[ShellApp] ShellApp created successfully"
  app.start
rescue => e
  puts "[ShellApp] Exception caught: #{e.class}"
  puts "[ShellApp] Message: #{e.message}"
  puts "[ShellApp] Backtrace:"
  puts e.backtrace.join("\n") if e.backtrace
end
puts "[ShellApp] Script ended"

これまでにやったこと

  • OS Framework
    • 基本的な機能の実装
    • FreeRTOSタスクでのマルチMicroRuby VM起動
      • マルチタスク化にあたって、メモリ空間の分離や、Tickの挙動などで、問題があったため、パッチをあててます。最終的にどう扱うかはもう少し検討したいところです。
    • FreeRTOSタスクでのゲストVM(Lua)起動
    • メモリプール管理
      • VMごとに独立したメモリプールを与えるように設計してます。アロケータについては、MicroRuby内ではデフォルトのestallocを利用し、その他ではTLSFを利用してます。estallocもマルチタスク環境で動くように設定してます。
    • コア間連携の通信プロトコルの基礎
      • 現状はソケットで実装
    • 簡易な描画API
      • LovyanGFXを前提としたRPCを実装
  • Linux向け
    • SDL2+LovyanGFXの実行環境
      • Audioは未実装
  • 実機向け
  • 基板
    • 試作基板の設計実装
      • ただし不具合あり
  • アプリケーション
    • 開発用の描画サンプルのみ

今後の主な開発アイテム

  • OS Framework
    • HIDのイベント管理/STDIOの差し替え
    • Window管理(表示順、移動)
    • アプリのライフサイクル
    • フルスクリーンモード
    • GUIデザインの刷新
  • Linux向け
    • Graphics系の拡充
    • Audio系の実装
  • 実機向け
    • コア間通信の実装
    • USB HOST対応(キーボード、マウス)
    • サブESP32のファームウェア実装
  • 基板
    • ESP32ボードのデバッグ
    • 量産型の設計、試作
  • アプリケーション
    • シェル
    • ファイルマネージャ
    • GPIOやI2Cデバイス連携のサンプルアプリ
    • etc...

今後

今後は、まずはRubyKaigiへのCFP投稿、うまくいけば発表を目指して完成度を上げていきます。

開発アイテムは多岐にわたり、発展課題も含めると、とても数カ月で完了する内容ではないので、来年はずっと少しずつ開発を進めて完成度を上げていくつもりです。

ある程度、安定したら基板もセットで、イベントで販売したりしてみたいですね。

このプロジェクトが形になってきたら、色々やってみたいことがあります。

  • VM間通信の強化
  • MicroPythonも取り込んで、MicroPython資産をどうにかして生かす
  • ドット絵エディタや音楽エディタも実装して、ゲーム開発環境として成立させる
  • GUIでI2CとかGPIOも操作できるようなアプリを作る
  • WiFiもつないで、ネットでアプリをシェアしたりDLできる仕組みを作る
  • Tab5やESP32-S3搭載の液晶パネル付きデバイスへの移植
  • LCD対応して、ミニキーボードも搭載して、PicoCalc風のデバイスを作る
  • Emscriptenでビルドできるようにして、ブラウザ上でも実行する。センサーとかの接続は、コンパニオンボードを開発してそれで行う

参考にしたプロジェクト

PicotronのFantasy Desktop

開発中に存在を知って、マイコン向けではないですが、UIのイメージが自分の理想にとても近かったです。

レトロな8bit風ゲーム開発環境です。

Picotron by Lexaloffle

Read more

PicoRubyとMicroRubyの歩き方

PicoRubyとMicroRubyの歩き方

この記事は、mrubyファミリ (組み込み向け軽量Ruby) Advent Calendar 2025に参加してます。 今日は、PicoRubyとMicroRubyについて、具体的にどんな技術なのか、どんな活用が可能なのか、いまいち想像がついていない方向けへの情報を提供できればと思っています。 PicoRubyとは PicroRubyとは、hasumikinさんが開発されたオリジナルのmrubyバイトコードコンパイラとmruby/cのVMを組み合わせた、Raspberry Pi Picoのようなメモリのリソースが少ない環境向けのRuby環境です。 mruby単体でもコンパイラやVMを持っていますが、それだとRaspberry Pi Picoで動かすにはちょっとメモリが足りなかったのもあり、開発されたようです。 PicoRubyは、限られたリソースでmruby互換のコードを動かすだけでなく、以下のような特徴を備えてます。 * フラッシュメモリ上のファイルシステムをFATで実装 * 動的コンパイルの仕組みを導入、ファイルシステム上のコードをrequireも可能に *

By kishima
PicoRuby向けのM5Unified/GFX mrbgemを作る話

PicoRuby向けのM5Unified/GFX mrbgemを作る話

この記事は、mrubyファミリ (組み込み向け軽量Ruby) Advent Calendar 2025に参加しています。 本日の記事では、先日のTokyuRuby会議16 でLTした内容をブログ記事としても残しておきたいと思います。 ESP32とPicoRuby PicoRubyはもともと、@hasumikin さんがRaspberry Pi Picoをターゲットとしてスタートしたプロジェクトです。 その後、ESP32への対応も@Yuuuさんの尽力もありなされています。(picoruby-esp32) PicoRubyをESP32のcomponentsとしてビルドしてリンクするような形でポーティングされています。 現在は主なESP32シリーズで、PicoRubyを動かすことができるようになっています。 M5Stackシリーズを使いたい ESP32を使いたい理由の一つとして、市場にいろいろなバリエーションのESP32をコアとしてパッケージングされた製品があるという点があると思います。 M5Unified/GFXを使いたい M5シリーズにはディスプレイやボタンや

By kishima
チキントマト煮と鶏レバーペーストのレシピの記憶

チキントマト煮と鶏レバーペーストのレシピの記憶

昨日はTokyuRuby会議16があり、そこで差し入れ料理の投票で1位を取ることができました。食べて頂いたみなさん、ありがとうございました。 長年スタッフ側で参加してて、いつか飯提供したい気持ちをずっと、温めてたんですが、今回は一般参加で飯提供の念願叶った上に、飯王までとれて最高でした。 その時のレシピを自分のためにも残しておきます。 分量は目分量でやってたので、そのあたりは適宜補完ください。 今回の料理の品目アイデア出しのブレストにはChatGPTにも手伝っていただきました。ありがとう。 チキントマト煮 材料 * 国産鶏むね肉(ハナマサで購入) * トマト缶 * 赤ワイン * たまねぎ * にんじん * にんにく * マッシュルーム * ローリエの葉 * ローズマリー(生) * 塩 * コンソメの素 * オリーブオイル * サラダオイル 下処理 鶏肉はまず塩を強めに降って揉み込んで数十分おいておく 塩で処理した鶏肉は水で洗い流して、余分な油を切り落として、一口サイズに切り分ける 切り分けた肉は皮を背にして、サラダオイルを引いたフラ

By kishima