ESP32 Deep Sleep: снижаем ток до 10 мкА для IoT на батарейке
ESP32 в активном режиме потребляет 80-240 мА, и батарейка 18650 на 2500 мА·ч садится за 10-30 часов. В режиме deep sleep ток падает до 10-150 мкА, и та же батарейка живёт месяцами. На этом и построен любой IoT-датчик: проснулся, снял показания, отправил их по WiFi, заснул.
Типичный цикл выглядит так: подъём занимает около 200-250 мс, чтение датчика и отправка данных укладываются в пару секунд, сон длится от минуты до часа. Средний ток такого устройства считается как сумма: 15 мА·с на цикл, разделённое на период сна.
Каркас deep sleep-скетча проверяется в нашем симуляторе ESP32: переменные с RTC_DATA_ATTR сохраняются между заходами в виртуальном планировщике, так что можно проверить саму логику счётчика.
Состояния сна ESP32
Режимов сна несколько, и их важно не путать. Modem sleep выключает WiFi между маячками, потребление остаётся на уровне десятков миллиампер. Light sleep останавливает CPU, но держит RAM, это примерно 0.4-0.8 мА. Deep sleep гасит всё, кроме RTC-домена и ультра-low-power сопроцессора, отсюда 10-150 мкА. Hibernation уходит ещё глубже, около 5-10 мкА, но выйти из него сложнее. Для датчика на батарейке нужен deep sleep.
Грабль с пином будильника: разбудить ESP32 можно только по RTC-пину. Список RTC GPIO у оригинального ESP32: 0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39. Пин 13 в этом списке есть, а вот GPIO 16-33, кроме перечисленных, не RTC. На вариантах ESP32-S2 и ESP32-C3 поддержка EXT0 отсутствует, там остаётся только EXT1.
Таймер будильник
#define uS_TO_S 1000000LLRTC_DATA_ATTR int bootCount = 0;void setup() { Serial.begin(115200); delay(100); bootCount++; Serial.printf("Просыпание #%d\n", bootCount); Serial.println("Спим 10 секунд..."); esp_sleep_enable_timer_wakeup(10 * uS_TO_S); esp_deep_sleep_start();}void loop() {}Ключевое здесь слово RTC_DATA_ATTR. Это атрибут переменной, которая лежит в RTC-памяти и переживает сон. Без него счётчик bootCount на каждом просыпании возвращался бы в ноль, и отследить, сколько раз проснулось устройство, было бы невозможно. Само просыпание это не перезагрузка в привычном смысле: чит стартует с вектора сброса, то есть с начала setup(), а loop() не выполняется вовсе. Поэтому вся логика живёт в setup(), а loop() остаётся пустым.
Таймер задаётся в микросекундах через esp_sleep_enable_timer_wakeup. Максимум ограничен разрядностью RTC-таймера: порядка 2^31 микросекунд, то есть примерно 36 минут за один заход. Если нужна пауза длиннее, есть два пути: будить по внешним часам RTC, если они подключены, или делать несколько последовательных циклов сна. Для большинства датчиков достаточно паузы в несколько минут.
Wake-up по кнопке (EXT0/EXT1)
EXT0 будит чип по одному RTC-пину и заданному уровню. EXT1 будит по нескольким пинам сразу, с логикой ANY (любой из них) или ALL (все сразу). Классический пример: кнопка на GPIO 0, то есть на выводе BOOT, будит по низкому уровню, и функция esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0) это описывает. Минус EXT0 в том, что при удержании кнопки возможен повторный запуск, поэтому в скетче после пробуждения стоит гасить или сбрасывать кнопку. Подробнее про режимы питания — в разделе продвинутая платформа.
| Режим | Ток | Что работает | Время выхода |
|---|---|---|---|
| Active (WiFi) | 80-240 мА | Всё + WiFi | 0 мс |
| Modem sleep | ~20 мА | WiFi off между маячками | 0 мс |
| Light sleep | 0.4-0.8 мА | CPU off, RAM on | ~1 мс |
| Deep sleep | 10-150 мкА | RTC + ULP | ~200 мс |
| Hibernation | 5-10 мкА | Минимум RTC | ~1 с |
Что сначала не получилось
Первый датчик я собрал как «заснул навсегда»: вызвал esp_deep_sleep_start() без настроенного будильника и больше не получил устройства. Пришлось перезагружать плату кнопкой, потому что RTC-таймер был не включён, а значит и разбудить чип было некому. Второй раз я наоборот забыл вызвать esp_sleep_enable_timer_wakeup, и устройство просыпалось мгновенно, гоняя WiFi по кругу и съедая батарейку за несколько часов.
Третья ошибка была в расчётах, и она стоила дороже всех: я посчитал средний ток как ток сна плюс ток активной фазы, забыв разделить активную фазу на длительность цикла. Получалось, что устройство потребляет 200 мА постоянно. Настоящая арифметика простая: если за цикл плата спит 600 секунд и работает 3 секунды на 150 мА, то вклад активной фазы это 150 мА × 3 с / 603 с, то есть меньше 1 мА.
Личный опыт: датчик на окне
Первое устройство на deep sleep было датчиком температуры на подоконнике: ESP32, DHT22 и питание от трёх пальчиковых батареек. Цикл был 10 минут сна, за это время датчик просыпался, читал температуру и отправлял значение в MQTT по WiFi. Счётчик просыпаний я держал в переменной с RTC_DATA_ATTR, и по нему проверял, что устройство действительно переживает циклы, а не зависает.
Вторую часть опыта дали первые замеры тока мультиметром. В deep sleep ESP32 показывал десятки микроампер, а в момент подъёма и подключения к WiFi ток прыгал до сотен миллиампер. Из этих данных и сложилась рабочая формула: чтобы батарейка 2500 мА·ч жила год, средний ток должен быть около 0.3 мА, и значит активная фаза в 3 секунды при 150 мА допустима, а вот при 60 секунды активной фазы батарейки не хватит и на месяц. Дальше я сократил время подъёма: отключал WiFi сразу после отправки, не ждал лишний десяток секунд на disconnect.
Авторская позиция: что взять новичку
Считайте энергопотребление до того, как покупать модули, иначе получите дорогую игрушку, которая живёт неделю. Первым делом возьмите ESP32, а не ESP8266: у него шире набор RTC-пинов и лучше управление сном. Дальше измеряйте ток мультиметром в двух точках, в простое и в момент подъёма, потому что суммарное потребление почти всегда оказывается больше расчётного. И не пытайтесь сразу сделать устройство, которое работает год: сначала добейтесь, чтобы оно просыпалось раз в минуту и не зависало, а потом уже увеличивайте интервал.