ESP32のディープスリープ:ウェイクアップ要因、消費電流、電池の持ち
ESP32のディープスリープを解説します。各スリープモードのµA単位の消費電流、タイマー・ボタン・PIR・タッチによるウェイクアップ(ext0/ext1)、RTCメモリのカウンター、18650電池の持ちの計算まで紹介します。
ESP32のディープスリープは、ESP32がCPU、Wi-FiとBluetoothの無線、メインメモリの電源を切り、小さなRTCドメインだけを動かし続ける電源モードです。このモードでは、Wi-Fiで送信中に240 mAあるチップの消費電流が約10 µAまで下がり、約2万4,000分の1になります。ボードは設定した時間が経ったとき、ボタンが押されたとき、PIRセンサーが動きを検知したとき、またはピンに触れたときに目を覚まし、数秒で処理を終えて再びスリープに戻ります。この仕組みのおかげで、電池で動く気象ステーション、庭のセンサー、ドアアラーム、遠隔地のロガーが、18650電池1本で数日ではなく何か月も動きます。
このガイドでは、ESP32のスリープモードと実際の消費電流、タイマー・ボタン・PIRセンサー(ext0とext1)・タッチでボードをウェイクアップさせる方法、スリープ中も消えないRTCメモリにカウンターや測定値を保持する方法、開発ボードがスリープ中でもミリアンペアを消費する理由、そして18650電池1本で何か月動くかをステップごとに計算する方法を学びます。Arduinoのスケッチは、ESP32 Arduinoコアのバージョン3.1以降向けに書いています。最後に、同じことをMicroPythonで行う方法も紹介します。

ESP32のディープスリープとは?どのように動くのか
ESP32の中には、2つの別々の世界があります。1つ目は、CPU、520 KBのRAM、Wi-FiとBluetoothの無線を含むメインドメインです。2つ目はRTCドメインで、専用の小さなタイマー、8 KBのRTC FASTメモリと8 KBのRTC SLOWメモリ、RTC GPIOピン、ULPと呼ばれる超低消費電力のコプロセッサを持っています。ディープスリープではメインドメインの電源が完全に切れ、RTCドメインだけが動き続けます。ウェイクアップのときが来ると、チップは実際には再起動します。ブートローダーが動き、プログラムがフラッシュから再び読み込まれ、setup()が最初から実行されます。そのため、ディープスリープを使うプログラムはすべての処理をsetup()の中で行い、loop()は一度も実行されません。通常の変数はウェイクアップのたびに初期化され、RTCメモリに置いた変数だけが保持されます。
ESP32のスリープモードと消費電流(µA)
| モード | 動き続けるもの | 電流(ESP32チップ) |
|---|---|---|
| 動作中:Wi-Fi送信(802.11b、19.5 dBm) | CPUと無線 | 240 mA |
| 動作中:Wi-Fi受信と待ち受け | CPUと無線 | 95〜100 mA |
| モデムスリープ、240 MHz | CPUは動作、無線はオフ | 30〜68 mA |
| モデムスリープ、80 MHz | CPUは低速で動作、無線はオフ | 20〜31 mA |
| ライトスリープ | CPUは一時停止、RAMは保持 | 0.8 mA |
| ディープスリープ(ULPオン) | RTCドメインとULPコプロセッサ | 150 µA |
| ディープスリープ | RTCタイマーとRTCメモリ | 10 µA |
| ハイバネーション | RTCタイマーと一部のRTCピンのみ | 5 µA |
| オフ(ENピンがLOW) | なし | 1 µA |
これらの値はEspressifのESP32データシートによるもので、チップ単体の消費電流です。表の中で注意したい点が2つあります。1つ目に、ライトスリープではプログラムが止まった行から続きを実行し、RAMの内容もすべて保持されますが、消費電流はディープスリープの80倍です。そのため、数秒程度の待ち時間に向いています。ディープスリープではプログラムが最初からやり直しになる代わりに、消費電流はマイクロアンペアまで下がります。数分から数時間の待ち時間にはこちらが正解です。2つ目に、どちらのスリープモードでもWi-FiやBluetoothの接続は維持されず、ウェイクアップのたびに接続し直す必要があります。新しいチップでは値が少し異なり、ESP32-C3はディープスリープで約5 µA、ESP32-S3は約7〜8 µAです。
さらに消費電流を下げたい場合は、RTCペリフェラルとRTCメモリの電源も切れます。esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF)と書き、ESP_PD_DOMAIN_RTC_SLOW_MEMとESP_PD_DOMAIN_RTC_FAST_MEMにも同じ行を書くと、チップはハイバネーションモードに近づき、約5 µAになります。この場合、RTCメモリの変数は失われ、ボードをウェイクアップできるのはタイマーかext1だけになります。ほとんどのプロジェクトでは、10 µAと5 µAの差はボードのほかの部分の消費に比べればごくわずかです。まずは次のセクションの問題を解決してください。
開発ボードがディープスリープ中もミリアンペアを消費するのはなぜ?
スケッチを書き込んでテスターをつなぐと、10 µAではなく5〜15 mAと表示されることがよくあります。原因はチップではなく、ボード上の追加部品です。安価なDevKitやNodeMCU-32Sボードの多くは、5 Vを3.3 Vに下げるためにAMS1117レギュレーターを使っていますが、このレギュレーターだけで無負荷でも標準で5 mAを消費します。これに常時点灯の電源LEDと、パソコンと通信するUSBシリアル変換チップ(CP2102やCH340)が加わります。合計すると通常は約8 mAになり、ESP32がスリープしている間も電池から1日に約190 mAhを奪います。このようなボードでは、3000 mAhの18650でも2週間がやっとです。電流を測るには、テスターを電池と回路の間に直列に入れ、スリープ中はµAレンジに切り替えます。詳しくは「テスターの使い方」のガイドを参照してください。
| 電流を消費する部分 | スリープ中の電流 | 対策 |
|---|---|---|
| ESP32モジュール(ディープスリープ) | ≈10 µA | すでに低いので対策不要 |
| AMS1117レギュレーター | 標準5 mA(自己消費電流) | MCP1700(自己消費電流1.6 µA)やHT7333(約4 µA)を使う |
| 電源LED | 1〜3 mA | LEDかその抵抗を取り外す |
| USBシリアル変換チップ(CP2102、CH340) | ボードによって異なり、mAの範囲になることも | USBチップのないボードかモジュール単体を使う |
| 100 kΩ + 100 kΩの電池電圧用分圧回路 | 21 µA(4.2 V時) | 1 MΩ + 1 MΩにするか、MOSFETで測定時だけ分圧回路をオンにする |
| 常に電源が入っているセンサー | センサーによってµAからmAまで | 測定時だけGPIOかMOSFET経由でセンサーに電源を供給する |
最初のディープスリープのスケッチ:タイマーでウェイクアップ
- Arduino IDEのボードマネージャでesp32 by Espressif Systemsパッケージ(バージョン3.1以降)をインストールし、ボードにESP32 Dev Moduleを選びます。
- 下のスケッチを書き込み、シリアルモニタを115200ボーで開きます。
- ボードは10秒ごとにウェイクアップします。起動回数が増えていき、ウェイクアップの原因がTimerと表示されることを確認してください。
- ENボタンを押すと、カウンターは1に戻ります。これはディープスリープからのウェイクアップではなく、通常のリセットだからです。
- 実際のプロジェクトでは、TIME_TO_SLEEPを600(10分)か3600(1時間)にします。
1// ESP32 deep sleep: zamanlayıcıyla uyanma ve RTC sayaç / timer wake-up and RTC counter
2#define uS_TO_S_FACTOR 1000000ULL // 1 saniye = 1.000.000 µs / 1 second = 1,000,000 µs
3#define TIME_TO_SLEEP 10 // uyku süresi, saniye / sleep time in seconds
4
5RTC_DATA_ATTR int bootCount = 0; // RTC belleğinde, derin uykuda silinmez / in RTC memory, kept in deep sleep
6
7void printWakeupReason() {
8 esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
9 switch (cause) {
10 case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Zamanlayici / Timer"); break;
11 case ESP_SLEEP_WAKEUP_EXT0: Serial.println("ext0 (tek pin / single pin)"); break;
12 case ESP_SLEEP_WAKEUP_EXT1: Serial.println("ext1 (coklu pin / multiple pins)"); break;
13 case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Dokunmatik / Touch"); break;
14 default: Serial.printf("Ilk acilis ya da reset / Power-on or reset (%d)\n", (int)cause); break;
15 }
16}
17
18void setup() {
19 Serial.begin(115200);
20 delay(500); // Seri Monitör'e zaman tanı / give the Serial Monitor time
21 bootCount++;
22 Serial.printf("Uyanis sayisi / Boot count: %d\n", bootCount);
23 printWakeupReason();
24
25 // Burada sensörü oku, veriyi gönder / read the sensor and send the data here
26
27 esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); // 10 s sonra uyan / wake after 10 s
28 Serial.println("Derin uyku basliyor / Entering deep sleep");
29 Serial.flush(); // yazı bitmeden uyuma / finish printing before sleeping
30 esp_deep_sleep_start(); // buradan sonrası çalışmaz / nothing below runs
31}
32
33void loop() {
34 // Derin uykuda loop() hiç çalışmaz / loop() never runs with deep sleep
35}スケッチの中心は2行です。esp_sleep_enable_timer_wakeup()はウェイクアップまでの時間をマイクロ秒で設定し、esp_deep_sleep_start()はチップをすぐにスリープさせます。この行の後に書いたものは一切実行されません。時間を計算するときは、ULL(64ビット)の接尾辞を忘れないでください。3600 * 1000000は32ビットのintをオーバーフローしますが、コンパイラは警告を出すだけで、ボードは間違った時間にウェイクアップするか、まったくウェイクアップしなくなります。Serial.flush()は送信バッファが空になるまで待ちます。これがないと最後の数行が途中で切れます。esp_sleep_get_wakeup_cause()はボードがウェイクアップした理由を返し、最初の電源投入やリセットの後はスリープ要因なしを返します。スリープ時間を数えるRTCタイマーは、デフォルトでは約150 kHzの内部RC発振器で動いています。水晶ほど安定しておらず、温度によって数%ずれることがあるので、時刻が重要なプロジェクトではウェイクアップ時にNTPで時刻を同期するのがおすすめです。
RTCメモリにカウンターや測定値を保持する
ウェイクアップの間で何かを覚えておく必要があるときは、変数の前にRTC_DATA_ATTRを付けます。コンパイラはその変数をメインRAMではなく、ディープスリープ中も電源が入っている8 KBのRTC SLOWメモリに配置します。カウンター、最近の測定値、Wi-Fiのチャンネル情報、最後に送信した温度など、小さなデータに最適です。ただし、このメモリは永続的ではありません。Espressifのドキュメントによると、RTCメモリのデータはディープスリープからのウェイクアップ以外のすべての再起動で初期化されます。電池を交換したとき、ENを押したとき、新しいコードを書き込んだとき、ESP.restart()を呼んだときにはカウンターがリセットされます。停電後も残す必要がある設定はPreferencesライブラリでフラッシュに保存してください。ただし、フラッシュには書き込み回数の寿命があるので、ウェイクアップのたびに保存するのは避けます。
| できごと | 通常の変数 | RTC_DATA_ATTR | Preferences(フラッシュ) |
|---|---|---|---|
| ディープスリープからのウェイクアップ | 初期化される | 保持される | 保持される |
| ENボタンまたはESP.restart() | 初期化される | 初期値に戻る | 保持される |
| 電池の取り外し、停電 | 初期化される | 消える | 保持される |
| 新しいコードの書き込み | 初期化される | 消える | 保持される(フラッシュを消去しない限り) |
このメモリを上手に使えば、電池の持ちを何倍にもできます。Wi-Fiへの接続は、ウェイクアップのたびに最もコストのかかる部分です。そこで下のスケッチは、電池電圧を10分ごとに測りますが、Wi-Fiは1時間に1回だけオンにします。6回分の測定値をRTCメモリの配列にためておき、6回目のウェイクアップでまとめて送信します。さらに、アクセスポイントのチャンネルとMACアドレス(BSSID)もRTCメモリに保存するので、次の接続ではESP32が全チャンネルをスキャンせずにそのアクセスポイントへ直接つながり、接続時間がはっきり短くなります。
1// RTC belleğinde ölçüm biriktirip saatte bir gönderme / buffer readings in RTC memory, send hourly
2#include <WiFi.h>
3
4const char* SSID = "AgAdi"; // Wi-Fi adı / Wi-Fi name
5const char* PASS = "Sifre123"; // Wi-Fi şifresi / Wi-Fi password
6const int BAT_PIN = 35; // 1M + 1M bölücünün orta noktası / divider midpoint
7const uint64_t SLEEP_US = 600ULL * 1000000ULL; // 10 dakika / 10 minutes
8const int BATCH = 6; // 6 ölçümde bir gönder / send every 6 readings
9
10RTC_DATA_ATTR float readings[BATCH]; // uykuda korunan ölçümler / readings kept in sleep
11RTC_DATA_ATTR int count = 0; // biriken ölçüm sayısı / number of stored readings
12RTC_DATA_ATTR int32_t savedChannel = 0; // son Wi-Fi kanalı / last Wi-Fi channel
13RTC_DATA_ATTR uint8_t savedBssid[6]; // modemin MAC adresi / access point MAC address
14
15bool connectFast() {
16 WiFi.mode(WIFI_STA);
17 if (savedChannel > 0) {
18 WiFi.begin(SSID, PASS, savedChannel, savedBssid); // taramadan bağlan / connect without scanning
19 } else {
20 WiFi.begin(SSID, PASS); // ilk sefer: normal bağlantı / first time: normal connect
21 }
22 unsigned long start = millis();
23 while (WiFi.status() != WL_CONNECTED) {
24 if (millis() - start > 10000) { // 10 s içinde bağlanamazsa vazgeç / give up after 10 s
25 savedChannel = 0; // bir dahaki sefere tara / scan next time
26 return false;
27 }
28 delay(50);
29 }
30 savedChannel = WiFi.channel();
31 memcpy(savedBssid, WiFi.BSSID(), 6);
32 return true;
33}
34
35void setup() {
36 Serial.begin(115200);
37 // Pili Wi-Fi açılmadan önce ölç / measure the battery before Wi-Fi starts
38 float v = analogReadMilliVolts(BAT_PIN) * 2 / 1000.0; // bölücü gerilimi yarıya indirir / divider halves it
39 readings[count++] = v;
40 Serial.printf("Olcum / Reading %d: %.2f V\n", count, v);
41
42 if (count >= BATCH) {
43 if (connectFast()) {
44 for (int i = 0; i < BATCH; i++) {
45 Serial.printf("Gonder / Send: %.2f V\n", readings[i]); // burada HTTP ya da MQTT / HTTP or MQTT here
46 }
47 count = 0;
48 } else {
49 for (int i = 1; i < BATCH; i++) readings[i - 1] = readings[i]; // en eskiyi at / drop the oldest
50 count = BATCH - 1;
51 }
52 WiFi.disconnect(true);
53 WiFi.mode(WIFI_OFF); // radyoyu kapat / turn the radio off
54 }
55
56 Serial.flush();
57 esp_sleep_enable_timer_wakeup(SLEEP_US);
58 esp_deep_sleep_start();
59}
60
61void loop() {
62}電池電圧は1 MΩ + 1 MΩの分圧回路で半分にし、ADC1チャンネルのGPIO 35で読み取ります。分圧回路の中点とGNDの間に100 nFのコンデンサを入れると、これほど高抵抗の分圧回路でも読み取り値が安定します。Wi-Fiを起動する前に測ることも重要です。無線が動いている間に流れる電流で、電池電圧が一瞬下がるからです。Wi-Fiに接続できなかった場合は、最も古い測定値を捨て、残りを次の試行のために保持します。スリープ前にWiFi.disconnect(true)とWiFi.mode(WIFI_OFF)で無線をきちんと止めるのも、Espressifが推奨している方法です。接続をさらに速くするには、WiFi.config()でボードに固定IPを割り当て、DHCPの待ち時間を省きます。分圧回路の計算は「分圧回路」のガイド、充電回路は「TP4056充電モジュール」のガイドにあります。
ボタンやPIRセンサーでウェイクアップ:ext0とext1
タイマーのほかに、外部からの信号でもESP32をウェイクアップできます。方法は2つあり、どちらもRTC GPIOピン(0、2、4、12〜15、25〜27、32〜39)でしか使えません。ext0は1本のピンを監視し、そのピンが選んだレベル(HIGHまたはLOW)になるとボードをウェイクアップさせます。RTCペリフェラルの電源を入れたままにするので、内部プルアップ抵抗とプルダウン抵抗はスリープ中も働きます。ext1はビットマスクで複数のピンを同時に監視します。無印ESP32のext1には2つのモードがあります。ESP_EXT1_WAKEUP_ANY_HIGHは選んだピンのどれかがHIGHになるとウェイクアップさせ、ESP_EXT1_WAKEUP_ALL_LOWはすべてのピンが同時にLOWになるとウェイクアップさせます。どれかのピンがLOWになるとウェイクアップするANY_LOWモードは、ESP32-S2、S3、C6、H2にしかありません。そのため、無印ESP32を複数のボタンでウェイクアップさせたい場合は、ボタンを3V3につなぎ、各ピンに10 kΩのプルダウン抵抗を付けます。複数のウェイクアップ要因を同時に有効にすることもできます。たとえばPIRと1時間ごとのタイマーの両方を有効にすると、ボードは先に来たほうでウェイクアップします。
| ウェイクアップ要因 | 関数 | ピン | メモ |
|---|---|---|---|
| タイマー | esp_sleep_enable_timer_wakeup() | ピン不要 | 時間はマイクロ秒、64ビット |
| ext0 | esp_sleep_enable_ext0_wakeup() | RTC GPIO 1本 | HIGHまたはLOWでウェイクアップ。タッチやULPとは併用不可 |
| ext1 | esp_sleep_enable_ext1_wakeup_io() | 複数のRTC GPIO | ANY_HIGHまたはALL_LOW。外付け抵抗を推奨 |
| タッチ | touchSleepWakeUpEnable() | T0〜T9(10ピン) | 値がしきい値を下回るとウェイクアップ。ext0とは併用不可 |
| ULPコプロセッサ | esp_sleep_enable_ulp_wakeup() | プログラムによる | スリープ中にセンサーを読み取れる。上級者向け |
サンプル回路の配線
| 部品 | 部品のピン | ESP32のピン | メモ |
|---|---|---|---|
| PIRセンサー(HC-SR501またはAM312) | OUT | GPIO 33 | ext0でウェイクアップさせる |
| PIRセンサー | VCC / GND | 5V(HC-SR501)または3V3(AM312) / GND | HC-SR501には4.5 V以上が必要 |
| ボタンA | 片方の足 / もう片方の足 | 3V3 / GPIO 25 | GPIO 25とGNDの間に10 kΩのプルダウン抵抗 |
| ボタンB | 片方の足 / もう片方の足 | 3V3 / GPIO 26 | GPIO 26とGNDの間に10 kΩのプルダウン抵抗 |
| タッチパッドまたは線 | ― | GPIO 27 (T7) | 短くすると安定して動く |
| LED + 220 Ω | アノード | GPIO 2 | 多くのDevKitでは、ボード上の青いLEDもこのピン |
| 電池電圧用分圧回路(1 MΩ + 1 MΩ) | 中点 | GPIO 35 | 中点とGNDの間に100 nF |
| 18650 + TP4056 + 3.3 V LDO | LDOの出力 / GND | 3V3 / GND | LDOの出力に470 µFのコンデンサ |
PIRセンサーでウェイクアップするスケッチ(ext0)
1// PIR ile uyanma (ext0) / wake on PIR motion (ext0)
2#include "driver/rtc_io.h"
3
4const gpio_num_t PIR_PIN = GPIO_NUM_33; // RTC GPIO olmalı / must be an RTC GPIO
5const int LED_PIN = 2; // uyarı LED'i / alert LED
6
7RTC_DATA_ATTR int motionCount = 0; // toplam hareket sayısı / total motion count
8
9void setup() {
10 Serial.begin(115200);
11 rtc_gpio_deinit(PIR_PIN); // pini yeniden normal GPIO yap / turn the pad back into a normal GPIO
12 pinMode(PIR_PIN, INPUT);
13 pinMode(LED_PIN, OUTPUT);
14
15 if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0) {
16 motionCount++;
17 Serial.printf("Hareket! / Motion! Toplam / Total: %d\n", motionCount);
18 digitalWrite(LED_PIN, HIGH); // burada bildirim gönderebilirsin / send a notification here
19 delay(1000);
20 digitalWrite(LED_PIN, LOW);
21 }
22
23 // PIR çıkışı LOW olana kadar bekle, yoksa hemen yeniden uyanır / wait for LOW or it wakes again at once
24 while (digitalRead(PIR_PIN) == HIGH) {
25 delay(100);
26 }
27
28 esp_sleep_enable_ext0_wakeup(PIR_PIN, 1); // pin HIGH olunca uyan / wake when the pin goes HIGH
29 rtc_gpio_pullup_dis(PIR_PIN);
30 rtc_gpio_pulldown_en(PIR_PIN); // boştayken LOW tut / keep it LOW while idle
31 Serial.println("Uyku / Sleep");
32 Serial.flush();
33 esp_deep_sleep_start();
34}
35
36void loop() {
37}ウェイクアップすると、スケッチはまずrtc_gpio_deinit()でピンを通常のGPIOに戻します。ext0でウェイクアップした後、ピンはRTCモードのままになっていて、この行がないとdigitalRead()が正しく動かないことがあります。動きを数えてLEDを点滅させた後、PIRの出力がLOWに戻るのを待ちます。この待機は欠かせません。出力がまだHIGHのままスリープすると、ボードはすぐにウェイクアップしてしまい、1秒間に何度もオン・オフを繰り返すループに陥ります。HC-SR501はポテンショメーターで設定した時間だけ出力をHIGHに保つので、待ち時間を短くするには時間調整用のポテンショメーターを最小に回してください。HC-SR501には4.5 V以上が必要なので、18650 1本で動かすプロジェクトでは、3.3 Vでも動く小型のAM312 PIRモジュールを選びます。センサーの設定については「PIR人感センサー」のガイドを参照してください。
2つのボタンでウェイクアップするスケッチ(ext1)
1// İki butonla uyanma (ext1) / wake with two buttons (ext1)
2#include "driver/rtc_io.h"
3
4const gpio_num_t BTN_A = GPIO_NUM_25; // buton A: 3V3'e, 10k pull-down / button A: to 3V3, 10k pull-down
5const gpio_num_t BTN_B = GPIO_NUM_26; // buton B: 3V3'e, 10k pull-down / button B: to 3V3, 10k pull-down
6const uint64_t WAKE_MASK = (1ULL << BTN_A) | (1ULL << BTN_B);
7
8void setup() {
9 Serial.begin(115200);
10 rtc_gpio_deinit(BTN_A); // pinleri normal GPIO'ya döndür / back to normal GPIO
11 rtc_gpio_deinit(BTN_B);
12 pinMode(BTN_A, INPUT);
13 pinMode(BTN_B, INPUT);
14
15 if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1) {
16 uint64_t pins = esp_sleep_get_ext1_wakeup_status(); // hangi pin uyandırdı? / which pin woke us?
17 if (pins & (1ULL << BTN_A)) Serial.println("Buton A: isigi ac / Button A: lights on");
18 if (pins & (1ULL << BTN_B)) Serial.println("Buton B: isigi kapat / Button B: lights off");
19 } else {
20 Serial.println("Ilk acilis / First boot");
21 }
22
23 while (digitalRead(BTN_A) == HIGH || digitalRead(BTN_B) == HIGH) {
24 delay(10); // butonlar bırakılana kadar bekle / wait until released
25 }
26
27 esp_sleep_enable_ext1_wakeup_io(WAKE_MASK, ESP_EXT1_WAKEUP_ANY_HIGH); // biri HIGH olunca / any goes HIGH
28 Serial.flush();
29 esp_deep_sleep_start();
30}
31
32void loop() {
33}ext1でウェイクアップした後、esp_sleep_get_ext1_wakeup_status()は、どのピンがボードをウェイクアップさせたかを示すビットマスクを返します。これで2つのボタンに別々の役割を持たせられます。片方で照明をつけてもう片方で消す、片方で測定値を送ってもう片方でメニューを開く、といった使い方です。ボタンは3V3につながり、ピンは10 kΩでGNDに引かれているので、離しているときはLOW、押すとHIGHと読み取られます。これはまさにANY_HIGHモードが想定している状態です。スケッチはスリープ前にボタンが離されるのを待ちます。そうしないと、押されたままのボタンがすぐにボードを再びウェイクアップさせてしまいます。_io関数のないESP32 Arduinoコアの2.xや3.0.xを使っている場合は、esp_sleep_enable_ext1_wakeup_ioの代わりに、同じ引数でesp_sleep_enable_ext1_wakeupと書いてください。プルダウン抵抗が必要な理由は「Arduinoに押しボタンをつなぐ方法」のガイドで説明しています。
タッチでウェイクアップ(touch wake-up)
ESP32の10本のピン(T0〜T9)は静電容量式タッチセンサーとして働き、ディープスリープ中でも指を検知できます。ピンにつないだ線、銅テープ、基板のパッドがボタンのように働き、薄いプラスチックや木のカバーの裏に置くこともできます。無印ESP32では、触れるとtouchRead()の値が下がります。短い線なら、触れていないときは通常60〜80、触れると10〜20になります。ウェイクアップのしきい値はこの2つの値の間に設定します。サンプルの40はよい出発点です。無印ESP32では、複数のタッチピンを同時にウェイクアップ要因にできます。一方、ESP32-S2とS3では1本のピンしかウェイクアップに使えず、触れると値が上がり、しきい値もまったく異なる尺度(Espressifのサンプルでは5000)になります。タッチによるウェイクアップはext0と併用できないので、ボタンも必要な場合はext1を使ってください。ピンの一覧とキャリブレーションについては「ESP32のタッチピン」のガイドを読んでください。
1// Dokunarak uyandırma: T7 = GPIO 27 / touch wake-up: T7 = GPIO 27
2#define THRESHOLD 40 // değer bunun altına inince uyanır / wakes when the value drops below this
3
4RTC_DATA_ATTR int touchWakes = 0;
5
6void setup() {
7 Serial.begin(115200);
8 delay(500);
9 if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TOUCHPAD) {
10 touchWakes++;
11 Serial.printf("Dokunusla uyanma / Touch wake #%d\n", touchWakes);
12 }
13 // Eşiği ayarlamak için dokunmadan ve dokunarak değeri izle / watch the value untouched and touched
14 Serial.printf("T7 degeri / value: %d\n", (int)touchRead(T7));
15
16 touchSleepWakeUpEnable(T7, THRESHOLD); // dokunmatikle uyanmayı aç / enable touch wake-up
17 Serial.flush();
18 esp_deep_sleep_start();
19}
20
21void loop() {
22}MicroPythonでESP32のディープスリープ
ESP32をMicroPythonでプログラミングしている場合は、もっと短いコードで同じことができます。machine.deepsleep(ms)は指定したミリ秒だけボードをスリープさせ、esp32.wake_on_ext0()はピンをウェイクアップ要因にし、machine.RTC().memory()はスリープ中も保持されるバイトバッファを提供します。ESP32ではデフォルトで2048バイトです。ウェイクアップするとmain.pyが最初から実行されます。machine.reset_cause()でディープスリープから復帰したかどうかが、machine.wake_reason()で何がウェイクアップさせたかがわかります。プルアップやプルダウンを設定したRTCピンは、スリープ中もその設定を保ちます。MicroPythonが初めての場合は、「MicroPythonの基本」のガイドから始めてください。
1import machine, esp32, time
2from machine import Pin
3
4rtc = machine.RTC()
5led = Pin(2, Pin.OUT)
6
7mem = rtc.memory() # RTC belleğini oku / read RTC memory
8count = int.from_bytes(mem, "little") if mem else 0
9
10if machine.reset_cause() == machine.DEEPSLEEP_RESET:
11 count += 1
12 if machine.wake_reason() == machine.EXT0_WAKE:
13 print("Butonla uyandi / Woken by button")
14 else:
15 print("Zamanlayiciyla uyandi / Woken by timer")
16else:
17 count = 0
18 print("Ilk acilis / First boot")
19
20rtc.memory(count.to_bytes(4, "little")) # sayacı sakla / store the counter
21print("Uyanis / Wake:", count)
22
23led.on() # kısa iş: ölç ve gönder / short task: measure and send
24time.sleep_ms(200)
25led.off()
26
27button = Pin(33, Pin.IN, Pin.PULL_DOWN) # buton GPIO 33 ile 3V3 arasında / button between GPIO 33 and 3V3
28esp32.wake_on_ext0(button, esp32.WAKEUP_ANY_HIGH) # basınca uyan / wake on press
29machine.deepsleep(60000) # 60 s uyu / sleep for 60 s一方、Raspberry Pi Picoには、ESP32のようなマイクロアンペア級のディープスリープがありません。MicroPythonのmachine.lightsleep(ms)はほとんどのクロックを止めてCPUを一時停止し、RAMを保持します。machine.deepsleep(ms)は同じようなスリープの後でボードをリセットします。これらのモードでも、ボードの消費電流はミリアンペアの範囲にとどまります。そのため、Picoで電池で何か月も動くデバイスを作りたい場合は、TPL5110のような電源タイマーで測定と測定の間にボードの電源を完全に切るほうがよい結果になります。Picoの基本については「Raspberry Pi Picoとは?」のガイドを参照してください。
18650で何か月も動くセンサー:電池の持ちの計算
スリープとウェイクアップを繰り返すデバイスの電池の持ちを求めるには、まず平均電流を計算します。平均電流 =(動作中の電流 × 動作時間 + スリープ電流 × スリープ時間)/ 全体の時間。次に、電池の使用可能な容量をこの平均で割ります。3000 mAhと表示された18650をすべて使えるわけではありません。低電圧カットオフ、レギュレーターの損失、劣化を考えると、約85%、つまり2550 mAhで見積もるのが現実的です。下の表は、10分ごとにウェイクアップしてWi-Fiでデータを送り、接続中に平均120 mAを消費するセンサーで計算したものです。
| シナリオ | スリープ電流 | 動作時間 | 平均電流 | 推定の持続時間 |
|---|---|---|---|---|
| DevKitボード | 8 mA | 3秒、120 mA | 8.56 mA | ≈12日 |
| DevKitボード、高速接続 | 8 mA | 1秒、120 mA | 8.19 mA | ≈13日 |
| モジュール単体 + MCP1700 + 1 MΩの分圧回路 | 15 µA | 3秒、120 mA | 0.61 mA | ≈173日(5.7か月) |
| モジュール単体、高速接続 | 15 µA | 1秒、120 mA | 0.215 mA | ≈494日(16か月) |
| モジュール単体、1時間ごとにまとめて送信 | 15 µA | 1時間に1秒のWi-Fi(120 mA)+ 5 × 0.3秒の測定(40 mA) | 0.065 mA | 理論上≈4.5年 |
結論ははっきりしています。開発ボードでは、動作時間を短くしてもほとんど変わりません。電池を本当に消耗させているのは、スリープ中の8 mAだからです。スリープ電流がマイクロアンペアまで下がると立場が逆転し、動作中の1秒1秒が効いてきます。Wi-Fiの接続を3秒から1秒に縮めるだけで、電池の持ちはほぼ3倍になります。平均電流が0.1 mAを下回ると、電池の自己放電が無視できなくなります。Li-ion電池は1か月に容量の約2〜3%を失うので、理論上の4.5年は実際には2年程度に縮みます。自分の値で計算するには電池寿命計算ツールを使ってください。mAhとWhについては「mAhとWh」のガイドで説明しています。
- 適切なボードを選ぶ:USBチップと電源LEDのないボードや、ESP32モジュール単体なら、スリープ中はマイクロアンペアしか消費しません。
- レギュレーターを交換する:AMS1117の代わりに、自己消費電流が数マイクロアンペアのLDOを使います。
- 測定時だけセンサーに電源を入れる:数mAのセンサーはGPIOから直接、それより大きな負荷はPチャネルMOSFETを通して電源を供給します。スリープ中にオフのままになるよう、MOSFETのゲートにプルアップ抵抗を付けてください。詳しくは「ArduinoでMOSFETを使う方法」のガイドにあります。
- Wi-Fiをオンにする回数を減らす:測定値をRTCメモリにためてまとめて送ります。近くの別のESP32にデータを送るなら、ESP-NOWはアクセスポイントに接続する必要がないので、はるかに短い時間で送信できます。
- 接続を速くする:固定IPと保存したチャンネル、BSSIDを使えば、Wi-Fiの接続は多くの場合1秒未満で終わります。
- 不要な待ち時間をなくす:完成したプロジェクトからシリアルモニタへの出力や長いdelay()の行を削除します。動作中の1ミリ秒ごとに電池が減っていきます。
- 分圧回路とLEDを忘れない:常時つながった100 kΩ + 100 kΩの分圧回路は、スリープ中のESP32の2倍の電流を消費します。
ESP32のディープスリープを使ったプロジェクトのアイデア
- 電池式の気象ステーション:BME280で温度、湿度、気圧を10分ごとに測り、1時間に1回MQTTでHome Assistantに送ります。
- 庭の水分センサー:1時間に1回ウェイクアップして土壌水分を測り、土が乾いたらスマホにメッセージを送ります。
- ドア・窓のアラーム:ドアが開くとリードスイッチがext0でESP32をウェイクアップさせ、Telegramボットがメッセージを送ります。
- 電池式の防犯センサー:PIRが動きを検知するとウェイクアップして通知を送り、それ以外のときは何か月もスリープするデバイスを作ります。
- タッチ式の玄関チャイム:木のパネルの裏に銅テープを貼ります。触れるとESP32がウェイクアップし、ESP-NOWで室内のチャイムに信号を送ります。
- 電池式の温度ロガー:防水のDS18B20で冷蔵庫や水槽の温度を測り、値をRTCメモリにためて1日に数回送信します。
よくある間違いと対策
| 症状 | 原因 | 対策 |
|---|---|---|
| スリープした直後にボードがウェイクアップする | ウェイクアップ用のピンがまだアクティブ(PIRがHIGH、ボタンが押されたまま)か、フローティング状態 | スリープ前にピンが待機時のレベルに戻るのを待ち、プルダウン抵抗かプルアップ抵抗を追加する |
| 予定の時間にウェイクアップしない | 時間の計算で32ビットのオーバーフロー(3600 * 1000000) | 定数にULLを付ける:3600ULL * 1000000ULL |
| カウンターが毎回1から始まる | 変数がRTC_DATA_ATTRになっていないか、ボードがリセットされた | RTC_DATA_ATTRを付ける。永続的なデータにはPreferencesを使う |
| スリープ中に5〜15 mAと測定される | レギュレーター、電源LED、USBチップ、分圧回路、センサー | 低消費電力のボードかモジュール単体、自己消費電流の小さいLDOを使う |
| シリアルモニタの最後の数行が切れる | バッファが空になる前にスリープした | esp_deep_sleep_start()の前にSerial.flush()を呼ぶ |
| ウェイクアップ後にdigitalRead()が間違った値を返す | ウェイクアップ用のピンがRTCモードのまま | ピンを使う前にrtc_gpio_deinit()を呼ぶ |
| GPIO 34〜39のボタンでウェイクアップしない | これらのピンには内部プル抵抗がなく、ピンがフローティング状態 | 10 kΩの抵抗を外付けする |
| スリープ中にコードを書き込めない | ボードに自動リセット回路がない。S3とC3ではスリープ中にネイティブUSBのポートが消える | BOOTを押したまま、EN(RESET)を押して離し、書き込みを開始する |
| スリープするとリレーやLEDの状態が変わる | ディープスリープ中はデジタルピンがフローティング状態(ハイインピーダンス)になる | プル抵抗を使うか、rtc_gpio_hold_en()でピンのレベルを保持する |
ESP32のディープスリープは、同じコードを電池で数日ではなく何か月も動かすためのカギです。処理は短くし、ウェイクアップ要因を正しく選び、ウェイクアップの間に必要な小さなデータはRTCメモリに保存し、ボード自体の消費電流を忘れないようにしましょう。ESP32の基本に戻りたい場合は、「ESP32とは?」のガイドから始めるのがおすすめです。BlueGraysアプリのESP32のディープスリープで節電(OLED + RTC RAMカウンター)プロジェクトでは、この記事のタイマーとRTCカウンターの仕組みを、OLEDディスプレイ付きの回路で、ステップごとの回路図と完成済みのコードを使って試せます。
よくある質問
ESP32はディープスリープでどのくらいの電流を消費しますか?
Espressifのデータシートによると、ESP32チップはRTCタイマーとRTCメモリがオンのディープスリープで約10 µA、ハイバネーションモードで5 µAを消費します。ただし、DevKitのような開発ボードでは、レギュレーター、電源LED、USBシリアル変換チップのせいで、スリープ中の電流は通常5〜15 mAになります。
ESP32をディープスリープからウェイクアップさせるにはどうすればいいですか?
方法は5つあります。RTCタイマー、1本のRTCピン(ext0)、複数のRTCピン(ext1)、タッチピン、ULPコプロセッサです。ウェイクアップ要因はesp_deep_sleep_start()を呼ぶ前に設定し、複数を同時に有効にできます。ただし、ext0とタッチによるウェイクアップだけは併用できません。
ESP32のディープスリープで変数は消えますか?
はい、通常の変数は消えます。ディープスリープからのウェイクアップは再起動であり、setup()が最初から実行されるからです。RTC_DATA_ATTRを付けた変数はRTCメモリに保存され、ウェイクアップの間も保持されますが、停電、ENボタンを押したとき、新しいコードを書き込んだときには初期値に戻ります。
ESP32は18650電池でどのくらい動きますか?
ボードと使い方によって、1日から1年以上まで変わります。Wi-Fiに常時接続したDevKitは3000 mAhの18650で1〜2日しか持たず、10分ごとにウェイクアップするDevKitでも約12日です。同じ処理をスリープ電流15 µAで行う低消費電力の回路なら、1回の接続にかかる時間によって5〜16か月動きます。
ESP32のライトスリープとディープスリープの違いは何ですか?
ライトスリープではCPUが一時停止しますがRAMは保持され、ウェイクアップするとプログラムは止まった行から続きを実行します。チップの消費電流は約0.8 mAです。ディープスリープではRAMの電源も切れ、消費電流は10 µAまで下がりますが、ウェイクアップのたびにプログラムは最初からやり直しになります。数秒の待ち時間にはライトスリープ、数分から数時間の待ち時間にはディープスリープが向いています。
ディープスリープ中のESP32にコードを書き込むにはどうすればいいですか?
自動リセット回路のあるボードでは、書き込みツールがENピンを使ってチップをリセットするので、通常は問題なく書き込みが始まります。始まらない場合は、BOOTボタンを押したまま、EN(RESET)を押して離し、それから書き込みを開始してください。ESP32-S3やC3のようなネイティブUSBのボードではスリープ中にポートが消えるので、この方法が必要になることがよくあります。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
ESP32でLEDを点滅
ESP32のGPIO出力の基本を学びます。デジタルピンからLEDを点灯・消灯させて、点滅させます。Arduino UNOの定番のLED点滅プロジェクトのESP32版です。Arduino環境で使う基本的な点滅コマンドは同じですが、ESP32のピン配置と3.3 VのGPIO電圧レベルはArduino UNOとは異なります。

ESP32でボタンによるLED制御(押している間だけ点灯)
ボタンを押している間はLEDが点灯し、離すと消灯します。ESP32のデジタル入力ピンと内蔵プルアップ抵抗の使い方を学びます。INPUT_PULLUPモードを使うため、外付け抵抗は不要です。

ESP32のディープスリープで節電(OLED + RTC RAMカウンター)
ESP32のディープスリープモードは、CPUとほとんどの周辺機器を停止し、消費電流を約10 µAまで抑えます。電池で動くIoT機器に欠かせない機能です。このプロジェクトでは、タイマー(15秒)、外部ピン(EXT0、ボタンはGPIO 33)、タッチ(T0/GPIO 4)の3種類の復帰方法を使います。RTC_DATA_ATTRを付けた変数はディープスリープ中も保持され、起動回数、タイマーによる復帰回数、ボタンによる復帰回数を継続して記録できます。復帰するたびにSSD1306 OLEDディスプレイに復帰理由と回数を表示します。

ESP32動体検知アラーム(PIR + ブザー + LED + Telegram)
PIRセンサーが動きを検知するとブザーとLEDを作動させ、同時にTelegramで持ち主に即座に通知するスマート防犯アラームです。アラームが繰り返される頻度を抑えるため、30秒のクールダウン(待ち時間)を設けています。この仕組みはPIRの検知が正しいかを確認したり、誤検知をなくしたりするものではありません。家の防犯、ガレージの監視、倉庫や作業部屋などに使えます。ESP32-CAMなら写真付きの通知を追加でき、通常のESP32 DevKit V1ではテキストで通知します。

ESP32 PIR人感センサーによる自動照明
このプロジェクトでは、HC-SR501 PIR人感センサーで部屋に入った人を検知し、LEDを自動で点灯させます。millis()で点灯時間を管理します。センサーの値がHIGHの間は最後に動きを検知した時刻を更新し、最後にHIGHを読み取ってから少なくとも10秒後にLEDを消灯します。現在のコードは各ループでdelay(50)により50 ms待機するため、完全にノンブロッキングではありません。階段や廊下、浴室、戸棚内の照明などに応用できます。

ESP32 DHT22温湿度計(LCDディスプレイ付き)
DHT22センサーで周囲の温度と湿度を測定し、16x2 I2C LCDにリアルタイムで表示するプロジェクトです。Adafruit DHTライブラリを使い、1本の信号線を使うプロトコルでセンサーからデータを読み取ります。IoT気象観測装置やスマートホームの自動化、温室の環境監視システムの基礎となります。ESP32のGPIO 4ピンを介してDHT22と通信します。

ESP32温度データロガー(DHT22 + DS1307 RTC + microSD + OLED)
ESP32で日時付きの温度・湿度記録システムを作ります!DHT22で1分ごとに温度と湿度を読み取り、DS1307 RTCから取得した日時とともにmicroSDカードへCSV形式で記録します。OLEDには最新の温度を大きな文字で表示し、湿度のパーセント値、日時、総記録数も表示します。SDカードの/temp_log.csvファイルはExcel、Google Sheets、Pythonのpandasで開いて分析できます。温室の監視、家や部屋の温度管理、実験室のデータ収集、食品の保管環境や養蜂箱の監視に適しています。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

