Дачное табло с температурой в теплице я питал от банки 18650 на 2000 мА·ч и был уверен в арифметике: даташит обещает 10 мкА в глубоком сне, значит, банка протянет годы. Протянуть она не смогла и пяти суток. USB-тестер за 300 рублей, вставленный в разрыв питания, показал 14 мА во сне — в тысячу раз больше обещания, и никакой esp_deep_sleep_start() на это не влиял. Виновником был не ESP32, а обвязка DevKit: USB-мост CP2102, стабилизатор AMS1117 с его собственными миллиамперами покоя и светодиод питания, который светил всю ночь, пока я не смотрел.
Этот урок — про честный энергобюджет: сколько плата ест в каждом режиме на самом деле, как свести время жизни батарейки к одной формуле и какие три строки уводят ESP32 в глубокий сон с будильником на таймере. Всё остальное в разделе — WiFi, HTTP, веб-сервер — мерьте в десятках и сотнях мА; сон — единственный режим, где девайс на батарейке живёт месяцами, и умение его считать отличает рабочий датчик от ёлочной игрушки, сдохшей к Новому году.
Сколько ест плата: чип, модуль, DevKit
Начну с даташитных цифр для чистого чипа при 3,3 В. Активный режим с WiFi: пики передачи до 240 мА, среднее при активной работе 80-130 мА. Modem sleep — WiFi остаётся подключённым, но радиомодуль дремлет между кадрами роутера: 20-30 мА в среднем. Light sleep: ядро спит, RAM и её содержимое живы, пробуждение за доли миллисекунды — 0,8-1 мА. Deep sleep: всё глухо, кроме RTC-контроллера и его 8 КБ памяти — 6-15 мкА у чистого модуля WROOM-32.
Разрыв между 10 мкА и 100 мА — четыре порядка, и вся экономика батарейки живёт в этой вилке: сколько секунд в час плата работает, а сколько спит. Но даташитные 10 мкА относятся к чипу, а вы покупаете плату, и её обвязка не спит никогда: DevKit V1 без USB-кабеля тянет во сне 10-20 мА. Мост USB, стабилизатор, светодиод питания — всё это висит на той же банке и не слышало ни про какой глубокий сон.
Поэтому моё железное правило: сначала измерь, потом считай. USB-тестер с экраном за 300-500 рублей в разрыве питания показывает и пики, и среднее за длительность; мультиметр тоже справится, если не лениться переключать диапазоны мА и мкА. На модуле WROOM-32, спаянном на свою плату без USB-чипа и с LED-питанием, я намерил 11 мкА во сне — обещание даташита подтвердилось. На DevKit — 14 мА. Обе цифры правдивы, но для батарейки они означают разницу между годом работы и пятью сутками.
Формула бюджета проста, как список покупок. Средний ток цикла: I_avg = (I_work × t_work + I_sleep × t_sleep) / T, где T — период цикла, например час. Дальше батарейка: дни = ёмкость_мАч / (I_avg_мА × 24). Возьмём типовую задачу — табло с HTTP-запросом к погодному серверу: 10 секунд работы при 120 мА и 3590 секунд сна при 12 мкА. Средний ток выходит около 348 мкА, и банка 2000 мА·ч по формуле даёт 240 суток — почти восемь месяцев.
В формуле два подвоха, и оба — в пользу пессимиста. Первый: саморазряд литиевой банки съедает 2-3 % ёмкости в месяц сам по себе, без всякой нагрузки, — за год это 500-700 мА·ч из моих 2000; поэтому реальный потолок батарейного девайса на годы — химия, а не арифметика, и я пишу в паспорте устройства «больше года», а не «по расчёту». Второй: 10 секунд работы — это с уже подключённым WiFi; роутер задумался, сервер тормозит — и работа заняла 40 секунд, а средний ток вырос втрое. Бодрствование без блокирующих пауз и с запасом по таймингу умеет планировщик на millis — здесь он превращается в статью энергобюджета.
Три строки сна и две ловушки
#include <WiFi.h>#include "esp_sleep.h" RTC_DATA_ATTR int boots = 0; // выживает сон: 8 КБ RTC-памяти void setup() { Serial.begin(115200); boots = boots + 1; Serial.print("boots="); Serial.println(boots); // ... здесь работа: опрос датчика, HTTP-запрос, отправка ... WiFi.mode(WIFI_OFF); // глушим радио до сна btStop(); // и Bluetooth заодно Serial.println("usnul na 60 s"); delay(100); // дать UART дописать строку esp_sleep_enable_timer_wakeup(60ULL * 1000000ULL); // 60 с в мкс esp_deep_sleep_start(); // не возвращается никогда} void loop() { } // сюда плата не доходитРазбор по строкам. esp_sleep_enable_timer_wakeup() принимает время в микросекундах — первая ловушка сидит здесь: 60 в аргументе — это не минуты, а 60 микросекунд, пробуждение раньше, чем Serial успевает напечатать строку. Час — это 3 600 000 000 мкс, что не влезает в 32-битный int, поэтому умножение пишут с суффиксом ULL: 3600ULL * 1000000ULL говорит компилятору считать в 64 битах. Без него час сна превращается в мусорное число, и компилятор ругнётся далеко не всегда — молча посчитает переполнение.
Вторая ловушка — порядок действий. esp_deep_sleep_start() не возвращается никогда: строки после него мертвы, loop() не наступает вовсе — вся работа живёт в setup() до вызова сна или в функциях, которые тот зовёт. Радио глушу заранее: WiFi.mode(WIFI_OFF) и btStop() — модем и Bluetooth доедают миллиамперы в последние секунды бодрствования, и глушить их дешевле до, а не после. И delay(100) перед сном — дать UART допечатать строку: без этой паузы последняя строка лога обрезается, и выглядит это как зависание платы.
Что выживает во сне — только RTC-память, 8 КБ, помеченные RTC_DATA_ATTR. Остальная RAM умирает, и после пробуждения setup() выполняется заново: millis() стартует с нуля, WiFi подключается заново, датчики инициализируются заново. Счётчики и настройки, которые должны пережить сон, живут в RTC_DATA_ATTR — это местный дешёвый аналог EEPROM, про износ которого я писал в уроке про счётчик включений: у RTC-памяти нет лимита перезаписи, зато объём всего 8 КБ, и живёт она вместе с батарейкой.
| Режим | Ток чипа при 3,3 В | Что живёт | Чем будят |
|---|---|---|---|
| Активный с WiFi | 80-130 мА среднее, пики 240 мА | Всё | — |
| Modem sleep | 20-30 мА | Ядро, RAM, WiFi-связь | Сам, между кадрами роутера |
| Light sleep | 0,8-1 мА | RAM и регистры, ядро спит | Прерывание, таймер; выход за мс |
| Deep sleep | 6-15 мкА чип; 10-20 мА DevKit | Только RTC-память 8 КБ | Таймер, кнопка, касание |
Считаем в симуляторе
- 1. Скетч вызывает esp_sleep_enable_timer_wakeup(60000000). Сколько будет спать плата?
- 2. Что переживёт глубокий сон на ESP32?
- 3. DevKit во сне ест 14 мА при даташитных 10 мкА. Кто съел разницу?
- 4. Что делает esp_deep_sleep_start() после входа в сон?
- 5. Банка на 2000 мА·ч, средний ток устройства 0,5 мА. На сколько хватит?
- Глубокий сон — единственный режим с мкА: чип 6-15 мкА, но DevKit во сне ест 10-20 мА из-за обвязки
- Таймер сна заряжают в микросекундах: 60ULL * 1000000ULL — минута; без ULL большие числа ломаются
- esp_deep_sleep_start() не возвращается: вся работа — в setup() до вызова, loop() мёртв
- Выживает только RTC_DATA_ATTR; WiFi.mode(WIFI_OFF), btStop() и delay(100) перед сном
- Дни жизни = мА·ч / (средний мА × 24); сначала тестер в разрыве, потом арифметика
Цифры из симулятора — приговор большинству батарейных проектов на DevKit: со сном он живёт примерно столько же, сколько без него, потому что 15 мА покоя съедают банку за пять-шесть суток при любом цикле. Экономика меняется только на чистом модуле: там доли секунды бодрствования в час переводят устройство из «перезаряжаю раз в неделю» в «забыл про него на год». Все промежуточные варианты — подольше глушить WiFi, спать чаще — дают проценты, а нужны порядки. Запомните ряд: 240 мА пик, 20-30 мА modem sleep, 1 мА light sleep, 10 мкА deep sleep — и выбирайте режим по задаче, а не по красоте названия.
Таймер — базовый будильник, но у сна есть и другие двери: пробуждение по кнопке без всякого таймера и по касанию тач-пина, а вместе с ними — шильдик причины пробуждения, по которому скетч понимает, кто его разбудил. Разбираем в следующем уроке про кнопку и причины. Если же платформа у вас другая — у ATmega328P на Uno свои режимы сна со своими мкА; я разбирал их рядом со сторожевым псом в уроке про таймеры и сон: подход тот же, цифры другие, формула бюджета одна на все микроконтроллеры. А список RTC-пинов, куда вешается кнопка пробуждения, я сверяю по уроку про распиновку DevKit — там же пины, которые нельзя трогать на старте.
Частые вопросы по теме урока
- Скетч вызывает esp_sleep_enable_timer_wakeup(60000000). Сколько будет спать плата?
- Аргумент таймера сна — микросекунды. 60 000 000 мкс — это ровно 60 секунд; минуты и часы умножают с суффиксом ULL, чтобы не переполнить 32 бита. 60 секунд: аргумент задаётся в микросекундах
- Что переживёт глубокий сон на ESP32?
- Во сне питается только RTC-домен: 8 КБ памяти и RTC-контроллер. RAM умирает, WiFi теряется, millis() после пробуждения стартует с нуля. Переменные с пометкой RTC_DATA_ATTR — 8 КБ RTC-памяти
- DevKit во сне ест 14 мА при даташитных 10 мкА. Кто съел разницу?
- Даташит описывает чип. На плате чип спит честно, но CP2102, AMS1117 и LED висят на банке и в глубокий сон не уходят — 10-20 мА в сумме. USB-мост, стабилизатор и светодиод питания платы
- Что делает esp_deep_sleep_start() после входа в сон?
- Вызов не возвращается никогда. Пробуждение — это перезагрузка: setup() выполняется с нуля, и счётчик boots с RTC_DATA_ATTR показывает это наглядно. Ничего: выполнение не продолжится, плата проснётся перезагрузкой в setup()
- Банка на 2000 мА·ч, средний ток устройства 0,5 мА. На сколько хватит?
- Дни = мА·ч / (мА × 24) = 2000 / (0,5 × 24) = 166 суток. Поправка на саморазряд литиевой банки — минус 2-3 % ёмкости в месяц. 166 суток: 2000 / 0,5 / 24