STM32 ยท PVD ยท Brownout ยท Power Management ยท Embedded

STM32 PVD (Programmable Voltage Detector):
Replacing External Supervisor ICs with On-Chip Brownout Detection

2026-06-27 ยท Davide Carrese

Every production embedded system needs reliable brownout detection. An MCU running at 3.3 V that experiences a slow ramp-down to 2.7 V will start producing bit-flips in SRAM, erratic GPIO levels, and corrupted flash writes long before the internal POR (Power-On Reset) threshold kicks in at roughly 1.7 V. The standard mitigation is an external supervisor IC โ€” TPS3823, MAX809, MCP1316 โ€” adding cost, PCB area, and a potential procurement headache.

Every STM32 built on a Cortex-M3 or newer core includes a Programmable Voltage Detector (PVD) that does exactly this, without external components. The PVD continuously compares VDD against a programmable threshold and generates either an interrupt or a reset when the supply drops below (or rises above) the selected level. On the G4, the PVD lives in the PWR peripheral, controlled via PWR_CR2 and monitored via PWR_SR2. On the U5 and H5 series, the block is even more flexible, with additional window modes and hysteresis options.

This article covers the register-level configuration of the STM32 PVD: threshold selection, interrupt versus event mode, typical brownout use cases, and the one pitfall that catches everyone on first use.

PVD Architecture Overview

The PVD is an analog comparator with an internal voltage reference. One input is the scaled VDD supply; the other is a programmable reference derived from the internal bandgap. The block diagram is remarkably simple:

VDD โ”€โ”ฌโ”€โ–บ Voltage Divider โ”€โ–บ [[PVD Comparator]] โ”€โ–บ PVDO flag (PWR_SR2 bit 11)
         โ”‚                              โ”‚
   External pin                Programmable threshold
   (optional)                  via PLS[2:0] in PWR_CR2

When VDD drops below the selected threshold, the comparator output goes high and the PVDO flag is set. This flag can trigger an interrupt on EXTI line 16 (PVD input), a wake-up event from low-power modes, or โ€” on newer series โ€” a direct hardware reset via the PVDR bit.

Available threshold levels (STM32G4 reference)

The PLS[2:0] field in PWR_CR2 selects one of eight rising thresholds. The falling threshold is approximately 100 mV lower due to built-in hysteresis:

PLS[2:0]Rising threshold (V)Typical falling (V)Designation
0002.001.90LEV0
0012.102.00LEV1
0102.302.19LEV2
0112.502.39LEV3
1002.652.54LEV4
1012.802.69LEV5
1102.952.84LEV6
1113.052.94LEV7

For a 3.3 V system, LEV7 (3.05 V rising / 2.94 V falling) is the closest match. This gives you roughly 300 mV of margin above the POR threshold of ~1.7 V, leaving comfortable headroom for an orderly shutdown.

Register-Level Configuration: PVD Interrupt Mode

Interrupt mode is the most common use case: the PVD generates an interrupt on EXTI line 16, and your ISR performs an emergency save (NVRAM write, EEPROM commit, actuator park) before VDD drops far enough to corrupt anything.

Step-by-step configuration (STM32G4, register level)

// 1. Enable the PVD clock (PWR is always in APB1 clock domain)
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;

// 2. Configure threshold to LEV7 (3.05 V rising) and enable PVD
PWR->CR2 &= ~PWR_CR2_PLS_Msk;            // Clear threshold bits
PWR->CR2 |= PWR_CR2_PLS_LEV7;            // Select LEV7
PWR->CR2 |= PWR_CR2_PVDE;                // Enable PVD

// 3. Connect PVD output to EXTI line 16
EXTI->IMR1  |= EXTI_IMR1_IM16;           // Unmask interrupt on line 16
EXTI->RTSR1 |= EXTI_RTSR1_RT16;          // Rising edge: VDD crosses threshold upward
EXTI->FTSR1 |= EXTI_FTSR1_FT16;          // Falling edge: VDD crosses threshold downward

// 4. Enable NVIC interrupt for PVD
NVIC_SetPriority(PVD_PVM_IRQn, 3);
NVIC_EnableIRQ(PVD_PVM_IRQn);

ISR implementation

void PVD_PVM_IRQHandler(void)
{
    // Clear the EXTI pending flag
    EXTI->PR1 |= EXTI_PR1_PIF16;

    // Read the PVDO flag: 1 = VDD below threshold, 0 = VDD above threshold
    if (PWR->SR2 & PWR_SR2_PVDO)
    {
        // VDD dropped below threshold โ€” emergency actions
        disable_interrupts();
        save_critical_registers_to_backup_sram();
        commit_pending_eeprom_pages();
        park_actuators();
        // Optionally enter STOP mode to reduce consumption during brownout
    }
    else
    {
        // VDD rose above threshold โ€” power restored
        restore_normal_operation();
    }
}

The PVDO flag must be read inside the ISR because the EXTI line fires on both edges. A common mistake is to only check one edge โ€” if your code assumes the interrupt always means "brownout", it will misinterpret a power-restored event as another brownout and call park_actuators() when it should be resuming normal operation.

PVD Event Mode: Wake from Low-Power States

The PVD can also generate a wake-up event from Stop or Standby mode without enabling the interrupt. This is useful for battery-powered devices that must shut down gracefully when the battery voltage drops below the safe operating range:

// Configure PVD (same threshold setup)
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;
PWR->CR2 &= ~PWR_CR2_PLS_Msk;
PWR->CR2 |= PWR_CR2_PLS_LEV7;     // 3.05 V
PWR->CR2 |= PWR_CR2_PVDE;         // Enable

// Connect to EXTI line 16 as event (not interrupt)
EXTI->EMR1 |= EXTI_EMR1_EM16;     // Event mask, not interrupt mask
EXTI->FTSR1 |= EXTI_FTSR1_FT16;   // Falling edge only

// Enter Stop mode
PWR->CR1 |= PWR_CR1_LPMS_STOP0;   // Stop 0 mode
__WFI();                           // Will wake when VDD drops below threshold

// After wake: read PVDO, handle shutdown sequence

In event mode the CPU wakes directly without executing an ISR โ€” the wake-up is treated as a reset-like exit from Stop. Check PVDO in the first few lines of main() to determine the wake source.

PVD Direct Reset Mode (STM32U5 / H5 / G0)

On newer STM32 families, the PVD can trigger a direct hardware reset when VDD drops below the threshold, completely eliminating the software response path. This is the closest you get to a zero-BOM supervisor IC:

// STM32U5 example: PVDR bit in PWR_CR1
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;
PWR->CR2 &= ~PWR_CR2_PLS_Msk;
PWR->CR2 |= PWR_CR2_PLS_LEV5;     // 2.80 V
PWR->CR2 |= PWR_CR2_PVDE;         // Enable PVD
PWR->CR1 |= PWR_CR1_PVDR;         // PVD reset mode (available on U5, H5, G0)

The part resets cleanly when VDD falls below the threshold. No ISR, no firmware response, no external IC. The reset source register (RCC_RSR / RSR) will show a PVD reset, which you can log for post-mortem analysis.

Practical Example: Emergency State Save on STM32G474

I recently used the PVD on a G474-based motor controller to save rotor position and PWM state during a brownout. The system runs at 3.3 V; below 2.9 V the gate drivers become unreliable, but the MCU continues operating until roughly 1.7 V. That gives us a ~1.2 V window โ€” approximately 15 ms at a moderate discharge rate โ€” to commit critical state.

Setup

#define PVD_THRESHOLD_LEV6  PWR_CR2_PLS_LEV6  // 2.95 V rising, 2.84 V falling

void pvd_init(void)
{
    RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;
    (void)PWR->CR2;  // Delay after clock enable

    PWR->CR2 &= ~PWR_CR2_PLS_Msk;
    PWR->CR2 |= PVD_THRESHOLD_LEV6;
    PWR->CR2 |= PWR_CR2_PVDE;

    EXTI->IMR1  |= EXTI_IMR1_IM16;
    EXTI->RTSR1 |= EXTI_RTSR1_RT16;
    EXTI->FTSR1 |= EXTI_FTSR1_FT16;

    NVIC_SetPriority(PVD_PVM_IRQn, 2);
    NVIC_EnableIRQ(PVD_PVM_IRQn);
}

void PVD_PVM_IRQHandler(void)
{
    EXTI->PR1 |= EXTI_PR1_PIF16;

    if (PWR->SR2 & PWR_SR2_PVDO)
    {
        // Brownout: save rotor angle to backup SRAM
        BACKUP_SRAM->rotor_theta = hrtim_get_theta();
        BACKUP_SRAM->pwm_duty_a = HRTIM1->CH1CVR;
        BACKUP_SRAM->pwm_duty_b = HRTIM1->CH2CVR;
        BACKUP_SRAM->pwm_duty_c = HRTIM1->CH3CVR;
        BACKUP_SRAM->checksum = crc32(&BACKUP_SRAM, sizeof(BACKUP_SRAM) - 4);
        BACKUP_SRAM->magic = 0xA5A5A5A5;

        // Disable PWM outputs immediately
        HRTIM1->CR2 &= ~HRTIM_CR2_MEN;
        GPIO_WriteLow(GPIOB, HRTIM_FAULT_PIN); // Assert brake

        __WFI();  // Wait for POR or external reset
    }
    else
    {
        // Power restored โ€” can resume if the MCU hasn't fully discharged
        if (BACKUP_SRAM->magic == 0xA5A5A5A5)
            restore_from_backup();
    }
}

The discharge time depends on the decoupling capacitor bank. A 10 ยตF + 100 nF on VDD provides roughly 10 ms from the LEV6 threshold (2.84 V) down to 1.7 V POR at a 50 mA load. If you need more time, add bulk capacitance โ€” 100 ยตF gives ~100 ms, enough to write multiple flash pages.

Practical Checklist

How I Would Approach This on a Client Project

On a production project I would use PVD interrupt mode with the following architecture:

Sources

๐Ÿ“ฌ Comments / discussion

Prefer email: comments@carrese.eu โ€” include the article URL so I can follow up. For corrections or deeper questions, I typically reply within 48 hours.