Embedded & IoT Bug Report

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.

Daniel Tran Daniel Tran @fun_dev · October 21, 2025 · 9 min read · 8 Views
Banner: ESP32 rebooting with brownout detector triggered when Wi-Fi starts

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:

  1. 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.
  2. 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 bod counter:
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 bod counter 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.defaults keeps CONFIG_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

Add a label

#esp32 #arduino #esp-idf #power-supply #wifi
Written by
Daniel Tran @fun_dev

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.

Advertisement

Comments

0

No comments yet. Be the first to comment!

Sign In to post a comment.