ESP32のライトスリープを調べる

ディープスリープより情報がないかも?

現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスで確認してください。

概要

一時的にチップの処理をとめて、省電力するモードです。ディープスリープはリセットされたように、setup()から実行し直されますが、ライトスリープはスリープした場所から再開されます。

一般的にディープスリープはよく使われていますが、ライトスリープはあまり使われていないようです。ただ、トリガーまで省電力で待機するなどを簡単に実現できます。

復帰の種類は以下の7種類あります。

  • タイマー
  • タッチセンサー
  • EXT0(RTC_IO)
  • EXT1(RTC_CNTL)
  • ULP
  • GPIO
  • UART

上5つはディープスリープと同じ方法です。GPIOはほぼEXT0と同じ機能ですが、複数のGPIOを対象にすることができます。UARTはシリアルの受信をトリガーに復帰することができます。

タイマーのサンプル

int bootCount = 0; // ライトスリープは通常の変数で良い
void setup() {
  // シリアル初期化
  Serial.begin(115200);
  // シリアル初期化待ち
  delay(1000);
  // 1000000us = 1sのタイマー設定
  esp_sleep_enable_timer_wakeup(1000000);
}
void loop() {
  // 起動回数カウントアップ
  bootCount++;
  Serial.printf("起動回数: %d ", bootCount);
  // 起動方法取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      : Serial.printf("外部割り込み(RTC_IO)で起動\n"); break;
    case ESP_SLEEP_WAKEUP_EXT1      : Serial.printf("外部割り込み(RTC_CNTL)で起動 IO=%llX\n", esp_sleep_get_ext1_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_TIMER     : Serial.printf("タイマー割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD  : Serial.printf("タッチ割り込みで起動 PAD=%d\n", esp_sleep_get_touchpad_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_ULP       : Serial.printf("ULPプログラムで起動\n"); break;
    case ESP_SLEEP_WAKEUP_GPIO      : Serial.printf("ライトスリープからGPIO割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_UART      : Serial.printf("ライトスリープからUART割り込みで起動\n"); break;
    default                         : Serial.printf("スリープ以外からの起動\n"); break;
  }
  // シリアルをすべて出力する
  Serial.flush();
  // ライトスリープ
  esp_light_sleep_start();
}

ディープスリープと違い、ライトスリープした場所に復帰します。そして変数などもすべて保存されているのでスローメモリを使う必要はありません。プログラムも普通のものとそれほど変わらなく組むことが可能です。

タッチのサンプル

int bootCount = 0;  // ライトスリープは通常の変数で良い
int touchPad = 0;   // タッチされたPAD
// タッチコールバック関数
void callback32(){
  touchPad = 32;
}
void callback33(){
  touchPad = 33;
}
void setup() {
  // シリアル初期化
  Serial.begin(115200);
  // シリアル初期化待ち
  delay(1000);
  // GPIO32, 33のタッチ有効、しきい値は環境によって異なるので反応しない場合には増減が必要
  // ただし32と33のタッチだけはなぜか番号が入れ替わっているので、GPIO32を取得するときは33を指定する必要がある
  static int threshold = 16;
  pinMode(GPIO_NUM_32, INPUT);
  pinMode(GPIO_NUM_33, INPUT);
  touchAttachInterrupt(GPIO_NUM_32, callback32, threshold);
  touchAttachInterrupt(GPIO_NUM_33, callback33, threshold);
  // タッチパッドをウェイクアップソースとして有効にする
  esp_sleep_enable_touchpad_wakeup();
}
void loop() {
  // 起動回数カウントアップ
  bootCount++;
  Serial.printf("起動回数: %d ", bootCount);
  // ライトスリープでsp_sleep_get_touchpad_wakeup_status()を呼び出したらリブートする? PADはコールバックで判定する
  //esp_sleep_get_touchpad_wakeup_status();
  Serial.printf("PAD: %d ", touchPad);
  // 起動方法取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      : Serial.printf("外部割り込み(RTC_IO)で起動\n"); break;
    case ESP_SLEEP_WAKEUP_EXT1      : Serial.printf("外部割り込み(RTC_CNTL)で起動 IO=%llX\n", esp_sleep_get_ext1_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_TIMER     : Serial.printf("タイマー割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD  : Serial.printf("タッチ割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_ULP       : Serial.printf("ULPプログラムで起動\n"); break;
    case ESP_SLEEP_WAKEUP_GPIO      : Serial.printf("ライトスリープからGPIO割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_UART      : Serial.printf("ライトスリープからUART割り込みで起動\n"); break;
    default                         : Serial.printf("スリープ以外からの起動\n"); break;
  }
  // シリアルをすべて出力する
  Serial.flush();
  // ライトスリープ
  esp_light_sleep_start();
}

タッチも割り込み関数によって復帰しますが、なぜかsp_sleep_get_touchpad_wakeup_status()を呼び出すとエラーがでてリセットされました。ディープスリープでは使えたのですが、ライトスリープでは使えないようです。

EXT0のサンプル

int bootCount = 0; // ライトスリープは通常の変数で良い
void setup() {
  // シリアル初期化
  Serial.begin(115200);
  // シリアル初期化待ち
  delay(1000);
  // GPIO37(M5StickCのHOMEボタン)がLOWになったら起動
  pinMode(GPIO_NUM_37, INPUT_PULLUP);
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_37, LOW);
}
void loop() {
  // 起動回数カウントアップ
  bootCount++;
  Serial.printf("起動回数: %d ", bootCount);
  // 起動方法取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      : Serial.printf("外部割り込み(RTC_IO)で起動\n"); break;
    case ESP_SLEEP_WAKEUP_EXT1      : Serial.printf("外部割り込み(RTC_CNTL)で起動 IO=%llX\n", esp_sleep_get_ext1_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_TIMER     : Serial.printf("タイマー割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD  : Serial.printf("タッチ割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_ULP       : Serial.printf("ULPプログラムで起動\n"); break;
    case ESP_SLEEP_WAKEUP_GPIO      : Serial.printf("ライトスリープからGPIO割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_UART      : Serial.printf("ライトスリープからUART割り込みで起動\n"); break;
    default                         : Serial.printf("スリープ以外からの起動\n"); break;
  }
  // シリアルをすべて出力する
  Serial.flush();
  // ライトスリープ
  esp_light_sleep_start();
}

特に特殊なことは必要ありません。

EXT1のサンプル

int bootCount = 0; // ライトスリープは通常の変数で良い
void setup() {
  // シリアル初期化
  Serial.begin(115200);
  // シリアル初期化待ち
  delay(1000);
  // GPIO26かGPIO36がHIGHになったら起動
  pinMode(GPIO_NUM_26, INPUT);
  pinMode(GPIO_NUM_36, INPUT);
  esp_sleep_enable_ext1_wakeup(BIT64(GPIO_NUM_26) | BIT64(GPIO_NUM_36), ESP_EXT1_WAKEUP_ANY_HIGH);
  // M5StickCだとGPIO0がプルアップされているのでGNDに落とすことでも起動した
  //pinMode(GPIO_NUM_0, INPUT);
  //esp_sleep_enable_ext1_wakeup(BIT64(GPIO_NUM_0), ESP_EXT1_WAKEUP_ALL_LOW);
}
void loop() {
  // 起動回数カウントアップ
  bootCount++;
  Serial.printf("起動回数: %d ", bootCount);
  // 起動方法取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      : Serial.printf("外部割り込み(RTC_IO)で起動\n"); break;
    case ESP_SLEEP_WAKEUP_EXT1      : Serial.printf("外部割り込み(RTC_CNTL)で起動 IO=%llX\n", esp_sleep_get_ext1_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_TIMER     : Serial.printf("タイマー割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD  : Serial.printf("タッチ割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_ULP       : Serial.printf("ULPプログラムで起動\n"); break;
    case ESP_SLEEP_WAKEUP_GPIO      : Serial.printf("ライトスリープからGPIO割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_UART      : Serial.printf("ライトスリープからUART割り込みで起動\n"); break;
    default                         : Serial.printf("スリープ以外からの起動\n"); break;
  }
  // シリアルをすべて出力する
  Serial.flush();
  // ライトスリープ
  esp_light_sleep_start();
}

こちらもディープスリープと同じような処理です。

ULPのサンプル

#include "esp32/ulp.h"
int bootCount = 0; // ライトスリープは通常の変数で良い
// スローメモリー変数割当
enum {
  SLOW_PROG_ADDR        // プログラムの先頭アドレス
};
void ULP_PROG(uint32_t us) {
  // ULPの起動間隔を設定
  ulp_set_wakeup_period(0, us);
  // ULPプログラム
  const ulp_insn_t  ulp_prog[] = {
    I_WAKE(),           // メインチップ起動
    I_HALT(),           // ULPプログラム停止
  };
  // 変数の分プログラムを後ろにずらして実行
  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(1000);
  // ULPを1秒間隔で起動
  ULP_PROG(1000000);
  // ULPをウェイクアップソースとして有効にする
  esp_sleep_enable_ulp_wakeup();
}
void loop() {
  // 起動回数カウントアップ
  bootCount++;
  Serial.printf("起動回数: %d ", bootCount);
  // 起動方法取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      : Serial.printf("外部割り込み(RTC_IO)で起動\n"); break;
    case ESP_SLEEP_WAKEUP_EXT1      : Serial.printf("外部割り込み(RTC_CNTL)で起動 IO=%llX\n", esp_sleep_get_ext1_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_TIMER     : Serial.printf("タイマー割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD  : Serial.printf("タッチ割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_ULP       : Serial.printf("ULPプログラムで起動\n"); break;
    case ESP_SLEEP_WAKEUP_GPIO      : Serial.printf("ライトスリープからGPIO割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_UART      : Serial.printf("ライトスリープからUART割り込みで起動\n"); break;
    default                         : Serial.printf("スリープ以外からの起動\n"); break;
  }
  // シリアルをすべて出力する
  Serial.flush();
  // ライトスリープ
  esp_light_sleep_start();
}

ULPもディープスリープとほぼ同じです。

GPIOのサンプル

int bootCount = 0; // ライトスリープは通常の変数で良い
void setup() {
  // シリアル初期化
  Serial.begin(115200);
  // シリアル初期化待ち
  delay(1000);
  // GPIO37(M5StickCのHOMEボタン)かGPIO39(M5StickCの右ボタン)がLOWになったら起動
  pinMode(GPIO_NUM_37, INPUT_PULLUP);
  pinMode(GPIO_NUM_39, INPUT_PULLUP);
  gpio_wakeup_enable(GPIO_NUM_37, GPIO_INTR_LOW_LEVEL);
  gpio_wakeup_enable(GPIO_NUM_39, GPIO_INTR_LOW_LEVEL);
  // GPIOをウェイクアップソースとして有効にする
  esp_sleep_enable_gpio_wakeup();
}
void loop() {
  // 起動回数カウントアップ
  bootCount++;
  Serial.printf("起動回数: %d ", bootCount);
  // 起動方法取得
  esp_sleep_wakeup_cause_t wakeup_reason;
  wakeup_reason = esp_sleep_get_wakeup_cause();
  switch(wakeup_reason){
    case ESP_SLEEP_WAKEUP_EXT0      : Serial.printf("外部割り込み(RTC_IO)で起動\n"); break;
    case ESP_SLEEP_WAKEUP_EXT1      : Serial.printf("外部割り込み(RTC_CNTL)で起動 IO=%llX\n", esp_sleep_get_ext1_wakeup_status()); break;
    case ESP_SLEEP_WAKEUP_TIMER     : Serial.printf("タイマー割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD  : Serial.printf("タッチ割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_ULP       : Serial.printf("ULPプログラムで起動\n"); break;
    case ESP_SLEEP_WAKEUP_GPIO      : Serial.printf("ライトスリープからGPIO割り込みで起動\n"); break;
    case ESP_SLEEP_WAKEUP_UART      : Serial.printf("ライトスリープからUART割り込みで起動\n"); break;
    default                         : Serial.printf("スリープ以外からの起動\n"); break;
  }
  // シリアルをすべて出力する
  Serial.flush();
  // ライトスリープ
  esp_light_sleep_start();
}

EXT0に似ていますが、複数のGPIOを指定することができます。

UARTのサンプル

すみません、M5StickCでは利用できないので、準備していません。

[SOLVED]esp_sleep_enable_uart_wakeup with CONFIG_CONSOLE_UART_CUSTOM configuration」に情報が少しだけあります。

基本的にUARTで受信トリガーにできるのは標準PIN配置のUARTだけで、UART0ではGPIO3、UART1ではGPIO9をRXとして設定する必要があります。

また、受信した信号エッジ数を復帰トリガーとしているので、最初の数文字は取りこぼします。他の信号線が接続されていない状況以外では積極的に利用することはないと思います。

まとめ

ライトスリープはちょっとした省電力にはいいと思いますが、扱いはちょっと難しいかもしれません。バッテリー駆動の場合にはディープスリープを使っちゃうので、あまり出番はないのかな?

また、サンプルソースの最新版はGitHubにアップされています。

コメント