ESP32 の ULP を定期的に実行する際の Tips を紹介します.
課題
ULP は RTC_SLOW_CLK に基づいて動作しますが,デフォルトでは内部で生成された 150kHz をクロックソースとして使用しています.一方,この 150kHz は温度によって周期が大きく変動するという困った性質を持っています.
そのため,下記のようなコードで,SENSE_INTERVAL 秒毎に ULP プログラムを動かそうとしてもうまくいきません.
1 2 |
REG_SET_FIELD(SENS_ULP_CP_SLEEP_CYC0_REG, SENS_SLEEP_CYCLES_S0, rtc_clk_slow_freq_get_hz()*SENSE_INTERVAL); |
なぜなら,rtc_clk_slow_freq_get_hz
の実装は次のように固定値になっているためです.
1 2 3 4 5 6 7 8 9 |
uint32_t rtc_clk_slow_freq_get_hz() { switch(rtc_clk_slow_freq_get()) { case RTC_SLOW_FREQ_RTC: return RTC_SLOW_CLK_FREQ_150K; case RTC_SLOW_FREQ_32K_XTAL: return RTC_SLOW_CLK_FREQ_32K; case RTC_SLOW_FREQ_8MD256: return RTC_SLOW_CLK_FREQ_8MD256; } return 0; } |
解決方法
ESP-IDF の components/soc/esp32/include/soc/rtc.h
には次の関数が用意されているのでこれを使って解決することができます.
- rtc_clk_cal
- クロックの周期を測定する関数
- rtc_time_us_to_slowclk
- クロックの周期に基づいて,指定された時間をクロック数に換算する関数
これらを組み合わせると,SENSE_INTERVAL 秒毎に ULP プログラムを動かすための設定は次のようにかけます.
1 2 3 4 5 |
#define CLOCK_MEASURE 1024 REG_SET_FIELD(SENS_ULP_CP_SLEEP_CYC0_REG, SENS_SLEEP_CYCLES_S0, rtc_time_us_to_slowclk((uint64_t)(SENSE_INTERVAL) * 1e6, rtc_clk_cal(RTC_CAL_RTC_MUX , CLOCK_MEASURE))); |
CLOCK_MEASURE はクロックの周期を算出するためにサンプリングするサイクル数です.大きいほど精度はあがりますが,その分時間がかかります.ESP-IDF のデフォルトが 1024 なのでここではそれに合わせています.
Deep Sleep に入る前に毎回上記を実行することで,秒オーダーでの誤差がほぼない状態で定期的に ULP を動かすことができます.
補足
ESP-IDF は起動時に一度だけ上記と同じ方法で RTC_SLOW_CLK の周期を取得しており,その値は esp_clk_slowclk_cal_get
で得ることができます.ただし,前述のとおり内部クロックは温度依存性が大きいので,起動時に算出されたこの値は長期に動作させる用途には適さないと思われます.
コメント