はじめに
WindowsのDocker Desktopを使ってRust開発環境を構築し、M5Stack(ESP32)に書き込んでシリアルモニター出力するまでのメモです。
とりあえず動いた、程度の内容ですので、ご了承ください。
組込みRust環境をDockerで構築することが便利かというと(?)の部分もありますが、始めてしまったので最後までやってみました。
なお、Dockerfile作成をはじめ環境構築手順については生成AIをフル活用しています。
目次
Docker環境構築準備
開発環境をDocker内に構築するため、まずはDocker DesktopをWindowsにインストールします。(Docker Desktopのインストールについては省略します。)
Dockerを使用する利点は、開発環境を容易に再現できることです。しかし、M5StackはUSBケーブルを介してWindowsに接続されるため、Docker内の開発環境と直接通信することはできません。この問題を解決するために、USB接続をWindowsからDocker Desktopが動作するWSL2に中継する設定が必要になります。
USBIPD-WINインストール
下記サイトの情報を参考にUSBIPD-WINをWindowsにインストールします。
Windows パッケージ マネージャーを利用する方法も記載されていますが、私の環境ではうまくいきませんでした。
そのため、インストーラーをダウンロードしてインストールしました。
ひとまず、インストールまで行います。
M5Stamp S3の接続
PCとデバイスをUSBケーブルで接続します。
その際、今回はM5Stamp S3(正確にはM5Capsul)を利用したのですが、このデバイスの場合には書き込みモード(Boot Mode)にするために、表面の BTN0(BOOT_SET)と書かれた小さなボタンを押しながら、リセットボタンを押します。
USB接続をWSL に中継する
Power Shellを管理者モードで起動します。
(Power Shellのアイコン上で右クリックして、管理者モードをクリックします。)
その後、下記コマンドを入力します。
usbipd list
すると、PCに接続されている各種接続情報が表示されます。
PS C:\project> usbipd list
Connected:
BUSID VID:PID DEVICE STATE
2-1 303a:1001 USB シリアル デバイス (COM3), USB JTAG/serial debug unit Not shared
2-3 04f3:0c55 ELAN WBF Fingerprint Sensor Not shared
2-4 046d:c52b Logitech USB Input Device, USB 入力デバイス Not shared
2-6 5986:211c HD Webcam Not shared
2-8 046d:c545 LIGHTSPEED Receiver, USB 入力デバイス Not shared
2-10 8087:0026 インテル(R) ワイヤレス Bluetooth(R) Not shared
私の環境の場合、BUSID 2-1がデバイスに接続されたUSBシリアルですので、これをWSLに中継します。
そのために、次のコマンドを入力します。
usbipd bind --busid 2-1
usbipd attach --wsl --busid 2-1
この状態でlist表示すると、2-1がattachされています。
BUSID VID:PID DEVICE STATE
2-1 303a:1001 USB シリアル デバイス (COM3), USB JTAG/serial debug unit Attached
2-3 04f3:0c55 ELAN WBF Fingerprint Sensor Not shared
2-4 046d:c52b Logitech USB Input Device, USB 入力デバイス Not shared
2-6 5986:211c HD Webcam Not shared
2-8 046d:c545 LIGHTSPEED Receiver, USB 入力デバイス Not shared
2-10 8087:0026 インテル(R) ワイヤレス Bluetooth(R) Not shared
Dockerビルド
Dockerfileとdocker-compose.ymlを準備します。
Dockerfile
# Rustの公式イメージをベースとする
FROM rust:latest
# 必要なパッケージのインストール
RUN apt-get update && apt-get install -y \
git \
wget \
curl \
python3 \
python3-pip \
python3-venv \
build-essential \
cmake \
ninja-build \
libudev-dev \
pkg-config \
libssl-dev \
usbutils \
screen \
nano \
&& rm -rf /var/lib/apt/lists/*
# sccacheのインストール
RUN cargo install sccache
# 環境変数の設定
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
ENV SCCACHE_DIR=/root/.cache/sccache
# ビルド生成物をワークディレクトリから外す
ENV CARGO_BUILD_TARGET_DIR=/tmp/target
# cargo-generateのインストール
RUN cargo install cargo-generate
# Rustのリンカーのためのプロキシーツール
RUN cargo install ldproxy
# シリアルモニター
RUN cargo install espmonitor
# RUN cargo install cargo-espflash
RUN cargo install espflash
# espupのインストール
RUN cargo install espup
RUN espup install
# ワークディレクトリの設定
WORKDIR /workspace
docker-compose.ym
version: '3.8'
services:
rust-esp32:
build: .
volumes:
- .:/workspace
environment:
- USER=root
devices:
- /dev/ttyACM0:/dev/ttyACM0
command: /bin/bash -c "source ~/export-esp.sh && tail -f /dev/null"
stdin_open: true
tty: true
devicesの/dev/ttyACM0を設定している2行は環境ごとに異なると可能性がありますので、最初はコメントアウトしておく必要があるかもしれません。
この2ファイルを準備したあと、下記コマンドでビルドしDockerを起動します。
docker-compose up -d
docker-compose exec rust-esp32 bash
Dockerが起動したら、先ほどコメントアウトしたポート部分に該当するポートを下記コマンドで調べて、修正します。
dmesg | grep tty
修正したら、Dockerを再起動してください。
Rustサンプルプログラム
次のコマンドを入力しサンプルプログラムをビルドします。
Project Nameを尋ねられるため、適当に入力してください。
今回は「test」にしました。
cargo generate --vcs none --git https://github.com/esp-rs/esp-idf-template cargo
また、MCU選択画面が出ますので、接続したデバイスの該当するMUCを選択してください。
? Which MCU to target? ›
esp32
esp32c2
esp32c3
esp32c6
esp32h2
esp32s2
❯ esp32s3
すると、入力したProject Nameのディレクトリが作成されます。
移動します。
cd test
ビルドします。
cargo build --release
releaseオプションを付けると早くビルドできるそうです。
デバイスへの書き込み
次のコマンドを入力します。
espflash flash /tmp/target/xtensa-esp32s3-espidf/release/test --list-all-ports
xtensa-esp32s3-espidf/release/testの部分は、プロジェクト名やMCUによって変わりますので、都度、変更してください。
targetディレクトリをtmpに移動したのは、ビルド高速化のためです。
Dockerfile内の環境変数で設定しています。
上記コマンドを実行すると、書き込むポートを指定する画面が表示されますので、書き込むポートで「y」を選択して書き込んでください。
root@037d5ee57cd7:/workspace/prog/test# espflash flash /tmp/target/xtensa-esp32s3-espidf/release/test --list-all-ports
✔ Use serial port '/dev/ttyACM0'? · yes
[2024-04-02T15:12:05Z INFO ] Serial port: '/dev/ttyACM0'
[2024-04-02T15:12:05Z INFO ] Connecting...
[2024-04-02T15:12:05Z INFO ] Using flash stub
Chip type: esp32s3 (revision v0.2)
Crystal frequency: 40 MHz
Flash size: 8MB
Features: WiFi, BLE
MAC address: 48:27:e2:e3:a8:b4
App/part. size: 395,520/1,048,576 bytes, 37.72%
[2024-04-02T15:12:06Z INFO ] Segment at address '0x0' has not changed, skipping write
[2024-04-02T15:12:06Z INFO ] Segment at address '0x8000' has not changed, skipping write
[2024-04-02T15:12:06Z INFO ] Segment at address '0x10000' has not changed, skipping write
[2024-04-02T15:12:06Z INFO ] Flashing has completed!
これで書き込みが完了しました。
このあと、デバイスをリセットします。すると、USB接続が切れてしまいますので、再度、USB接続をアタッチします。
usbipd attach --wsl --busid 2-1
シリアルモニタ(espmonitor)で受信
シリアルモニタで受信します。
espmonitor /dev/ttyACM0
受信したデータは次の通り。最後から2行目に「Hello, world!」が表示されています。
root@037d5ee57cd7:/workspace/prog/test# espmonitor /dev/ttyACM0
ESPMonitor 0.10.0
Commands:
CTRL+R Reset chip
CTRL+C Exit
Opening /dev/ttyACM0 with speed 115200
Resetting device... done
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0x28 (SPI_FAST_FLASH_BOOT)
Saved PC:0x4037815a
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fce3818,len:0x16f8
load:0x403c9700,len:0x4
load:0x403c9704,len:0xc00
load:0x403cc700,len:0x2eb0
entry 0x403c9908
I (27) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (28) boot: compile time Jun 7 2023 08:07:32
I (29) boot: Multicore bootloader
I (33) boot: chip revision: v0.2
I (36) boot.esp32s3: Boot SPI Speed : 40MHz
I (41) boot.esp32s3: SPI Mode : DIO
I (46) boot.esp32s3: SPI Flash Size : 4MB
I (51) boot: Enabling RNG early entropy source...
I (56) boot: Partition Table:
I (60) boot: ## Label Usage Type ST Offset Length
I (67) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (74) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (82) boot: 2 factory factory app 00 00 00010000 00100000
I (89) boot: End of partition table
I (94) esp_image: segment 0: paddr=00010020 vaddr=3c040020 size=12544h ( 75076) map
I (121) esp_image: segment 1: paddr=0002256c vaddr=3fc90800 size=027f4h ( 10228) load
I (124) esp_image: segment 2: paddr=00024d68 vaddr=40374000 size=0b2b0h ( 45744) load
I (140) esp_image: segment 3: paddr=00030020 vaddr=42000020 size=3e304h (254724) map
I (204) esp_image: segment 4: paddr=0006e32c vaddr=4037f2b0 size=014d4h ( 5332) load
I (211) boot: Loaded app from partition at offset 0x10000
I (211) boot: Disabling RNG early entropy source...
I (224) cpu_start: Multicore app
I (224) cpu_start: Pro cpu up.
I (224) cpu_start: Starting app cpu, entry point is 0x40375a58
I (0) cpu_start: App cpu up.
I (242) cpu_start: Pro cpu start user code
I (242) cpu_start: cpu freq: 160000000 Hz
I (243) cpu_start: Application information:
I (245) cpu_start: Project name: libespidf
I (251) cpu_start: App version: 1
I (255) cpu_start: Compile time: Apr 2 2024 14:09:01
I (261) cpu_start: ELF file SHA256: 0000000000000000...
I (267) cpu_start: ESP-IDF: v5.1.3
I (272) cpu_start: Min chip rev: v0.0
I (277) cpu_start: Max chip rev: v0.99
I (281) cpu_start: Chip rev: v0.2
I (286) heap_init: Initializing. RAM available for dynamic allocation:
I (294) heap_init: At 3FC93900 len 00055E10 (343 KiB): DRAM
I (300) heap_init: At 3FCE9710 len 00005724 (21 KiB): STACK/DRAM
I (306) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (312) heap_init: At 600FE010 len 00001FD8 (7 KiB): RTCRAM
I (320) spi_flash: detected chip: gd
I (323) spi_flash: flash io: dio
W (327) spi_flash: Detected size(8192k) larger than the size in the binary image header(4096k). Using the size in the binary image header.
W (341) pcnt(legacy): legacy driver is deprecated, please migrate to `driver/pulse_cnt.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 (376) app_start: Starting scheduler on CPU1
I (376) main_task: Started on CPU0
I (386) main_task: Calling app_main()
I (386) test: Hello, world!
I (396) main_task: Returned from app_main()
USB接続解除
USB接続をWSLに接続したままだと、WindowsからUSBデバイスにアクセスできないため解除しておきます。
usbipd detach --busid 2-1
usbipd unbind -a