uchan が 2021年01月17日12時28分51秒 に編集
初版
タイトルの変更
ESP32のULPプログラミングのはまりポイント
タグの変更
ESP32
ESP-IDF
記事種類の変更
セットアップや使用方法
本文の変更
ESP32はULP(Ultra Low Power)コプロセッサが載っていて、システムのほとんどを停止させつつ小さい電力でプログラムを実行し続けることができます。ULPコプロセッサを使ったプログラミングをする上で筆者がはまった点を説明します。 ## ULPコプロセッサに関する文書 ESP-IDF(Espressif IoT Development Framework)のドキュメント内にULPコプロセッサを使ったプログラミングの説明が載っています。必読です。 [ULP Coprocessor programming](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp.html) テクニカルリファレンスマニュアル(TRM)にはGPIOとRTC IOのマッピング等が載っています。こちらも必携。 [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf) ULPで信号パルスを数えるサンプルです。ULPプログラムの起動方法やULPからIOを読む方法、ULPとメインコアで変数を共有する方法など、たくさんのヒントが詰まっています。 [ULP Pulse Counting Example](https://github.com/espressif/esp-idf/tree/master/examples/system/ulp) ## はまりポイント 筆者がULPプログラミングを始めるにあたり、いくつかの罠を経験しました。主なものを紹介します。 ### CMakeLists.txtへの設定追加 ESP-IDFドキュメントの[Compiling the ULP Code](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp.html#compiling-the-ulp-code)の手順2"Call `ulp_embed_binary` from the component CMakeLists.txt after registration."に掲載されている設定は、トップレベルのCMakeLists.txtではなく、**コンポーネント**のCMakeLists.txtに追記する必要があります。 [ハローワールド](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html#get-started-start-project)の例では次のようなファイル構造になっています。 $HOME/esp/ esp-idf/ hello_world/ CMakeLists.txt main/ CMakeLists.txt hello_world_main.c この2つのCMakeLists.txtのうち、mainの中に入っているCMakeLists.txtに設定を追記します。mainディレクトリは(ESP-IDFの用語で)コンポーネントとして認識されます。したがって、先ほどのドキュメントに記載されている"from the component CMakeLists.txt"の"component"はmainディレクトリのことを意味します。 コンポーネントについては [API Guids > Build System > Example Project](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#example-project) が分かりやすいかと思います。通常、コンポーネントはcomponentディレクトリに入れておきますが、mainディレクトリはcomponentディレクトリの外側にあってもコンポーネントとして認識される、特別な名前だということですね。 ### 存在しないマクロを使うとリンクエラーになることがある ULPプログラムの中からIOを読むには[REG_RD](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp_instruction_set.html#reg-rd-read-from-peripheral-register)という命令を使います。 この命令を使いやすくしたマクロとしてREAD_RTC_REGが定義されています。このマクロは次のように使います。 .text .global entry entry: READ_RTC_REG(RTC_GPIO_IN_REG, 14 + 11, 1) ここでマクロ名`RTC_GPIO_IN_REG`を存在しない名前に間違えると、アセンブルがエラーになるかと思いきや、アセンブルは通ってしまいます。その代わり「`.__operator' に対する定義されていない参照です」というリンク時エラーになります。マクロ名のミスであることに気づきにくいですよね。筆者はこれで3時間くらい溶かしました。 ### IOポート番号がGPIOとRTC IOで異なる メインコアとULPコプロセッサからは、同じIOポートが異なる番号で見えます。例えばIO0(ESP32-DevKitでBootボタンが接続されているポート)は、メインコアからは0番でアクセスし、ULPコプロセッサからは11番でアクセスします。ESP32 TRMの"5.11 RTC_MUX Pin List"に対応表が載っています。 GPIO0をULPプログラムで読み取る例で説明します。メインコアで実行するプログラム(hello_world_main.c)では次のように「0」を使ってIOの設定を行います。 rtc_gpio_init(0); rtc_gpio_set_direction(0, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_dis(0); rtc_gpio_pullup_en(0); 一方、ULPプログラムでは次のように「11」で入力します。 entry: READ_RTC_REG(RTC_GPIO_IN_REG, 14 + 11, 1) ### IOをRTCに繋ぎ変えるとGPIOとしては使えない `rtc_gpio_init()`を使ってIOをRTC IOとして設定すると、以後`gpio_set_level()`を使って出力することができなくなります。`gpio_get_level()`で入力することは何故かできましたが。なんでだろう。`rtc_gpio_set_level()`を使うとRTC IOへメインコアから出力ができます。 ### ウェイクアップ後もULPコプロセッサは動作を続ける Deep Sleep状態でULPコプロセッサを動かし、WAKE命令でメインコアを起動すると、最初に電源を入れたときのようにプログラムが最初から実行されます。いかにもシステム全体がリセットされたかのように。しかし、メインコアがウェイクアップした後もULPコプロセッサは動き続けます。メインコアとULPコプロセッサは独立したプロセッサなので、まあ考えてみれば当然なのです。 [ulp_set_wakeup_period()のドキュメント](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp.html#_CPPv421ulp_set_wakeup_period6size_t8uint32_t)によれば、ULPコプロセッサの動作を止めるには`RTC_CNTL_ULP_CP_SLP_TIMER_EN `を0にすればよいとのことです。これをULPプログラムで実現するには次のようにします。 WRITE_RTC_REG(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN_S, 1, 0)