ESP32の超省電力処理装置=ULPのプログラムが判った
ULPとは「Ultra Low Power」の略で、日本語では「超低消費電力」です。
google先生によると以下な感じですね。
スペック:
動作周波数:8MHz
レジスタ:4つの16ビットレジスタ
命令数:約30個
メモリ:8KBの低速メモリ
消費電力が少ない
ULPとメインプロセッサの連携:
ULPは、メインプロセッサから指示を受けて動作を開始できます。
ULPが処理を終えると、メインプロセッサに割り込みを送信できます。
ULPは、メインプロセッサのRAMにアクセスしてデータを読み書きできます。
ESP32ダイアグラム
マークした所がULP
当方、C言語もたしなみますが、mycropythonから使ってみたいので、色々調べました。
ESP32のmycropythonに標準で付いてくる
from esp32 import ULP
を使っての方法だと、他でULP用のプログラムをコンパイルしないといけないので、
敷居高いなーと思ってたんですが、
以下のURLになんか良さげなのがありました。
https://pypi.org/project/micropython-esp32-ulp/#files
micropython-esp32-ulp 1.2.0
これだと、pythonのソース中にULP用のプログラムを書いてあげれば実行可能です。
サイトにインストール方法も書いてましたが、
自分は以下の方法で環境構築しました。
環境構築
下記URLからダウンロード
https://pypi.org/project/micropython-esp32-ulp/#files
tar.gzを取得
micropython-esp32-ulp-1.2.0.tar.gz
解凍すると以下のように展開されるので
micropython-esp32-ulp-1.2.0
+-esp32_ulp
+-assemble.py
+-definesdb.py
+-link.py
+-nocomment.py
+-opcodes.py
+-parse_to_db.py
+-preprocess.py
+-soc.py
+-util.py
+-__init__.py
+-__main__.py
ampyでESP32に入れる。
ex...
ampy -p com3 -d 1 mkdir esp32_ulp
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/assemble.py esp32_ulp/assemble.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/definesdb.py esp32_ulp/definesdb.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/link.py esp32_ulp/link.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/nocomment.py esp32_ulp/nocomment.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/opcodes.py esp32_ulp/opcodes.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/parse_to_db.py esp32_ulp/parse_to_db.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/preprocess.py esp32_ulp/preprocess.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/soc.py esp32_ulp/soc.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/util.py esp32_ulp/util.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/__init__.py esp32_ulp/__init__.py
ampy -p com3 -d 1 put micropython-esp32-ulp-1.2.0¥esp32_ulp/__main__.py esp32_ulp/__main__.py
これで、mycropythonから呼び出し可能です。
さっそくLチカ
参考
https://github.com/micropython/micropython-esp32-ulp
これの中の
https://github.com/micropython/micropython-esp32-ulp/blob/master/examples/blink.py
参考に、いろいろいじくったのが以下のプログラムです。
コメントが邪魔だったので外して解説代わりに日本語コメント入れています。
初めての人にはすごく難しいですね。
GPIO2にLEDを繋いで点滅してくれます。
UARTにはprintで出力されたメモリの値magicとstateが表示され、
stateは0→1→0→1と切り替わっています。
プログラム
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_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 token, 0xcafe # magic token
.text
magic: .long 0
state: .long 0
.global entry
entry:
# load magic flag
move r0, magic
ld r1, r0, 0
# 初期化済みか?
sub r1, r1, token
jump after_init, eq # すでにmagic token=0xcafeが入ってたらJUMP
init:
# GPIO2をULPに接続する1=GPIOをULPに接続)
WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1);
# GPIO2を出力に設定する
WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + gpio, 1, 1)
# magicにtoken(0xcafe)を入れる
move r0, magic
move r1, token
st r1, r0, 0
after_init:
move r1, state
ld r0, r1, 0
move r2, 1
sub r0, r2, r0 # 0と-1を繰り返す
st r0, r1, 0 # 値を保存
jumpr on, 0, gt # r0の値が0より大きい場合はJUMP
jump off # 無条件にJUMP
on:
# GPIOをONにする
WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 1)
jump exit
off:
# GPIOをOFFにする
WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 0)
jump exit
exit:
halt # 停止
"""
binary = src_to_binary(source) # アセンブル
load_addr, entry_addr = 0, 8
ULP_MEM_BASE = 0x50000000 #ULPのSLOWメモリ
ULP_DATA_MASK = 0xffff # 下位 16 bits マスク
ulp = ULP() #ULP初期化
ulp.set_wakeup_period(0, 500000) # 0.5秒単位でULPプログラムを呼び出す
ulp.load_binary(load_addr, binary) #バイナリを0x50000000番地に書き込む
ulp.run(entry_addr) #entry_addrから実行(先頭バイトは変数格納用)
while True:
print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK), # magic token
hex(mem32[ULP_MEM_BASE + load_addr + 4] & ULP_DATA_MASK) # current state
)
アセンブラの解説は公式を見て下さい。
公式 https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ulp_instruction_set.html
簡単なものだけ上げておきます。
sub r1, r2, r3 // R1 = R2 - R3
ld r1, r2, offset //R1にR2のアドレスの内容を読み込む R1=R2[offset]
st r1, r2, offset //R1の内容をR2のアドレスへ書き込む R2[offset]=R1
move r1, 1 //R1に値1を入れる
Addr:
MOVE R1, Addr //R1にAddrのアドレスを入れる
label:
jump label, eq //実行行を移す GOTO 直前の演算結果がzeroならばJUMP
jumpr label, 1, eq //R0の中身を比較して同じならJUMP
adc r0, sar_sel, mux_sel //ADCから読み込み-->r0
stage_rst //ステージカウントレジスタをリセット(0)
label: //8回ループ
stage_inc 1 //ステージカウントレジスタに1加算
jumps label, 8, lt //ステージカウントレジスタが8未満ならJUMP
lsh r2,r2,1 // r2を1ビット左シフト
halt //停止
wake //本体復帰
大文字小文字はどちらでもOK
上記のr0、r1、r2、r3はCPUに内蔵されている16ビットのレジスタ
ステージングレジスタはカウントしてくれるレジスタで8ビット、
これ専用にjumpsってのを用意してくれてる。
adcのmux_selはいつものGPIOの番号とは異なるULPのGPIO番号
ハマりポイントとしていくつかありますので上げていきます。
あっちこっちの説明にrtcって書いてるのはULPの事。
マクロの定義は追加で出来ないらしく、push、pop、ret、psrなどを使った
あっちこっちのサンプルのコードはそのまま実行できない。
GPIOの番号が普通のC言語とか、mycropythonでやってきたものとは
異なる。ULP専用のGPIO番号が振られていてそれを書きます。
一覧
その他にも、外部レジスタが何種類も有り、
それがさらに複雑さに磨きをかけています。
deepsleepからの復帰はmicropythonでesp32.wake_on_ulp(True)を仕掛けておきますが、
esp32.wake_on_ext0()と同時に使えません。エラーになります。
esp32.wake_on_ext1()は同時に使える
おまじない(外部レジスタに設定するためのマクロ)全部ではない
#GPIOを出力に設定
WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + gpio, 1, 1)
#GPIOをHIにする
WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 1)
#GPIOをLOにする
WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 0)
#GPIOをULPに接続
WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_MUX_SEL_M, 1, 1)
#GPIOからr0に読み込む
READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 1)
#本体復帰可能か確認 可能ならr0に1が返る
READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
#ULPタイマー停止
WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)
上記のRTC_xxな定義はC言語ならヘッダーインクルードすれば良し。
なのですが、当方micropythonで書きたいので、
これらはC言語のソースから拾ってきます。
おまじないの定義
READ_RTC_REG(rtc_reg, low_bit, bit_width)
WRITE_RTC_REG(rtc_reg, low_bit, bit_width, value)
READ_RTC_FIELD(rtc_reg, field)
WRITE_RTC_FIELD(rtc_reg, field, value)
WRITE_RTC_REG/READ_RTC_REG等のパラメータの探し方
https://github.com/pycom/esp-idf-2.0/blob/master/components/driver/rtc_module.c
の43行目からテーブルがある。
それぞれの行の一番右側コメントの値がGPIOの番号
ex..
{RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_MUX_SEL_M, RTC_IO_TOUCH_PAD0_FUN_SEL_S, RTC_IO_TOUCH_PAD0_FUN_IE_M, RTC_IO_TOUCH_PAD0_RUE_M, RTC_IO_TOUCH_PAD0_RDE_M, RTC_IO_TOUCH_PAD0_SLP_SEL_M, RTC_IO_TOUCH_PAD0_SLP_IE_M, RTC_CNTL_TOUCH_PAD0_HOLD_FORCE_M, 10}, //4
番号の一致する行の1番、2番、4番パラメータを参照し
https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_reg.h
から値を探す
GPIO番号の変換
.set gpio4, 10 # gpio4
GPIOが何番になるか?
https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_channel.h
から値を探す
GPIO毎の初期化一覧
https://github.com/pycom/esp-idf-2.0/blob/master/components/driver/rtc_module.c
GPIOと値の変換
https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_channel.h
レジスタと数字の変換
https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_reg.h
RTC_IO_TOUCH_PAD0_MUX_SEL_M部の変換
https://github.com/pycom/esp-idf-2.0/blob/master/components/esp32/include/soc/rtc_io_reg.h
レジスタの情報
https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
以下はよく使うと思われるポイントをまとめてみた。
deepsleep中にULPからの割り込みで本体復帰
mycropythonではesp32.wake_on_ulp(True)をやってからdeelsleepします。
#ULP割り込みON
esp32.wake_on_ulp(True)
#sleep
machine.deepsleep(3600*1000)
本体の状況によっては復帰コマンド(wake)を受け付けない。
ULPでは本体復帰可能かを確かめて、可能=1ならwakeを行う。
wakeを行う時にそれ以降ULPプログラムが実行されないようにタイマー停止も行う。
wakeUp:
#本体復帰OKか確認
READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
#OKならr0に1が入る
and r0, r0, 1
jump wakeUp, eq #結果が0ならJUMP 1ならwake
#本体復帰
wake
#ULPタイマー停止
WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)
jump exit
exit:
halt
GPIOから値を入力
#define DR_REG_RTCIO_BASE 0x3ff48400
#define RTC_GPIO_IN_REG (DR_REG_RTCIO_BASE + 0x24)
#define RTC_GPIO_IN_NEXT_S 14
#GPIO14------------------------------------------
#define RTC_IO_TOUCH_PAD6_REG (DR_REG_RTCIO_BASE + 0xac)
#define RTC_IO_TOUCH_PAD6_MUX_SEL_M (BIT(19))
#define RTC_IO_TOUCH_PAD6_FUN_IE_M (BIT(13))
.set gpio14, 16 # gpio14
state: .long 0
entry:
# GPIO14
WRITE_RTC_REG(RTC_IO_TOUCH_PAD6_REG, RTC_IO_TOUCH_PAD6_MUX_SEL_M, 1, 1)
WRITE_RTC_REG(RTC_IO_TOUCH_PAD6_REG, RTC_IO_TOUCH_PAD6_FUN_IE_M, 1, 1)
chk1:
#GPIO14-----------------------------------------------
# GPIOからr0に読む
READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + gpio14, 1)
#押されてたら1 (PULLDOWNの場合。PULLUPは0)
jumpr on1, 1, eq
jump chk2
on1:
move r3, state
move r0, 14
st r0, r3, 0
jump wakeUp
chk2:
次の処理
wakeUp:
復帰処理
GPIOからアナログ(ADC)入力
mycropythonでADCを初期化しておく
pin = 34
ADC(Pin(pin),atten=ADC.ATTN_11DB)
結果を変数領域から取得する
u12 = mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK
print("Volt:",u12*(3.3/4095))
ULPではADCから値を読み込んで変数領域へ格納
#gpio34
.set sar_sel, 0
.set mux_sel, 7
.text
val: .long 0
adc r0, sar_sel, mux_sel
move r3, val
st r0, r3, 0
そのまま実行できるサンプルプログラムを以下からダウンロードできます。
https://drive.google.com/file/d/1qiiXNS387NZzNazFAt0WiogwVG5Z2qiZ/view?usp=sharing