前回PyPIからESP32版MicroPythonで動作するULP開発環境をダウンロード、ターゲットのESP32 DevKitC機にインストールしました。ULPこそはESP32(Xtansaコア機)が搭載する「3つめの」プロセッサ・コアであります。メインのコアがお休みしているときにも密に動作する影の存在。結構強力。
※「MicroPython的午睡」投稿順 Indexはこちら
ULPとは何ぞや
ESP32(Xtansaコア機、RISC-Vコア機は異なる)は、よく知られているとおり、2個のCPUコア(Xtansa LX6)を搭載しています。勝手な理解では一方はネットワーク(WiFiなど)のために動作しており、もう一方がアプリコード、そこにはMicroPythonも含まれる、を実行しているツインヘッドなシステムであるようです。2個のCPUは、ほぼ対称な構造でメモリや周辺装置は「共有」しているものの、仕事をすみ分けているのでSMPとは違うと思います。
しかし、ゴージャスに2個CPUコアであるにも関わらず、3個目の小さいコアも搭載されておるのです。
ULP Coprocessor
と呼称されとります。ULPはUltra-Low-Power Processorのことらしいです。以下にULPについて勝手に「まとめてみた」図を掲げます。
その特徴を列挙すると以下のようになるかと。
-
- メインのコアからみるとAPBバスにぶら下がった周辺装置にみえる
- RTCと密に関係している
- 8MHz動作の16ビット?CPU
- メインCPUがスリープ中も動作可能
- メインCPUをスリープから起床させることも可能
- ULP自身をスリープに入れることも可能
- ULP自体はスリープに入ってもタイマで勝手に「ちょくちょく」起きるので、寝坊する心配は少ない
- 一部メモリと周辺回路にアクセスできる
- 周辺回路の中にはGPIOなども含まれ入出力も可能
似たものを考えると、Raspberry Pi Picoが搭載するIOステートマシンPIOがちょっと似ているかと思います。PIOは「IO直接制御のためのステートマシン」でその制御はマイクロコード風、ULPはプロセッサというだけあってCPUっぽいですが、よく見ればステートマシンな雰囲気を醸してます。ただ、規模的にはULPの方がかなり仕様がデカイです。
順番に調べたことをメモ書きしていきます。
1番、メインのコアは、ULPが制御できるメモリや周辺装置にもアクセスできます。MicroPython上のULP開発環境でも、メインコア上で生成したULP用のオブジェクトコードはULP用のメモリにメインコアで書き込んだ後、ULPにGoをかけることで実行開始されます。
2番、RTCと密接にして不可分。ULPはもともとRTC(リアル・タイム・クロック、実時間時計)の制御のためのサブシステムであるようです。それで眠り込んでいるメインのCPUを叩き起こすのが第1義的なお仕事みたいです。傘下にはRTC関係のレジスタがずらりと並んでます。
3番、実行時はRTC用の高速クロック8MHzで動作しています。しかし、ULP自身が眠っているときも8MHzのままだと電力が心配なので、自分が寝ているときは150kHzのRCオシレータのクロックで起床するようです。レジスタは16bit x 4本しかありませんが(他にビット数不明のPCあり)、8MHz動作なのでちょっとしたマイクロコントローラ相当の性能?
4番、上記のように、RTC系のクロックで動作しているので、CPUがおねんねしていてもULPは動作できます。
5番、ULPはメイン側のプロセッサに割り込みをかけられるので目覚ましも可能と。
6番、ULPのプログラムはHALT命令で終わるのを常としてます。HALT命令によりULP自身もSleepに入ります。
7番、そしたら眠り込んで起きられないのでないかい?と思いますが、ULPには付属のULPタイマという番人がついてます。HALTするとこいつがカウントをはじめ、ExpireするとULPを起床させます。つまりHALTしても一定時間後には起きざるを得ない宿命?っす。なお、ULPタイマのExpire期間もプログラマブルなので、自分自身の寝て起きての間隔も操作可能。
8番、ULPはULP「傘下の」メモリとペリフェラル・レジスタにアクセス可能です。メモリは、RTC_SLOW_MEMという名の8Kバイト、0x5000_0000番地から 0x5000_1FFF番地です。IOは0x3FF4_8000番地から0x3FF4_8FFF番地の4Kバイト分です。すべて32ビット幅のアクセスなのですが、かなり変則です。
-
- メモリからの命令フェッチは32ビットのフル幅
- メモリへのデータアクセスは読み書きとも下16ビット幅のみ。上16ビットはデータアクセス不可。
- IOへのデータアクセス1回は、指定のビット位置からの最大8ビットまでのフィールド。
9番、バラエティ豊かなのが「傘下の」ペリフェラルです。ESP32のデータシートを(部分的に)読んでいて?だったのがRTC-GPIOとかいう端子名がフツーのGPIOと併記されていることでしたが、ようやく分かりました。RTC-GPIOというのは、このULPから操作可能なGPIO端子だったです。同じIO端子にメイン側とは異なる番号が振られてます。合計18端子。さらに逐次比較(SAR)型のADCやI2Cまで含まれてます。そいつらの動作を監視してメインCPUを起床させるなどという技も使える筈。
MicroPython上のULP開発環境でBlinkしてみた
今回は、ULPについて調べただけでほぼ力尽きてしまった感じっす。しかし、学んだことを噛みしめながら、以下のExampleを読んだら、よくわかった気がしました(調子のいい事言ってホントか?)
micropython-esp32-ulp/examples/blink.py
ULP向けのアセンブラ部分を読んでいて気になるのが、以下のような記述です。
WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + gpio, 1, 1)
上記のような部分はアセンブラには見えません。調べてみると、このULP開発環境の「プリプロセス」で使用されているPython関数でした。一種のマクロみたいな使い方です。ULPのアセンブリ言語命令、とくにIOレジスタの操作はニーモニックはともかく、変則的なレジスタアクセスなどをエンコードしているので、素のアセンブリ言語命令がとっても書きずらそうです。そこで上記のようなプリプロセッサ命令で、IOアクセス命令を生成してました。
やっていることが理解できたところで、Thonny環境から上記のサンプル・プログラムを実機に書き込み動かしてみました。ボード端子のGPIO2番ピンをULPからトグルさせるプログラムです。実機ピンを観察した様子が以下に。
周期1秒、デューティ50%の波形がでておりますやん。サンプルプログラムそのままだし。