ESP32は、高い処理性能に加え、Wi-FiやBluetoothを搭載した高機能マイコンです。
一方で、バッテリー駆動のIoT機器では、消費電力をいかに抑えるかが重要な課題になります。
ESP32には、CPUや周辺機能の動作を停止して消費電力を大幅に削減できるDeep Sleep機能が搭載されています。
センサーを一定時間ごとに起動してデータを送信したり、ボタン操作があるまで待機したりするIoT機器では、Deep Sleepは欠かせない機能です。
ESP32で開発を始めると、
- Deep Sleepとは何?
- Light Sleepとの違いは?
- GPIOやタイマーで復帰できるの?
- 復帰後はプログラムがどこから実行されるの?
といった疑問を持つ方も多いでしょう。
この記事では、Deep Sleepの基本からESP32で利用するメリット、実務で意識している設計ポイントまで、現役エンジニアの視点で分かりやすく解説します。
Deep Sleepとは?
Deep Sleepは、ESP32の消費電力を大幅に削減するための省電力モードです。
Deep Sleepへ移行すると、
- CPU
- Wi-Fi
- Bluetooth
- 多くの周辺回路
が停止します。
必要最小限の回路だけを動作させることで、通常動作時と比較して大幅に消費電力を削減できます。
そのため、電池駆動のIoT機器では最もよく利用される省電力機能の一つです。
Deep Sleepが使われる場面
ESP32では、次のような用途でDeep Sleepが利用されています。
- 温度・湿度ロガー
- スマートメーター
- 人感センサー
- 環境モニター
- バッテリー駆動の無線センサー
- 定期的にクラウドへデータを送信するIoT機器
例えば、10分ごとに起動してセンサーを読み取り、Wi-Fiでデータを送信した後に再びDeep Sleepへ移行することで、バッテリー寿命を大幅に延ばすことができます。
Deep SleepとLight Sleepの違い
ESP32には、複数の省電力モードがあります。
Light Sleep
- CPUを停止
- メモリの内容を保持
- 復帰が速い
- 消費電力は比較的少ない
短時間の待機や、すぐに処理を再開したい場合に適しています。
Deep Sleep
- CPUを停止
- Wi-Fi・Bluetooth停止
- 消費電力を大幅に削減
- 復帰後は基本的にリセットと同じ動作
長時間待機するIoT機器では、Deep Sleepが適しています。
Deep Sleepからの復帰方法
ESP32では、さまざまな方法でDeep Sleepから復帰できます。
代表的なものは次のとおりです。
タイマー
一定時間が経過すると自動で起動します。
例えば、
- 1分ごと
- 10分ごと
- 1時間ごと
など、周期的な処理に適しています。
GPIO
特定のGPIOが変化したときに起動できます。
例えば、
- タクトスイッチ
- ドアセンサー
- 人感センサー
など、イベント駆動型のIoT機器に適しています。
その他
ESP32シリーズによっては、
- タッチセンサー
- ULP(Ultra Low Power)コプロセッサ
などによる復帰にも対応しています。
利用できる機能はESP32シリーズごとに異なるため、使用するデバイスの技術資料を確認してください。
Deep Sleep利用時の注意点
Deep Sleepから復帰すると、多くの場合はリセット後と同じようにapp_main()から実行が開始されます。
そのため、
「Deep Sleep前の続きから処理が再開される」
と考えるのではなく、
「新しく起動した」
と考えることが重要です。
必要なデータはRTCメモリや不揮発性メモリへ保存するなど、状態保持を考慮した設計が必要になります。
ESP32でDeep Sleepを使用するメリット
ESP-IDFでは、Deep Sleepを利用するAPIが用意されています。
主なメリットは次のとおりです。
- バッテリー寿命を延ばせる
- 定期起動が容易
- GPIOによるイベント起動が可能
- IoT機器との相性が良い
- ESP-IDF APIで簡単に利用できる
開発環境
ESP-IDF:v5.5
ターゲット:ESP32-C5
開発環境:Visual Studio Code
Wakeup方式:タイマー
スリープ時間:10秒
LED:GPIO8
接続回路
ESP32-C5
GPIO8 ─── 330Ω ─── LED ─── GND
GPIO8をHighにするとLEDが点灯します。
CMakeLists.txt
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES
esp_driver_gpio
)
サンプルプログラム
#include <inttypes.h>
#include <stdint.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_sleep.h"
/*
* 使用するLEDのGPIO番号です。
*
* 使用するESP32-C5開発ボードに合わせて
* 適宜変更してください。
*/
#define LED_GPIO GPIO_NUM_8
/*
* Deep Sleepから復帰するまでの時間です。
*
* esp_sleep_enable_timer_wakeup()には
* マイクロ秒単位で指定します。
*/
#define DEEP_SLEEP_TIME_SECONDS 10ULL
#define MICROSECONDS_PER_SECOND 1000000ULL
#define DEEP_SLEEP_TIME_US \
(DEEP_SLEEP_TIME_SECONDS * MICROSECONDS_PER_SECOND)
/*
* LEDを点灯しておく時間です。
*/
#define LED_ON_TIME_MS 500
/*
* Deep Sleepへ移行する前に、
* ログ出力が完了するまで待つ時間です。
*/
#define LOG_FLUSH_WAIT_MS 100
static const char *TAG = "DEEP_SLEEP_SAMPLE";
/*
* RTC_DATA_ATTRを付けた変数は、
* Deep Sleep中もRTC/LPメモリへ保持されます。
*
* 電源を切った場合やリセット条件によっては
* 値が失われます。
*/
RTC_DATA_ATTR static uint32_t s_boot_count = 0;
/**
* LEDを接続したGPIOを初期化します。
*/
static void led_gpio_initialize(void)
{
const gpio_config_t led_config = {
.pin_bit_mask = (1ULL << LED_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(
gpio_config(&led_config));
/*
* 起動時はLEDを消灯します。
*/
ESP_ERROR_CHECK(
gpio_set_level(
LED_GPIO,
0));
}
/**
* Deep Sleepからの復帰理由を表示します。
*/
static void log_wakeup_cause(void)
{
esp_sleep_wakeup_cause_t wakeup_cause =
esp_sleep_get_wakeup_cause();
switch (wakeup_cause) {
case ESP_SLEEP_WAKEUP_TIMER:
ESP_LOGI(
TAG,
"Wakeup cause: timer");
break;
case ESP_SLEEP_WAKEUP_EXT0:
ESP_LOGI(
TAG,
"Wakeup cause: EXT0");
break;
case ESP_SLEEP_WAKEUP_EXT1:
ESP_LOGI(
TAG,
"Wakeup cause: EXT1");
break;
case ESP_SLEEP_WAKEUP_GPIO:
ESP_LOGI(
TAG,
"Wakeup cause: GPIO");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
ESP_LOGI(
TAG,
"Wakeup cause: touchpad");
break;
case ESP_SLEEP_WAKEUP_ULP:
ESP_LOGI(
TAG,
"Wakeup cause: ULP");
break;
case ESP_SLEEP_WAKEUP_UNDEFINED:
/*
* 電源投入、ENリセット、ソフトウェアリセットなど、
* Deep Sleep以外から起動した場合です。
*/
ESP_LOGI(
TAG,
"Wakeup cause: normal boot");
break;
default:
ESP_LOGI(
TAG,
"Wakeup cause: %d",
(int)wakeup_cause);
break;
}
}
/**
* LEDを一定時間点灯します。
*/
static void indicate_wakeup_with_led(void)
{
ESP_ERROR_CHECK(
gpio_set_level(
LED_GPIO,
1));
ESP_LOGI(
TAG,
"LED ON");
vTaskDelay(
pdMS_TO_TICKS(LED_ON_TIME_MS));
ESP_ERROR_CHECK(
gpio_set_level(
LED_GPIO,
0));
ESP_LOGI(
TAG,
"LED OFF");
}
/**
* タイマーによるWakeupを設定します。
*/
static void configure_timer_wakeup(void)
{
ESP_ERROR_CHECK(
esp_sleep_enable_timer_wakeup(
DEEP_SLEEP_TIME_US));
ESP_LOGI(
TAG,
"Timer wakeup configured: %" PRIu64 " seconds",
DEEP_SLEEP_TIME_SECONDS);
}
/**
* Deep Sleepへ移行します。
*
* esp_deep_sleep_start()を呼び出すと、
* 通常はこの関数から戻りません。
*/
static void enter_deep_sleep(void)
{
ESP_LOGI(
TAG,
"Entering Deep Sleep");
/*
* ログがシリアルへ出力される時間を確保します。
*/
vTaskDelay(
pdMS_TO_TICKS(LOG_FLUSH_WAIT_MS));
esp_deep_sleep_start();
/*
* 通常、この位置へ到達することはありません。
*/
}
void app_main(void)
{
/*
* 通常起動時は0から開始し、
* Deep Sleep復帰ごとに値が増加します。
*/
s_boot_count++;
led_gpio_initialize();
ESP_LOGI(
TAG,
"Deep Sleep sample started");
ESP_LOGI(
TAG,
"Boot count: %" PRIu32,
s_boot_count);
ESP_LOGI(
TAG,
"LED GPIO: %d",
LED_GPIO);
log_wakeup_cause();
/*
* 起動したことをLEDで通知します。
*/
indicate_wakeup_with_led();
/*
* 10秒後に復帰するタイマーを設定します。
*/
configure_timer_wakeup();
/*
* Deep Sleepへ移行します。
*/
enter_deep_sleep();
}
サンプルコードのポイント
LED用GPIOを初期化する
最初に、起動を知らせるLED用GPIOを出力へ設定します。
const gpio_config_t led_config = {
.pin_bit_mask = (1ULL << LED_GPIO),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
今回はGPIO8を使用しています。
その後、gpio_config()で設定を反映します。
ESP_ERROR_CHECK( gpio_config(&led_config));
起動直後はLEDを消灯するため、出力をLowへ設定します。
ESP_ERROR_CHECK( gpio_set_level( LED_GPIO, 0));
GPIO8はサンプル用の設定です。
使用するESP32-C5開発ボードで、GPIO8が他の機能や搭載部品に使用されていないことを確認してください。
Deep Sleep復帰回数をRTCメモリへ保存する
今回のサンプルでは、起動回数を次の変数で管理しています。
RTC_DATA_ATTR static uint32_t s_boot_count = 0;
RTC_DATA_ATTRを付けた変数は、Deep Sleep中も保持されるRTC/LPメモリ領域へ配置されます。
そのため、Deep Sleepから復帰した後も、前回の値を引き継ぐことができます。
app_main()の先頭で次のように値を増加させています。
s_boot_count++;
初回起動では1、1回目のDeep Sleep復帰後は2、次の復帰後は3というように増加します。
ただし、次の場合は値が保持されないことがあります。
- 電源を切った
- RTCメモリが保持されないリセットが発生した
- ファームウェアを書き換えた
- RTCメモリの設定を変更した
永続的に保存する必要があるデータは、NVSなどの不揮発性メモリへ保存してください。
Deep Sleepからの復帰理由を取得する
ESP32がどの要因で起動したかは、次の関数で確認できます。
esp_sleep_wakeup_cause_t wakeup_cause = esp_sleep_get_wakeup_cause();
戻り値を確認することで、タイマー、GPIO、ULPなど、復帰要因に応じた処理を実行できます。
今回のタイマー復帰では、次の値になります。
ESP_SLEEP_WAKEUP_TIMER
通常の電源投入やリセットから起動した場合は、次の値になります。
ESP_SLEEP_WAKEUP_UNDEFINED
この違いを利用すると、
- 初回起動時だけ初期設定を行う
- タイマー復帰時はセンサー測定だけを行う
- GPIO復帰時はユーザー操作を処理する
といった分岐を実装できます。
起動時にLEDを点灯する
Deep Sleepから復帰したことを確認できるように、LEDを500ms間点灯します。
ESP_ERROR_CHECK( gpio_set_level( LED_GPIO, 1));
vTaskDelay( pdMS_TO_TICKS(LED_ON_TIME_MS));
ESP_ERROR_CHECK( gpio_set_level( LED_GPIO, 0));
今回の設定では、次の値を使用しています。
#define LED_ON_TIME_MS 500
LEDが500ms点灯した後に消灯し、タイマーWakeupを設定してDeep Sleepへ移行します。
実際のバッテリー駆動製品では、LEDの点灯も消費電力へ影響します。
製品化する場合は、点灯時間を短くするか、デバッグ時だけLEDを点灯する構成も検討してください。
タイマーによるWakeupを設定する
Deep Sleepから10秒後に復帰するため、次の関数を使用します。
ESP_ERROR_CHECK( esp_sleep_enable_timer_wakeup( DEEP_SLEEP_TIME_US));
引数には、Wakeupまでの時間をマイクロ秒単位で指定します。
今回の定義は次のとおりです。
#define DEEP_SLEEP_TIME_SECONDS 10ULL
#define MICROSECONDS_PER_SECOND 1000000ULL
#define DEEP_SLEEP_TIME_US \ (DEEP_SLEEP_TIME_SECONDS * MICROSECONDS_PER_SECOND)
10秒は10,000,000µsなので、次の計算になります。
10秒 × 1,000,000µs = 10,000,000µs
周期を変更する場合は、DEEP_SLEEP_TIME_SECONDSの値を変更します。
例えば60秒ごとに起動する場合は、次のように設定します。
#define DEEP_SLEEP_TIME_SECONDS 60ULL
Deep Sleepへ移行する
Wakeup条件を設定した後、次の関数でDeep Sleepへ移行します。
esp_deep_sleep_start();
この関数を実行するとCPUと多くのデジタル周辺機能が停止し、通常は関数から戻りません。
タイマーWakeupが発生すると、ESP32-C5は再起動に近い動作を行い、再びapp_main()から処理を開始します。
Deep Sleep前の処理位置から続きを実行するわけではない点に注意してください。
ログ出力を待ってからDeep Sleepへ入る
Deep Sleepへ移行すると、UARTなど多くの周辺機能も停止します。
そのため、ログ出力直後にDeep Sleepへ入ると、シリアルモニターへ最後のログが表示されない場合があります。
今回のサンプルでは、次の待機処理を入れています。
vTaskDelay( pdMS_TO_TICKS(LOG_FLUSH_WAIT_MS));
設定値は100msです。
#define LOG_FLUSH_WAIT_MS 100
これは記事用のシンプルな対策です。
製品コードでは、使用しているログ出力方式やUARTの送信状態に応じて、送信完了を確認してからDeep Sleepへ移行する設計も検討してください。
実行結果
プログラムを書き込み、シリアルモニターを開くと、初回起動時には次のようなログが表示されます。
I (310) DEEP_SLEEP_SAMPLE: Deep Sleep sample started
I (310) DEEP_SLEEP_SAMPLE: Boot count: 1
I (310) DEEP_SLEEP_SAMPLE: LED GPIO: 8
I (310) DEEP_SLEEP_SAMPLE: Wakeup cause: normal boot
I (320) DEEP_SLEEP_SAMPLE: LED ON
I (820) DEEP_SLEEP_SAMPLE: LED OFF
I (820) DEEP_SLEEP_SAMPLE: Timer wakeup configured: 10 seconds
I (820) DEEP_SLEEP_SAMPLE: Entering Deep Sleep
約10秒後にタイマーWakeupが発生すると、次のようなログが表示されます。
I (310) DEEP_SLEEP_SAMPLE: Deep Sleep sample started
I (310) DEEP_SLEEP_SAMPLE: Boot count: 2
I (310) DEEP_SLEEP_SAMPLE: LED GPIO: 8
I (310) DEEP_SLEEP_SAMPLE: Wakeup cause: timer
I (320) DEEP_SLEEP_SAMPLE: LED ON
I (820) DEEP_SLEEP_SAMPLE: LED OFF
I (820) DEEP_SLEEP_SAMPLE: Timer wakeup configured: 10 seconds
I (820) DEEP_SLEEP_SAMPLE: Entering Deep Sleep
以降も10秒ごとに起動し、Boot countが増加します。
Boot count: 3
Boot count: 4
Boot count: 5
GPIO8へ接続したLEDが起動ごとに500ms点灯すれば、タイマーWakeupは正常に動作しています。
掲載するログは表示例です。
記事へ掲載する際は、可能であれば実機で取得したログへ差し替えてください。
Deep Sleep利用時によくあるトラブル
1.Deep Sleepから復帰しない
タイマーWakeupが正しく設定されていない可能性があります。
次の関数が、esp_deep_sleep_start()より前に実行されていることを確認してください。
esp_sleep_enable_timer_wakeup(DEEP_SLEEP_TIME_US);
また、時間の単位がマイクロ秒であることにも注意してください。
10秒を指定する場合、値は10ではなく10,000,000です。
2.復帰後に前回の処理の続きから動かない
Deep Sleepから復帰すると、基本的に通常起動と同じようにapp_main()から処理が始まります。
次のような処理にはなりません。
Deep Sleep前の関数
↓
Deep Sleep
↓
同じ関数の続き
実際には次のような流れです。
app_main()
↓
Deep Sleep
↓
Wakeup
↓
app_main()から再実行
必要な状態は、RTCメモリやNVSなどへ保存してください。
3.起動回数が保持されない
変数にRTC_DATA_ATTRが付いているか確認してください。
RTC_DATA_ATTR static uint32_t s_boot_count = 0;
通常のRAMへ置かれた変数は、Deep Sleep復帰時に初期化されます。
また、電源を切った場合はRTCメモリの内容も失われるため、起動回数は0から始まります。
4.LEDが点灯しない
次の項目を確認してください。
- LEDの向きが正しいか
- 330Ωの電流制限抵抗が接続されているか
- LEDのカソードがGNDへ接続されているか
- GPIO番号が実際の配線と一致しているか
- GPIO8が基板上の他機能と競合していないか
GPIO8は今回のサンプル用です。
使用する開発ボードに合わせて変更してください。
5.Deep Sleep中の消費電流が高い
Deep Sleepへ移行しただけで、基板全体の消費電流が必ず小さくなるとは限りません。
次の回路が電力を消費している可能性があります。
- 電源LED
- USB−UART変換IC
- LDO
- 外付けセンサー
- プルアップ/プルダウン抵抗
- DC-DCコンバータ
- 接続したデバッグツール
- GPIOから給電されている外部回路
マイコン単体のDeep Sleep電流ではなく、基板全体の消費電流を測定してください。
開発ボードは電源LEDやUSB回路を搭載しているため、データシートに記載されたチップ単体のDeep Sleep電流より大きくなることがあります。
6.ログの最後が表示されない
UARTの送信完了前にDeep Sleepへ入っている可能性があります。
今回のサンプルでは100ms待機しています。
vTaskDelay(pdMS_TO_TICKS(LOG_FLUSH_WAIT_MS));
ログ量が多い場合やボーレートが低い場合は、待機時間を長くする必要があります。
製品コードでは、送信完了を確認してからDeep Sleepへ移行する方法も検討してください。
7.Wakeup理由が常にnormal bootになる
タイマーWakeupではなく、次の原因で再起動している可能性があります。
- 電源電圧の低下
- EN端子のリセット
- ウォッチドッグリセット
- ソフトウェアリセット
- USB接続によるリセット
- ファームウェアの再書き込み
リセット理由も併せて確認すると、原因を切り分けやすくなります。
実務での設計ポイント
Deep Sleepを使う目的は、単にCPUを停止させることではありません。
製品全体の平均消費電流を下げることが目的です。
実務では、次の処理を1サイクルとして設計します。
- Wakeupする
- 必要な周辺回路へ電源を供給する
- センサーを初期化する
- データを取得する
- 必要に応じてWi-FiやBLEで送信する
- データを保存する
- 周辺回路を停止する
- 次回Wakeupを設定する
- Deep Sleepへ移行する
重要なのは、起動時間をできるだけ短くすることです。
Deep Sleep中の電流が小さくても、Wakeup後にWi-Fi接続やセンサー待機で長時間動作すると、平均消費電流が増加します。
平均消費電流で考える
バッテリー寿命を考える場合は、Deep Sleep電流だけではなく、1サイクル全体の平均電流を確認します。
例えば、
- 通常動作:100mAで2秒
- Deep Sleep:0.1mAで58秒
という1分周期のシステムでは、通常動作時間が短くても平均電流へ大きく影響します。
そのため、次の両方を改善する必要があります。
- Deep Sleep中の消費電流
- Wakeup後の処理時間と消費電流
外付け回路の電源も制御する
ESP32-C5だけをDeep Sleepへ入れても、外付けセンサーや通信モジュールが動作し続けていれば消費電流は下がりません。
必要に応じて、
- ロードスイッチ
- PチャネルMOSFET
- 電源イネーブル端子
- センサーのSleepモード
などを利用し、外付け回路も停止させます。
ただし、GPIOから直接大きな負荷へ給電する設計は避けてください。
起動理由ごとに処理を分ける
Wakeup理由を確認し、処理内容を分けると効率的です。
例えば、
- 通常起動:初期設定や動作確認
- タイマー復帰:センサー測定と送信
- GPIO復帰:ユーザー操作の処理
- ULP復帰:異常値の確認
という構成です。
これにより、起動するたびに不要な処理を実行せずに済みます。
Deep SleepとLight Sleepの使い分け
Deep Sleep
長時間の待機に向いています。
代表的な用途は次のとおりです。
- 数分~数時間ごとの定期起動
- バッテリー駆動センサー
- 通常時はほとんど動作しない機器
- GPIOイベントがあるまで待機する機器
消費電力を大きく下げられる一方、復帰後は基本的にapp_main()から再実行されます。
Light Sleep
比較的短時間の待機に向いています。
代表的な用途は次のとおりです。
- 短時間のアイドル
- メモリ状態を保持したい場合
- Deep Sleepより速い復帰が必要な場合
- 処理の続きを再開したい場合
Deep Sleepより消費電力は大きくなりますが、システム状態を保持しやすい利点があります。
選択の考え方
次のように考えると分かりやすくなります。
- 長時間停止し、再起動型の処理でよい:Deep Sleep
- 短時間停止し、処理を継続したい:Light Sleep
- CPUは動かしながら無線だけ停止したい:Modem Sleep
用途に応じて、消費電力と復帰時間、状態保持のバランスを考えて選択してください。
まとめ
Deep Sleepは、ESP32-C5を使ったバッテリー駆動機器で重要な省電力機能です。
今回のサンプルでは、次の処理を実装しました。
- 起動回数をRTCメモリへ保存
- Wakeup理由を表示
- 起動時にLEDを点灯
- 10秒後のタイマーWakeupを設定
- Deep Sleepへ移行
- 復帰後に
app_main()から再実行
Deep Sleepを利用する際は、マイコン単体だけでなく、基板全体の消費電力を考えることが重要です。
また、Deep Sleep中の電流だけでなく、Wakeup後にどれだけ短時間で処理を完了できるかが、バッテリー寿命を左右します。
Deep Sleepを「停止機能」としてではなく、
起動 → 処理 → 保存 → 停止
という1サイクルの設計として考えることで、より効率的なIoT機器を構築できます。
技術は、経験から価値になる。
DLROW Design | 現場エンジニア