Перейти к содержанию

ADR-012: Brownout detector отключён в wifiInit() как stand-only workaround

Дата: 2026-05-27 Статус: Accepted (workaround, требует пересмотра перед production-релизом на отдельных PSU) Связано: ADR-002 WiFi-стабильность


Контекст

При сборке f1d28c8 на стенде устройство стало падать в бесконечный boot-loop с симптомом:

I (5554) wifi_manager: wifiInit started
Brownout detector was triggered
rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)

Через 2–3 итерации проходило само и стартовало с TX-power = 15 dBm (значение, ранее заданное оператором через Web UI и сохранённое в NVS).

Корневая причина: USB-питание ноутбука, через которое запитан стендовый прототип, не тянет пиковый ток радиомодуля ESP32 в момент первого WiFi.mode(WIFI_AP_STA). Просадка rail ниже 2.43В → RTC_CNTL_BROWN_OUT_REG срабатывает → SW_CPU_RESET → новый boot → повтор.

WiFi.setTxPower(), которым мы ограничиваем мощность, применяется после WiFi.mode() — не успевает помочь самому первому пику. Эталонный production-PSU (5В/2A, low-ESR кондёрами) должен тянуть этот пик без проблем, но текущий стенд тянет не всегда.

После прохождения старта железо работает стабильно — детектор слишком чувствителен к коротким спайкам в стенд-конфигурации.

Решение

В src/wifi_manager.cpp::wifiInit(), до WiFi.mode(), добавлен:

WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
ESP_LOGW(TAG, "brownout detector DISABLED (stand workaround)");

Это полностью отключает brownout detector чипа на всё время работы. Запись в лог уровня WARN, чтобы в любой диагностике видеть, что защита не активна.

Что мы понимаем про последствия

  • Реального brownout-сценария защита больше не ловит. Если рейл действительно просядет (не короткий пик, а длительная просадка от плохого PSU / разряженного аккумулятора), CPU начнёт работать в неопределённой зоне; flash-write может оставить мусор.
  • Стенд тянет ток — после прохождения boot устройство работает, без crashes и stack canary. Просто пиковый старт борцовый.
  • Workaround временный — для production-PSU не нужен и должен быть пересмотрен. Не оставляем как «по умолчанию для всех».

Условия отката

Удалить запись в RTC_CNTL_BROWN_OUT_REG (или обернуть в #ifdef AWDC_STAND_BUILD) когда выполнено хотя бы одно из:

  1. Production-PSU прошёл стенд-тест без boot-loop на 50 циклах power-cycle подряд на холодном устройстве — детектор можно вернуть, реальной просадки не происходит.
  2. Найдено и устранено корневое причина пика (например, добавлены bulk-кондёры на 3.3В рейле прямо у ESP32 — стандартный фикс для маржинального питания).
  3. Принято решение поставлять с собственным заведомо мощным PSU и явно положить ответственность за питание на нашу сторону.

В любом из этих случаев — снести WRITE_PERI_REG строку, перепрошить стендовое устройство, проверить 50 циклов холодного boot, далее выкатить как fix-релиз.

Альтернативы, которые отвергнуты

  • WiFi.setTxPower() до WiFi.mode() — невозможно, setTxPower требует уже инициализированный WiFi (esp_wifi_init). Любая попытка call'а до mode() — silently игнорируется.
  • Понижение TX-power через прямой esp_wifi_set_max_tx_power() после esp_wifi_init но до esp_wifi_start — теоретически возможно, но требует переписать всю WiFi-инициализацию мимо Arduino-обёртки. Высокая цена, риск регрессий по STA-стабильности.
  • WIFI_PS_MIN_MODEM / WIFI_PS_MAX_MODEM (power-save) — снижают средний ток, но не пиковый при первом включении радио. Боевой пик отдельно.
  • Программная задержка между mode() и реальной работой WiFi — не помогает, пик уже произошёл к моменту, когда задержка началась.
  • Аппаратный мост / external watchdog — выходит за рамки прошивки.

Что должно случиться, если кто-то будет дебажить странности

Если устройство ведёт себя странно (несанкционированные resets, flash corruption), проверка №1 — питание. Прицепить осциллограф к 3.3В рейлу прямо у ESP32, посмотреть просадку при mode(). Если ниже ~2.7В дольше 50мкс — питание маржинальное, brownout мог бы помочь; без него — отсюда могут плыть баги. Это и есть момент возврата детектора.

Связанное