はんだ付けから始めるEmbedded Rust on Espressif
はじめに
突然ですがここに秋月電子で購入したESP32-C3があります。
1個310円と他のESP32と比べても安価でCPUにRISC-Vを使ったチップです。
以下のドキュメントはESP32シリーズを製造しているEspressifによるRustのハンズオンドキュメントです。
今回これを読みながらESP32-C3でRustを動かして遊んでみます。
Embedded Rust on Espressif
The Rust on ESP Book
ESP32単体ではPCと接続してプログラムを書き込めないので、以下の記事を参考に書き込み回路を作成します。
ESP32に捨てずに取っておいたカーボン抵抗の切れ端などではんだ付けをして、ブレットボード上に回路を作成しました。ESP32は3v3駆動なのでレギュレータで5vから落とします。
ESP32-C3はUSBシリアルの変換モジュールがなしで書き込めるのでラクですね。
環境構築
回路が出来たら、Rustの開発環境を構築します。
以下はubuntu 22.04で作業しています。
まずRustをインストールします。
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
RustをインストールしたらRISC-V and Xtensa Targetsの手順に従います。
$ cargo install espup
$ espup install
$ cat export-esp.sh >> .bashrc
stdで開発するのでstd Development Requirementsに従って以下を入れます。
$ cargo install ldproxy
ESP-IDFに必要なものを入れておきます。
$ sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
今回はubuntuにインストールして環境構築をしましたが、dockerで行う方法も提供されています。
プログラムの書き込み
環境構築が出来たら、プログラムを作成して実行してみます。
cargo-generate
をインストールしたらプロジェクトを生成します。
プロジェクト生成時にtargetにesp32c3を指定します。
$ cargo install cargo-generate
$ cargo generate esp-rs/esp-idf-template cargo
⚠️ Favorite `esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template.git
🤷 Project Name: blink
🔧 Destination: /home/satoken/work/rust/blink ...
🔧 project-name: blink ...
🔧 Generating template ...
✔ 🤷 Which MCU to target? · esp32c3
✔ 🤷 Configure advanced template options? · false
🔧 Moving generated files into: `/home/satoken/work/rust/blink`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /home/satoken/work/rust/blink
生成されたmain.rs
は起動するとHello, world!
を出力して終了するプログラムです。
fn main() {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("Hello, world!");
}
ビルドして実行する前にespflashツールが入ってなかったので入れておきました。
$ wget https://github.com/esp-rs/espflash/releases/download/v2.1.0/espflash-x86_64-unknown-linux-gnu.zip
$ unzip espflash-x86_64-unknown-linux-gnu.zip
$ mv espflash $HOME/.cargo/bin
ESP32-C3をPCに接続すると/dev/ttyACM0
で認識するので、書き込みポートを環境変数にセットしておきます。
/dev/ttyACM0
へのアクセス権限を付与するためdialoutグループにユーザを追加します。
$ export ESPFLASH_PORT=/dev/ttyACM0
$ sudo adduser satoken dialout
$ newgrp dialout
cargo build
をしてcargo run
を実行するとプログラムが書き込まれた後、実行されてメッセージが出力されました。
$ cargo run
Finished dev [optimized + debuginfo] target(s) in 0.09s
Running `espflash flash --monitor target/riscv32imc-esp-espidf/debug/blink`
[2023-11-28T15:48:05Z INFO ] Serial port: '/dev/ttyACM0'
[2023-11-28T15:48:05Z INFO ] Connecting...
[2023-11-28T15:48:06Z INFO ] Using flash stub
Chip type: esp32c3 (revision v0.4)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi, BLE
MAC address: a0:76:4e:b3:87:60
App/part. size: 480,000/4,128,768 bytes, 11.63%
[00:00:00] [========================================] 13/13 0x0 [00:00:00] [========================================] 1/1 0x8000 [00:00:03] [========================================] 240/240 0x10000 [2023-11-28T15:48:10Z INFO ] Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xd (SPI_FAST_FLASH_BOOT)
Saved PC:0x40380832
0x40380832 - read_id_core
at /home/satoken/work/rust/blink/.embuild/espressif/esp-idf/v5.1.1/components/spi_flash/esp_flash_api.c:435
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd5820,len:0x171c
0x3fcd5820 - _heap_start
at ??:??
load:0x403cc710,len:0x968
0x403cc710 - _iram_data_start
at ??:??
load:0x403ce710,len:0x2f68
0x403ce710 - _iram_data_start
at ??:??
SHA-256 comparison failed:
Calculated: 1d06b938c0222bf626e0bdf46178b1b37ab24d03f0360fc8fcf7153c2571deaf
Expected: 68d7bdf643ba446b8ed7ae8423241d442fd052b2bc77091100ba06fd65dcf8d5
Attempting to boot anyway...
entry 0x403cc710
0x403cc710 - _iram_data_start
at ??:??
I (43) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (43) boot: compile time Jun 7 2023 07:59:10
I (44) boot: chip revision: v0.4
I (48) boot.esp32c3: SPI Speed : 40MHz
I (53) boot.esp32c3: SPI Mode : DIO
I (57) boot.esp32c3: SPI Flash Size : 4MB
I (62) boot: Enabling RNG early entropy source...
I (68) boot: Partition Table:
I (71) boot: ## Label Usage Type ST Offset Length
I (78) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (86) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (93) boot: 2 factory factory app 00 00 00010000 003f0000
I (101) boot: End of partition table
I (105) esp_image: segment 0: paddr=00010020 vaddr=3c050020 size=1e378h (123768) map
I (141) esp_image: segment 1: paddr=0002e3a0 vaddr=3fc8a000 size=01058h ( 4184) load
I (142) esp_image: segment 2: paddr=0002f400 vaddr=40380000 size=00c18h ( 3096) load
I (147) esp_image: segment 3: paddr=00030020 vaddr=42000020 size=4c04ch (311372) map
I (223) esp_image: segment 4: paddr=0007c074 vaddr=40380c18 size=09264h ( 37476) load
I (236) boot: Loaded app from partition at offset 0x10000
I (236) boot: Disabling RNG early entropy source...
I (247) cpu_start: Unicore app
I (247) cpu_start: Pro cpu up.
I (256) cpu_start: Pro cpu start user code
I (256) cpu_start: cpu freq: 160000000 Hz
I (256) cpu_start: Application information:
I (259) cpu_start: Project name: libespidf
I (264) cpu_start: App version: 1
I (269) cpu_start: Compile time: Nov 29 2023 00:42:28
I (275) cpu_start: ELF file SHA256: 0000000000000000...
I (281) cpu_start: ESP-IDF: v5.1.1
I (286) cpu_start: Min chip rev: v0.3
I (290) cpu_start: Max chip rev: v0.99
I (295) cpu_start: Chip rev: v0.4
I (300) heap_init: Initializing. RAM available for dynamic allocation:
I (307) heap_init: At 3FC8BF90 len 00050780 (321 KiB): DRAM
I (313) heap_init: At 3FCDC710 len 00002950 (10 KiB): STACK/DRAM
I (320) heap_init: At 50000010 len 00001FD8 (7 KiB): RTCRAM
I (328) spi_flash: detected chip: generic
I (331) spi_flash: flash io: dio
W (335) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (344) sleep: Configure to isolate all GPIO pins in sleep state
I (350) sleep: Enable automatic switching of GPIO sleep configuration
I (358) app_start: Starting scheduler on CPU0
I (362) main_task: Started on CPU0
I (362) main_task: Calling app_main()
I (362) blink: Hello, world!
I (372) main_task: Returned from app_main()
Lチカ
電子工作におけるHelloWorld、Lチカを実行するためにmain.rsを以下のように変えます。
LEDはIO0に接続しているので、gpio0を出力にセットします。
use esp_idf_svc::hal::delay::FreeRtos;
use esp_idf_svc::hal::peripherals::Peripherals;
use esp_idf_svc::hal::gpio::PinDriver;
fn main() -> anyhow::Result<()> {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
let peripherals = Peripherals::take()?;
let mut led = PinDriver::output(peripherals.pins.gpio0)?;
loop {
led.set_high()?;
FreeRtos::delay_ms(500);
led.set_low()?;
FreeRtos::delay_ms(500);
}
}
cargo run
を実行します。
$ cargo run
Compiling anyhow v1.0.75
Compiling blink v0.1.0 (/home/satoken/work/rust/blink)
Finished dev [optimized + debuginfo] target(s) in 1.49s
Running `espflash flash --monitor target/riscv32imc-esp-espidf/debug/blink`
[2023-11-28T15:53:10Z INFO ] Serial port: '/dev/ttyACM0'
[2023-11-28T15:53:10Z INFO ] Connecting...
[2023-11-28T15:53:10Z INFO ] Using flash stub
Chip type: esp32c3 (revision v0.4)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi, BLE
MAC address: a0:76:4e:b3:87:60
App/part. size: 478,272/4,128,768 bytes, 11.58%
[00:00:00] [========================================] 13/13 0x0 [00:00:00] [========================================] 1/1 0x8000 [00:00:03] [========================================] 239/239 0x10000 [2023-11-28T15:53:15Z INFO ] Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xd (SPI_FAST_FLASH_BOOT)
Saved PC:0x40380832
0x40380832 - read_id_core
at /home/satoken/work/rust/blink/.embuild/espressif/esp-idf/v5.1.1/components/spi_flash/esp_flash_api.c:435
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd5820,len:0x171c
0x3fcd5820 - _nimble_common_end
at ??:??
load:0x403cc710,len:0x968
0x403cc710 - _iram_bss_end
at ??:??
load:0x403ce710,len:0x2f68
0x403ce710 - _iram_bss_end
at ??:??
SHA-256 comparison failed:
Calculated: 1d06b938c0222bf626e0bdf46178b1b37ab24d03f0360fc8fcf7153c2571deaf
Expected: 68d7bdf643ba446b8ed7ae8423241d442fd052b2bc77091100ba06fd65dcf8d5
Attempting to boot anyway...
entry 0x403cc710
0x403cc710 - _iram_bss_end
at ??:??
I (43) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (43) boot: compile time Jun 7 2023 07:59:10
I (44) boot: chip revision: v0.4
I (48) boot.esp32c3: SPI Speed : 40MHz
I (53) boot.esp32c3: SPI Mode : DIO
I (57) boot.esp32c3: SPI Flash Size : 4MB
I (62) boot: Enabling RNG early entropy source...
I (68) boot: Partition Table:
I (71) boot: ## Label Usage Type ST Offset Length
I (78) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (86) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (93) boot: 2 factory factory app 00 00 00010000 003f0000
I (101) boot: End of partition table
I (105) esp_image: segment 0: paddr=00010020 vaddr=3c050020 size=1e188h (123272) map
I (141) esp_image: segment 1: paddr=0002e1b0 vaddr=3fc8a000 size=01070h ( 4208) load
I (142) esp_image: segment 2: paddr=0002f228 vaddr=40380000 size=00df0h ( 3568) load
I (147) esp_image: segment 3: paddr=00030020 vaddr=42000020 size=4bb00h (310016) map
I (223) esp_image: segment 4: paddr=0007bb28 vaddr=40380df0 size=090ech ( 37100) load
I (235) boot: Loaded app from partition at offset 0x10000
I (235) boot: Disabling RNG early entropy source...
I (247) cpu_start: Unicore app
I (247) cpu_start: Pro cpu up.
I (256) cpu_start: Pro cpu start user code
I (256) cpu_start: cpu freq: 160000000 Hz
I (256) cpu_start: Application information:
I (259) cpu_start: Project name: libespidf
I (264) cpu_start: App version: 1
I (268) cpu_start: Compile time: Nov 29 2023 00:42:28
I (274) cpu_start: ELF file SHA256: 0000000000000000...
I (280) cpu_start: ESP-IDF: v5.1.1
I (285) cpu_start: Min chip rev: v0.3
I (290) cpu_start: Max chip rev: v0.99
I (295) cpu_start: Chip rev: v0.4
I (300) heap_init: Initializing. RAM available for dynamic allocation:
I (307) heap_init: At 3FC8C1E0 len 00050530 (321 KiB): DRAM
I (313) heap_init: At 3FCDC710 len 00002950 (10 KiB): STACK/DRAM
I (320) heap_init: At 50000010 len 00001FD8 (7 KiB): RTCRAM
I (327) spi_flash: detected chip: generic
I (331) spi_flash: flash io: dio
W (335) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (343) sleep: Configure to isolate all GPIO pins in sleep state
I (350) sleep: Enable automatic switching of GPIO sleep configuration
I (357) app_start: Starting scheduler on CPU0
I (362) main_task: Started on CPU0
I (362) main_task: Calling app_main()
I (362) gpio: GPIO[0]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
LEDが点滅しました。
環境構築とLチカプログラムが出来たので、次にフルカラーLEDを光らせてみます。
Neopixel
自宅にあった以下のNeopixelのフルカラーLEDを点滅させてみます。
RustでNeopixelを制御するようのドライバには以下を使用しました。
READMEの記述に沿って、Cargo.tomlのdependenciesに追記します。
exampleフォルダにあるプログラムをコピペしました。
LED_PINを0にして、LEDの数は20個なので20を指定します。
use smart_leds::hsv::hsv2rgb;
use smart_leds::hsv::Hsv;
use smart_leds::SmartLedsWrite;
use std::thread::sleep;
use std::time::Duration;
use esp_idf_svc::sys::esp_random;
use ws2812_esp32_rmt_driver::driver::color::LedPixelColorGrbw32;
use ws2812_esp32_rmt_driver::{LedPixelEsp32Rmt, RGB8};
const LED_PIN: u32 = 0; // 2: M5Stamp C3 Mate, 8: ESP32-C3-DevKitM-1
const NUM_PIXELS: usize = 20;
fn main() -> ! {
let mut ws2812 = LedPixelEsp32Rmt::<RGB8, LedPixelColorGrbw32>::new(0, LED_PIN).unwrap();
let mut hue = unsafe { esp_random() } as u8;
println!("hue is {}", hue);
loop {
let pixels = std::iter::repeat(hsv2rgb(Hsv {
hue,
sat: 255,
val: 8,
}))
.take(NUM_PIXELS);
ws2812.write(pixels).unwrap();
sleep(Duration::from_millis(100));
hue = hue.wrapping_add(10);
}
}
これを実行します。
$ cargo run
Compiling blink v0.1.0 (/home/satoken/work/rust/blink)
Finished dev [optimized + debuginfo] target(s) in 1.13s
Running `espflash flash --monitor target/riscv32imc-esp-espidf/debug/blink`
[2023-11-28T17:05:27Z INFO ] Serial port: '/dev/ttyACM0'
[2023-11-28T17:05:27Z INFO ] Connecting...
[2023-11-28T17:05:27Z INFO ] Using flash stub
Chip type: esp32c3 (revision v0.4)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi, BLE
MAC address: a0:76:4e:b3:87:60
App/part. size: 489,968/4,128,768 bytes, 11.87%
[00:00:00] [========================================] 13/13 0x0 [00:00:00] [========================================] 1/1 0x8000 [00:00:03] [========================================] 246/246 0x10000 [2023-11-28T17:05:32Z INFO ] Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xd (SPI_FAST_FLASH_BOOT)
Saved PC:0x40380832
0x40380832 - rmt_driver_isr_default
at /home/satoken/work/rust/blink/.embuild/espressif/esp-idf/v5.1.1/components/driver/deprecated/rmt_legacy.c:897
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcd5820,len:0x171c
0x3fcd5820 - _heap_start
at ??:??
load:0x403cc710,len:0x968
0x403cc710 - _coredump_iram_start
at ??:??
load:0x403ce710,len:0x2f68
0x403ce710 - _coredump_iram_start
at ??:??
SHA-256 comparison failed:
Calculated: 1d06b938c0222bf626e0bdf46178b1b37ab24d03f0360fc8fcf7153c2571deaf
Expected: 68d7bdf643ba446b8ed7ae8423241d442fd052b2bc77091100ba06fd65dcf8d5
Attempting to boot anyway...
entry 0x403cc710
0x403cc710 - _coredump_iram_start
at ??:??
I (43) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (43) boot: compile time Jun 7 2023 07:59:10
I (44) boot: chip revision: v0.4
I (48) boot.esp32c3: SPI Speed : 40MHz
I (53) boot.esp32c3: SPI Mode : DIO
I (57) boot.esp32c3: SPI Flash Size : 4MB
I (62) boot: Enabling RNG early entropy source...
I (68) boot: Partition Table:
I (71) boot: ## Label Usage Type ST Offset Length
I (78) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (86) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (93) boot: 2 factory factory app 00 00 00010000 003f0000
I (101) boot: End of partition table
I (105) esp_image: segment 0: paddr=00010020 vaddr=3c050020 size=1ed78h (126328) map
I (141) esp_image: segment 1: paddr=0002eda0 vaddr=3fc8b200 size=010ech ( 4332) load
I (143) esp_image: segment 2: paddr=0002fe94 vaddr=40380000 size=00184h ( 388) load
I (147) esp_image: segment 3: paddr=00030020 vaddr=42000020 size=4c970h (313712) map
I (224) esp_image: segment 4: paddr=0007c998 vaddr=40380184 size=0b028h ( 45096) load
I (239) boot: Loaded app from partition at offset 0x10000
I (239) boot: Disabling RNG early entropy source...
I (250) cpu_start: Unicore app
I (251) cpu_start: Pro cpu up.
I (259) cpu_start: Pro cpu start user code
I (260) cpu_start: cpu freq: 160000000 Hz
I (260) cpu_start: Application information:
I (263) cpu_start: Project name: libespidf
I (268) cpu_start: App version: 1
I (272) cpu_start: Compile time: Nov 29 2023 01:35:42
I (278) cpu_start: ELF file SHA256: 0000000000000000...
I (284) cpu_start: ESP-IDF: v5.1.1
I (289) cpu_start: Min chip rev: v0.3
I (294) cpu_start: Max chip rev: v0.99
I (299) cpu_start: Chip rev: v0.4
I (303) heap_init: Initializing. RAM available for dynamic allocation:
I (311) heap_init: At 3FC8D240 len 0004F4D0 (317 KiB): DRAM
I (317) heap_init: At 3FCDC710 len 00002950 (10 KiB): STACK/DRAM
I (323) heap_init: At 50000010 len 00001FD8 (7 KiB): RTCRAM
I (331) spi_flash: detected chip: generic
I (334) spi_flash: flash io: dio
W (339) rmt(legacy): legacy driver is deprecated, please migrate to `driver/rmt_tx.h` and/or `driver/rmt_rx.h`
W (349) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (358) sleep: Configure to isolate all GPIO pins in sleep state
I (364) sleep: Enable automatic switching of GPIO sleep configuration
I (372) app_start: Starting scheduler on CPU0
I (377) main_task: Started on CPU0
I (377) main_task: Calling app_main()
以下のようにLEDが光りました。
100msごとにLED20個分のカラーパターンを生成して書き込んでいるので、クリスマスツリーのように色が変わりながら点灯します。
こんな感じで開発ができるようになったので、引き続きESP32-C3とRustで遊んでみたいと思います。
参考
Discussion