ESP32(Xtansaコア機)が搭載する「3つめの」プロセッサ・コアULPをMicroPythonから制御するための環境をESP32 DevKitC機にインストール済です。これは使ってみるしかないです。ということでまずはハイパルスのパルス幅を測定してみることにいたしました。まあね、できるっちゃできるけれども。
※「MicroPython的午睡」投稿順 Indexはこちら
ULPはメモリとIOを傘下に従えており、メインのプロセッサとは独立な処理を行うことができます。メインとは違って何か一つの仕事に「専念」させる事もできるし、ソフト制御でハード的な仕事もできるのではないかしらん、と考えました。
実験してみるにあたり念頭にあったのは、Raspberry Pi PicoのPIOというステートマシンです。Raspberry Pi PicoのPIOはプログラマブルなIOステートマシンで、ソフトウエア的なステートマシン記述で入出力と条件判断を含めた操作を記述可能です。ある意味アセンブラよりも直接的にハードを操作できるのでほぼほぼハードウエアのような感じで多少複雑なIO制御を行うことが可能デス。
一方、ULPもステートマシン的なものを実現可能です。ただし、ラズパイPicoのPIOと比べるとハードというよりは専用の小規模マイコンという感じです。今回は外部から与えた波形のハイ期間を測るという目的で使ってみました。アイディアは簡単、傘下の入力端子がローからハイに遷移したらカウントを始め、ハイからローに遷移したらそのときのカウント値をホストのMicroPythonから読める場所に格納しておく、というだけのもの。
まあね、できるんだけれども、やってみて痛感したのが速度っすね。ラズパイPicoのPIOがマシンの限度いっぱいのクロック周波数でも走らせられるのに対して、ULPは8MHz動作です。あんまり速度的に多くを望んではダメみたいです。
今回実験に使用したMicroPythonソースコード
前回、前々回と使わせていただいた、以下のページにあるExamplesを切ったはったでつなぎ合わせて自前のコードを混ぜ合わせたフランケン風です。
micropython-esp32-ulp/examples/
MicroPythonのコードの中に、突如、ULPのアセンブラソースが登場、そのくせ、アセンブラソースの中にMicroPython関数がプリプロセッサ・マクロ風に呼び出されているという独特のスタイルです。慣れればどうってことない?ホントか。
from esp32 import ULP from machine import mem32 from esp32_ulp import src_to_binary source = """\ #define DR_REG_RTCIO_BASE 0x3ff48400 #define RTC_IO_TOUCH_PAD0_REG (DR_REG_RTCIO_BASE + 0x94) #define RTC_IO_TOUCH_PAD0_MUX_SEL_M (BIT(19)) #define RTC_IO_TOUCH_PAD0_FUN_IE_M (BIT(13)) #define RTC_GPIO_IN_REG (DR_REG_RTCIO_BASE + 0x24) #define RTC_GPIO_IN_NEXT_S 14 #define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x9c) #define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19)) #define RTC_GPIO_OUT_REG (DR_REG_RTCIO_BASE + 0x0) #define RTC_GPIO_ENABLE_REG (DR_REG_RTCIO_BASE + 0xc) #define RTC_GPIO_ENABLE_S 14 #define RTC_GPIO_OUT_DATA_S 14 #define RTCIO_GPIO2_CHANNEL 12 .set gpio, RTCIO_GPIO2_CHANNEL .set channel, 10 .text cnt: .long 0 .global entry entry: # GPIO2 as a monitor output pin WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1); WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + gpio, 1, 1) # GPIO4 as a input WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_MUX_SEL_M, 1, 1) WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_FUN_IE_M, 1, 1) move r3, cnt # r3, base pointer move r2, 0 # r2, count register move r1, 0 # r1, old value register rewind: READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 0) and r0, r0, 1 # Get Bit 0 only jumpr rewind, 1, eq # if read value == 1 then rewind readr0: READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 0) and r0, r0, 1 # Get Bit 0 only jumpr lblzero, 0, eq # if read value == 0 then lblzero # case read value == 1 move r0, r1 # r0 <- old value(r1) jumpr lblclear, 0, eq # if old value == 0 (&&read value==1) then lblclear # case old value == 1 && read value ==1 add r2, r2, 1 # r2(counter)++ jump readr0 # goto next read lblclear: WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 1) move r2, 0 # clear r2(counter) move r1, 1 # r1(old value) <- 1 jump readr0 # goto next read lblzero: # case read value == 0 move r0, r1 # r0 <- old value(r1) jumpr readr0, 0, eq # if old value == 0 (&& read value==0) then readr0 # case old value == 1 && read value == 0 WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 0) st r2, r3, 0 # [r3+0] <- r2(counter) move r1, 0 # r1(old value) <- 0 exit: halt """ binary = src_to_binary(source) cnt_addr, entry_addr = 0, 4 ULP_MEM_BASE = 0x50000000 ULP_DATA_MASK = 0xffff ulp = ULP() ulp.set_wakeup_period(0, 50000) ulp.load_binary(cnt_addr, binary) mem32[ULP_MEM_BASE + cnt_addr] = 0x0 ulp.run(entry_addr) while True: print(hex(mem32[ULP_MEM_BASE + cnt_addr] & ULP_DATA_MASK))
なお、最初、ESP32のTechnical Referenceマニュアルを見ながらアセンブラ書いてましたが、Web上の以下のページの方がずっと分かり易いことに気づきました。最初からそう言ってくれよ。。。
ESP32 ULP Coprocessor Instruction Set
実機で実行
上記のプログラムは、GPIO4番に入力されたパルスのハイ幅を指折り数えたカウンタ値としてMicroPythonで読み出すようになっています。1カウントがxx秒と定数決められればハイパルスをMicroPythonで計算できると。
今回は外から規定の信号(50%デューティ)を与えてそのとき何カウントかという測定としてます。また、ちゃんとやってるぞ、というオシルシに、ハイパルスのカウント中、GPIO2番にハイを出力するようにしています。GPIO2番に(微妙に遅れるけど)GPIO4番の波形のコピーが出力されていたらそこで測定が行われているという感じっす。
以下、黄色が外部からGPIO4番に与えた100Hzの波形。青がULPがパルス幅測定をするときにコピーした波形GPIO2番出力です。ULPは1回測定するとHaltしてしまい。後でTimerで起こされてまた測定というゆるゆるしたループで実験しているので、お休み時間がかなりあります。
100Hzで50%デューティなので、ハイパルス幅は額面5msとなる筈。上記の0x514は10進1300なので、1カウントは3.85μsec という感じかと。
つづいて1kHz。同様に計算すると1カウントは3.88μsec。
最後は10kHz.
まあMicroPythonのソフトでポーリングするよりは微妙にレゾリューション細かくて、かつ、MicroPython側の処理に負荷がかからないです。でも、タイマのインプットキャプチャ機能とかとは比べちゃいけないな。多分ラズパイPicoのPIOであればこれより一桁細かいレゾリューション達成できる筈。知らんけど。
もともとRTCの低消費電力な番人的立ち位置のULPだから、こういうのは目的外使用?どうだか。