世界を動かす技術を、日本語で。

Mousefood – マイクロコントローラー向けの組み込み端末UIを構築する

概要

Mousefood は、 no-std 環境対応の Ratatui 向け組み込みグラフィックスバックエンド。 組み込み機器での テキストUI描画 を強力にサポート。 豊富なフォント・カラーテーマ、主要EPDドライバ対応。 シミュレータ や多様なハードウェアでの動作実績。 MIT/Apache 2.0ライセンス で幅広い利用が可能。

Mousefood概要とクイックスタート

  • Mousefood は、 Ratatui のための 組み込みグラフィックスバックエンド 提供。
  • no-std 環境でも動作可能、リソース制約下でのUI描画を実現。
  • 依存追加cargo add mousefoodコマンドで導入。
  • 基本的なセットアップ例
    • モックディスプレイや各種ディスプレイドライバ(ILI9341, ST7735, SSD1306等)に対応。
    • EmbeddedBackendTerminalの組み合わせで描画環境構築。
use mousefood::embedded_graphics::{mock_display::MockDisplay, pixelcolor::Rgb888};
use mousefood::prelude::*;
use ratatui::widgets::{Block, Paragraph};
use ratatui::{Frame, Terminal};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut display = MockDisplay::<Rgb888>::new();
    let backend = EmbeddedBackend::new(&mut display, EmbeddedBackendConfig::default());
    let mut terminal = Terminal::new(backend)?;
    terminal.draw(draw)?;
    Ok(())
}

fn draw(frame: &mut Frame) {
    let block = Block::bordered().title("Mousefood");
    let paragraph = Paragraph::new("Hello from Mousefood!").block(block);
    frame.render_widget(paragraph, frame.area());
}

特殊文字・フォント対応

  • embedded-graphics 標準フォントは 文字種が限定的 (ASCII, ISO 8859, JIS X0201)。
    • Ratatui のウィジェット描画に必要な 罫線・点字・特殊記号 が不足。
  • Mousefood はデフォルトで embedded-graphics-unicodefonts を使用し、 広範囲な文字 へ対応。
  • 省メモリ・高速化 目的で、フォント機能を 無効化可能
    • 代替案として ibm437 フォントも利用可能(記号サポートは限定的)。

太字・斜体フォントサポート

  • 太字・斜体 修飾対応。
    • EmbeddedBackendConfig対応フォント を指定。
    • レギュラーフォント のみ指定時は フォールバック 利用。
    • 全フォントは同一サイズ 必須。
use mousefood::embedded_graphics::{mock_display::MockDisplay, pixelcolor::Rgb888};
use mousefood::{EmbeddedBackend, EmbeddedBackendConfig, fonts};
use ratatui::Terminal;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut display = MockDisplay::<Rgb888>::new();
    let config = EmbeddedBackendConfig {
        font_regular: fonts::MONO_6X13,
        font_bold: Some(fonts::MONO_6X13_BOLD),
        font_italic: Some(fonts::MONO_6X13_ITALIC),
        ..Default::default()
    };
    let backend = EmbeddedBackend::new(&mut display, config);
    let _terminal = Terminal::new(backend)?;
    Ok(())
}

カラーテーマ

  • 色テーマEmbeddedBackendConfigcolor_themeでカスタマイズ可能。
    • デフォルトは ANSIパレット
  • テーマ例
use mousefood::{ColorTheme, EmbeddedBackend, EmbeddedBackendConfig};
use mousefood::embedded_graphics::{mock_display::MockDisplay, pixelcolor::Rgb888};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut display = MockDisplay::<Rgb888>::new();
    let theme = ColorTheme {
        background: Rgb888::new(5, 5, 5),
        foreground: Rgb888::new(240, 240, 240),
        yellow: Rgb888::new(255, 200, 0),
        ..ColorTheme::ansi()
    };
    let config = EmbeddedBackendConfig {
        color_theme: theme,
        ..Default::default()
    };
    let backend = EmbeddedBackend::new(&mut display, config);
    Ok(())
}
  • 組み込み済みテーマ
    • ColorTheme::ansi():標準ANSIカラー(デフォルト)
    • ColorTheme::tokyo_night():Tokyo Nightダークテーマ(青・紫系)

シミュレーター利用

  • embedded-graphics-simulator クレートを使い、 PC上でMousefood動作をシミュレート 可能。
  • 実行例
    • git clone https://github.com/ratatui/mousefood.git
    • cd mousefood/examples/simulator
    • cargo run

EPD(電子ペーパーディスプレイ)対応

  • WeAct Studio製EPD (weact-studio-epdドライバ):
    • epd-weactフィーチャ有効化で対応。
    • flush_callbackパターンでディスプレイ更新。
use mousefood::prelude::*;
use weact_studio_epd::graphics::Display290BlackWhite;
use weact_studio_epd::WeActStudio290BlackWhiteDriver;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // SPI・GPIO・ディレイプロバイダ設定
    let mut driver = WeActStudio290BlackWhiteDriver::new(spi_interface, busy, rst, delay);
    let mut display = Display290BlackWhite::new();
    driver.init()?;
    let config = EmbeddedBackendConfig {
        flush_callback: Box::new(move |d| {
            driver.full_update(d).expect("epd update failed");
        }),
        ..Default::default()
    };
    let backend = EmbeddedBackend::new(&mut display, config);
    let _terminal = Terminal::new(backend)?;
    Ok(())
}
  • Waveshare製EPD (epd-waveshareドライバ):
    • epd-waveshareフィーチャ有効化で対応。
    • examples/epd-waveshare-demo参照。

パフォーマンス・ハードウェアサポート

  • 組み込み機器のフラッシュ容量は限定的、フォント機能利用時は opt-level = 3 推奨(バイナリサイズ増加傾向)。
  • ハードウェア非依存設計、主要MCUでの動作実績:
    • ESP32 (Xtensa)
    • ESP32-C6 (RISC-V)
    • STM32
    • RP2040
    • RP2350

ドキュメント・コントリビューション

  • APIドキュメントdocs.rsで公開。
  • コントリビューション歓迎、PR前にガイドライン要確認。

Mousefood採用プロジェクト

  • Tuitar :ポータブルギタートレーニングツール
  • Mnyaoo32 :ESP32でIRCメッセージを消費するユニークなアプリ
  • Phone-OS :ESP32 CYD向けのモダンな電話OS
  • 新規プロジェクト掲載希望 はPRで追加可能

ライセンス

  • Apache 2.0 および MITデュアルライセンス 採用。

Hackerたちの意見

こんにちは、オルフン。CYD(チープイエローディスプレイ)と一緒に使えるかな?

ページの下の方に「Phone-OS - ESP32 CYD用のモダンな電話OS」って書いてあるから、どうやらサポートされてるみたいだね。

今日学んだこと: https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display

たぶんそうだね。ちょっと確認したら、もうembedded-graphicsを使っているから、Mousefoodを直接接続できるよ。ただ、タッチスクリーンはちょっと厄介かも。イベントハンドラー側で少しハッキングが必要かもしれない。でも、端末のセルの抽象に座標をマッピングすれば、たぶんうまくいくと思う。

いいね!最近ラズベリーパイPicoを勉強し始めたところなんだ。Pico 2/2WとMousefoodに使える特定のディスプレイを誰かおすすめしてくれない?

ああ、これが私のラストの学び方だ!バブルティーとMousefoodのおかげでGoを学んだんだ(組み込みシステムプログラマーとしての仕事とターミナルへの愛を組み合わせてる)。

バブルティーは本当にクールだね。これがほとんどのインタラクティブCLIの作り方なの?

「Mousefood - Ratatuiのためのno-std組み込みグラフィックスバックエンド!」だから100% Rustだね。ESP32、RPi2040、さらにはSTM32でも動くよ。いくつかのディスプレイが言及されていて、e-inkも含まれてる。

これすごい!Ratatuiが組み込みで使えるのはめっちゃクールだね!非同期で組み込みに対応するのかな、例えばembassyとか。

その通り、他の埋め込みRustアプリケーションでも動くよ。バックエンドは、embedded-graphicsライブラリとRatatuiウィジェットレンダラーの間の橋渡しをするだけだから。

マインクラフトのMOD、ComputerCraftのUIスタイルを思い出すな。

あの美学を復活させるぞ!

ComputerCraftは、僕がコーディングを学ぶきっかけの一部だった。最初に最も安全なドアロックプログラムを作ろうとしたときに、パスワードハッシュについて学んだんだ。最初は生のSHA-256を使ってたけど、フォーラムで誰かがPBKDF2を教えてくれた... あの頃が懐かしいな。

Embedded-graphicsには、スペースを節約するために非常に限られた文字セットのビットマップフォントが含まれている(ASCII、ISO 8859、またはJIS X0201)。これにより、Ratatuiのウィジェットのほとんどを描画することが不可能になる。Ratatuiはボックス描画のグリフやブレイル、その他の特殊文字を多用しているからね。ビットマップディスプレイがあれば、フォントベースのハックに頼らずに線やその他のものを描くだけで済む。

確かに、でもそれは本題とは関係ないね。ファンシーなカスタムフォントを使ったテキストベースのグラフィックスは、めちゃくちゃ効率的なんだ。これが、64KiB未満のRAMを持つマシンで『The Last Ninja』や『Turrican』の素晴らしいグラフィックスを実現した方法なんだよ。最近の埋め込みデバイスでも同じことが言える。テキストに制約をかけることで、実行時パフォーマンスと開発者の生産性が両方とも向上するんだ。

みんな、クレートに興味を持ってくれてありがとう!今、YouTubeでライブ中(メンテナンスとテストをしてる)。質問があれば気軽に参加してね! https://www.youtube.com/watch?v=PoYEQJbYNMc

すごく面白いプロジェクトだけど、組み込みでのRustはどうなんだろう。まだ試してないけど、C/C++と比べた経験がある人いる?

僕の経験はArduinoの配線と、Rustのembassyを使ったものだね。趣味の視点からだけど。Rustの組み込みはHALレイヤーを使ってて、ベンダーに依存しないんだ(C++のテンプレートみたいにコンパイル時に解決される)。まだすべてをカバーしてるわけじゃないけど、GPIO、SPI、I2Cなどの基本は押さえてる。これでN個のドライバーとM個のベンダーSDKの問題を避けられる。I2CドライバーはHALに対して書けばいいし、アプリケーション内で特定のHALをインスタンス化するだけ。ベンダーロックインも減るしね。セットアッププロセスでは、どのピンを使うかを選ぶために少しチップ特有のコードが必要だけど、それを越えればベンダーに依存しない状態になれる。そういえば、APIはコンパイル時に周辺機器の設定が有効かどうかを確認するために、ちょっとしたパターン(typestateと呼ばれる)を使ってる。GPIO2を「取る」と、もう一度取ることはできないから、同じピンを二つの異なるコードに間違って渡すことはない。ドライバーが出力として設定されたピンを期待している場合、入力ピンを渡すこともできない(本当に必要なら、ピンを「ランタイムで動的」に変えることもできるけど、逃げ道はある)。それから、embassyフレームワークがある。これはRTOSの代替で、Rustのasync/awaitタスクを静的に割り当ててスケジュールするんだ。いくつかの優先度レベルのスケジューラーがあって、内部ではスケジューラー間で協調的にタスクを処理するけど、割り込みを使ってスケジューラー間でプリエンプトすることもできる。asyncを使うことで、組み込みでのGPIO待機などが楽になる。自分で割り込みハンドラーを書く必要もないし、チップを低電力状態にするタイミングを考える必要もない。スケジューラーとHALのfutureがそれをやってくれるからね。とはいえ、C++はまだチュートリアルやドライバー、チップサポートが充実した大きなエコシステムだ。でも、Rustの世界でも急速に進化してる。例えば、ESP32シリーズはRustで公式のベンダーサポートがあるし、他にも1、2社がサポートを追加中だよ(もしくはそのプロセスにある)。RP2040などの人気チップもサポートされてるし、NRFやST用のHALも見たことがあるけど、触ったことはない。一般的なチップのドライバーも存在するから、少なくとも試してみる価値はあると思う。ただ、使いたいものに対してどのHALやドライバーがあるか、事前に確認して、その完成度もチェックした方がいいよ。2年前、ESP32でI2Sのことをやりたかったけど、まだサポートがなかった。1年前にはサポートがあったけど、DMA関連はまだちょっと不便だった。またそのうちチェックしてみるつもり。

でも、最近のTUIはみんなreact/solid(claude code, opencode)だし、これもtypescriptでやるべきだよね(神様、そうじゃなくてよかった。なんで人はウェブをどこにでも持ち込むのか、全然理解できない)。