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

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

https://zenn.dev/okuoku/scraps/02f4849195561c

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

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

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67446/73997ad7-69ef-248c-d432-7780efbf2775.png)

# ESP32開発環境の構成

ESP32の開発環境は、

- https://github.com/espressif/crosstool-NG
- https://github.com/espressif/esp-idf

の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のツールチェーンを用意する方法](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/linux-setup-scratch.html) が書かれているので基本的にはその通りにすれば良い。ただしいくつかハマる点があった。

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

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

```sh
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` で [修正](https://github.com/crosstool-ng/crosstool-ng/commit/f2ac970b9b0ed2ad15e10d07a20e82e161bd11b3#diff-a8b5fcb92af19211cca4b3fed8e83e9fb48bf42c84ae078c073a7d743f6a99f5) されているのを確認。 

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のダウンロードに失敗するようになった。

https://github.com/crosstool-ng/crosstool-ng/issues/1609

とりあえず 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開発環境を使う方法](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#using-esp-idf-in-custom-cmake-projects) の解説もちゃんとあるため、ESP32以外の環境もターゲットするプロジェクトであればこちらのドキュメントに従って組み込んだ方が良いかもしれない。

- https://github.com/okuoku/esp32test/tree/5abff8b65306bb45c77e09facadce205a371c3b0
 - submoduleとしてESP-IDF自体も入れている https://github.com/okuoku/esp32test/tree/5abff8b65306bb45c77e09facadce205a371c3b0/external

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

## 環境変数のoverride

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

### Configure中の環境変数

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

- https://github.com/okuoku/esp32test/blob/5abff8b65306bb45c77e09facadce205a371c3b0/CMakeLists.txt#L4-L5

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

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

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

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

- https://github.com/okuoku/esp32test/blob/5abff8b65306bb45c77e09facadce205a371c3b0/CMakeLists.txt#L7-L13

```cmake
# 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`は以下のような内容になる:

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

今回はESP32をCOM5に接続しているので、Cygwin的には `/dev/ttyS4` に見える。環境変数 `ESPPORT` は `ninja 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のパッケージとかツールチェインの準備などは避けようが無くてなんとも惜しい気はする。