Введение
В embedded-проектах с батарейным питанием управление энергопотреблением часто сводят к нескольким вызовам sleep() и проверке кнопки включения. Пока устройство простое — этого хватает. Но как только появляется аккумулятор, зарядка, дисплей, кнопка питания и требования к пользовательскому поведению, такой подход начинает разваливаться.
Типичные ситуации, с которыми сталкиваются на практике:
● устройство выключено, но подключили зарядку — что должно происходить?
● зарядка идёт, пользователь нажал кнопку — включаться или нет?
● батарея критически разряжена — как корректно отказать во включении?
● устройство долго не используется — когда и как его выключать?
● устройство вышло из Deep Sleep — это пробуждение или «холодный старт»?
● контроллер питания подал питание сам по себе — это включение или ошибка?

Во многих примерах и обучающих материалах для ESP32 (и других MCU) эти сценарии либо игнорируются, либо решаются точечно: условием в main(), флагом в RTC-памяти, таймером без чёткой логики. В результате управление питанием размазывается по коду, поведение устройства становится трудно предсказуемым, а добавление нового сценария часто ломает уже существующие.
Отдельную сложность добавляет Deep Sleep. В ESP32 он обеспечивает минимальное энергопотребление, но при этом полностью обесточивает SRAM. После пробуждения система фактически стартует заново, и без явной архитектуры становится неочевидно, чем «пробуждение» отличается от сброса и как восстановить корректное состояние устройства.
При этом на рынке существует множество контроллеров питания (PMIC), таких как MP2722, AXP, BQ-серии и другие, которые берут на себя зарядку аккумулятора, переключение источников питания и даже обработку кнопки включения. Однако готовых библиотек или фреймворков, которые бы системно управляли жизненным циклом устройства — от выключенного состояния до сна и зарядки — практически нет. Обычно есть драйвер PMIC и всё остальное предлагается «решить на уровне приложения».
В этой статье я хочу показать другой подход: рассматривать управление питанием не как набор отдельных функций, а как конечный автомат состояний, который принимает события от контроллера питания и пользовательского ввода и принимает решения о дальнейшей жизни устройства. На примере ESP32, Deep Sleep и контроллера питания MP2722 я разберу архитектуру такого решения и покажу демо-устройство с дисплеем и LVGL, где все эти сценарии можно увидеть вживую.
Три типовых сценария, которые выглядят похоже, но требуют разной логики
На практике задачи управления питанием сильно зависят от сценария использования устройства. Я бы выделил три наиболее распространённых варианта.
Устройство с постоянным питанием и аккумулятором как резервом
Это могут быть: стационарные контроллеры, сетевые устройства, панели управления. Основное питание подаётся постоянно, а аккумулятор используется как резервный источник. В таком сценарии Deep Sleep часто не нужен, но важны: корректная работа при пропадании сети; автоматическое восстановление после возврата питания; управление зарядкой и состоянием батареи без участия пользователя.
Ошибки здесь обычно проявляются в некорректных переходах при восстановлении питания и странном поведении после кратковременных отключений.
Устройство в режиме логгера / датчика / маяка
Здесь всё наоборот: устройство большую часть времени спит, а просыпается лишь периодически или по событию. Типичные требования: минимальное энергопотребление в спящем режиме; чётко определённые источники пробуждения; гарантированный возврат в сон после выполнения задачи. В этом сценарии Deep Sleep — основной режим работы, и без формального управления состояниями легко получить либо избыточное энергопотребление, либо зависание в активном состоянии.
Устройство с экраном (плеер, терминал, «почти телефон»)
Самый сложный вариант: есть пользовательский интерфейс; есть кнопка питания с разными типами нажатий; есть ожидания пользователя (включение, блокировка, сон, выключение); есть зарядка и поведение «на зарядке». Здесь управление питанием напрямую влияет на UX. Пользователь не думает о Deep Sleep или PMIC — он ожидает понятного и стабильного поведения. Любая неоднозначность сразу воспринимается как баг.
Общий вывод для всех этих сценариев один: попытки решать управление питанием локально, без общей модели, рано или поздно приводят к сложности и ошибкам. Именно здесь появляется необходимость рассматривать питание как отдельную подсистему с чётко определёнными состояниями и событиями, а не как набор разрозненных условий.
Формулировка задачи и требований к Power Management
Если обобщить все описанные выше сценарии, становится понятно, что задача управления питанием сводится не к отдельным API вызовам или работе с регистрами, а к управлению состояниями устройства и переходами между ними.
В рамках одного проекта необходимо уметь:
● однозначно определять, в каком состоянии сейчас находится устройство;
● понимать, почему произошёл переход в это состояние (кнопка, зарядка, таймер, пробуждение);
● гарантировать, что каждое событие обрабатывается ровно один раз и в правильном контексте;
● обеспечить одинаковую логику поведения:
● при холодном старте,
● при пробуждении из сна,
● при внешних событиях (зарядка, кнопка, питание).
При этом важно разделить ответственность:
● железо (PMIC, GPIO, wake-up источники) лишь предоставляет сырые сигналы и статусы;
● логика управления питанием принимает решения;
● прикладной код реагирует на события, но не определяет правила переходов.
Основные требования к системе управления питанием
Из этого формируются ключевые требования:
Явная модель состояний
Состояния (Sleep, Active, Charging, Shutdown и т.д.) должны быть определены явно, а не выведены из набора флагов.
Событийная модель
Все внешние воздействия (кнопка, зарядка, таймеры, состояние PMIC) должны преобразовываться в события, а не обрабатываться напрямую в if/else.
Неблокирующая логика
Подсистема управления питанием не должна блокировать выполнение других задач и опрос входов. Все периодические действия — через короткие итерации и таймеры.
Единая точка принятия решений
Переходы между состояниями должны происходить в одном месте, а не быть размазанными по всему коду.
Независимость от конкретного железа
PMIC, кнопка, источники пробуждения — это адаптеры. Замена MP2722 на другой контроллер не должна менять логику FSM.
Предсказуемость и расширяемость
Добавление нового сценария (например, «очень долгое удержание кнопки») не должно ломать существующее поведение.
Реализация
Диаграмма состояний PowerManagement представлена ниже.

На диаграмме представлены следующие состояния:
● PM_INIT - с него начинается работа PowerManagement. В этом состоянии инициируется PowerManagement, проверяется состояние кнопки, статус зарядного устройства (подключено или нет) и флаг просыпания устройства из DeepSleep. Сразу выключает устройство, если было подано питание на устройство, но условий для дальнейшего включения не соблюдено (просыпание из DeepSleep, удержание кнопки включения или зарядка).
● PM_OFF_CHARGER - состояние, в которое переходит устройство, если оно было выключено, но подключили зарядное устройство. Может быть полезно для PMIC, которые не могут физически отключить питание потребителя, пока подключено зарядное устройство. Также полезно для случаев, когда на выключенном устройстве на зарядке нужно отобразить статус, что оно заряжается (например, на экране). В этом состоянии выполняется отдельный цикл off_charger_loop, который может быть использован для опроса PMIC из этого состояния
● PM_SETUP - инициализация остальной части устройства и внешней периферии микроконтроллера. Например, был инициирован модем логгера, а сам микроконтроллер уходит в DeepSleep до следующего пробуждения по таймеру или по прерыванию модема.
● PM_DEV_IDLE и PM_DEV_ACTIVE - циклические состояние работающего устройства. Как правило, вызывают общий рабочий pm_loop, который периодически опрашивает состояние PMIC, замеряет напряжение батареи, и в случае необходимости шлет события и принимает меры по питанию в некоторых случаях (перегрев батареи или сильный разряд). В состоянии PM_DEV_IDLE PowerManagement ведет счет времени неактивности устройства (как правило, сколько времени с устройством не взаимодействуют), и запускает выбранное действие по истечении времени неактивности (ничего не делать, выключиться или уснуть). Пользователь может запросить перевод в активный режим и выход из него. В активном режиме учет времени неактивности не ведется. Может пригодиться для длительных процессов, таких как обновление ПО.
● PM_SLEEP_PREPARE, PM_SHUTDOWN_PREPARE и PM_REBOOT_PREPARE - подготовительные состояния перед сном, выключением или перезагрузкой. Нужны, чтобы показать на экране, что произойдет, а также выполняется своё действие. Например, перед режимом сна нужно перевести периферию в энергосберегающий режим, а перед выключением нужно отправить PMIC команду отложенного выключения питания. В колбэке для PM_SHUTDOWN_PREPARE в конце отправьте устройство в DeepSleep.
● PM_SLEEP и PM_SHUTDOWN - состояния-заглушки, до них выполнение не доходит.
При этом, PowerManagement принимает колбэки для "датчиков" состояний (состояние кнопки, состояние зарядки, состояние пробуждения), для обработки состояния и для циклов обработки циклических состояний.
Для рассылки событий PowerManagement используется системный event_loop. События могут быть вызваны как из самого PowerManagement, так и из циклов.
Демонстрация работы
В данном демо-проекте использовались контроллер питания MP2722 и дисплейный модуль Guition JC4827W543C, модуль дорабатывал:
отключил контроллер питания от пауэрбанка IP5306 (у него кривая схема управления питания и с контроллера никак нельзя с ним взаимодействовать, версия не-I2C)
вывел отвод для подачи питания с моего контроллера питания
пробросил кнопку на IO16
Заключение
Библиотека PowerManagement оформлена как ESP-IDF компонент и заточена для использования на микроконтроллерах серии ESP32x.
Библиотека не привязана к конкретному контроллеру питания, так что она применима и к другим реализациям подсистемы питания и потребителей.
С помощью данной библиотеки покрываются большинство сценариев управления устройством с точки зрения питания и пользовательского опыта. По крайней мере, те сценарии, с которыми работал я лично. Если есть какие-то замечания или другие сценарии, то опишите их в комментариях.
Ссылки
Библиотека (gitflic): https://gitflic.ru/project/dmitrij99925/powermanagement-espidf
Библиотека (github): https://github.com/dmitrij9992905/PowerManagement-espidf/tree/master
Демо-проект: https://gitflic.ru/project/dmitrij99925/primer-proekta-dlya-upravleniya-kontrollerom-pitaniya-mp2722
Глоссарий
PMIC - Power Management Integrated Circuit, микросхема для общим управлением питанием устройства, либо его частями. может поддерживать управление зарядкой аккумулятора.