Why Does My ESP32 Keep Brownout-Resetting?
Last updated 20 July 2026 · 11 min read
Direct Answer
An ESP32 brownout reset happens when its supply rail dips below the hardware brownout detector's threshold, most often from a Wi-Fi transmit current spike hitting inadequate decoupling, an undersized cable or regulator, or a marginal battery. Diagnose it by reading the reset-cause register (esp_reset_reason()), watching for the rst:0xc / "Brownout detector was triggered" message in the boot log, and confirming the rail sag on a scope, rather than by disabling the detector, which removes the warning but not the underlying power problem.
Detailed Explanation
A brownout reset happens when the ESP32's supply rail drops below a voltage its digital logic can operate on reliably, and the chip's hardware brownout detector (BOD) forces a reset before that undervoltage condition causes unpredictable behaviour, a corrupted flash write, or a hung radio state machine. It's a protective mechanism, not a firmware bug in itself, though the sagging voltage that trips it is very often something in your hardware design or system load.
For a broader introduction to the platform, see the ESP32 topic hub. This page covers one specific failure mode in depth: the supply rail sagging far enough to trip the hardware brownout detector.
The clearest fingerprint of a brownout reset is in the boot log. Right after the reset, idf.py monitor (or any serial terminal attached to the UART0 console) prints something like:
rst:0xc (RTC_SW_CPU_RST),boot:0x13 (SPI_FAST_FLASH_BOOT)
or, on many ESP32 revisions, a direct message:
Brownout detector was triggered
followed immediately by the normal ROM bootloader banner and a full restart. If you see rst:0xc alongside the word "brownout," or a standalone "Brownout detector was triggered" line with no crash backtrace, no assert, and no exception dump before it, that's the signature to look for. Compare that with a panic reset, which prints a register dump, a backtrace, and usually a Guru Meditation Error banner, or a watchdog reset (TG0WDT_SYS_RST, RTCWDT_RTC_RST), which names the specific watchdog that expired. A brownout reset generally has none of that: firmware was running, the voltage sagged, and the chip reset with no warning from the code that was executing.
How the brownout detector works
The ESP32 brownout detector is a hardware analog comparator that watches the internal supply rail against a fixed internal reference. When the monitored rail drops below the configured threshold, the BOD asserts a reset independently of the CPU or any software running on it. That independence is deliberate: a CPU running on a collapsing supply cannot reliably detect its own fault or shut down cleanly.
The threshold is configurable through the RTC_CNTL_BROWN_OUT_REG register documented in Espressif's ESP32 Technical Reference Manual, and exposed at the application level in ESP-IDF as the CONFIG_ESP32_BROWNOUT_DET_LVL Kconfig option (idf.py menuconfig → Component config → ESP32-specific → Brownout Detector). It offers several discrete threshold steps; the default is set to trip with margin above the point where the digital core stops behaving predictably.
Distinguishing brownout from other ESP32 reset causes
The esp_reset_reason() API in esp_system.h returns an enum naming the actual cause of the last reset, and checking it in app_main() on every boot is the single most useful addition if resets are showing up in the field:
#include "esp_system.h"
void app_main(void) {
esp_reset_reason_t reason = esp_reset_reason();
switch (reason) {
case ESP_RST_BROWNOUT:
ESP_LOGW("boot", "Last reset: BROWNOUT");
break;
case ESP_RST_TASK_WDT:
case ESP_RST_INT_WDT:
case ESP_RST_WDT:
ESP_LOGW("boot", "Last reset: WATCHDOG");
break;
case ESP_RST_PANIC:
ESP_LOGW("boot", "Last reset: PANIC (exception/fault)");
break;
case ESP_RST_DEEPSLEEP:
ESP_LOGI("boot", "Woke from deep sleep");
break;
default:
ESP_LOGI("boot", "Reset reason: %d", reason);
}
}
This separates a brownout from resets that look similar in a field report of "it just restarts sometimes":
ESP_RST_PANIC: an unhandled exception (bad pointer, stack overflow, failed assert). The boot log shows a full register dump and backtrace before the reset. This is a firmware bug, not a power problem.ESP_RST_TASK_WDT/ESP_RST_INT_WDT: the FreeRTOS task watchdog or interrupt watchdog fired because a task blocked the scheduler or an ISR ran too long. See what a watchdog timer does and how to use one for how these timeouts are configured and serviced.ESP_RST_WDT/ESP_RST_RTC_WDT: the RTC watchdog fired, often because boot or deep-sleep exit took longer than its configured timeout, or the chip hung before FreeRTOS even started.ESP_RST_DEEPSLEEP: an intentional wake from deep sleep, not a fault at all, but easy to misclassify as an unexpected reset if you aren't checking the reason.ESP_RST_BROWNOUT: the supply rail sagged below the BOD threshold. This is the failure mode this page covers.
STM32 parts expose a comparable reset-cause register (RCC_CSR/RCC_RSR) for the same purpose; see why an STM32 keeps resetting unexpectedly if you're working across both platforms and want to compare the diagnostic approach. If your ESP32 logs show ESP_RST_BROWNOUT correlating with a specific event, Wi-Fi associating, a motor or relay switching nearby, or a battery running low, you have your answer and can move straight to root cause instead of chasing a firmware bug that isn't there.
Practical Examples
A typical failure pattern
The most common real-world pattern: a board runs fine on a bench supply or a well-behaved USB port, then brownout-resets intermittently once deployed, often right as Wi-Fi associates or starts transmitting. It's a common enough signature that it's worth walking through why.
Espressif's datasheet lists ESP32 Wi-Fi TX current reaching a peak in the hundreds of milliamps (device- and configuration-dependent; check the specific datasheet table for your module and confirm with your own measurement) for short transmit bursts. That current arrives as a fast transient, not a steady load, so a multimeter reading the average current shows a comfortable-looking number while missing the peak entirely. If local decoupling capacitance near the 3V3 pin is thin, or the trace, cable, or regulator feeding that rail has meaningful resistance or inductance, the rail sags for the duration of the transient. If it sags past the BOD threshold, even for a few microseconds, the reset fires.
Capturing it on a scope
The way to confirm this, rather than guess, is to put a scope probe directly on the 3V3 pin at the board, with the ground clip as short as possible, and trigger on the reset event or on a GPIO toggled just before a Wi-Fi operation. Look at:
- The steady-state rail voltage under no load, to confirm the supply is correctly regulated to begin with.
- The rail during the suspected event (Wi-Fi connect, a TX burst, a relay or motor turning on nearby), with the scope AC-coupled or in high-resolution mode so a small dip doesn't disappear into the DC offset.
- Any ringing or overshoot on the falling edge of the dip, which often points at insufficient bulk capacitance rather than insufficient decoupling.
A dip that bottoms out near or below the configured BOD threshold, timed to coincide with the reset, is close to conclusive.
Reading the boot log methodically
Combine the scope capture with the serial log from idf.py monitor. A brownout-caused reset shows the "Brownout detector was triggered" message (or the rst:0xc code and reset reason) immediately followed by the ROM bootloader banner, with nothing from the application layer in between, because the application never got the chance to log anything before the hardware reset fired. See how to debug embedded firmware for the broader toolkit (logic analyser, debug probe, trace output) when the boot log alone doesn't settle it. If you see any application log line, an assert, or a backtrace immediately before the reset, it isn't a brownout: something in firmware ran and then faulted.
Design Considerations
Decoupling and bulk capacitance
Local decoupling absorbs the fast edges of a current transient before they propagate back to the regulator and sag the rail. For a board carrying a Wi-Fi or BLE radio module, a small ceramic capacitor placed as close as possible to the module's supply pin (for the fastest edges) plus a larger bulk capacitor nearby (commonly in the tens of microfarads, though the right value depends on the regulator's output impedance and the actual transient current profile) is the standard approach. See decoupling capacitor placement for the general placement rules this follows. Treat the capacitor values in a module's reference design as a minimum, not a target to trim down.
Cable, connector, and regulator sizing
A USB cable, connector, or regulator with too much series resistance for the peak current the radio draws produces exactly this symptom: fine on the bench with a short, low-resistance cable, resetting once deployed on a longer or thinner cable, through a hub, or off a supply sized only for the average current rather than the peak. Check:
- The regulator's datasheet for maximum output current and, just as importantly, its transient response and output impedance at the frequencies the load transient contains.
- Cable gauge and length. A long, thin USB cable can drop several hundred millivolts under a few hundred milliamps of transient load, often enough on its own to cross the BOD threshold.
- Whether the regulator has enough headroom between its input and the 3.3V output to keep regulating correctly when the input itself sags under load, particularly relevant on USB-powered boards where the 5V rail is shared with other loads.
Adjusting RTC_CNTL_BROWN_OUT_REG / CONFIG_ESP32_BROWNOUT_DET_LVL
ESP-IDF exposes the brownout threshold as CONFIG_ESP32_BROWNOUT_DET_LVL in menuconfig, which writes the corresponding value into RTC_CNTL_BROWN_OUT_REG at startup. Lowering this threshold makes the detector trigger at a lower voltage, and in some cases genuinely makes an intermittent reset go away, because the rail sag no longer crosses the (now lower) trip point.
Treat this as a diagnostic tool, not a fix. Lowering the threshold doesn't improve the power supply; it makes the ESP32 tolerate a worse one, right up until the voltage sags far enough to cause the memory corruption or radio lockup the detector exists to prevent, at which point the failure is far harder to diagnose than a clean reset. Raising the threshold, by contrast, gives an earlier and more conservative warning of marginal power, useful during bring-up specifically to surface a marginal rail before the product ships. Use threshold adjustment to characterise the problem, not to hide it in production. For designs that need reset supervision independent of the ESP32's own on-chip BOD, an external voltage supervisor IC provides a second, physically separate check on the same rail.
Ground return and layout
A shared or high-impedance ground return path between the radio's transient current and the rest of the board can produce a similar-looking symptom even when the 3.3V rail itself measures fine: the transient current returning through a thin or shared ground trace creates a voltage difference across the board that shows up as noise on other signals, and in marginal cases can be read by the BOD's reference circuitry as rail movement. Keep the radio module's ground return short and, where possible, routed on its own low-impedance path back to the main ground plane rather than daisy-chained through other high-current returns.
Common Mistakes
- Disabling the brownout detector entirely to make the resets "stop."
CONFIG_ESP32_BROWNOUT_DET_LVLcan be set to disable the detector, and doing so does make the visible resets go away, because the mechanism reporting the problem is gone, not the problem itself. The ESP32 then runs on an undervoltage rail with no protection: flash writes can corrupt mid-write, the radio state machine can lock up in a way that looks like a firmware bug, and the actual field failure becomes far harder to diagnose than a clean, logged brownout reset ever was. Fix the power design instead of masking the symptom. - Assuming every unexplained reset is a brownout, or that none of them are, without checking
esp_reset_reason(). Watchdog timeouts, hard faults, and brownouts can all present as "the device restarted" to someone reading a field log without the reset-cause register. Read it on every boot and log it somewhere that survives the reset. - Measuring supply current with a multimeter and concluding the power design is fine. A multimeter reports an averaged reading and won't show a transient current spike lasting microseconds to low milliseconds. Use a scope on the rail, or a current analyser with sufficient bandwidth, whenever a radio, motor, or other high-transient load shares the supply.
- Copying a reference design's decoupling but skipping the specified bulk capacitance because it "looks like overkill." Module reference designs specify decoupling and bulk capacitance based on the module's measured transient current. Trimming it to save board space or cost is a common source of field brownout reports that don't reproduce on the bench with a short, low-resistance bench-supply lead.
A brownout that keeps reappearing after the obvious decoupling and cable fixes usually needs the hardware and firmware looked at together rather than firmware in isolation. Zeus Design's embedded team works across both sides of ESP32 products for exactly this kind of intermittent field failure.
Frequently Asked Questions
- Is it safe to disable the ESP32's brownout detector?
- Not as a fix for a live problem. Disabling CONFIG_ESP32_BROWNOUT_DET_LVL removes the hardware's ability to catch an undervoltage condition before it corrupts a flash write or locks up the radio state machine. The visible resets stop because the mechanism reporting the fault is gone, not because the fault is gone. The one legitimate use is temporary, controlled testing during bring-up, where you deliberately want to see what happens below the normal threshold with the board on the bench and nothing at stake. In production firmware, leave the detector enabled and fix the power design instead.
- Why does my ESP32 only reset when Wi-Fi connects or transmits, not all the time?
- Because Wi-Fi transmit current is a short, high-amplitude pulse rather than a steady load. Espressif's datasheet lists ESP32 Wi-Fi TX current peaking in the hundreds of milliamps for brief bursts (check the specific datasheet table for your module and variant, and confirm with your own measurement), so a marginal supply, cable, or decoupling network can hold up fine under the chip's average current draw and still sag past the BOD threshold for the few microseconds a transmit burst demands. Connecting to Wi-Fi, sending an ESP-NOW packet, or reconnecting after a drop are common trigger events precisely because each one involves a TX burst.
- Does lowering the brownout threshold fix the reset?
- Sometimes the visible reset goes away, but the underlying power problem does not. A lower threshold tolerates a lower rail voltage before resetting, which can mask a marginal supply right up until the rail sags far enough to actually cause the memory corruption or radio lockup the detector exists to prevent, a failure mode that is much harder to diagnose than a clean brownout reset. Use threshold adjustment to characterise how marginal the supply is during bring-up, not as the production fix.
References
Related Questions
Why Does My STM32 Keep Resetting Unexpectedly?
STM32 unexpected resets are caused by watchdog timeout, brown-out, hard fault, or power decoupling issues. Use the RCC reset flags to identify the root cause.
ESP32 Variants Compared: How Do You Choose the Right One?
Compare ESP32 variants: ESP32 classic, S3 (ML/USB), S2 (USB), C3 and C6 (RISC-V BLE+WiFi), and H2 (Thread/Zigbee). When to choose each.
How Do You Set Up Wi-Fi and Provision an ESP32 Device?
Covers ESP32 Wi-Fi station and AP mode in ESP-IDF, event-loop connection handling, SoftAP provisioning, and the ESP32 HTTP server for local API endpoints.
How Do You Manage Power and Use Deep Sleep on the ESP32?
ESP32 power modes: active, modem sleep, light sleep, deep sleep; RTC timer, GPIO, and ULP wake sources; measured currents and battery runtime estimation.
ESP-IDF vs Arduino for ESP32: Which Framework Should You Use?
ESP-IDF gives full FreeRTOS control and is production-grade; Arduino is faster to start. Covers the differences, limitations of each, and when to switch.
How Should You Place Decoupling Capacitors on a PCB?
Decoupling capacitors need to sit close to the power pin they protect with a short, low-inductance path to ground. Here's how placement affects performance.
Related Forum Discussions
ESP32 Matter device advertises fine over BLE but commissioning fails every time — stale QR code after a firmware rebuild?
Bringing up my first Matter product on an ESP32-C6 using esp-matter (built on ESP-IDF 5.2). It's a basic on/off light accessory for now, jus
ESP32 keeps dropping Wi-Fi after 20–30 minutes in deployed location — reconnect loop doesn't always recover
Having a frustrating one. Built an ESP32 environmental monitor (SHT40 temp/humidity, reports to an MQTT broker every 5 minutes). Works flawl
Is a double-sided PCB enough for a simple ESP32 sensor board, or should I go multi-layer?
Building a little battery-powered sensor board around an ESP32 module (the kind with the PCB antenna already built into the module, not desi