ESP32に搭載されているコプロセッサーのULPまわりを調べました。最新情報はM5StickC非公式日本語リファレンスにまとめてあります。
ULPとは
ULP (Ultra Low Power)で、省電力で動くコプロセッサーです。ESP32のメインコアとは別にあり、メインコアがスリープ状態でも動かすことができます。
省電力で動く反面、複雑な処理や高速処理ができないデメリットと、プログラミングがアセンブラなところがネックです。
そして情報が極端に少ないです。
サンプルスケッチ
#include "esp32/ulp.h"
#include "driver/rtc_io.h"
void ULP_BLINK_RUN(uint32_t us);
void setup() {
// 300ms間隔でULPプログラムを実行
ULP_BLINK_RUN(300000);
}
void loop() {
}
void ULP_BLINK_RUN(uint32_t us) {
// ULPの起動間隔を設定
ulp_set_wakeup_period(0, us);
// ブリンクの状態を保存する変数初期化(0からプログラムが収納されるので、2047から逆順利用が安全)
const int var_blink_state = 2047;
RTC_SLOW_MEM[var_blink_state] = 0;
// ブリンクするPIN(14bitオフセットして指定する)
const int pin_blink_bit = RTCIO_GPIO26_CHANNEL + 14;
const gpio_num_t pin_blink = GPIO_NUM_26;
// GPIO26の初期化(出力に設定して初期値0)
rtc_gpio_init(pin_blink);
rtc_gpio_set_direction(pin_blink, RTC_GPIO_MODE_OUTPUT_ONLY);
rtc_gpio_set_level(pin_blink, 0);
// ULPプログラム
const ulp_insn_t ulp_prog[] = {
I_MOVI(R3, var_blink_state), // R3 = var_blink_state
I_LD(R0, R3, 0), // R0 = RTC_SLOW_MEM[R3(var_blink_state)]
M_BL(1, 1), // IF R0 < 1 THAN GOTO M_LABEL(1)
// R0 == 1の時実行(LED点灯)
I_WR_REG(RTC_GPIO_OUT_REG, pin_blink_bit, pin_blink_bit, 1), // pin_blink_bit = 1
I_SUBI(R0, R0, 1), // R0 = R0 - 1
I_ST(R0, R3, 0), // RTC_SLOW_MEM[R3(12)] = R0
M_BX(2), // GOTO M_LABEL(2)
// R0 < 1の時実行(LED消灯)
M_LABEL(1), // M_LABEL(1)
I_WR_REG(RTC_GPIO_OUT_REG, pin_blink_bit, pin_blink_bit, 0),// pin_blink_bit = 0
I_ADDI(R0, R0, 1), // R0 = R0 + 1
I_ST(R0, R3, 0), // RTC_SLOW_MEM[R3(var_blink_state)] = R0
M_LABEL(2), // M_LABEL(2)
I_HALT() // プログラム停止
};
// 実行
size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t);
ulp_process_macros_and_load(0, ulp_prog, &size);
ulp_run(0);
}
300ms間隔でGPIO26に接続したLEDを光らせるサンプルです。本当はM5StickCで内蔵しているGPIO10が使いたかったのですが、ULPからはアクセスできないpinでした。

上記のようにGPIO26にLEDと抵抗を接続してBlinkしています。
解説
本来はアセンブラなので、.sファイルを使って開発をしますが、通常の環境ではアセンブラが使えないので、組み込みマクロを使ってプログラムをしています。
以下ハマりどころだけ解説します。
ピンについて
まずGPIOですが、そのままの番号ではなく、RTCのピン番号に14ビットオフセットした値で指定する必要があります。内部的にはRTC経由でアクセスをおり、初期化などもRTC経由で行っています。
RTCIO_GPIO36_CHANNELはesp32_gpioMux[GPIO_NUM_36].rtcでも同じ値です。RTCで利用できないIOで、RTCIO_GPIO03_CHANNELなどの場合には未定義でコンパイルエラーになりますが、 esp32_gpioMux[GPIO_NUM_3].rtcは-1になるので注意してください。
| RTC | GPIO | Bit | Arduinoでの記述 |
| RTC_GPIO0 | GPIO36 | 14 | RTCIO_GPIO36_CHANNEL + 14 |
| RTC_GPIO1 | GPIO37 | 15 | RTCIO_GPIO37_CHANNEL + 14 |
| RTC_GPIO2 | GPIO38 | 16 | RTCIO_GPIO38_CHANNEL + 14 |
| RTC_GPIO3 | GPIO39 | 17 | RTCIO_GPIO39_CHANNEL + 14 |
| RTC_GPIO4 | GPIO34 | 18 | RTCIO_GPIO34_CHANNEL + 14 |
| RTC_GPIO5 | GPIO35 | 19 | RTCIO_GPIO35_CHANNEL + 14 |
| RTC_GPIO6 | GPIO25 | 20 | RTCIO_GPIO25_CHANNEL + 14 |
| RTC_GPIO7 | GPIO26 | 21 | RTCIO_GPIO26_CHANNEL + 14 |
| RTC_GPIO8 | GPIO33 | 22 | RTCIO_GPIO33_CHANNEL + 14 |
| RTC_GPIO9 | GPIO32 | 23 | RTCIO_GPIO32_CHANNEL + 14 |
| RTC_GPIO10 | GPIO04 | 24 | RTCIO_GPIO04_CHANNEL + 14 |
| RTC_GPIO11 | GPIO00 | 25 | RTCIO_GPIO00_CHANNEL + 14 |
| RTC_GPIO12 | GPIO02 | 26 | RTCIO_GPIO02_CHANNEL + 14 |
| RTC_GPIO13 | GPIO15 | 27 | RTCIO_GPIO15_CHANNEL + 14 |
| RTC_GPIO14 | GPIO13 | 28 | RTCIO_GPIO13_CHANNEL + 14 |
| RTC_GPIO15 | GPIO12 | 29 | RTCIO_GPIO12_CHANNEL + 14 |
| RTC_GPIO16 | GPIO14 | 30 | RTCIO_GPIO14_CHANNEL + 14 |
| RTC_GPIO17 | GPIO27 | 31 | RTCIO_GPIO27_CHANNEL + 14 |
上記の関係になっており、RTCではすべてのピンに接続されていません。GPIO26はRTC_GPIO7に対応しており、7+14で21ビット目になります。
スローメモリについて
ESP32では8192バイトのスローメモリと呼ばれる領域があり、ULPと通常のプログラムの両方からアクセスが可能です。uint32_tとして定義してあるので、添字0から2047までの2048個の変数になります。
32ビットですが、実際に利用するのは下位16ビットだけで、上位16ビットは ULPで保存したときの5ビット左シフトしたプログラムカウンター(PC)が設定されています。
また、ULPプログラムのsizeが11の場合には、添字0から10までプログラムが収納されていますので、自由に使えるのは添字11から2047までになります。sizeをみて連番を割り振るか、2047から逆順に使っていくなどしないとプログラムを壊すので注意が必要です。
まとめ
やっぱり情報が極端に少ないです。スローメモリも内容をダンプしながら確認して、やっと理解ができました。アセンブラで記述するときには.globalのラベルを宣言しておいて、C言語からそのラベル名でアクセスできるのですが、マクロの場合には自分で管理しないと駄目みたいですね。


コメント