ESP-IDFを使ってみる

環境構築


ESP32の開発環境はいくつかあります。
よく使われる開発環境にArduino for ESP32がありますが、この環境ではESP32の持つ全ての機能を使うことができません。
また、もともとシングルコア用の開発環境なので、マルチコア、マルチタスクの開発には不向きです。
頑張ればマルチコア、マルチタスクも動きますが、コードが非常に汚くなります。

そこで、Espressifが提供しているESP-IDFを紹介します。
安定版の構築手順はこ ちら、最新版の構築手順はこ ちらに公開されています。

この開発環境はものすごい勢いで更新されています。
バグがどんどん生まれて、どんどん直っています。

こちら
に Release Support Schedule と Release and SoC Compatibility が公開されています。
新しいSoCのリリースに合わせて、バージョンがどんどん上がっています。

以下では最新版の構築手順を紹介します。
今回使用したプラットフォームはCore-i3で動くUbuntu 20.04です。
WSL(Windows Subsystem for Linux)にも環境は構築できましたが遅くて使い物になりません。



esp-idf v4.1からインストール方法がガラッと変わりました。
それまでは、ビルド時に使用するpythonパッケージとtoolchainを個別にインストールする必要が有りましたが、
esp-idf v4.1以降ではこれらは1つのコマンドで全てインストールすることができます。
公式のインストール手順がこ ちらに公開されています。
公式のインストール手順では$HOME/espの下にインストールすることになっていますが、私は$HOME直下にインストールしています。

esp-idf v5.4(あたり)からインストールには、python3.9以上が必要になりました。
python3.8の環境にインストールすると以下のエラーになります。
python3はシステムと密接に関係しているので、これだけを更新すると思わぬトラブルになります。
思い切って、OSを更新した方が安全です。
$ python3 --version
Python 3.8.10

$ ./install.sh
INFO: Using IDF_PATH '/home/nop/esp-idf' for installation.
Detecting the Python interpreter
Checking "python3" ...
Python 3.9+ is not installed! Please see the documentation for how to install it.

esp-idfが既にインストールされている環境に、新しいバージョンをインストールする場合、
「. export.sh」が実行されていると、python仮想環境に切り替わっているので、正しくインストールできません。
ログインスクリプト(.profileや.bashrc)でexport.shを実行している場合は、これをコメントアウトしてから、再ログイン する必要が有ります。

最新開発版は以下の手順でインストールすることができます。
$ python --version
-bash: /usr/bin/python: そのようなファイルやディレクトリはありません

$ python3 --version
Python 3.8.12

$ sudo apt install git build-essential wget flex bison gperf python3-dev python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0

aptでインストールされるpipは古いバージョンなので最新に更新する

$ python3 -m pip --version

$ python3 -m pip install --upgrade pip

wheelも更新しておく

$ python3 -m pip install --upgrade wheel

$ cd $HOME

$ git clone --recursive https://github.com/espressif/esp-idf.git

$ cd esp-idf

$ ./install.sh
INFO: Using IDF_PATH '/home/nop/esp-idf' for installation.
Detecting the Python interpreter
Checking "python3" ...
Python 3.11.2
"python3" has been detected
Checking Python compatibility
Installing ESP-IDF tools

All done! You can now run:

  . ./export.sh

$ . ./export.sh

$ idf.py --version
ESP-IDF v5.0



インストールされているcmakeのバージョンが古いとexport.shが以下の警告を出します。
$ . ./export.sh
Setting IDF_PATH to '/home/nop/esp-idf'
Detecting the Python interpreter
Checking "python3" ...
Python 3.8.10
"python3" has been detected
Checking Python compatibility
Checking other ESP-IDF version.
Using a supported version of tool cmake found in PATH: 3.16.3.
However the recommended version is 3.24.0.
Adding ESP-IDF tools to PATH...

cmakeのアップデートは色々なところで紹介されていますが、おそらく以下が一番簡単な方法です。
$ cmake --version
cmake version 3.16.3

$ sudo apt remove --purge cmake

$ python3 -m pip install cmake --upgrade

pipでインストールするcmakeは .local/bin にインストールされるので、一旦ログアウトし、再びログイン します。

$ cmake --version
cmake version 3.25.0



toolchainは一旦、$HOME/.espressif/distにダウンロードされてから、$HOME/.espressif /toolsに展開されます。
ESP32-S2のULP(Ultra Low Power)コプロ用のtoolchainが追加されています。
コンパイラーもESP32用、ESP32-S2用、ESP32-Cx用(riscv32)、ESP32-S3用(S2にBluetooth V5を追加したCore) と、それぞれに別れています。
$ ls .espressif/dist
binutils-esp32s2ulp-linux-amd64-2.28.51-esp-20191205.tar.gz
binutils-esp32ulp-linux-amd64-2.28.51-esp-20191205.tar.gz
openocd-esp32-linux64-0.10.0-esp32-20200709.tar.gz
riscv32-esp-elf-gcc8_4_0-crosstool-ng-1.24.0-123-g64eb9ff-linux-amd64.tar.gz
xtensa-esp32-elf-gcc8_4_0-esp-2020r3-linux-amd64.tar.gz
xtensa-esp32s2-elf-gcc8_4_0-esp-2020r3-linux-amd64.tar.gz
xtensa-esp32s3-elf-gcc8_4_0-esp-2020r3-linux-amd64.tar.gz

$ ls .espressif/tools
esp32s2ulp-elf  openocd-esp32    xtensa-esp32-elf    xtensa-esp32s3-elf
esp32ulp-elf    riscv32-esp-elf  xtensa-esp32s2-elf

$ ls .espressif/tools/esp32s2ulp-elf/
2.28.51-esp-20191205
$ ls .espressif/tools/openocd-esp32/
v0.10.0-esp32-20200709
$ ls .espressif/tools/xtensa-esp32-elf
esp-2020r3-8.4.0
$ ls .espressif/tools/xtensa-esp32s2-elf
esp-2020r3-8.4.0
$ ls .espressif/tools/xtensa-esp32s3-elf
esp-2020r3-8.4.0
$ ls .espressif/tools/riscv32-esp-elf
1.24.0.123_64eb9ff-8.4.0

$ cd .espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf/bin
$ ./xtensa-esp32-elf-cc --version
xtensa-esp32-elf-cc (crosstool-NG esp-2020r3) 8.4.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE

$ cd .espressif/tools/xtensa-esp32s2-elf/esp-2020r3-8.4.0/xtensa-esp32s2-elf/bin
$ ./xtensa-esp32s2-elf-cc --version
xtensa-esp32s2-elf-cc (crosstool-NG esp-2020r3) 8.4.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cd .espressif/tools/riscv32-esp-elf/1.24.0.123_64eb9ff-8.4.0/riscv32-esp-elf/bin
$ ./riscv32-esp-elf-cc --version
riscv32-esp-elf-cc (crosstool-NG 1.24.0.123_64eb9ff) 8.4.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

.profile か .bashrc の末尾にesp-idfが使用するビルドツールへのパス(正確にはpython仮想環境へ切り替えるスクリプトの実行)を追加します。
$ vi $HOME/.profile

# set PATH to esp-idf
. $HOME/esp-idf/export.sh

この時点で、アカウントにUSBポートのアクセス権を与えておきます。
これをしないと、ファームの書き込み時にパーミッションのエラーとなります。
ESP32をUSB経由でホストマシンに接続すると、USBポートを所有しているグループが分かります。
$ ls -l /dev/ttyUSB*
crw-rw---- 1 root dialout 188, 0  2月 10 08:50 /dev/ttyUSB0
$ sudo usermod -a -G dialout ログインユーザ名

再起動すると、設定が有効になります。



esp-idf-v4.0までは、toochainへのPATHとIDF_PATHの環境変数を設定する必要が有り ました。
esp-idf v4.1以降では、IDF_PATH環境変数はexport.shの中で設定されます。
$ env | grep IDF_PATH
IDF_PATH=/home/nop/esp-idf



バージョンアップなどで、必要なツール類のバージョンが上がったときは、以下の様なエラーとなります。
$ idf.py flash
Toolchain path: /home/nop/xtensa-esp32-elf-esp-2020r3/bin/xtensa-esp32-elf-gcc
Toolchain version: esp-2020r3
Compiler version: 8.4.0
The following Python requirements are not satisfied:
gdbgui==0.13.2.0
To install the missing packages, please run "/home/nop/esp-idf/install.sh"

この場合は、pipコマンドを使って、インストールされているパッケージをアンインストールしてから、再度インストール します。
$ python3 -m pip uninstall gdbgui

$ cd esp-idf

$ ./install.sh

インストールされてるpipモジュールの依存関係が邪魔をして、個別のパッケージをアップデートできないことが有ります。
そのようなときは、以下の手順で一旦インストールされているパッケージを全て削除するとうまくいきます。
パッケージはpython仮想環境から削除されるので、実環境には何も影響を与えません。
$ cd $HOME

$ pip freeze > piplist.txt

$ python3 -m pip uninstall -r piplist.txt -y

$ cd esp-idf

$ ./install.sh



$HOME/esp-idf/examplesディレクトには多数のサンプルソースが含まれています。
動作確認のために、$HOME/esp-idf/examples/get-started/blinkをビルドします。

ESP32-S2のサポートに伴い、ビルドツールがmakeからidf.py(中身はcmake)に変更になりました。
ESP-IDF Ver4.4では、今までの様にmakeも使えますが、makeではESP32-S2やESP32-C3のファームはビルドできません。
ESP-IDF Ver5からはmakeは完全に使えなくなりました。

ESP32の場合は以下の手順でビルドします。
$ cd $HOME/esp-idf/examples/get-started/blink

$ idf.py set-target esp32

$ idf.py menuconfig

$ idy.py flash monitor

ESP32-S2の場合は以下の手順でビルドします。
$ cd $HOME/esp-idf/examples/get-started/blink

$ idf.py set-target esp32s2

$ idf.py menuconfig

$ idy.py flash monitor

ESP32-C3の場合は以下の手順でビルドします。
$ cd $HOME/esp-idf/examples/get-started/blink

$ idf.py set-target esp32c3

$ idf.py menuconfig

$ idy.py flash monitor



ESP-IDFでは以下のメニューを使ってプロジェクトごとの設定を行うことができます。


このサンプルではExample Configurationを使ってLチカするGPIOを指定します。


[S]キーを押すとカレントディレクトリにsdkconfigが作られます。


ビルドはsdkconfigファイルの定義に従って行われます。
ビルド条件を変えるときは、idf.py menuconfig を実行するか、あるいは直接sdkconfigファイルを編集します。

menuconfigで設定できる項目の詳細はこ ちらに公開されています。
設定できる項目は500以上あり、ESP-IDFのバージョンが上がるに従って、増えています。
また、ESP-IDFのバージョンが上がるに従って、項目の名前が変わり、非推奨となる項目が多数あります。
非推奨となった項目とその代替は、Deprecated options and their replacementsにまとめられています。



menuconfigでは、以下の手順で特定の項目を探すことができます。
まず以下の画面で[/]を入力します。


テキストボックスに目的の項目名を入力します。


該当する項目名の一覧が表示されます。


矢印キーで目的の項目移動し、Enterキーを押します。


これでESP_WIFI_ENABLE_WPA3_SEAを変更することができます。




menuconfig コマンドを使って sdkconfig を編集しますが、sdkconfig.defaultsのファイルが有るときは、その内容が反映されます。
デフォルトでは無効になっている項目を有効にしたり、デフォルトとは違う値に変更したいときに使います。
よく使うのが以下の定義です。
xtensaシリーズのCPUクロックのデフォルトは160MHzになっていますが、これを定義しておくことで240MHzが使われます。
#
# ESP32-specific
#
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240

#
# ESP32S2-specific
#
CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ=240

#
# ESP32S3-specific
#
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240

main/Kconfig.projbuild ファイルが有ると、メニューに新しい項目を追加する事ができます。
これがすごく便利で、例えばルーターのSSIDとかパスワードなど、環境に依存する項目を登録しておけば、ビルド時に変更することができます。
また、SPIやI2Cで使用するGPIOもここに登録しておけば、好きなGPIOに変更することができます。
これを使いこなすと、1つのソースで、挙動の全く違うファームを作ることができます。
Kconfig.projbuildはKconfig Languageで記述します。
Kconfig Languageの書き方については、こ ちらなどに公開されています。
ESP32のモデルが増えて、各モデル毎に使えるGPIOの上限が違うので、だんだん複雑になっていま す。
Kconfig Languageの記述テクニックをこ ちらにまとめ ま した。

sdkconfig.defaultはターゲットごとに作成することができます。
こ ちらにターゲット毎にデフォルトを設定している例が有ります。



idf.pyのオプションは以下で表示することができます。
$ idf.py
Usage: idf.py [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...

  ESP-IDF CLI build management tool. For commands that are not known to idf.py an attempt to execute it as a build system target will be
  made. Selected target: esp32

Options:
  --version                       Show IDF version and exit.
  --list-targets                  Print list of supported targets and exit.
  -C, --project-dir PATH          Project directory.
  -B, --build-dir PATH            Build directory.
  -w, --cmake-warn-uninitialized / -n, --no-warnings
                                  Enable CMake uninitialized variable warnings for CMake files inside the project directory. (--no-
                                  warnings is now the default, and doesn't need to be specified.) The default value can be set with the
                                  IDF_CMAKE_WARN_UNINITIALIZED environment variable.
  -v, --verbose                   Verbose build output.
  --preview                       Enable IDF features that are still in preview.
  --ccache / --no-ccache          Use ccache in build. Disabled by default. The default value can be set with the IDF_CCACHE_ENABLE
                                  environment variable.
  -G, --generator [Ninja|Unix Makefiles]
                                  CMake generator.
  --no-hints                      Disable hints on how to resolve errors and logging.
  -D, --define-cache-entry TEXT   Create a cmake cache entry. This option can be used at most once either globally, or for one subcommand.
  -p, --port PATH                 Serial port. The default value can be set with the ESPPORT environment variable. This option can be used
                                  at most once either globally, or for one subcommand.
  -b, --baud INTEGER              Baud rate for flashing. It can imply monitor baud rate as well if it hasn't been defined locally. The
                                  default value can be set with the ESPBAUD environment variable. This option can be used at most once
                                  either globally, or for one subcommand.
  --help                          Show this message and exit.

Commands:
  add-dependency               Add dependency to the manifest file.
  all                          Aliases: build. Build the project.
  app                          Build only the app.
  app-flash                    Flash the app only.
  bootloader                   Build only bootloader.
  bootloader-flash             Flash bootloader only.
  build-system-targets         Print list of build system targets.
  clang-check                  run clang-tidy check under current folder, write the output into "warnings.txt"
  clang-html-report            generate html report to "html_report" folder by reading "warnings.txt" (may take a few minutes). This
                               feature requires extra dependency "codereport". Please install this by running "pip install codereport"
  clean                        Delete build output files from the build directory.
  confserver                   Run JSON configuration server.
  coredump-debug               Create core dump ELF file and run GDB debug session with this file.
  coredump-info                Print crashed task’s registers, callstack, list of available tasks in the system, memory regions and
                               contents of memory stored in core dump (TCBs and stacks)
  create-component             Create a new component.
  create-manifest              Create manifest for specified component.
  create-project               Create a new project.
  create-project-from-example  Create a project from an example in the ESP Component Registry.
  docs                         Open web browser with documentation for ESP-IDF
  efuse-common-table           Generate C-source for IDF's eFuse fields.
  efuse-custom-table           Generate C-source for user's eFuse fields.
  encrypted-app-flash          Flash the encrypted app only.
  encrypted-flash              Flash the encrypted project.
  erase-flash                  Erase entire flash chip.
  erase-otadata                Erase otadata partition.
  flash                        Flash the project.
  fullclean                    Delete the entire build directory contents.
  gdb                          Run the GDB.
  gdbgui                       GDB UI in default browser.
  gdbtui                       GDB TUI mode.
  menuconfig                   Run "menuconfig" project configuration tool.
  merge-bin
  monitor                      Display serial output.
  openocd                      Run openocd from current path
  partition-table              Build only partition table.
  partition-table-flash        Flash partition table only.
  post-debug                   Utility target to read the output of async debug action and stop them.
  python-clean                 Delete generated Python byte code from the IDF directory
  qemu                         Run QEMU.
  read-otadata                 Read otadata partition.
  reconfigure                  Re-run CMake.
  save-defconfig               Generate a sdkconfig.defaults with options different from the default ones
  set-target                   Set the chip target to build.
  show-efuse-table             Print eFuse table.
  size                         Print basic size information about the app.
  size-components              Print per-component size information.
  size-files                   Print per-source-file size information.
  uf2                          Generate the UF2 binary with all the binaries included
  uf2-app                      Generate an UF2 binary for the application only
  update-dependencies          Update dependencies of the project

以下のコマンド以外はあまり使い道が思いつきません。

  • idf.py --version:現在のバージョンを表示する
  • idf.py --list-targets:サポートしているMCUの一覧を表示する
  • idf.py set-target:ターゲットのMCUを設定する
  • idf.py menuconfig:Kconfig.projbuildの内容に従ってメニューを表示し、メニューに従ってsdkconfigを作る
  • idf.py flash:ESP32にファームを書き込む
  • idf.py clean:buildディレクトリから実行形式ファイルだけを消す
  • idf.py fullclean:buildディレクトリから全てのファイルを消す(rm -r buildと同じ)
  • idf.py erase_flash:NVS領域も含めてFLASH全体を初期化する→結構便利です
  • idf.py monitor:標準出力をモニターする
  • idf.py save-defconfig:非標準の項目だけをsdkconfig.defaultsに出力する

  • idf.py menuconfigで値を変えた場合、idf.py save-defconfigを行うと、変更した項目だけを抽出しsdkconfig.defaultsに保存してくれます。
    これで、まっさらな環境からビルドするときも、同じ環境を再現することができます。

    idf.py set-targetは内部的に以下のコマンドを実行しています。
    1.clearing the build directory (idf.py fullclean)
    2.removing the sdkconfig file (mv sdkconfig sdkconfig.old)
    3.configuring the project with the new target (idf.py -DIDF_TARGET=esp32 reconfigure)

    これらのコマンドを実行するとbuildディレクトリにtargetに応じたファイルが作られます。
    $ ls build
    CMakeCache.txt         compile_commands.json  flash_project_args
    CMakeFiles             config                 flasher_args.json
    Makefile               config.env             kconfigs.in
    app-flash_args         esp-idf                kconfigs_projbuild.in
    bootloader             flash_app_args         ldgen_libraries
    bootloader-flash_args  flash_args             ldgen_libraries.in
    bootloader-prefix      flash_args.in          partition_table-flash_args
    cmake_install.cmake    flash_bootloader_args  project_description.json

    flashやerase_flashと同時によく使うオプションが[--port]です。
    これでUSBデバイスを指定します。
    $ idf.py flash --port /dev/ttyUSB0
    あるいは
    $ idf.py flash -p /dev/ttyUSB0

    また、M5StickCやM5StickC+の様にSoCにESP32-PICOを使っている場合、Flash時のbaudrateを指 定する必要が有ります。
    $ idf.py flash --baud 115200
    あるいは
    $ idf.py flash -b 115200




    ビルドとFlashへの書き込みが終わると、GPIO5が Lチカします。
    menuconfigでLチカするポートを変更して、再びビルドします。


    ファームのビルドと書き込みが終わると、GPIO4がLチカします。



    $HOME/esp-idf/examples/get-started/hello_worldに簡単なサンプルが有るので、これをビルドして みます。
    このサンプルには設定が必要な項目が無いので、そのまま[ ESC ]で抜けます。


    ファームのビルドと書き込みの後に、シリアルモニタを起動します。
    $ idf.py flash monitor

    このプログラムはコア数やFLASHサイズを表示し、10秒後に再起度するだけのプログラムです。
    シリアルモニタからは Ctrl+] で抜けられます




    ESP32チップのバージョンによっては、このようなワーニングが出ることが有ります。


    実害はなさそうですが気になるときはmenuconfigで、ESP32のRevisonを変更するとワーニ ングは出なくなります。
    ただしESP-IDF V4ではこの変更は、CANのBitRateに微妙に影響します。
    詳細はこちらに詳しく 紹介されていますが、ESP-IDF V4の環境で、Rev2以上を設定すると、CANのBitRateが指定した値の半分になるようです。
    この仕様はESP-IDF V5ではなくなりました。




    続く....