ESP32はULP(Ultra Low Power)コプロセッサが載っていて、システムのほとんどを停止させつつ小さい電力でプログラムを実行し続けることができます。ULPコプロセッサを使ったプログラミングをする上で筆者がはまった点を説明します。
ULPコプロセッサに関する文書
ESP-IDF(Espressif IoT Development Framework)のドキュメント内にULPコプロセッサを使ったプログラミングの説明が載っています。必読です。
ULP Coprocessor programming
テクニカルリファレンスマニュアル(TRM)にはGPIOとRTC IOのマッピング等が載っています。こちらも必携。
ESP32 Technical Reference Manual
ULPで信号パルスを数えるサンプルです。ULPプログラムの起動方法やULPからIOを読む方法、ULPとメインコアで変数を共有する方法など、たくさんのヒントが詰まっています。
ULP Pulse Counting Example
はまりポイント
筆者がULPプログラミングを始めるにあたり、いくつかの罠を経験しました。主なものを紹介します。
CMakeLists.txtへの設定追加
ESP-IDFドキュメントのCompiling the ULP Codeの手順2"Call ulp_embed_binary
from the component CMakeLists.txt after registration."に掲載されている設定は、トップレベルのCMakeLists.txtではなく、コンポーネントのCMakeLists.txtに追記する必要があります。
ハローワールドの例では次のようなファイル構造になっています。
$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 が分かりやすいかと思います。通常、コンポーネントはcomponentディレクトリに入れておきますが、mainディレクトリはcomponentディレクトリの外側にあってもコンポーネントとして認識される、特別な名前だということですね。
存在しないマクロを使うとリンクエラーになることがある
ULPプログラムの中からIOを読むにはREG_RDという命令を使います。
この命令を使いやすくしたマクロとして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()のドキュメントによれば、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)
投稿者の人気記事
-
uchan
さんが
2021/01/17
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する