Руководства · 14 мин чтения

Глубокий сон ESP32: источники пробуждения, потребляемый ток и время работы от батареи

Глубокий сон ESP32 простыми словами: ток в µA в каждом режиме сна, пробуждение по таймеру, кнопке, PIR-датчику и касанию (ext0/ext1), счётчики в памяти RTC и расчёт работы от 18650.

Глубокий сон ESP32 (deep sleep) — это режим питания, в котором ESP32 отключает процессорные ядра, радио Wi-Fi и Bluetooth и основную память, оставляя работать только небольшой домен RTC. В этом режиме потребление микросхемы падает с 240 mA при передаче по Wi-Fi примерно до 10 µA, то есть в двадцать четыре тысячи раз. Плата просыпается, когда истекает заданное время, нажимается кнопка, PIR-датчик замечает движение или кто-то касается пина; за пару секунд она делает свою работу и снова засыпает. Именно так метеостанции на батарейках, садовые датчики, дверные сигнализации и удалённые регистраторы работают от одного аккумулятора 18650 месяцами, а не днями.

В этом руководстве разберём режимы сна ESP32 и их реальные токи, пробуждение по таймеру, кнопкой, PIR-датчиком (ext0 и ext1) или касанием, хранение счётчиков и показаний в памяти RTC, которая переживает сон, причины, по которым отладочные платы даже во сне потребляют миллиамперы, и пошаговый расчёт того, сколько месяцев проработает один аккумулятор 18650. Скетчи Arduino написаны для ядра ESP32 для Arduino версии 3.1 и новее, а в конце покажем, как сделать то же самое на MicroPython.

Отладочная плата ESP32 (DevKit)
Отладочная плата ESP32: модуль в металлическом экране потребляет в глубоком сне около 10 µA, а стабилизатор, светодиод питания и USB-UART-преобразователь на плате поднимают эту цифру до миллиампер.

Что такое глубокий сон ESP32 и как он работает?

Внутри ESP32 есть два отдельных мира. Первый — основной домен с процессорными ядрами, 520 КБ ОЗУ и радио Wi-Fi и Bluetooth. Второй — домен RTC: у него свой небольшой таймер, 8 КБ памяти RTC FAST и 8 КБ памяти RTC SLOW, пины RTC GPIO и сверхмаломощный сопроцессор ULP. В глубоком сне основной домен полностью обесточен, работает только домен RTC. Когда приходит время просыпаться, микросхема на самом деле перезапускается: запускается загрузчик, программа заново загружается из флеш-памяти, и setup() выполняется с самого начала. Поэтому в программе с глубоким сном вся работа делается внутри setup(), а loop() не выполняется вообще. Обычные переменные сбрасываются при каждом пробуждении; сохраняются только переменные, размещённые в памяти RTC.

Режимы сна ESP32 и потребляемый ток (µA)

РежимЧто продолжает работать?Ток (микросхема ESP32)
Активный: передача Wi-Fi (802.11b, 19,5 dBm)Процессор и радио240 mA
Активный: приём и прослушивание Wi-FiПроцессор и радио95–100 mA
Сон модема (modem sleep), 240 MHzПроцессор работает, радио выключено30–68 mA
Сон модема (modem sleep), 80 MHzПроцессор работает медленнее, радио выключено20–31 mA
Лёгкий сон (light sleep)Процессор приостановлен, ОЗУ сохраняется0,8 mA
Глубокий сон с включённым ULPДомен RTC и сопроцессор ULP150 µA
Глубокий сон (deep sleep)Таймер RTC и память RTC10 µA
Гибернация (hibernation)Только таймер RTC и некоторые пины RTC5 µA
Выключено (пин EN в LOW)Ничего1 µA

Эти значения взяты из даташита ESP32 от Espressif и относятся только к самой микросхеме. В таблице стоит обратить внимание на два момента. Во-первых, в лёгком сне программа продолжает с той строки, где остановилась, и всё в ОЗУ сохраняется, но потребление в восемьдесят раз выше, чем в глубоком сне, так что он подходит для пауз в несколько секунд. В глубоком сне программа начинается заново, зато потребление падает до микроампер, поэтому это правильный выбор для ожидания в минуты или часы. Во-вторых, ни один из режимов сна не сохраняет соединение 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 стоит стабилизатор AMS1117, который понижает 5 V до 3,3 V, и только он сам по себе обычно тратит 5 mA даже без нагрузки. Добавь к этому постоянно горящий светодиод питания и USB-UART-преобразователь для связи с компьютером (CP2102 или CH340). Вместе они обычно дают около 8 mA и даже пока ESP32 спит, высасывают из батареи примерно 190 mAh в день, так что аккумулятора 18650 на 3000 mAh с такой платой едва хватает на две недели. Чтобы измерить ток, включи мультиметр последовательно между батареей и схемой и во время сна переключи его на диапазон µA; подробности в руководстве Как пользоваться мультиметром.

Источник потребленияТок во снеРешение
Модуль ESP32 (глубокий сон)≈10 µAИ так мало, ничего делать не нужно
Стабилизатор AMS1117Обычно 5 mA (ток покоя)Поставь MCP1700 (ток покоя 1,6 µA) или HT7333 (около 4 µA)
Светодиод питания1–3 mAВыпаяй светодиод или его резистор
USB-UART-преобразователь (CP2102, CH340)Зависит от платы, может доходить до единиц mAИспользуй плату без USB-микросхемы или голый модуль
Делитель для батареи 100 kΩ + 100 kΩ21 µA (при 4,2 V)Поставь 1 MΩ + 1 MΩ или включай делитель через MOSFET только на время измерения
Датчики с постоянным питаниемОт µA до mA в зависимости от датчикаПитай датчик от GPIO или через MOSFET только на время измерения
Никогда не подключай Li-ion аккумулятор вроде 18650 напрямую к пину 3V3: заряженная Li-ion ячейка даёт 4,2 V, а ESP32 допускает не больше 3,6 V. Подавать батарею на пин VIN — тоже плохое решение: на AMS1117 падает около 1 V, и когда ячейка разряжена наполовину, выход опускается ниже 3 V. Правильно — питать пин 3V3 через LDO-стабилизатор на 3,3 V с малым током покоя и малым падением напряжения. Маленькие LDO вроде MCP1700 и HT7333 выдают только 250 mA, поэтому поставь на их выход не меньше 470 µF, чтобы покрыть пики тока Wi-Fi. Ячейки LiFePO4, которые даже полностью заряженными не превышают 3,6 V, можно подключать прямо к 3V3 без стабилизатора.

Первый скетч с глубоким сном: пробуждение по таймеру

  1. В «Менеджере плат» (Boards Manager) Arduino IDE установи пакет esp32 by Espressif Systems (версии 3.1 или новее) и выбери плату ESP32 Dev Module.
  2. Загрузи скетч ниже и открой монитор порта на скорости 115200 бод.
  3. Плата просыпается каждые 10 секунд: видно, как растёт счётчик запусков, а причина пробуждения выводится как Timer.
  4. Нажми кнопку EN: счётчик вернётся к 1, потому что это обычный сброс, а не пробуждение из глубокого сна.
  5. В настоящем проекте поставь TIME_TO_SLEEP равным 600 (10 минут) или 3600 (1 час).
C++ (Arduino IDE)
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}

Сердце скетча — две строки: esp_sleep_enable_timer_wakeup() задаёт время пробуждения в микросекундах, а esp_deep_sleep_start() сразу отправляет микросхему спать; всё, что написано после этой строки, никогда не выполнится. При расчёте времени не забывай суффикс ULL (64 бита): 3600 * 1000000 переполняет 32-битный int, компилятор выдаёт только предупреждение, а плата просыпается не вовремя или не просыпается вовсе. Serial.flush() ждёт, пока опустеет буфер передачи; без неё последние строки обрезаются. esp_sleep_get_wakeup_cause() сообщает, почему плата проснулась, а после первого включения или сброса не возвращает никакого источника сна. Таймер RTC, отсчитывающий время сна, по умолчанию работает от внутреннего RC-генератора примерно на 150 kHz. Он не так стабилен, как кварц, и может уходить на несколько процентов с температурой, поэтому в проектах, где важно точное время, при пробуждении лучше синхронизировать время по NTP.

Счётчики и показания в памяти RTC

Если между пробуждениями нужно что-то помнить, поставь перед переменной RTC_DATA_ATTR: компилятор разместит её не в основном ОЗУ, а в 8 КБ памяти RTC SLOW, которая остаётся под питанием в глубоком сне. Это идеально для небольших данных: счётчиков, последних показаний, номера канала Wi-Fi или последней отправленной температуры. Но эта память не постоянная: по документации Espressif данные в памяти RTC инициализируются заново при каждом перезапуске, кроме пробуждения из глубокого сна. Замена батареи, нажатие EN, загрузка нового кода или вызов ESP.restart() сбрасывают счётчик. Настройки, которые должны пережить отключение питания, сохраняй во флеш-память через библиотеку Preferences, но не при каждом пробуждении: у флеш-памяти ограниченное число циклов записи.

СобытиеОбычная переменнаяRTC_DATA_ATTRPreferences (флеш)
Пробуждение из глубокого снаСбрасываетсяСохраняетсяСохраняется
Кнопка EN или ESP.restart()СбрасываетсяВозвращается к начальному значениюСохраняется
Батарея снята, питание пропалоСбрасываетсяТеряетсяСохраняется
Загружен новый кодСбрасываетсяТеряетсяСохраняется (если флеш не стирается)

Если использовать эту память с умом, время работы от батареи можно увеличить в разы. Подключение к Wi-Fi — самая дорогая часть каждого пробуждения. Поэтому скетч ниже измеряет напряжение батареи каждые 10 минут, а Wi-Fi включает только раз в час: шесть показаний копятся в массиве в памяти RTC, и на шестом пробуждении отправляются все сразу. Кроме того, он сохраняет в памяти RTC канал и MAC-адрес (BSSID) точки доступа, так что при следующем подключении ESP32 идёт прямо к этой точке доступа, не сканируя все каналы, и время подключения заметно сокращается.

C++ (Arduino IDE)
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Ω и читается на GPIO 35 — канале ADC1. Конденсатор 100 nF между средней точкой делителя и GND делает показания стабильными даже при таком высокоомном делителе. Важно и то, что измерение делается до включения Wi-Fi: ток, который потребляет работающее радио, на мгновение просаживает напряжение батареи. Если подключиться к Wi-Fi не удалось, самое старое показание отбрасывается, а остальные сохраняются до следующей попытки. Аккуратное выключение радио через WiFi.disconnect(true) и WiFi.mode(WIFI_OFF) перед сном — это то, что рекомендует и Espressif. Чтобы ещё ускорить подключение, задай плате статический IP через WiFi.config() и пропусти ожидание DHCP. Расчёт делителя есть в руководстве Делитель напряжения, а схема зарядки — в руководстве Модуль зарядки TP4056.

Пробуждение кнопкой или PIR-датчиком: ext0 и ext1

Кроме таймера, ESP32 может разбудить и сигнал из внешнего мира. Для этого есть два способа, и оба работают только на пинах RTC GPIO: 0, 2, 4, 12–15, 25–27 и 32–39. ext0 следит за одним пином и будит плату, когда на нём появляется выбранный уровень (HIGH или LOW); поскольку при этом периферия RTC остаётся под питанием, встроенные подтягивающие и стягивающие резисторы работают и во сне. ext1 следит сразу за несколькими пинами через битовую маску. На классическом ESP32 у ext1 два режима: ESP_EXT1_WAKEUP_ANY_HIGH будит плату, когда любой из выбранных пинов переходит в HIGH, а ESP_EXT1_WAKEUP_ALL_LOW — когда все они одновременно в LOW. Режим ANY_LOW, который будит при переходе любого пина в LOW, есть только на ESP32-S2, S3, C6 и H2. Поэтому, чтобы будить классический ESP32 несколькими кнопками, подключи кнопки к 3V3, а на каждый пин поставь стягивающий резистор (pull-down) 10 kΩ. Можно включить и несколько источников сразу: например, если включены и PIR, и ежечасный таймер, плата проснётся от того, что сработает первым.

Источник пробужденияФункцияПиныПримечание
Таймерesp_sleep_enable_timer_wakeup()Пин не нуженВремя в микросекундах, 64 бита
ext0esp_sleep_enable_ext0_wakeup()Один пин RTC GPIOБудит по HIGH или LOW; нельзя совмещать с касанием или ULP
ext1esp_sleep_enable_ext1_wakeup_io()Несколько пинов RTC GPIOANY_HIGH или ALL_LOW; рекомендуются внешние резисторы
КасаниеtouchSleepWakeUpEnable()T0–T9 (10 пинов)Будит, когда значение падает ниже порога; нельзя совмещать с ext0
Сопроцессор ULPesp_sleep_enable_ulp_wakeup()Зависит от программыМожет читать датчики во сне; для продвинутых

Подключение примерной схемы

ДетальПин деталиПин ESP32Примечание
PIR-датчик (HC-SR501 или AM312)OUTGPIO 33Будит плату через ext0
PIR-датчикVCC / GND5V (HC-SR501) или 3V3 (AM312) / GNDHC-SR501 нужно не меньше 4,5 V
Кнопка AОдин вывод / другой вывод3V3 / GPIO 25Стягивающий резистор 10 kΩ между GPIO 25 и GND
Кнопка BОдин вывод / другой вывод3V3 / GPIO 26Стягивающий резистор 10 kΩ между GPIO 26 и GND
Сенсорная площадка или провод–GPIO 27 (T7)Чем короче, тем надёжнее работает
Светодиод + 220 ΩАнодGPIO 2На большинстве DevKit на этом же пине встроенный синий светодиод
Делитель для батареи (1 MΩ + 1 MΩ)Средняя точкаGPIO 35100 nF между средней точкой и GND
18650 + TP4056 + LDO на 3,3 VВыход LDO / GND3V3 / GNDКонденсатор 470 µF на выходе LDO
Не используй GPIO 0, 2, 12 и 15 как входы пробуждения. Это пины конфигурации при старте (strapping): если кнопка или датчик удерживают их на неправильном уровне во время сброса, плата уходит в режим прошивки или не запускается вовсе. Например, если GPIO 12 при загрузке в HIGH, напряжение флеш-памяти устанавливается на 1,8 V, и плата не может загрузиться. GPIO 34–39 работают только на вход и не имеют встроенных подтягивающих и стягивающих резисторов, так что при пробуждении через эти пины ставь резистор снаружи. Полную карту пинов своей платы можно посмотреть в утилите распиновки.

Скетч пробуждения от PIR-датчика (ext0)

C++ (Arduino IDE)
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}

После пробуждения скетч сначала возвращает пин в режим обычного GPIO через rtc_gpio_deinit(). После пробуждения через ext0 пин остаётся в режиме RTC, и без этой строки digitalRead() может работать неправильно. Затем скетч считает движение, мигает светодиодом и ждёт, пока выход PIR вернётся в LOW. Это ожидание обязательно: если уйти в сон, пока выход ещё в HIGH, плата тут же проснётся и попадёт в цикл, включаясь и выключаясь несколько раз в секунду. HC-SR501 держит выход в HIGH в течение времени, заданного потенциометром, поэтому, чтобы сократить ожидание, выкрути потенциометр времени на минимум. Так как HC-SR501 нужно не меньше 4,5 V, для проектов с питанием от одного 18650 выбирай маленький PIR-модуль AM312, который работает и от 3,3 V. Настройки датчика описаны в руководстве PIR-датчик движения.

Скетч пробуждения двумя кнопками (ext1)

C++ (Arduino IDE)
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() возвращает битовую маску, по которой видно, какой пин разбудил плату. Так двум кнопкам можно дать разные задачи: одна включает свет, другая выключает, или одна отправляет показание, а другая открывает меню. Кнопки подключены к 3V3, а пины стянуты к GND резисторами 10 kΩ, поэтому в отпущенном состоянии читается LOW, а в нажатом — HIGH; именно этого и ждёт режим ANY_HIGH. Перед сном скетч ждёт, пока кнопки отпустят, иначе всё ещё нажатая кнопка сразу же снова разбудит плату. Если используется ядро ESP32 для Arduino версии 2.x или 3.0.x, где функции _io нет, пиши вместо esp_sleep_enable_ext1_wakeup_io функцию esp_sleep_enable_ext1_wakeup с теми же параметрами. Зачем нужен стягивающий резистор, объясняется в руководстве Как подключить кнопку к Arduino.

Пробуждение касанием (touch wake-up)

Десять пинов ESP32 (T0–T9) работают как ёмкостные сенсорные датчики и замечают палец даже в глубоком сне. Провод, полоска медного скотча или площадка на печатной плате, подключённые к пину, ведут себя как кнопка и могут прятаться даже за тонкой пластиковой или деревянной крышкой. На классическом ESP32 значение touchRead() при касании падает: с коротким проводом без касания обычно читается 60–80, а при касании — 10–20. Порог пробуждения ставится между этими значениями; 40 из примера — хорошая отправная точка. На классическом ESP32 источником пробуждения можно сделать сразу несколько сенсорных пинов. А на ESP32-S2 и S3 будить микросхему может только один пин, значение при касании растёт, а порог задаётся совсем в другом масштабе (5000 в примере Espressif). Пробуждение касанием нельзя совмещать с ext0; если нужна ещё и кнопка, используй ext1. Список пинов и калибровка — в руководстве Сенсорные пины ESP32.

C++ (Arduino IDE)
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}

Глубокий сон ESP32 на MicroPython

Если 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.

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) останавливает большинство тактовых сигналов, приостанавливает процессор и сохраняет ОЗУ, а machine.deepsleep(ms) после такого же сна перезагружает плату. Даже в этих режимах потребление платы остаётся на уровне миллиампер. Поэтому, если нужно устройство на Pico, которое месяцами работает от батарей, лучше полностью отключать питание платы между измерениями с помощью таймера питания вроде TPL5110. Основы Pico описаны в руководстве Что такое Raspberry Pi Pico.

Датчик, который месяцами работает от 18650: расчёт времени работы батареи

Чтобы найти время работы от батареи для устройства, которое засыпает и просыпается, сначала считают средний ток: Средний ток = (Ток в активном режиме × Время активности + Ток во сне × Время сна) / Общее время. Затем полезную ёмкость батареи делят на этот средний ток. Аккумулятор 18650 с надписью 3000 mAh нельзя использовать полностью; с учётом отсечки по низкому напряжению, потерь в стабилизаторе и старения реалистично брать около 85%, то есть 2550 mAh. Таблица ниже рассчитана для датчика, который просыпается каждые 10 минут, отправляет данные по Wi-Fi и во время подключения потребляет в среднем 120 mA.

СценарийТок во снеВремя активностиСредний токПримерное время работы
Плата DevKit8 mA3 с, 120 mA8,56 mA≈12 дней
Плата DevKit, быстрое подключение8 mA1 с, 120 mA8,19 mA≈13 дней
Голый модуль + MCP1700 + делитель на 1 MΩ15 µA3 с, 120 mA0,61 mA≈173 дня (5,7 мес.)
Голый модуль, быстрое подключение15 µA1 с, 120 mA0,215 mA≈494 дня (16 мес.)
Голый модуль, пакетная отправка раз в час15 µA1 с Wi-Fi в час (120 mA) + 5 × 0,3 с измерений (40 mA)0,065 mAТеоретически ≈4,5 года

Вывод очевиден: на отладочной плате сокращение времени активности почти ничего не меняет, потому что батарею на самом деле сажают 8 mA во сне. Как только ток во сне падает до микроампер, баланс переворачивается, и на счету каждая секунда бодрствования: если сократить подключение к Wi-Fi с 3 секунд до 1, время работы от батареи вырастает почти втрое. Когда средний ток опускается ниже 0,1 mA, начинает сказываться саморазряд батареи. Li-ion ячейка теряет около 2–3% ёмкости в месяц, поэтому теоретические 4,5 года на практике сжимаются примерно до 2 лет. Чтобы посчитать со своими значениями, воспользуйся калькулятором времени работы от батареи, а про mAh и Wh читай в руководстве Что значит mAh.

  • Выбери правильную плату: платы без USB-микросхемы и светодиода питания или голый модуль ESP32 потребляют во сне микроамперы.
  • Замени стабилизатор: вместо AMS1117 поставь LDO с током покоя в несколько микроампер.
  • Питай датчик только во время измерения: датчики, потребляющие несколько mA, питай прямо от GPIO, а более мощную нагрузку — через P-канальный MOSFET; поставь на затвор MOSFET подтягивающий резистор, чтобы во сне транзистор оставался закрыт. Подробности в руководстве MOSFET и Arduino.
  • Включай Wi-Fi редко: копи показания в памяти RTC и отправляй пакетами; если данные идут на другой ESP32 поблизости, ESP-NOW передаёт их намного быстрее, потому что не нужно подключаться к точке доступа.
  • Ускорь подключение: со статическим IP и сохранёнными каналом и BSSID подключение к Wi-Fi часто занимает меньше секунды.
  • Убери лишние ожидания: в готовом проекте удали вывод в монитор порта и длинные строки delay(); каждая миллисекунда бодрствования оплачивается из батареи.
  • Не забывай про делитель и светодиоды: постоянно подключённый делитель 100 kΩ + 100 kΩ потребляет вдвое больше тока, чем спящий ESP32.

Идеи проектов с глубоким сном ESP32

  • Метеостанция на батарейках: измеряй температуру, влажность и давление датчиком BME280 каждые 10 минут и раз в час отправляй их в Home Assistant по MQTT.
  • Датчик влажности для сада: просыпайся раз в час, измеряй влажность почвы и присылай сообщение на телефон, когда земля пересохнет.
  • Сигнализация на дверь и окно: при открытии двери геркон будит ESP32 через ext0, а Telegram-бот присылает сообщение.
  • Охранный датчик на батарейках: сделай устройство, которое просыпается и отправляет оповещение, когда PIR замечает движение, а всё остальное время месяцами спит.
  • Сенсорный дверной звонок: наклей медный скотч за деревянной панелью; касание будит ESP32, и он подаёт сигнал на звонок в доме через ESP-NOW.
  • Регистратор температуры на батарейках: измеряй температуру в холодильнике или аквариуме водонепроницаемым DS18B20, копи значения в памяти RTC и отправляй их несколько раз в день.

Частые ошибки и как их исправить

СимптомПричинаРешение
Плата просыпается сразу после засыпанияПин пробуждения всё ещё активен (PIR в HIGH, кнопка нажата) или «висит в воздухе»Перед сном дождись, пока пин вернётся в исходный уровень, и добавь стягивающий или подтягивающий резистор
Плата не просыпается в ожидаемое время32-битное переполнение при расчёте времени (3600 * 1000000)Добавь к константам ULL: 3600ULL * 1000000ULL
Счётчик всегда начинается с 1Переменная не объявлена как RTC_DATA_ATTR, или плата была сброшенаДобавь RTC_DATA_ATTR; для постоянных данных используй Preferences
Во сне намеряно 5–15 mAСтабилизатор, светодиод питания, USB-микросхема, делитель или датчикиМаломощная плата или голый модуль, LDO с малым током покоя
Последние строки в мониторе порта обрезаныПлата ушла в сон раньше, чем опустел буферВызывай Serial.flush() перед esp_deep_sleep_start()
После пробуждения digitalRead() возвращает неверные значенияПин пробуждения остался в режиме RTCПеред использованием пина вызови rtc_gpio_deinit()
Кнопка на GPIO 34–39 не будит платуУ этих пинов нет встроенных подтягивающих резисторов, поэтому пин «висит в воздухе»Добавь внешний резистор 10 kΩ
Код не загружается, пока плата спитНа плате нет схемы автосброса; на S3 и C3 встроенный USB-порт во сне пропадаетУдерживая BOOT, нажми и отпусти EN (RESET), затем запусти загрузку
Реле или светодиод меняют состояние, когда плата засыпаетВ глубоком сне цифровые пины «висят в воздухе» (высокоимпедансное состояние)Поставь подтягивающий резистор или удерживай уровень пина через rtc_gpio_hold_en()
На время разработки добавь в начало setup() временную паузу на 5 секунд или условие, которое пропускает сон, пока нажата кнопка. Так будет время открыть монитор порта и загрузить новый код, даже если плата всё время спит; только не забудь убрать это, когда проект будет готов. Если на батарейном питании плата время от времени сама перезагружается, вероятнее всего, виноваты пики тока Wi-Fi; решения — в руководстве Ошибка brownout на ESP32.

Глубокий сон ESP32 — ключ к тому, чтобы один и тот же код работал от батареи месяцами, а не днями: делай работу короткой, выбирай правильный источник пробуждения, храни нужные между пробуждениями небольшие данные в памяти RTC и никогда не забывай о собственном потреблении платы. Если хочется вернуться к основам ESP32, хорошее начало — руководство Что такое ESP32. В приложении BlueGrays в проекте «Глубокий сон ESP32: экономия батареи (OLED + счётчик в RTC RAM)» можно попробовать логику таймера и счётчика в RTC из этой статьи на схеме с OLED-дисплеем, с пошаговой схемой подключения и готовым кодом.

Частые вопросы

Какой ток потребляет ESP32 в глубоком сне?

По даташиту Espressif микросхема ESP32 в глубоком сне с включёнными таймером RTC и памятью RTC потребляет около 10 µA, а в режиме гибернации — 5 µA. Но на отладочных платах вроде DevKit стабилизатор, светодиод питания и USB-UART-преобразователь обычно поднимают ток во сне до 5–15 mA.

Как разбудить ESP32 из глубокого сна?

Способов пять: таймер RTC, один пин RTC (ext0), несколько пинов RTC (ext1), сенсорные пины и сопроцессор ULP. Источник пробуждения настраивается до вызова esp_deep_sleep_start(), и можно включить несколько сразу; вместе нельзя использовать только ext0 и пробуждение касанием.

Теряются ли переменные в глубоком сне ESP32?

Да, обычные переменные теряются, потому что пробуждение из глубокого сна — это перезапуск, и setup() выполняется с начала. Переменные с пометкой RTC_DATA_ATTR хранятся в памяти RTC и сохраняются между пробуждениями, но после отключения питания, нажатия кнопки EN или загрузки нового кода возвращаются к начальным значениям.

Сколько ESP32 проработает от аккумулятора 18650?

От дня до более чем года, в зависимости от платы и сценария использования. DevKit, постоянно подключённая к Wi-Fi, проработает от 18650 на 3000 mAh всего 1–2 дня, а DevKit, которая просыпается каждые 10 минут, — около 12 дней. Маломощная схема, выполняющая ту же работу с током во сне 15 µA, проработает от 5 до 16 месяцев в зависимости от длительности каждого подключения.

Чем лёгкий сон ESP32 отличается от глубокого?

В лёгком сне (light sleep) процессор приостановлен, но ОЗУ сохраняется; после пробуждения программа продолжает с той строки, где остановилась, а микросхема потребляет около 0,8 mA. В глубоком сне обесточивается и ОЗУ, потребление падает до 10 µA, но при каждом пробуждении программа начинается заново. Лёгкий сон подходит для пауз в несколько секунд, глубокий — для ожидания в минуты или часы.

Как загрузить код в ESP32, который находится в глубоком сне?

На платах со схемой автосброса загрузка обычно начинается без проблем, потому что загрузчик сбрасывает микросхему через пин EN. Если нет, удерживай кнопку BOOT, нажми и отпусти EN (RESET), затем запусти загрузку. На платах со встроенным USB, таких как ESP32-S3 и C3, порт во сне пропадает, поэтому этот способ часто обязателен.

Все проекты
Мигающий светодиод на ESP32
#103Новичок

Мигающий светодиод на ESP32

Учит базовой работе с выходами GPIO на ESP32. Эффект мигания получается включением и выключением светодиода с цифрового пина. Это ESP32-версия классического проекта мигания на Arduino UNO; базовые команды мигания в среде Arduino те же, но расположение пинов ESP32 и уровни GPIO 3,3 V отличаются от Arduino UNO.

4.7(37)
99
Управление светодиодом кнопкой на ESP32 (удержание)
#104Новичок

Управление светодиодом кнопкой на ESP32 (удержание)

Светодиод горит, когда кнопка нажата, и гаснет, когда её отпускают. Показывает, как используются цифровой входной пин ESP32 и встроенный подтягивающий резистор. Режим INPUT_PULLUP избавляет от внешнего резистора.

4.6(36)
55
Глубокий сон ESP32: экономия батареи (OLED + счётчик в RTC RAM)
#135Продвинутый
Premium

Глубокий сон ESP32: экономия батареи (OLED + счётчик в RTC RAM)

Режим глубокого сна (Deep Sleep) у ESP32 снижает потребление до ~10 µA, выключая процессор и большую часть периферии, — это важно для IoT-устройств на батарейках. В этом проекте используются три источника пробуждения: таймер (15 секунд), внешний пин (EXT0 — кнопка GPIO 33) и касание (T0/GPIO 4). Переменные с RTC_DATA_ATTR сохраняются во время глубокого сна — счётчики загрузок, пробуждений по таймеру и по кнопке ведутся непрерывно.

4.3(10)
16
Сигнализация движения на ESP32 (PIR + зуммер + светодиод + Telegram)
#145Продвинутый
Premium

Сигнализация движения на ESP32 (PIR + зуммер + светодиод + Telegram)

Умная охранная сигнализация, которая при обнаружении движения PIR-датчиком включает зуммер и светодиод и одновременно сразу отправляет владельцу уведомление в Telegram. В ней есть 30-секундная пауза, ограничивающая частоту повторных тревог; этот механизм не проверяет срабатывания PIR и не устраняет ложные тревоги. Подходит для дома, гаража, склада или мастерской. С ESP32-CAM можно добавить уведомления с фото, а с обычной ESP32 DevKit V1 приходят текстовые.

0.0(0)
9
Автоматическое освещение с PIR-датчиком движения на ESP32
#121Средний
Premium

Автоматическое освещение с PIR-датчиком движения на ESP32

В этом проекте PIR-датчик движения HC-SR501 замечает, что кто-то вошёл в комнату, и автоматически включает светодиод. Время свечения отслеживается через millis(); каждое показание HIGH обновляет время последнего движения, и светодиод гаснет не раньше чем через 10 секунд после последнего HIGH. Текущий код ещё и ждёт 50 ms на каждом цикле через delay(50), поэтому цикл не полностью неблокирующий. Подходит для освещения лестниц, шкафов и ванной.

4.4(24)
38
Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)
#117Средний

Температура и влажность с DHT22 на ESP32 (с LCD-дисплеем)

Этот проект измеряет температуру и влажность воздуха датчиком DHT22 и в реальном времени показывает данные на I2C LCD 16x2. Данные читаются с датчика по однопроводному протоколу через библиотеку Adafruit DHT. Проект служит основой для IoT-метеостанций, умного дома или систем мониторинга среды в теплицах. Связь с DHT22 идёт через пин GPIO 4 у ESP32.

4.8(38)
56
Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Продвинутый
Premium

Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

Собери на ESP32 регистратор температуры с отметками времени! Каждую минуту читай температуру и влажность с DHT22, бери реальное время из DS1307 RTC и записывай всё на карту microSD в формате CSV. OLED показывает текущую температуру (крупным шрифтом), влажность в процентах, время и счётчик записей. Файл /temp_log.csv на SD-карте открывается в Excel, Google Sheets или Python pandas — идеально для теплиц, комнат, лабораторий и хранения продуктов.

4.7(7)
12