🐋

Dev Container で ESP-IDF 開発環境を構築する

2024/12/29に公開

Dev Container 上で ESP-IDF 開発環境を構築する方法を紹介します.

本当に Dev Container で開発する必要がありますか?

そもそも, Dev Container を使って開発環境を構築する必要があるのでしょうか. ファームウェア開発の場合, 開発端末とホストPCとの接続が必要です. このため Docker コンテナ内に開発環境を用意してもコンテナホストへの依存が避けられません. 事実として MacOS では Docker コンテナに直接 USB デバイスをマウントすることができないため, Dev Container を使って開発環境を構築することはできません[1]. またホストPCで直接開発するよりもレイヤーが増えるため, それに伴う問題が発生します.

ではなぜ Dev Container で開発環境を構築する必要があるのでしょうか. 私は開発環境の再現性とコード化のために Dev Container を利用します. また Dockerfile によってコード化された一部は CI/CD にも流用できます. Docker と Dev Container に関して十分な知識があれば, ESP-IDF の開発環境を構築するのもそれほど難しくはありません.

ESP-IDF は公式から Docker イメージ[2]や VS Code の拡張機能[3]が提供されています. しかしこれをそのまま使っても Dev Container として使い勝手がよくなく, 開発環境をカスタマイズするのも難しいです. そのため, 今回は一から Dev Container の開発環境を構築します.

環境

  • Visual Studio Code1.96.2
  • Dev Containers 0.394.0
  • usbipd-win(Windows 環境のみ)

Dev Container 内には以下をインストールします.

先述の通り MacOS では利用できません.

以下の Dev Container template を使うと簡単に構築できます.

https://github.com/toms74209200/devcontainer-esp-idf

$ devcontainer templates apply --template-id=ghcr.io/toms74209200/devcontainer-esp-idf/esp-idf

実際に作成した開発環境の例は以下.

https://github.com/toms74209200/esp-idf-devcontainer-example

開発環境の使い方

まずは構築した開発環境の使い方を説明します.

デバイスをマウントする

WSL2を使う場合は Windows ホストから usbipd-win を使ってWSL2にデバイスをマウントします.

https://learn.microsoft.com/ja-jp/windows/wsl/connect-usb

Docker コンテナにUSBデバイスをマウントするには実行時変数として指定します[4]. Dev Container では devcontainer.jsonrunArgs として追加できます.

.devcontainer/devcontainer.json
	"name": "C++",
	"build": {
		"dockerfile": "Dockerfile",
		"args": { 
			"ESP_IDF_VERSTION": "v5.3.2",
			"TARGET_DEVICE": "esp32"
		}
	},
	"features": {
		"ghcr.io/devcontainers/features/python:1": {},
	},

+	"runArgs": ["--device=/dev/ttyUSB0:/dev/ttyUSB0"],

ESP-IDF のコマンドを使う

この Dev Container では ESP-IDF のコマンドとして idf というエイリアスを登録しています. 中身は idf.py のため同じコマンドをそのまま使うことができます[5].

プロジェクトのビルド.

$ idf build
Executing action: all (aliases: build)
Running ninja in directory /workspaces/esp-idf-devcontainer-example/build

~~~

Project build complete. To flash, run:
 idf.py flash
or
 idf.py -p PORT flash
or
 python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash --flash_mode dio --flash_size 2MB --flash_freq 40m 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/main.bin
or from the "/workspaces/esp-idf-devcontainer-example/build" directory
 python -m esptool --chip esp32 -b 460800 --before default_reset --after hard_reset write_flash "@flash_args"

デバイスへの書き込みとデバッグモニタ.

$ idf flash monitor
Executing action: flash
Serial port /dev/ttyUSB0
Connecting.......
Detecting chip type... Unsupported detection protocol, switching and trying again...
Connecting....
Detecting chip type... ESP32
Running ninja in directory /workspaces/esp-idf-devcontainer-example/build

~~~

I (324) heap_init: Initializing. RAM available for dynamic allocation:
I (331) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (337) heap_init: At 3FFB43F8 len 0002BC08 (175 KiB): DRAM
I (343) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (349) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (356) heap_init: At 4008CC90 len 00013370 (76 KiB): IRAM
I (364) spi_flash: detected chip: gd
I (366) spi_flash: flash io: dio
W (370) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (387) main_task: Started on CPU0
I (397) main_task: Calling app_main()
Hello, world!

これで実際に Dev Container 上で ESP-IDF の開発環境が動作することを確認できました.


VS Code上でC/C++の開発を行うために C/C++CMake Tools といった拡張機能がインストールされています. 通常エディタの設定を行う .vscode/settings.json のほかに .vscode/c_cpp_properties.json からC/C++開発環境の設定を行うことができます. ESP-IDF 組み込みのコンパイラ[6]を使うように設定すれば, VS Code上でも問題なく使うことができます.

Dev Container 設定ファイルの作成

詳しい Dev Container のセットアップについて説明します. ESP-IDF の環境構築の方法については公式ドキュメントがあるため, これを参照すれば問題ありません. Dev Container で構築する場合の違いについて解説します.

ツールのセットアップ

ESP-IDF を使えるようにするためにはインストールスクリプトを実行する必要があります. まずはインストールスクリプトの実行に必要な依存関係のインストールとインストールに必要なファイル類が置いてある ESP-IDF のリポジトリのクローンを行います.

RUN apt-get update && apt-get -y install --no-install-recommends \
    git \
    wget \
    flex \
    bison \
    gperf \
    python3 \
    python3-venv \
    cmake \
    ninja-build \
    ccache \
    libffi-dev \
    libssl-dev \
    dfu-util \
    libusb-1.0-0

ARG ESP_IDF_VERSTION="latest"
ARG TARGET_DEVICE="esp32"

RUN mkdir -p /opt && cd /opt \
    && git clone --filter=blob:none --no-checkout --depth 1 --single-branch -b ${ESP_IDF_VERSTION} --recursive https://github.com/espressif/esp-idf.git \
    && cd /opt/esp-idf \
    && git sparse-checkout init --cone \
    && git sparse-checkout set components tools \
    && git checkout \
    && git submodule update --init --recursive --recommend-shallow --depth 1 \
    && ./install.sh ${TARGET_DEVICE}

ESP-IDF のリポジトリの中で ESP-IDF のインストールに必要なのは setcomponents, tools ディレクトリのみなので, sparse-checkout でそのディレクトリのみをチェックアウトします. あとは install.sh を実行するだけです.

開発環境のベースイメージには公式が出しているC++の Dev Container を使います.

https://github.com/microsoft/vscode-remote-try-cpp

先ほどインストールを実行したレイヤーと実際に開発環境として利用するレイヤーを分けてインストールした ESP-IDF をそのまま持ってこれるようにします. こうすることでキャッシュが利いてイメージビルドが速くなると信じています(未検証).

FROM debian:bookworm AS install-esp-idf

FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12

COPY --from=install-esp-idf /opt/esp-idf /opt/esp-idf
COPY --from=install-esp-idf --chown=vscode /root/.espressif /home/vscode/.espressif

ESP-IDF を使うために必要な依存関係を改めてインストールします.

RUN apt-get update && apt-get -y install --no-install-recommends \
    libffi-dev \
    libssl-dev \
    dfu-util \
    libusb-1.0-0

C/C++の依存関係は mcr.microsoft.com/devcontainers/cpp に, Python は ghcr.io/devcontainers/features/python からインストールします. ESP-IDF には直接関係ありませんが, clang-tidy と clang-format を使えるように ghcr.io/toms74209200/features/clang-tools も入れています.

.devcontainer/devcontainer.json
	"features": {
		"ghcr.io/devcontainers/features/python:1": {},
		"ghcr.io/toms74209200/features/clang-tools:latest": {}
	},

ESP-IDF を使うには $HOME 以下にインストールされた $HOME/.espressifIDF_TOOLS_PATH という環境変数として登録する必要があります. このために export.sh というファイルがあります. ターミナルを開いたときに export.sh が実行されるようにします.

RUN echo "source /opt/esp-idf/export.sh" >> /home/vscode/.bashrc

本来不要ですが, idf.pyidf として登録します.

.devcontainer/idf
#!/bin/bash

/opt/esp-idf/tools/idf.py $@
COPY ./idf /opt/idf
RUN ln -s /opt/idf /usr/bin/idf

これで開発環境ができました. 公式の Dev Container template をもとにすれば, 独自の Dev Container を作るのもそれほど難しくはありません. 君だけの Dev Container を作ろう!

脚注
  1. 「残念ながら、USB デバイス(あるいはシリアルポート)はコンテナへのパススルーができません。これはハイパーバイザ・レベルのサポートを必要とするからです。」 回避策として Mac 上で Linux 仮想環境を用意してその上で Docker コンテナを使う, USB デバイスをマウントせずに開発環境を利用する, などの方法が考えられます. ↩︎

  2. espressif/idf - Docker Image | Docker Hub ↩︎

  3. ESP-IDF - Visual Studio Marketplace ↩︎

  4. ホスト・デバイスをコンテナに追加(--device)| docker run — Docker-docs-ja 24.0 ドキュメント ↩︎

  5. IDF Frontend - idf.py - ESP32 - — ESP-IDF Programming Guide v5.3.2 documentation ↩︎

  6. $HOME/.espressif/tools/xtensa-esp-elf/esp-<version>/xtensa-esp-elf/bin/xtensa-esp32-elf-c++ にインストールされています. ↩︎

Discussion