Rust × ESP32開発が本格化:esp-hal 1.0のインパクトと始め方

2025年10月、ESP32向けRust HAL「esp-hal」がバージョン1.0.0に到達しました。これはマイコンベンダーが公式にサポートする初の安定版Rust HALであり、組込みRust界にとって画期的な出来事です。この記事では、esp-hal 1.0の意義から、実際にLチカを動かすコード、Embassyによる非同期化、よくあるハマりどころまでを、手を動かせるレベルで解説します。

esp-hal 1.0.0とは

esp-halはEspressifが公式に開発・メンテナンスしている、ESP32ファミリー向けのRustハードウェア抽象化レイヤー(HAL)です。2025年2月にベータ版、10月に正式リリースされました。「1.0」という数字が意味するのは、APIに対するセマンティックバージョニングの約束です。つまり、安定版として公開された型・関数のシグネチャは、1.x系の間は破壊的変更を受けません。これにより、業務プロダクトでも安心して依存できるようになりました。

1.0で安定化された機能:

  • HAL初期化(esp_hal::init)と設定
  • GPIO、UART、SPI、I2Cドライバ
  • 時間モジュール(Instant、Duration、Rate)
  • async/blockingの両方のドライバモード
  • Embassyなどの非同期ランタイムとの互換性

その他の機能はunstableフィーチャーフラグの下に配置され、段階的に安定化される予定です。unstableな機能を使うこと自体は可能ですが、その部分のAPIは将来のマイナーバージョンで変わる可能性がある、という位置づけです。次の大きな安定化ターゲットはWi-Fi/Bluetooth/ESP-NOWスタック「esp-radio」です。

2つの開発アプローチ:no_std と std

ESP32でRustを使う方法は大きく2つあります。どちらを選ぶかで、ツールチェーンも書き方も変わるため、最初の分岐点として最も重要です。

1. no_std + esp-hal(ベアメタル)

OSを使わず、ハードウェアを直接制御するアプローチです。バイナリサイズが小さく、起動が速く、リアルタイム性が必要な場面に適しています。esp-hal 1.0はこのパスの安定版です。標準ライブラリ(std)は使えず、ヒープ確保も自分でesp-allocなどを足して初めて使えます。

2. std + esp-idf-hal(IDF上で動作)

ESP-IDF(C言語のESP32公式フレームワーク)の上にRust標準ライブラリを載せるアプローチです。スレッド、ネットワーキング、ファイルシステムなど標準ライブラリの機能がフルに使えます。バイナリは大きくなりますが、Wi-Fi/BLEを含む既存のIDF資産をそのまま使えるのが強みです。

どちらを選ぶべきか

観点no_std + esp-halstd + esp-idf-hal
バイナリサイズ小さい大きい
起動速度速いやや遅い
Wi-Fi / BLEesp-radio(安定化進行中)IDF経由で安定利用可
標準ライブラリ使えない(no_std)フル利用可
非同期Embassyと好相性スレッド/async両対応
向いている用途低消費電力・リアルタイム制御ネットワーク機器・複雑なアプリ

迷ったら、Wi-Fi/BLEを今すぐ本格的に使いたいなら std + esp-idf-hal、低レイヤを学びたい・省電力やリアルタイム制御が主目的なら no_std + esp-hal、と覚えておくとよいでしょう。

環境構築:始め方

ここからは no_std + esp-hal を例に、実際に手を動かす手順を示します。重要なポイントは、ターゲットのアーキテクチャによってツールチェーンが分かれることです。

  • RISC-Vチップ(ESP32-C2/C3/C6、ESP32-H2):標準のRustツールチェーンでそのままコンパイル可能。espupは不要。
  • Xtensaチップ(ESP32、ESP32-S2、ESP32-S3):Espressif提供のカスタムツールチェーンが必要で、espupで導入します。

RISC-V系(ESP32-C3など)の場合

# Rust本体(未導入なら)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# C3向けターゲットを追加(imc。C6/H2はimac)
rustup target add riscv32imc-unknown-none-elf

# フラッシュ&モニタツール
cargo install espflash

# プロジェクト雛形を生成(no_std用の新しいテンプレートツール)
cargo install esp-generate
esp-generate --chip esp32c3 my-project

Xtensa系(ESP32 / S3など)の場合

# ESP32用ツールチェーンマネージャ
cargo install espup
espup install

# 生成された環境変数読み込みスクリプトをシェルに反映
. $HOME/export-esp.sh

# あとはRISC-Vと同様に esp-generate でプロジェクト生成
cargo install esp-generate
esp-generate --chip esp32 my-project

std側(esp-idf-hal)を使いたい場合は、cargo install cargo-generate 後に cargo generate esp-rs/esp-idf-template でstd用テンプレートを生成します。

最初のコード:LチカをRustで

esp-hal 1.0でのGPIO制御の基本形です。esp-generate が生成する雛形がそのまま動くので、ここでは中身を理解するためにLチカの最小構成を示します。

Cargo.toml(依存関係の例)

[dependencies]
esp-hal = { version = "1.0.0", features = ["esp32c3"] }
esp-backtrace = { version = "0.15", features = ["esp32c3", "panic-handler", "println"] }
esp-println = { version = "0.13", features = ["esp32c3"] }

src/main.rs

#![no_std]
#![no_main]

use esp_hal::clock::CpuClock;
use esp_hal::delay::Delay;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::main;
use esp_println::println;
use esp_backtrace as _;

#[main]
fn main() -> ! {
    // クロックを最大に設定してHALを初期化
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    // オンボードLED(基板により GPIO 番号は異なる)
    let mut led = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
    let delay = Delay::new();

    println!("blink start");

    loop {
        led.toggle();
        delay.delay_millis(500);
    }
}

ビルドして実機に書き込むのは、テンプレートに設定済みのランナー経由で1コマンドです。

# ビルド・フラッシュ・シリアルモニタまで一気に
cargo run --release

※ GPIO番号・依存クレートのバージョンはチップや時期で変わります。最終的な正解はesp-generateが生成した雛形なので、APIの細部は生成物とesp-halのdocs.rsで確認してください。

非同期で書く:Embassyとの組み合わせ

esp-hal 1.0の大きな魅力が、非同期ランタイムEmbassyとの統合です。esp-hal-embassyを足すと、async/awaitで割り込み駆動の処理を、まるで普通の非同期Rustのように書けます。複数のセンサを並行してポーリングするような用途で、状態機械を手書きするより遥かに読みやすくなります。

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_hal::clock::CpuClock;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::timer::systimer::SystemTimer;
use esp_backtrace as _;

#[esp_hal_embassy::main]
async fn main(_spawner: Spawner) {
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    // Embassyのタイムドライバを起動
    let systimer = SystemTimer::new(peripherals.SYSTIMER);
    esp_hal_embassy::init(systimer.alarm0);

    let mut led = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());

    loop {
        led.toggle();
        // delayと違い、待っている間CPUはスリープ/他タスク実行できる
        Timer::after(Duration::from_millis(500)).await;
    }
}

ブロッキングのDelayはその間CPUを占有しますが、Timer::after().awaitは待機中に他のタスクへ実行を譲ります。これがasyncにする最大のメリットです。

LLVM Xtensaアップストリーム化

ESP32・ESP32-S2・ESP32-S3が採用するXtensaアーキテクチャのLLVMサポートが、本家LLVMへのアップストリーム化が進んでいます。基本ISAの大部分はすでにLLVM本体に取り込まれており、将来的にはEspressifのカスタムLLVMフォークなしで、標準のRustツールチェーンからXtensaターゲットにコンパイルできるようになります。そうなればespupすら不要になり、RISC-Vチップと同じ手軽さでESP32が扱えるようになります。

なお、RISC-VベースのESP32-C系/H系チップはすでに標準ツールチェーンでコンパイル可能です。これから新規に始めるなら、ツールチェーンのハマりが少ないESP32-C3やC6から入るのがおすすめです。

よくあるハマりどころ

  • Xtensaターゲットがビルドできない. $HOME/export-esp.sh をシェルに読み込み忘れているケースが大半。新しいターミナルを開くたびに必要です。
  • フラッシュ時にポートが見つからない:Linuxではdialoutグループへの追加(sudo usermod -aG dialout $USER)と再ログインで解決することが多いです。
  • unstableなAPIでコンパイルエラー:該当機能のunstableフィーチャーをCargo.tomlで有効化する必要があります。エラーメッセージに必要なフィーチャー名が出ます。
  • リンカエラー / メモリ不足--releaseでビルドするとサイズと最適化が改善します。開発中もサイズが厳しい場面ではopt-level = "s"を検討します。

よくある質問(FAQ)

Q. C/C++(ESP-IDF)から乗り換える価値はある?

メモリ安全性(バッファオーバーフローやuse-after-freeをコンパイル時に排除)と、強力な型・所有権による設計の堅牢さが得られます。一方で、Wi-Fi/BLEを多用する既存資産が多い現場では、まだIDF(C)側の方が情報も枯れています。新規・低レイヤ・安全性重視ならRust、という判断がバランス良いでしょう。

Q. どのチップから始めるべき?

ツールチェーンが標準のままで済むRISC-V系(ESP32-C3 / C6)が最も導入が楽です。Xtensa系のESP32/S3はespupが必要な分、最初の一歩がやや重くなります。

Q. esp-hal と esp-idf-hal、結局どっち?

上の比較表のとおりです。「OSなしで軽く速く」ならesp-hal(no_std)、「標準ライブラリとIDF資産をフルに使いたい」ならesp-idf-hal(std)です。

関連記事

  • ESP32の最新動向まとめ(2025-2026):新チップ・Wi-Fi 6・AI対応まで完全解説
  • ESP32-S3でTinyML入門:エッジAI推論を実装する方法

まとめ

esp-hal 1.0.0のリリースにより、ESP32でのRust開発は「実験的」から「プロダクション対応」へと段階が上がりました。RISC-V系チップなら標準ツールチェーンでLチカまで数分、Embassyを足せば非同期処理も自然に書けます。メモリ安全性とゼロコスト抽象化というRustの利点を、IoTデバイス開発で活かせる時代が来ています。C/C++に慣れた組込みエンジニアも、ぜひRust on ESP32を試してみてください。

この記事を書いた人

安河内 竜二(Ryuji Yasukochi) — セキュリティ研究者・組込みエンジニア。車載地図ソフトの脆弱性 CVE-2026-33524 / CVE-2026-33666 の発見者。当ブログの記事はすべて本人の実測・実装に基づく一次情報です。詳しいプロフィール

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

IP: 取得中...
216.73.216.95216.73.216.95