Добавить в избранное

ESP32 deep sleep: автономные проекты на батарее на месяцы

Чтение ≈ 5 мин

Энергосбережение на ESP32: режимы сна, deep sleep, пробуждение по таймеру и по GPIO, RTC-память для сохранения данных, измерение потребления и проект метеостанции на батарее на годы.

Зачем сон микроконтроллеру

ESP32 в активной работе ест 80–240 мА — от аккумулятора 18650 (~3000 мА·ч) протянет сутки-двое. Для автономного датчика, просыпающегося раз в час, это катастрофически мало. Deep sleep отключает почти всё: процессор, Wi-Fi, периферию — остаются только RTC-часы и слабые сторожа. Потребление падает до ~10–150 мкА — батарея живёт месяцами.

Три режима сна ESP32

  • Light sleep — процессор остановлен, RAM и периферия живы; просыпается мгновенно по GPIO/таймеру; ~0.8 мА.
  • Deep sleep — почти всё выключено, только RTC-подсистема; ~10–150 мкА; при пробуждении — полный рестарт.
  • Hibernation — ещё глубже, даже RTC-таймер выключен; просыпается только по GPIO.

Для автономки — deep sleep: почти ноль потребления, а «рестарт» вместо пробуждения для скетча прозрачен.

Сон по таймеру

Простейший сценарий: просыпаться раз в N минут, сделать дело и снова спать:

const uint64_t SLEEP_US = 10 * 60 * 1000000ULL;  // 10 минут в микросекундах

void setup() {
    Serial.begin(115200);
    readSensor();
    sendData();

    esp_sleep_enable_timer_wakeup(SLEEP_US);
    esp_deep_sleep_start();        // дальше этой строки код не выполнится!
}
void loop() {}                     // не дойдёт никогда

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

RTC-память: данные между снами

Обычные переменные стираются при рестарте. Пометив переменную RTC_DATA_ATTR, вы кладёте её в RTC-область, которая не обесточивается в deep sleep:

RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR float lastTemp = NAN;

void setup() {
    bootCount++;
    Serial.printf("пробуждение #%d, было %.1f\n", bootCount, lastTemp);
    lastTemp = dht.readTemperature();
    // ...
    esp_deep_sleep_start();
}

bootCount накапливается через все сны — ведём статистику без EEPROM.

Пробуждение по GPIO: кнопка или датчик

Вместо таймера — внешний сигнал: датчик движения, дождевая капля, кнопка:

// проснуться когда GPIO2 станет HIGH
esp_sleep_enable_ext0_wakeup(GPIO_NUM_2, 1);

// или по любому из нескольких пинов — ext1 (только RTC GPIO)
esp_sleep_enable_ext1_wakeup(
    (1ULL << GPIO_NUM_4) | (1ULL << GPIO_NUM_5),
    ESP_EXT1_WAKEUP_ANY_HIGH);

Только RTC-GPIO пины работают в deep sleep — на ESP32 это GPIO 0,2,4,12–15,25–27,32–39. Проверяйте карту вашей платы.

Причина пробуждения

После рестарта полезно знать, что нас разбудило:

void setup() {
    esp_sleep_wakeup_cause_t why = esp_sleep_get_wakeup_cause();
    switch (why) {
        case ESP_SLEEP_WAKEUP_TIMER:     Serial.println("таймер"); break;
        case ESP_SLEEP_WAKEUP_EXT0:      Serial.println("GPIO"); break;
        case ESP_SLEEP_WAKEUP_EXT1:      Serial.println("мульти-GPIO"); break;
        case ESP_SLEEP_WAKEUP_UNDEFINED: Serial.println("питание/reset"); break;
        default: break;
    }
}

Полный пример: метеостанция на батарее

#include <DHT.h>
#include <WiFi.h>
#include <HTTPClient.h>

RTC_DATA_ATTR int bootCount = 0;
DHT dht(4, DHT22);

void setup() {
    bootCount++;
    dht.begin();
    float t = dht.readTemperature();

    WiFi.begin("SSID", "pass");
    int tries = 0;
    while (WiFi.status() != WL_CONNECTED && tries++ < 40) delay(500);

    if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        http.begin("https://api.example.com/log?t=" + String(t));
        http.GET();
        http.end();
    }

    WiFi.disconnect(true);
    esp_sleep_enable_timer_wakeup(30 * 60 * 1000000ULL);  // 30 минут
    esp_deep_sleep_start();
}
void loop() {}

Цикл: проснулся → измерил → отправил → спать. Активная фаза ~5–10 секунд, сон 30 минут — на 18650 хватит на год.

Сколько реально съедает

Проверьте амперметром:

  • Актив с Wi-Fi: ~150–240 мА.
  • Актив без Wi-Fi: ~50–80 мА.
  • Deep sleep на голом чипе: ~10 мкА.
  • На DevKit-плате: ~5–10 мА (светодиоды, USB-UART, стабилизатор едят!).

Для реальной автономки берите «голые» платы (ESP32-WROVER bare, FireBeetle) или отключайте питание периферии транзистором — иначе стабилизатор AMS1117 съест батарею быстрее чипа.

Типичные проблемы

  • Устройство не спит: loop с кодом вместо esp_deep_sleep_start; после начала сна ничего не выполнится.
  • Потребление выше 100 мкА: светодиод питания на DevKit, USB-UART чип, стабилизатор — они живы в сне.
  • Не просыпается по GPIO: пин не RTC-GPIO или уровень неправильный (ext0 ловит уровень, не фронт).
  • Данные теряются: переменная без RTC_DATA_ATTR или в обычной памяти — рестарт обнуляет.
  • Wi-Fi долго не подключается: сокращайте попытки, кэшируйте канал/BSSID — экономит секунды активности.

Итог

Deep sleep превращает ESP32 из «50 мА активности» в «10 мкА сна»: esp_sleep_enable_timer_wakeup, RTC_DATA_ATTR для данных, EXT0/EXT1 для пробуждения по событию. Схема простая: в setup делаем всё дело, в конце — esp_deep_sleep_start, loop пустой. И помните — DevKit-платы съедают экономию своей обвязкой, для реальной автономки берите минимальные платы.

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

#include <WiFi.h>
#include <DHT.h>

RTC_DATA_ATTR int wakeCount = 0;
RTC_DATA_ATTR float lastTemp = -100;

const uint64_t SLEEP_US = 10 * 60 * 1000000ULL;  // 10 минут

void setup() {
    Serial.begin(115200);
    ++wakeCount;

    esp_sleep_wakeup_cause_t why = esp_sleep_get_wakeup_cause();
    Serial.printf("пробуждение #%d, причина: %d\n", wakeCount, why);

    DHT dht(4, DHT22);
    dht.begin();
    float t = dht.readTemperature();
    if (!isnan(t)) lastTemp = t;
    Serial.printf("температура: %.1f\n", lastTemp);

    // отправка по Wi-Fi, если нужно
    WiFi.begin("SSID", "pass");
    if (WiFi.waitForConnectResult() == WL_CONNECTED) {
        // mqtt.publish("home/temp", String(lastTemp));
        WiFi.disconnect(true);
    }

    esp_sleep_enable_timer_wakeup(SLEEP_US);
    Serial.println("сон на 10 минут");
    Serial.flush();
    esp_deep_sleep_start();
}
void loop() {}

Пробуждение по кнопке

// RTC_GPIO 4 — проснуться при LOW (кнопка замыкает на GND)
esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, 0);
esp_deep_sleep_start();

Пробуждение по нескольким пинам

// любой из двух пинов RTC_GPIO
esp_sleep_enable_ext1_wakeup(
    (1ULL << GPIO_NUM_33) | (1ULL << GPIO_NUM_32),
    ESP_EXT1_WAKEUP_ANY_HIGH);

Питание от аккумулятора

  • DevKit-платы жрут в сне 5–10 мА (USB-UART, светодиод, стабилизатор).
  • Голый чип (ESP32-WROOM) — ~10 мкА.
  • Pro Mini/FireBeetle/Olimex — минимальная обвязка, микроамперный сон.
  • Отключайте питание периферии транзистором — датчики тоже жрут в standby.

Сколько реально потребляет

| Режим | DevKit | Голый чип | |---|---|---| | Актив + Wi-Fi | 150–240 мА | 150–240 мА | | Актив без Wi-Fi | 50–80 мА | 50–80 мА | | Light sleep | ~2 мА | ~0.8 мА | | Deep sleep | 5–10 мА | ~10 мкА | | Hibernation | ~5 мА | ~5 мкА |

Типичные проблемы

  • Просыпается сразу: пин в неправильном состоянии, шум на линии.
  • Данные потеряны: переменная без RTC_DATA_ATTR — обычная память обнуляется.
  • Не спит: loop крутится вместо esp_deep_sleep_start.
  • Долгое подключение к Wi-Fi: тратит батарею; сохраняйте канал/BSSID в RTC.
  • Периферия жрёт: отключайте питание датчиков транзистором.
Поделиться
Понравился материал?

Комментарии

Добавить комментарий