ESP32の開発環境をCygwinで用意する


EDIT: 最新のESP-IDF(5.0)ではそもそも起動チェックで撥ねられるようになった

最近M5Stackを買ったのでその開発環境をCygwinで用意することにした。特にメリットは無いのでWindowsユーザは素直にWindowsネイティブ環境を使った方が良いと思う。

また、 今のところArduinoランタイムは最新のIDFでビルドできないため使えない 。真面目にやれば多分できるけど個人的にArduino部分は使わない予定なので。。

とりあえず、Cygwin上のgcc、binutils、CMake、Ninjaでアプリケーションをビルドしてmonitorするところまではできた。この辺のノウハウはLinuxやmacOSのようなサポートされた環境でも超最新のESP-IDFを追いたければ役に立つかもしれない。

ESP32開発環境の構成

ESP32の開発環境は、

の2つのリポジトリに分かれて公開されている。(他にもOpenOCDとかも有るがとりあえずM5StackではJTAG使えないんで。。)

これらのリポジトリには:

  1. コンパイラ等のツールチェイン(Crosstool-NGでビルドしたやつ)
  2. CMakeで構築されたビルドシステム
  3. Pythonで書かれた各種ツール(monitorやflashツールなど)
  4. FreeRTOS等のライブラリ(= コンポーネント)

が含まれる。ツールチェインはCrosstool-NGでビルドする必要があるが、それ以外のコンポーネント(2. 〜 4.)は基本的に本家のESP-IDFリポジトリ( https://github.com/espressif/esp-idf )とそのsubmodule群に格納されていて、Crosstool-NGをビルドしてesp-idfリポジトリを(recursiveに)チェックアウトするだけでSDKが揃うようになっている。

ツールチェインの準備(Crosstool-NG)

ESP32の公式サイトにLinux向けの crosstool-ngを使用してXtensaのツールチェーンを用意する方法 が書かれているので基本的にはその通りにすれば良い。ただしいくつかハマる点があった。

ビルドの依存関係としては、基本的にはRPM系Linuxで似た名前をしているパッケージを探して導入しておけばOKだが、手元では help2mancurses-devel を追加でインストールする必要があった。

ひととおりのビルドが完了したあと、 PATH にツールチェインが含まれていることを確認する。

oku@stripe ~
$ xtensa-esp32-elf-
xtensa-esp32-elf-addr2line.exe   xtensa-esp32-elf-gcov.exe
xtensa-esp32-elf-ar.exe          xtensa-esp32-elf-gcov-dump.exe
xtensa-esp32-elf-as.exe          xtensa-esp32-elf-gcov-tool.exe
xtensa-esp32-elf-c++.exe         xtensa-esp32-elf-gdb.exe
xtensa-esp32-elf-c++filt.exe     xtensa-esp32-elf-gprof.exe
xtensa-esp32-elf-cc.exe          xtensa-esp32-elf-ld.bfd.exe
xtensa-esp32-elf-cpp.exe         xtensa-esp32-elf-ld.exe
xtensa-esp32-elf-ct-ng.config    xtensa-esp32-elf-nm.exe
xtensa-esp32-elf-elfedit.exe     xtensa-esp32-elf-objcopy.exe
xtensa-esp32-elf-g++.exe         xtensa-esp32-elf-objdump.exe
xtensa-esp32-elf-gcc.exe         xtensa-esp32-elf-ranlib.exe
xtensa-esp32-elf-gcc-8.2.0.exe   xtensa-esp32-elf-readelf.exe
xtensa-esp32-elf-gcc-ar.exe      xtensa-esp32-elf-size.exe
xtensa-esp32-elf-gcc-nm.exe      xtensa-esp32-elf-strings.exe
xtensa-esp32-elf-gcc-ranlib.exe  xtensa-esp32-elf-strip.exe

ビルドシステムは PATH からこれらのツールを検索する。

Cygwinで大小文字を区別するファイルシステムを使用する

Crosstool-NGでは、ファイル名の大小文字を区別しないファイルシステム上ではビルドできないようになっている。macOSでは専用のディスクイメージを作ってマウントするといった方法を使うが、CygwinではWSL1の機能を使用して大小文字の区別をするファイルシステムを使用できる(NTFS限定)。

Crosstool-NGのビルドディレクトリで、

chattr +C .

のように、 chattr コマンドに C オプションを渡すと、指定したディレクトリ以下でファイル名の大小文字が区別されるようになる。

なぜかlibintlがリンクされない問題 (最新版で修正済)

EDIT: 最新の esp-2021r2修正 されているのを確認。

Crosstool-NGはメニューベースのコンフィギュレータ(Kconfig)が付属していて、それがcursesを使用している。これがどうも正常にCygwin上でビルドされないらしく、手動で -lintl を追加しないとビルドできなかった。

/cygdrive/f/m5/esp32/crosstool-NG/kconfig/conf.c:90: undefined reference to `libintl_gettext'

いわゆるGNUビルドシステムでは環境変数 LDFLAGS でリンカのコマンドライン引数を追加できるので、それを利用して、

LDFLAGS=-lintl ./configure --enable-local && make

で動作するKconfigをビルドできた。

Newlibが正常にビルドできない問題 (最新版で修正済)

EDIT: 最新の esp-2021r2 で修正されているのを確認。

Crosstool-NGでツールチェインをビルドしていると、何故かNewlibの内部でビルドに失敗する症状が出た。

[ERROR]    configure.in:17: error: possibly undefined macro: LIB_AC_PROG_CC
[ERROR]    make[4]: *** [Makefile:167: /cygdrive/f/m5/esp32/crosstool-NG/.build/xtensa-esp32-elf/src/newlib/libgloss/xtensa/configure] Error 1

エラーメッセージにある LIB_AC_PROG_CC 自体はリポジトリ内の aclocal.m4 で宣言されていてパスも通っているので無いってことは無いと思うんだけど。。とりあえず、近所の riscv ディレクトリからコピーしてお茶を濁すことにした。

cd ~/esp32/crosstool-NG/.build/xtensa-esp32-elf/src/newlib/libgloss/xtensa
cp ../riscv/aclocal.m4 .

(最近発生した) isl がダウンロードに失敗する

INRIAのサーバが無くなってしまったのでislのダウンロードに失敗するようになった。

とりあえず git からチェックアウトするようにして適当に処理した。

Pythonパッケージのインストール

ESP32の開発環境ツールの依存関係は requirements.txt に書かれているので、それを単に pip でインストールすれば良い。ただ、Cygwinでは gevent のインストールに失敗するので、

GEVENTSETUP_EMBED_LIBUV=0  python3.8 -m pip install --verbose -r ~/esp32/esp-idf/requirements.txt

のように、 GEVENTSETUP_EMBED_LIBUV=0 を環境変数として設定した状態でインストールする必要がある。

CMakeプロジェクトのビルド

ESP32の開発環境はCMakeベースではあるものの、標準の環境では project() コマンドを置き換えて必要な設定を自動でやるといった特殊化が目立つ実装になっている。公式のドキュメントには 通常のCMakeプロジェクトでESP32開発環境を使う方法 の解説もちゃんとあるため、ESP32以外の環境もターゲットするプロジェクトであればこちらのドキュメントに従って組み込んだ方が良いかもしれない。

今回は、"チェックアウトするだけでESP-IDFも含めて準備ができるリポジトリ"を用意してみた。こうすることで、特定バージョンのESP-IDFと同時に管理できて便利かもしれない。

環境変数のoverride

重要なポイントは、通常のESP32開発環境では環境変数 IDF_PATH の設定が必要になっているが、このリポジトリは自動的に環境変数を設定してビルドするようになっている点と言える。esp-idfにはOSやTCPスタックのような大量のコードが含まれているため、構成管理上は複数バージョンの混在を意識したい。

Configure中の環境変数

Configure中の環境変数は、CMakeの set コマンドで書き換えることができる。ESP-IDFのCMake環境はconfigure中にいくつかpythonツールを呼び出し、それらも IDF_PATH 環境変数に依存しているためこの対応が必要になる。

set(idf_path ${CMAKE_CURRENT_LIST_DIR}/external/esp-idf)
set(ENV{IDF_PATH} ${idf_path})

CMakeのcustom commandターゲットの環境変数

ESP32のツールチェイン(gccbinutils)はIDF_PATHに依存していないが、イメージ作成ツール等のいくつかのビルド中に呼ばれるツールが IDF_PATH 環境変数に依存している。このため、これらのツールを起動する際にも環境変数をoverrideする必要がある。

今回は、環境変数をoverrideした上で runtool.sh を呼ぶようにプロパティ RULE_LAUNCH_CUSTOM を設定した(ドキュメントではMakefileにしか効かないと書かれているが、実際にはNinjaでも使用できる)

# Generate custom launcher
set(launcher_file ${CMAKE_CURRENT_BINARY_DIR}/runtool.sh)
set(launcher_src "#!/bin/sh\nexport ESPPORT=${PORT}\nexport IDF_PATH=${idf_path}\nexec \"$@\"\n")
file(WRITE ${launcher_file} ${launcher_src})
set_property(GLOBAL
    PROPERTY RULE_LAUNCH_CUSTOM
    ${launcher_file})

生成されるruntool.shは以下のような内容になる:

#!/bin/sh
export ESPPORT=/dev/ttyS4
export IDF_PATH=/home/oku/repos/esp32test/external/esp-idf
exec "$@"

今回はESP32をCOM5に接続しているので、Cygwin的には /dev/ttyS4 に見える。環境変数 ESPPORTninja flash で起動できるファームウェア書き込みツールで使用できる。シリアルモニタも ninja monitor で起動できるが、手元の環境ではCMakeに指定したpythonを見てくれなかったので、専用のcustom commandを用意している( https://github.com/okuoku/esp32test/blob/5abff8b65306bb45c77e09facadce205a371c3b0/CMakeLists.txt#L33-L39 )。

かんそう

今回Crosstool-NGにnewlibのビルドまでやらせているが、気持ちとしてはnewlibのバージョンもGitで管理したい。。そもそもCrosstool-NGでgccやgdbをビルドしなくても、手でビルドすれば同じことではあるので、もっと新しいgccを使ったりした方が面白いかもしれない。

SDKのインストールがGitのチェックアウトで終わるのは便利ではあるけど、Pythonのパッケージとかツールチェインの準備などは避けようが無くてなんとも惜しい気はする。