The puzzle
A controller shares its supply with a motor. Once in a while, when the motor starts, the stored settings come back corrupted, and on some power-ups a relay clicks on for a moment before the firmware has even started. Neither is a bug in any line of code. Both happen in the time when the supply is neither properly on nor properly off, or when the firmware is not yet in control. What does a chip do while its voltage sags, how does firmware find out afterwards, and how do you keep the hardware safe in the meantime?
STEP 1
Between on and off
Every chip is specified to work over a supply range. Below the minimum, gates switch more slowly and flip-flops may not capture what they should: a CPU can misread an instruction, a flash write can store the wrong bits. Nothing about this is gradual or polite. A power-on reset circuit holds the chip in reset until the supply has risen past a threshold (unit 5, lesson 1); a brown-out detector does the same job while running, forcing a reset when the supply falls below its threshold. As long as the brown-out threshold sits above the lowest voltage at which the logic is specified, the chip is either running correctly or held in reset, never in between.
The RP2040 shows what the settings look like. Its brown-out detector is enabled at reset with its threshold at 0.860 V (reset value 0x91 of the BOD register), selectable from 0.473 V to 1.118 V. Those values sit around the 1.10 V that the on-chip regulator gives the digital core by default, not around the 3.3 V I/O supply. And it records a brown-out in the same HAD_POR bit as a power-on reset.
A reset only stops misbehaviour. To prepare for a brown-out, firmware needs a warning while the supply is still good. The STM32F4’s programmable voltage detector (PVD) compares the supply against a threshold chosen with the PLS bits and raises an interrupt through EXTI line 16 when it crosses; the HAL calls a weak HAL_PWR_PVDCallback() that the application overrides. The PVD is stopped in Standby.
STEP 2
How long the lights stay on
When the input power disappears, the capacitance on the supply rail keeps the circuit running while it discharges. With a load current drawn from a capacitance , the voltage falls at volts per second, so the time between two thresholds is
That is for a load that draws a constant current, such as circuitry behind a linear regulator. Behind a switching regulator the load draws roughly constant power P, and the stored energy ½CV² gives
which is also why bulk capacitance on a higher-voltage rail upstream of the regulator stores more energy than the same capacitance downstream. That time, from the early warning to the brown-out reset, is all the firmware has to save its state.
↑ This step uses the figure at the top of the page.
Two consequences follow. The save must be short and predictable: programming a record into flash that is already erased, not erasing a sector (unit 3, lesson 4), which takes far longer. And firmware can buy time by cutting the load current as soon as the warning arrives: switch off the radio, the display and the motor driver, and the same capacitor lasts several times longer. Many designs avoid the problem altogether by making every save safe against power loss at any instant (lesson 5), so that no warning is needed.
STEP 3
Reading the reset cause
After any reset, the start-up code should read the reset-cause registers once, early, before anything overwrites them, and decide how to start.
After every reset, firmware reads the reset-cause registers once, early, and decides how to start. A watchdog reset means something went wrong: count it in memory that survives the reset, and after several in a row stop retrying the same thing and enter a safe mode. On the RP2040 a watchdog timeout and a planned watchdog_reboot() both set WATCHDOG_REASON; the SDK tells them apart by a marker in scratch register 4. On the STM32 the RCC flags stay set until cleared with RMVF, so clear them after reading.
On the RP2040, two registers together tell the story. WATCHDOG_REASON has TIMER and FORCE bits and reads zero after any hardware reset, so it is checked first; the SDK distinguishes a watchdog timeout after watchdog_enable() from a deliberate watchdog_reboot() by the marker watchdog_enable() leaves in scratch register 4. CHIP_RESET then says whether the last chip-level reset came from power-on or brown-out (HAD_POR, one bit for both), the RUN pin (HAD_RUN) or the debug port (HAD_PSM_RESTART). It is not a record of the latest reset: a reset through the Cortex-M AIRCR register, as a debugger or NVIC_SystemReset() might issue, does not update it, so it can still show the bits of an earlier power-on. HAD_POR = 1 therefore does not prove that the latest reset was a power-on, and an AIRCR reset cannot be identified from these registers; for planned resets use watchdog_reboot() or leave a marker in a scratch register first (unit 3, lesson 6).
On the STM32F4 the RCC keeps one flag per source. The pair BORRST (“POR/PDR or BOR reset”) and PORRST (“POR/PDR reset”) separates the two cases the RP2040 cannot: a brown-out sets only the first, a power-on both. The flags stay set until software clears them with the RMVF bit, so read all of them first, then clear them:
STEP 4
Safe states
For some time after power-up, during every reset and during a brown-out, the firmware is not in control of its pins. On most chips GPIO pins come out of reset as inputs, possibly with a weak pull; what an external load does during that time is decided by the circuit, not the code. The relay in the opening clicked because its driver transistor’s gate floated until the firmware configured the pin.
The fix is in hardware first: give every output that controls something with consequences a pull resistor to its safe state, the state that is acceptable when the controller knows nothing, such as a gate pull-down that keeps a heater or motor driver off (unit 7, lesson 5). Then in firmware: drive outputs to their safe values as the very first step of start-up, before anything slow; return them there when a fault is detected; and make the safe mode of lesson 3 a state in which outputs stay safe and the device only reports and waits.
STEP 5
Worked example: enough time to save?
A board draws 20 mA from a 3.3 V rail with 100 µF on it. Its (illustrative) early-warning detector trips at 3.0 V and the brown-out reset at 2.8 V. The warning window is
A 0.5 ms save fits; a 2 ms save does not. Two fixes: raise the capacitance to , plus a margin for capacitor tolerance and ageing; or switch loads off at the warning so that only 5 mA remains, which stretches the same 100 µF to ms.
MYTHS AND FACTS
Common misconceptions
A brown-out just resets the chip
Only if the detector is enabled and set above the lowest specified voltage. Otherwise the chip runs, unreliably, while the voltage passes through the range where nothing is guaranteed.
A brown-out reset is the same as a power-on
The RP2040 records both in one bit, but an STM32 tells them apart, and a brown-out says the supply is marginal: check it before starting heavy loads.
The reset-cause flags describe only the last reset
STM32 flags stay set until software clears them, so after several resets more than one may be set; and even a single reset sets several, because on the STM32F4 every internal reset also pulls the NRST pin and sets PINRSTF, which is why the pin is tested last.
Outputs are off until the firmware turns them on
Pins typically reset as inputs; the load is controlled by whatever the external circuit does with a floating or weakly pulled line.
Check yourself
Answer in your head, then open the card.
A rail has 470 µF and a 50 mA load. How long does it take to fall from 3.1 V to 2.9 V?
t = C·ΔV / I = 470 µF × 0.2 V / 50 mA = 94 µC / 50 mA = 1.88 ms.
An STM32 starts with BORRST set and PORRST clear. What happened, and what should the firmware do differently from a power-on?
A brown-out reset: the supply dipped below the brown-out threshold while running. SRAM and any interrupted write are suspect; log the event and check that the supply has recovered (for example that the battery voltage is adequate) before switching on heavy loads that could cause another dip.
The RP2040 headers give a default brown-out threshold of 0.860 V and a lowest core-regulator setting of 0.85 V. What should you check before calling vreg_set_voltage(VREG_VOLTAGE_0_85)?
That the brown-out threshold is below the new core voltage: the default 0.860 V is above 0.85 V. If the detector watches that supply, as its threshold range suggests, lower BOD.VSEL first. Also check in the datasheet which clock frequencies are allowed at the lower voltage.
Why must outputs be made safe in hardware and not only by the first lines of main()?
Before main() runs there is the reset itself, the boot ROM, the start-up code and possibly a brown-out, during which the pins are in their reset state and the firmware cannot drive them. Only the external circuit, together with each pad’s reset-default pull (a weak pull-down on every RP2040 GPIO; pulls on the SWD/JTAG pins of an STM32), defines the load’s state then.
Sources (4)
- Raspberry Pi Ltd, pico-sdk 1.5.1, rp2040/hardware_regs/include/hardware/regs/vreg_and_chip_reset.h — BOD reset value 0x91: EN = 1, VSEL = 1001 “0.860V (default)”, thresholds 0.473 V to 1.118 V; VREG VSEL default 1011 “1.10V”; CHIP_RESET: HAD_POR “Last reset was from the power-on reset or brown-out detection blocks”, HAD_RUN “from the RUN pin”, HAD_PSM_RESTART “from the debug port”
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_watchdog/watchdog.c and regs/watchdog.h — WATCHDOG_REASON: TIMER and FORCE bits, “Both bits are zero for the case of a hardware reset”; watchdog_enable() writes 0x6ab73121 to scratch[4], which watchdog_enable_caused_reboot() checks; watchdog_reboot() writes 0 or the boot magic to scratch[4] and sets TRIGGER when its delay is 0
- STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_rcc.h — RCC_FLAG_BORRST “POR/PDR or BOR reset”, RCC_FLAG_PINRST, RCC_FLAG_PORRST “POR/PDR reset”, RCC_FLAG_SFTRST, RCC_FLAG_IWDGRST, RCC_FLAG_WWDGRST, RCC_FLAG_LPWRRST; __HAL_RCC_GET_FLAG(); __HAL_RCC_CLEAR_RESET_FLAGS() “Set RMVF bit to clear the reset flags”
- STMicroelectronics, stm32f4xx-hal-driver, Src/stm32f4xx_hal_pwr.c — “The PVD is used to monitor the VDD power supply by comparing it to a threshold selected by the PVD Level (PLS[2:0] bits in the PWR_CR)”; the PVDO flag is “internally connected to the EXTI line16 and can generate an interrupt”; “The PVD is stopped in Standby mode”; HAL_PWR_PVDCallback() is a weak function for the application