🦀

はんだ付けから始めるEmbedded Rust on Espressif

2023/12/06に公開

はじめに

突然ですがここに秋月電子で購入したESP32-C3があります。
1個310円と他のESP32と比べても安価でCPUにRISC-Vを使ったチップです。

https://akizukidenshi.com/catalog/g/gM-17493/

以下のドキュメントはESP32シリーズを製造しているEspressifによるRustのハンズオンドキュメントです。
今回これを読みながらESP32-C3でRustを動かして遊んでみます。

Embedded Rust on Espressif
The Rust on ESP Book

ESP32単体ではPCと接続してプログラムを書き込めないので、以下の記事を参考に書き込み回路を作成します。

https://note.com/rcat999/n/nd5af859891fb

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を制御するようのドライバには以下を使用しました。

https://github.com/cat-in-136/ws2812-esp32-rmt-driver/

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で遊んでみたいと思います。

参考

https://interface.cqpub.co.jp/magazine/202305/
https://esp-rs.github.io/book/introduction.html
https://esp-rs.github.io/std-training/01_intro.html
https://github.com/esp-rs/esp-idf-hal/tree/master/examples

Discussion