uchanのアイコン画像

ESP32のULPプログラミングのはまりポイント

uchan 2021年01月17日に作成

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)
1
uchanのアイコン画像
本業はプログラマですが、昔から電子工作は趣味でやってます。初めてのプログラミング言語はPICアセンブラです。 モジュールを買ってきて組み合わせるだけでなく、部品の動作原理をきちんと理解して回路を設計することに楽しさを感じます。 2021年より「uchanの電子工作ラボ」という施設を運営しています。はんだごてや測定器が使えます。 https://uchan.net/lab/ 2021年3月22日に「ゼロからのOS自作入門」を出版しました。Amazon→ https://amzn.to/2NP3FUj
  • uchan さんが 2021/01/17 に 編集 をしました。 (メッセージ: 初版)
ログインしてコメントを投稿する