概要
前回はビット演算を行いましたが、今回は残った命令をすべて紹介したいと思います。
今回は命令より、仕組み解説が大変なのでスケッチを紹介してから、命令の解説を行います。
ADC(アナログ入力)
#include "esp32/ulp.h" #include "driver/rtc_io.h" #include "driver/adc.h" #include "UlpDebug.h" // Slow memory variable assignment enum { SLOW_ADC, SLOW_PROG_ADDR // Program start address }; void ULP(uint32_t us) { // Set ULP activation interval ulp_set_wakeup_period(0, us); // Slow memory initialization memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); uint8_t adc_pin = GPIO_NUM_36; uint8_t adc_adc; uint32_t adc_channel; if ( esp32_gpioMux[adc_pin].adc < 10 ) { // ADC1 adc_adc = 0; adc_channel = esp32_gpioMux[adc_pin].adc; adc1_config_channel_atten((adc1_channel_t)adc_channel, ADC_ATTEN_DB_11); adc1_config_width(ADC_WIDTH_BIT_12); } else { // ADC2 adc_adc = 1; adc_channel = esp32_gpioMux[adc_pin].adc % 10; adc2_config_channel_atten((adc2_channel_t)adc_channel, ADC_ATTEN_DB_11); } Serial.printf("adc_adc : %d\n", adc_adc); Serial.printf("adc_channel : %d\n", adc_channel); // ULP Program const ulp_insn_t ulp_prog[] = { I_ADC(R0, adc_adc, adc_channel), I_MOVI(R2, SLOW_ADC), // R2 = SLOW_COUNT I_ST(R0, R2, 0), // MEM[R2(SLOW_COUNT)] = R1 I_HALT() // Stop the program }; // Run the program shifted backward by the number of variables size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t); ulp_process_macros_and_load(SLOW_PROG_ADDR, ulp_prog, &size); ulp_run(SLOW_PROG_ADDR); } void setup() { Serial.begin(115200); delay(50); // Execute ULP program at 300ms intervals ULP(300000); } void loop() { // For debug output ulpDump(); // Wait delay(1000); }
ULPからADCでアナログ入力を行うためには、ADCの初期化処理が必要になります。
GPIO | ADC | チャンネル |
36 | ADC1(0) | 0 |
37 | ADC1(0) | 1 |
38 | ADC1(0) | 2 |
39 | ADC1(0) | 3 |
32 | ADC1(0) | 4 |
33 | ADC1(0) | 5 |
34 | ADC1(0) | 6 |
35 | ADC1(0) | 7 |
4 | ADC2(1) | 0 |
0 | ADC2(1) | 1 |
2 | ADC2(1) | 2 |
15 | ADC2(1) | 3 |
13 | ADC2(1) | 4 |
12 | ADC2(1) | 5 |
14 | ADC2(1) | 6 |
27 | ADC2(1) | 7 |
25 | ADC2(1) | 8 |
26 | ADC2(1) | 9 |
上記の対応になるのですが、さすがに覚えることは難しいのでテーブルがないか確認したところesp32_gpioMuxのadcが使えそうでした。
if ( esp32_gpioMux[adc_pin].adc < 10 ) { // ADC1 adc_adc = 0; adc_channel = esp32_gpioMux[adc_pin].adc; adc1_config_channel_atten((adc1_channel_t)adc_channel, ADC_ATTEN_DB_11); adc1_config_width(ADC_WIDTH_BIT_12); } else { // ADC2 adc_adc = 1; adc_channel = esp32_gpioMux[adc_pin].adc % 10; adc2_config_channel_atten((adc2_channel_t)adc_channel, ADC_ATTEN_DB_11); }
adcが10未満の場合にはADC1で、数値がそのままチャンネルになります。ADC1とADC2で初期化方法も異なります。
ADC2の場合には10以上の数値になるので、10のあまりを取得してチャンネル番号に変換しています。
ADC_ATTEN_DB_11は11dbで、通常のanalogRead()と同じ設定値です。adc1_config_width()関数で標準値の12ビットを設定しています。ADC2は全体設定する項目がなく、取得時に指定するのですが未設定の場合おそらく12ビットになっている気がします。。。
この辺は情報がかなり少ないので追いきれませんでした。そして取得した数値もかなりぶれているので、個人的にはADCをULPから取得するのはおすすめしません。
ADC1のGPIO36でためしたところ、5V入力で約1000の数値、3.3Vで約500の数値が戻ってきました。ADC2のGPIO26でためしたところ、0Vで約4000、3.3Vで約0になりました。
戻ってくる数値が違っているので、どこか初期化がおかしい気がします。
GPIO入力(デジタル入力)とスリープ復帰
#include "esp32/ulp.h" #include "driver/rtc_io.h" #include "UlpDebug.h" // Slow memory variable assignment enum { SLOW_BTN_STATE, // Btn status SLOW_PROG_ADDR // Program start address }; void ULP(uint32_t us) { // Set ULP activation interval ulp_set_wakeup_period(0, us); // Slow memory initialization memset(RTC_SLOW_MEM, 0, CONFIG_ULP_COPROC_RESERVE_MEM); // Blink status initialization RTC_SLOW_MEM[SLOW_BTN_STATE] = 1; // PIN to btnA (specify by +14) const int pin_btn_bit = RTCIO_GPIO37_CHANNEL + 14; const gpio_num_t pin_btn = GPIO_NUM_37; // GPIO37 initialization rtc_gpio_init(pin_btn); rtc_gpio_set_direction(pin_btn, RTC_GPIO_MODE_INPUT_ONLY); // ULP Program const ulp_insn_t ulp_prog[] = { I_MOVI(R3, SLOW_BTN_STATE), // R3 = SLOW_BTN_STATE I_RD_REG(RTC_GPIO_IN_REG, pin_btn_bit, pin_btn_bit), // R0 = RTC_GPIO_IN_REG[pin_btn_bit:pin_btn_bit] I_ST(R0, R3, 0), // MEM[R3(SLOW_BTN_STATE)] = R0 M_BGE(0, 1), // IF R0 >= 1 THAN GOTO M_LABEL(0) I_WAKE(), // WAKE M_LABEL(0), I_HALT() // Stop the program }; // Run the program shifted backward by the number of variables size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t); ulp_process_macros_and_load(SLOW_PROG_ADDR, ulp_prog, &size); ulp_run(SLOW_PROG_ADDR); } void setup() { Serial.begin(115200); delay(50); Serial.println("Boot"); // ULPをウェイクアップソースとして有効にする esp_sleep_enable_ulp_wakeup(); // Execute ULP program at 300ms intervals ULP(300000); // ディープスリープ esp_deep_sleep_start(); } void loop() { }
ディープスリープから、ボタン入力で復帰するスケッチです。
GPIO37を入力用の初期化をして、スリープにはいってGPIO37がLOWになるとI_WAKE()でスリープ復帰しています。
命令紹介
アナログ入力 – ADC
I_ADC(reg_dest, adc_idx, pad_idx)
regにadc_idx(0:ADC1, 1:ADC2)のpad_idx(チャンネル番号)の電圧を代入します。
I_ADC(R0, ADC1, 0),
レジスタ読込 – REG_RD
I_RD_REG(reg, low_bit, high_bit)
R0にアドレスの内容を、low_bitとhigh_bitで範囲指定して読み込みます。
I_RD_REG(RTC_GPIO_IN_REG, pin_btn_bit, pin_btn_bit), // R0 = RTC_GPIO_IN_REG[pin_btn_bit:pin_btn_bit]
スリープ復帰 – WAKE
I_WAKE()
スリープから復帰します。
I_WAKE(),
命令一覧
命令 | アセンブリ | Cマクロ | 処理 |
ノーオペレーション | NOP | – | – |
代入 | MOVE | I_MOVI(reg_dest, imm_) I_MOVR(reg_dest, reg_src) | reg_dest = imm_ reg_dest = reg_src |
メモリロード | LD | I_LD(reg_dest, reg_addr, offset_) | reg_dest = MEM[reg_addr + offset_] |
メモリストア | ST | I_ST(reg_val, reg_addr, offset_) | MEM[reg_addr + offset_] = reg_val |
足し算 | ADD | I_ADDI(reg_dest, reg_src,
imm_) I_ADDR(reg_dest, reg_src1, reg_src2) | reg_dest = reg_src + imm_ reg_dest = reg_src1 + reg_src2 |
引き算 | SUB | I_SUBI(reg_dest, reg_src,
imm_) I_SUBR(reg_dest, reg_src1, reg_src2) | reg_dest = reg_src – imm_ reg_dest = reg_src1 – reg_src2 |
アンド | AND | I_ADDI(reg_dest, reg_src,
imm_) I_ADDR(reg_dest, reg_src1, reg_src2) | reg_dest = reg_src &
imm_ reg_dest = reg_src1 & reg_src2 |
オア | OR | I_ORI(reg_dest, reg_src,
imm_) I_ORR(reg_dest, reg_src1, reg_src2) | reg_dest = reg_src | imm_ reg_dest = reg_src1 | reg_src2 |
左シフト | LSH | I_LSHI(reg_dest, reg_src,
imm_) I_LSHR(reg_dest, reg_src, reg_shift) | reg_dest = reg_src <<
imm_ reg_dest = reg_src1 << reg_src2 |
右シフト | RSH | I_RSHI(reg_dest, reg_src,
imm_) I_RSHR(reg_dest, reg_src, reg_shift) | reg_dest = reg_src >>
imm_ reg_dest = reg_src1 >> reg_src2 |
ラベル | – | M_LABEL(label_num) | – |
ジャンプ | JUMP | M_BX(label_num) M_BXZ(label_num) M_BXF(label_num) | GOTO label_num IF zero_flag THAN GOTO label_num IF overflow_flag THAN GOTO label_num |
R0比較ジャンプ | JUMPR | M_BL(label_num, imm_value) M_BGE(label_num, imm_value) | IF R0 < imm_value THAN GOTO
label_num IF R0 >= imm_value THAN GOTO label_num |
アナログ入力 | ADC | I_ADC(reg_dest, adc_idx, pad_idx) | – |
レジスタ読込 | REG_RD | I_RD_REG(reg, low_bit, high_bit) | reg = REG_RD[reg][high_bit:low_bit] |
レジスタ書込 | REG_WR | I_WR_REG(reg, low_bit, high_bit, val) | REG_WR[reg][high_bit:low_bit] = val |
ウエイト | WAIT | I_DELAY(cycles_) | Delay cycles_ |
停止 | HALT | I_HALT() | – |
スリープ復帰 | WAKE | I_WAKE() | – |
ULPタイマー停止 | – | I_END() | – |
資料集
ULP命令集
- https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/ulp_instruction_set.html
- https://lang-ship.com/reference/unofficial/M5StickC/System/ULP/instruction/
上がオフィシャルの資料で、下が私が翻訳した資料です。ULPがわかっていないときに翻訳したので、誤りがあると思います。
Cマクロ
Cマクロのオフィシャルの資料です。ここの内容を解説しましたが、ここにはI_ADC()が書かれていません。定義ファイルから拾ってきて解説しています。
ESP32の内部温度を取得するI_TSENS()も定義はされているのですが、未実装と書いてある資料もあるので、今回は省略しました。
ULP機械語
詳細情報はテクニカルマニュアルのULPのページを参考にしてください。命令集にあったTSENS命令やNOP命令がテクニカルマニュアルにはなぜか載っていません、、、
まとめ
ここまでで、Cマクロで通常使う命令はすべて解説が終わりました。ここまで命令の説明だけやってきたので、どのようにプログラミングするのかはあまり触れてきませんでした。
また、マシン語の構造などについても次回から解説したいと思います。
コメント