ESP32 reboots with Brownout detector was triggered as soon as Wi-Fi starts
Our greenhouse sensor boards boot-looped the moment WiFi.begin() ran, logging Brownout detector was triggered. The 3.3 V rail sagged under Wi-Fi transmit peaks from an undersized LDO and thin cable. How we measured it, fixed the power path and why disabling the detector is the wrong fix.
We build small sensor nodes for a commercial greenhouse: an ESP32 module on our own two-layer board, a soil probe, a temperature and humidity sensor, and Wi-Fi back to a gateway. The first batch of 40 boards from a new assembly run behaved perfectly on the bench when powered from a laptop. Installed in the greenhouse, 23 of them never came online. Plugged into a serial adapter on site, every failing board printed the same thing a fraction of a second after starting Wi-Fi, and then rebooted, forever:
[ 412][I][main.cpp:58] setup(): connecting to greenhouse-iot
Brownout detector was triggered
ets Jul 29 2019 12:21:46
rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
...
In ESP-IDF 5.x the brownout line is emitted under the BOD log tag; on some boards only part of it
appears because the supply collapses while the UART is still flushing.
Reproducing it
The failure was tied to the power source, not the firmware. In the lab we reproduced it every time with the greenhouse setup: the 5 V, 1 A wall adapters used on site and the 3 m cables run along the benches. With a laptop USB port and a 30 cm cable, the same boards connected fine. We added a reset reason check to the firmware so failures would be counted even without a serial cable attached:
#include <Arduino.h>
#include <Preferences.h>
#include "esp_system.h"
Preferences prefs;
void setup() {
Serial.begin(115200);
prefs.begin("diag", false);
if (esp_reset_reason() == ESP_RST_BROWNOUT) {
prefs.putUInt("bod", prefs.getUInt("bod", 0) + 1);
}
Serial.printf("brownout resets so far: %u\n", prefs.getUInt("bod", 0));
// ... sensors, then WiFi.begin()
}
We write the counter early, before Wi-Fi starts, while the supply is still quiet.
What we ruled out
- Firmware or Arduino core version. Flashing the previous firmware release, and a bare
WiFi.begin()sketch with nothing else, still reset on the greenhouse supply. - Faulty modules. We swapped the ESP32 module between a failing and a working board; the fault followed the board's power circuit and supply, not the module.
- A short or a sensor drawing current. Idle current with Wi-Fi off was 48 mA on both good and bad boards, and disconnecting the sensors changed nothing.
Root cause
An oscilloscope on the 3.3 V rail at the module's power pin showed the answer. During Wi-Fi start-up the rail dropped from 3.30 V to about 2.35 V in short dips of a few hundred microseconds, each lining up with a radio transmit burst. The default brownout threshold on the ESP32 is about 2.43 V, so the detector correctly reset the chip. The dips came from two parts of the power path: the regulator on this board revision was a small SOT-23 LDO rated for 200 mA, chosen to save cost, with only a 1 µF output capacitor; and the 3 m thin-gauge cable dropped enough voltage at peak current that the LDO's input fell close to its dropout limit. On the laptop with a short cable the input stayed high enough for the LDO to cope, barely.
What is happening under the hood
The ESP32's radio draws much more current while transmitting than the chip does otherwise. Espressif's datasheet lists about 240 mA for 802.11b transmission at +19.5 dBm, and the current arrives in bursts with fast edges. Start-up is the worst moment: the radio calibrates and the station scans and transmits at full power while the CPU is busy too.
A regulator cannot respond instantly to a step in load. Between the step and the regulator's response, local capacitance supplies the current. With too little capacitance, too little regulator headroom, or too much resistance upstream, the rail sags.
| Factor | Our board | Effect during a TX burst |
|---|---|---|
| Regulator rating | 200 mA LDO | Runs out of current at peaks |
| Output capacitance | 1 µF ceramic | Little local energy to cover the burst |
| Input supply | 5 V adapter via 3 m thin cable | Input voltage sags, LDO nears dropout |
| Brownout threshold | Default level 0, about 2.43 V | Detector resets the chip |
The brownout detector is a hardware comparator. ESP-IDF exposes it as CONFIG_ESP_BROWNOUT_DET
(enabled by default) with selectable thresholds from about 2.43 V to 2.80 V on the ESP32. It resets
the chip because running below the rated supply voltage can corrupt flash writes, RAM contents and
peripheral state.
Espressif's hardware design guidelines recommend a 3.3 V supply able to deliver at least 500 mA, at least 10 µF at the main power input, and 10 µF on the analog power rail together with smaller decoupling capacitors near the pins. Our board missed both the current rating and the bulk capacitance.
Environment
| Component | Version / value |
|---|---|
| Module | ESP32-WROOM-32E (ESP32 chip revision v3.0) |
| Framework | Arduino-ESP32 3.3.2 (based on ESP-IDF 5.5.1), PlatformIO |
| Regulator (rev B) | SOT-23 LDO, 3.3 V, 200 mA rated, 1 µF output capacitor |
| Supply | 5 V 1 A wall adapters, 3 m 28 AWG USB cables |
| Wi-Fi | 2.4 GHz station, default TX power |
| Brownout setting | Default (enabled, level 0) |
Minimal reproduction
Any sketch that starts Wi-Fi reproduces it on an undersized supply:
#include <WiFi.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin("lab-ssid", "lab-password"); // placeholder credentials
}
void loop() {
delay(1000);
Serial.println(WiFi.status() == WL_CONNECTED ? "connected" : "waiting");
}
Who is affected
Custom ESP32 boards with small LDOs or little bulk capacitance, dev boards powered through long or thin USB cables, battery-powered designs near the end of discharge, and breadboard builds where the 3.3 V pin is fed through jumper wires. The same applies to other ESP32 family chips, with their own threshold values. Reaching for a lower TX power or a disabled detector hides it on the bench and leaves the field units fragile.
The fix
We fixed the power path on a new board revision and reworked the installed boards by hand.
1. Regulator with real headroom. Rev C uses a 3.3 V LDO rated for 600 mA with low dropout at that current, in a footprint that also accepts a buck converter for a future battery variant.
2. Bulk and decoupling capacitance near the module.
Rev B Rev C
5V ─ LDO(200 mA) ─┬─ 3V3 ─ ESP32 5V ─┬─ 10 µF ─ LDO(600 mA) ─┬──────┬──────┬─ 3V3 ─ ESP32
1 µF ESD 22 µF 100 µF 0.1 µF (at pins)
The 100 µF low-ESR capacitor sits within a few millimetres of the module's 3V3 pin, the 0.1 µF directly at the pin, and the 10 µF at the board's power input as the guidelines suggest.
3. Supply and cabling. On site, the 3 m thin cables were replaced with 1 m cables with thicker power conductors, and for runs that had to be long we moved the adapter closer to the board instead.
For the 40 rev B boards already built, we replaced the LDO with a pin-compatible 600 mA part and hand-soldered a 100 µF capacitor across 3V3 and GND at the module. All 40 then connected on the greenhouse supply.
Alternatives and trade-offs
- Disable the brownout detector. This is the most common advice online and the wrong one. In
ESP-IDF it is
CONFIG_ESP_BROWNOUT_DET=n; in Arduino sketches people clear the detector register. The board then keeps running through voltage dips below its rated range, which shows up later as corrupted NVS or flash writes, random exceptions and Wi-Fi failures that are much harder to diagnose. We use it only for a few seconds on the bench to confirm a diagnosis, never in firmware that ships. - Lower Wi-Fi TX power.
WiFi.setTxPower(WIFI_POWER_8_5dBm)reduces the current peaks and made our rev B boards boot about two times out of three. It is a useful diagnostic and a reasonable setting for short-range links, but it shrinks radio range and still leaves no margin. - Raise the brownout threshold. A higher level only makes resets happen earlier. It is useful when you want a guaranteed margin above the minimum for flash operations, not as a fix.
How to test the fix
We test power, not just whether Wi-Fi connects:
- Oscilloscope on the 3V3 pin, trigger on falling edge at 3.1 V, AC coupling off, during 20 cold boots with Wi-Fi start. Pass if the minimum stays above 3.0 V.
- A stress sketch that reconnects Wi-Fi every 5 seconds and runs an HTTP upload in a loop for 12
hours on the actual site adapter and cable, then reads the
bodcounter:
void loop() {
WiFi.disconnect(true);
delay(500);
WiFi.begin("lab-ssid", "lab-password");
while (WiFi.status() != WL_CONNECTED) delay(50);
uploadSample(); // ~4 KB POST to the test gateway
delay(5000);
}
pio run -t upload -e stress && pio device monitor -b 115200 | tee stress-$(date +%F).log
Pass criterion: zero brownout resets in 12 hours on three boards.
Confirming in production
- The
bodcounter is reported in each node's hourly telemetry. Across the 40 reworked boards and 60 rev C boards, it has stayed at zero for the six weeks since installation. - The scope capture on a rev C board during Wi-Fi start-up shows a minimum of 3.18 V, against 2.35 V on rev B.
- Daily data completeness from the greenhouse went from 43 percent of expected readings in the first week to 99.7 percent; the remainder are gateway outages.
Regression guard
- Board bring-up for any new revision includes the scope test and the 12-hour stress run with the worst-case cable we ship.
- Firmware CI checks that
sdkconfig.defaultskeepsCONFIG_ESP_BROWNOUT_DET=y, so nobody disables it "temporarily" in a release build. - The design review checklist lists the regulator's current rating and the bulk capacitance at the module as explicit items.
Security implications
Brownouts are more than a reliability issue. Undervoltage is a known fault-injection technique for bypassing checks in microcontrollers, and the brownout detector is part of the defence. Keeping it enabled, together with secure boot and flash encryption where the product needs them, avoids weakening that protection. A boot loop also prevented over-the-air updates from completing, so the power fix was a precondition for receiving security patches.
Prevention checklist
- Size the 3.3 V supply for at least 500 mA, not for the average current.
- Put bulk and decoupling capacitance close to the module's power pins.
- Test with the real adapters and cable lengths used in the field.
- Log the reset reason and report brownout counts in telemetry.
- Keep the brownout detector enabled in every shipped build.
References
- Espressif: ESP32 Series Datasheet (RF power consumption) — https://documentation.espressif.com/esp32_datasheet_en.pdf
- Espressif: ESP32 Hardware Design Guidelines, schematic checklist — https://docs.espressif.com/projects/esp-hardware-design-guidelines/en/latest/esp32/schematic-checklist.html
- ESP-IDF v5.5.1: Fatal errors, brownout — https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32/api-guides/fatal-errors.html
- ESP-IDF v5.5.1: Miscellaneous system APIs,
esp_reset_reason()— https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32/api-reference/system/misc_system_api.html
Add a label
Embed this post
<iframe src="https://hibug.me/esp32-brownout-detector-reset-wifi" width="700" height="420" frameborder="0" loading="lazy"></iframe>
I have spent most of my career on the side of software that pages people at night: payment integrations, web platforms with real traffic, CI/CD pipelines, Kubernetes clusters and the databases underneath them. Somewhere along the way I started keeping a notebook of every bug that cost more than an hour — the exact error, what we ruled out, the real root cause and how we proved the fix. Hibug is where those notes live. Every post follows the same path: Investigate, Explain, Fix, Verify — with the environment spelled out, the fix tested, and the security angle checked.
Related Stories
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.