はじめに
これまで、マイコンは Raspberry Pi Pico 2 W を使ってきましたが、別のマイコンも試してみたくなり、ESP32 を触ってみることにしました。
また、普段は MicroPython で書いていますが、Rust に結構興味があるので、今回は Rust で動かしてみます。
ボード上の LED で L チカするところまでやってみます。
ESP32 を開発している Espressif が Rust での book を公開しているので、それに沿ってやっていきます。
環境
- 開発ボード:
Freenove ESP32 WROOM Board - ホストPC:
Mac- Rust でクロスコンパイルできるので、たぶんなんでも大丈夫です
build target について
ESP32 シリーズは、チップによって CPU のアーキテクチャが違います。
今回使う ESP32-WROOM-32E は、データシートに
Built around ESP32 series of SoCs, Xtensa® dual-core 32-bit LX6 microprocessor
とあるので、中身は無印の ESP32(Xtensa)だとわかります。
RISC-V のチップであれば、
$ rustup target add riscv32imc-unknown-none-elf # ESP32-C2, ESP32-C3
$ rustup target add riscv32imac-unknown-none-elf # ESP32-C6, ESP32-H2
のように、通常の Rust のツールチェーンに target を追加するだけでビルドできます。
どちらも Tier 2 の target になっています。
Xtensa の場合は Rust(というよりもバックエンドの LLVM)で、まだ十分にサポートされていないので、Espressif が fork した Rust のコンパイラを使う必要があります。
これをインストールするためのツールとして、espup が用意されています。
espup によって、
- Xtensa に対応した、Espressif の Rust の fork
- Xtensa に対応した、LLVM の fork
- リンクのための GCC
がインストールされます。
やってみる
ツールチェーンのインストール
まずは、上で説明した通り Xtensa に対応するために espup をインストールします。
$ cargo install espup --locked
$ espup install --targets esp32
[info]: Installing the Espressif Rust ecosystem
[info]: Checking Rust installation
[info]: Installing Xtensa Rust 1.97.0.0 toolchain
[info]: Installing GCC (xtensa-esp-elf)
[info]: Installing Xtensa LLVM
--targets を指定しない場合は、すべてのチップ向けのツールチェーンがインストールされるので、今回は esp32 だけにしています。
インストールが終わると、esp という名前のツールチェーンが追加されます。
$ rustup toolchain list
stable-aarch64-apple-darwin (active, default)
esp
また、espup install を実行すると、ホームディレクトリに export-esp.sh が作られます。
export LIBCLANG_PATH="/Users/tsuzuki-takaaki/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-21.1.3_20260408/esp-clang/lib"
export PATH="/Users/tsuzuki-takaaki/.rustup/toolchains/esp/xtensa-esp-elf/esp-16.1.0_20260609/xtensa-esp-elf/bin:$PATH"
rustc や cargo は、rustup が rust-toolchain.toml を見て esp ツールチェーンのものを呼び出してくれますが、リンクに使う GCC に関しては、rustup が切り替えてくれないので、このスクリプトを読んで、PATH を通す必要があります。
このファイルは、ターミナルを開くたびに読み込む必要があります。
ツールのインストール
Rust での ESP32 の開発が簡単になるように、ツールが用意されているので、それをインストールします。
-
esp-generate -
espflash
$ cargo install esp-generate --locked
$ cargo install espflash --locked
プロジェクトの作成
esp-generate を使ってプロジェクトを作ります。
$ esp-generate --headless -o esp32 -o esp32-wroom-32e blinky
esp-generate は、何も指定しないと TUI でオプションを選ぶ形になりますが、今回は --headless で直接指定しています。
-o esp32 でチップを、-o esp32-wroom-32e でモジュールを指定しています。
オプションの一覧は、
$ esp-generate list-options
でみることができます。
blinky % tree
.
├── build.rs
├── Cargo.toml
├── rust-toolchain.toml
└── src
├── bin
│ └── main.rs
└── lib.rs
3 directories, 5 files
rust-toolchain.toml で、esp ツールチェーンを使うように指定されています。
[toolchain]
channel = "esp"
そのため、このディレクトリの中では esp ツールチェーンが使われます。
$ rustup toolchain list
stable-aarch64-apple-darwin (default)
esp (active)
.cargo/config.toml では、build target と、cargo run したときに実行するコマンドが指定されています。
# Keep compilation settings in this file and user/runtime settings in `esp-config.toml`.
include = ["esp-config.toml"]
[target.xtensa-esp32-none-elf]
runner = "espflash flash --monitor --chip esp32"
[build]
rustflags = [
"-C", "link-arg=-nostartfiles",
]
target = "xtensa-esp32-none-elf"
[unstable]
build-std = ["core"]
runner に espflash flash --monitor が指定されているので、cargo run でビルドから書き込みまででき、書き込んだあとはそのままシリアルモニタになります。
L チカする
esp-generate によって、以下のファイルが生成されます。
#![no_std]
#![no_main]
#![deny(
clippy::mem_forget,
reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
holding buffers for the duration of a data transfer."
)]
#![deny(clippy::large_stack_frames)]
use esp_hal::clock::CpuClock;
use esp_hal::main;
use esp_hal::time::{Duration, Instant};
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();
#[allow(
clippy::large_stack_frames,
reason = "it's not unusual to allocate larger buffers etc. in main"
)]
#[main]
fn main() -> ! {
// generator version: 2.0.0
// generator parameters: -o esp32 -o esp32-wroom-32e
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
// The following pins are used to bootstrap the chip. They are available
// for use, but check the datasheet of the module for more information on them.
// - GPIO0
// - GPIO2
// - GPIO5
// - GPIO12
// - GPIO15
// These GPIO pins are in use by some feature of the module and should not be used.
let _gpio6 = peripherals.GPIO6;
let _gpio7 = peripherals.GPIO7;
let _gpio8 = peripherals.GPIO8;
let _gpio9 = peripherals.GPIO9;
let _gpio10 = peripherals.GPIO10;
let _gpio11 = peripherals.GPIO11;
let _gpio16 = peripherals.GPIO16;
let _gpio20 = peripherals.GPIO20;
loop {
let delay_start = Instant::now();
while delay_start.elapsed() < Duration::from_millis(500) {}
}
// for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.2.2/examples
}
ピンの配置図を見ると、LED は、
- LED TX
- LED RX
- LED IO2
の 3 つが用意されていることがわかります。
今回は、LED IO2 を光らせます。
esp-hal でだいぶ抽象化されているので、Output とピンを指定するだけで LED を光らせることができます。
#![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;
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
loop {}
}
// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description>
esp_bootloader_esp_idf::esp_app_desc!();
#[main]
fn main() -> ! {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
let mut led_io2 = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
let delay = Delay::new();
loop {
led_io2.toggle();
delay.delay_millis(500);
}
}
$ cargo run
