STM32 · PVD · Brownout · Power Management · Embedded

PVD (Programmable Voltage Detector) STM32:
Sostituire i Supervisor IC Esterni con il Brownout Detection Integrato

2026-06-27 · Davide Carrese

Ogni sistema embedded in produzione ha bisogno di un rilevamento affidabile del brownout. Un MCU alimentato a 3.3 V che subisce una discesa lenta fino a 2.7 V inizierà a produrre bit-flip nella SRAM, livelli GPIO erratici e scritture flash corrotte ben prima che la soglia interna di POR (Power-On Reset) intervenga a circa 1.7 V. La soluzione standard è un supervisor IC esterno — TPS3823, MAX809, MCP1316 — che aggiunge costo, area PCB e una potenziale criticità di approvvigionamento.

Tutti gli STM32 basati su Cortex-M3 o superiore includono un Programmable Voltage Detector (PVD) che fa esattamente questo, senza componenti esterni. Il PVD confronta continuamente VDD con una soglia programmabile e genera un interrupt o un reset quando l'alimentazione scende sotto (o sale sopra) il livello selezionato. Sul G4, il PVD risiede nel periferico PWR, controllato tramite PWR_CR2 e monitorato tramite PWR_SR2. Sulle serie U5 e H5 il blocco è ancora più flessibile, con modalità window e opzioni di isteresi aggiuntive.

Questo articolo copre la configurazione a livello di registri del PVD degli STM32: selezione della soglia, modalità interrupt versus event, casi d'uso tipici per brownout e l'unico trabocchetto che becca tutti al primo utilizzo.

Architettura del PVD

Il PVD è un comparatore analogico con un riferimento di tensione interno. Un ingresso è la VDD scalata; l'altro è un riferimento programmabile derivato dal bandgap interno. Lo schema a blocchi è sorprendentemente semplice:

VDD ─┬─► Partitore di Tensione ─► [[Comparatore PVD]] ─► Flag PVDO (PWR_SR2 bit 11)
         │                                  │
   Pin esterno                     Soglia programmabile
   (opzionale)                     tramite PLS[2:0] in PWR_CR2

Quando VDD scende sotto la soglia selezionata, l'uscita del comparatore va alta e il flag PVDO viene impostato. Questo flag può attivare un interrupt sulla linea EXTI 16 (ingresso PVD), un evento di wake-up da modalità low-power, o — sulle serie più recenti — un reset hardware diretto tramite il bit PVDR.

Livelli di soglia disponibili (riferimento STM32G4)

Il campo PLS[2:0] in PWR_CR2 seleziona uno tra otto livelli di soglia di salita. La soglia di discesa è circa 100 mV più bassa grazie all'isteresi incorporata:

PLS[2:0]Soglia salita (V)Tipica discesa (V)Designazione
0002.001.90LEV0
0012.102.00LEV1
0102.302.19LEV2
0112.502.39LEV3
1002.652.54LEV4
1012.802.69LEV5
1102.952.84LEV6
1113.052.94LEV7

Per un sistema a 3.3 V, LEV7 (3.05 V salita / 2.94 V discesa) è la scelta più vicina. Questo dà circa 300 mV di margine sopra la soglia POR di ~1.7 V, lasciando spazio sufficiente per uno spegnimento ordinato.

Configurazione a Registri: Modalità Interrupt PVD

La modalità interrupt è il caso d'uso più comune: il PVD genera un interrupt sulla linea EXTI 16, e la ISR esegue un salvataggio di emergenza (scrittura NVRAM, commit EEPROM, parcheggio attuatori) prima che VDD scenda abbastanza da corrompere qualcosa.

Configurazione passo-passo (STM32G4, livello registri)

// 1. Abilita il clock PWR (PWR è sempre nel dominio APB1)
RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN;

// 2. Configura la soglia a LEV7 (3.05 V salita) e abilita PVD
PWR->CR2 &= ~PWR_CR2_PLS_Msk;            // Pulisci bit soglia
PWR->CR2 |= PWR_CR2_PLS_LEV7;            // Seleziona LEV7
PWR->CR2 |= PWR_CR2_PVDE;                // Abilita PVD

// 3. Collega l'uscita PVD alla linea EXTI 16
EXTI->IMR1  |= EXTI_IMR1_IM16;           // Abilita interrupt su linea 16
EXTI->RTSR1 |= EXTI_RTSR1_RT16;          // Fronte di salita: VDD supera soglia
EXTI->FTSR1 |= EXTI_FTSR1_FT16;          // Fronte di discesa: VDD scende sotto soglia

// 4. Abilita NVIC per PVD
NVIC_SetPriority(PVD_PVM_IRQn, 3);
NVIC_EnableIRQ(PVD_PVM_IRQn);

Implementazione ISR

void PVD_PVM_IRQHandler(void)
{
    // Pulisci il flag pending di EXTI
    EXTI->PR1 |= EXTI_PR1_PIF16;

    // Leggi il flag PVDO: 1 = VDD sotto soglia, 0 = VDD sopra soglia
    if (PWR->SR2 & PWR_SR2_PVDO)
    {
        // VDD sceso sotto soglia — azioni di emergenza
        disable_interrupts();
        salva_registri_critici_in_backup_sram();
        commit_pagine_eeprom_pendenti();
        parcheggia_attuatori();
        // Opzionale: entra in STOP per ridurre consumo durante brownout
    }
    else
    {
        // VDD risalito sopra soglia — alimentazione ripristinata
        ripristina_funzionamento_normale();
    }
}

Il flag PVDO va letto dentro la ISR perché la linea EXTI scatta su entrambi i fronti. Un errore comune è controllare solo un fronte — se il codice assume che l'interrupt significhi sempre "brownout", interpreterà un evento di ripristino come un altro brownout e chiamerà parcheggia_attuatori() quando dovrebbe riprendere il funzionamento normale.

Modalità Event PVD: Wake-up da Stati Low-Power

Il PVD può anche generare un evento di wake-up dalle modalità Stop o Standby senza abilitare l'interrupt. Questo è utile per dispositivi a batteria che devono spegnersi ordinatamente quando la tensione scende sotto il range operativo sicuro:

// Configura PVD (stessa impostazione soglia)
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;         // Abilita

// Collega a EXTI linea 16 come evento (non interrupt)
EXTI->EMR1 |= EXTI_EMR1_EM16;     // Event mask, non interrupt mask
EXTI->FTSR1 |= EXTI_FTSR1_FT16;   // Solo fronte di discesa

// Entra in Stop mode
PWR->CR1 |= PWR_CR1_LPMS_STOP0;   // Stop 0 mode
__WFI();                           // Si risveglia quando VDD scende sotto soglia

// Dopo il wake: leggi PVDO, gestisci la sequenza di spegnimento

In modalità event la CPU si risveglia direttamente senza eseguire una ISR — il wake è trattato come un'uscita da Stop simile a un reset. Controlla PVDO nelle prime righe di main() per determinare la sorgente di wake.

Modalità Reset Diretto PVD (STM32U5 / H5 / G0)

Sulle famiglie STM32 più recenti, il PVD può attivare un reset hardware diretto quando VDD scende sotto la soglia, eliminando completamente il percorso software di risposta. Questa è la soluzione più vicina a un supervisor IC a BOM zero:

// Esempio STM32U5: bit PVDR 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;         // Abilita PVD
PWR->CR1 |= PWR_CR1_PVDR;         // Modalità reset PVD (disponibile su U5, H5, G0)

Il componente si resetta pulitamente quando VDD scende sotto la soglia. Nessuna ISR, nessuna risposta firmware, nessun IC esterno. Il registro sorgente reset (RCC_RSR / RSR) mostrerà un reset PVD, che puoi registrare per analisi post-mortem.

Esempio Pratico: Salvataggio Stato di Emergenza su STM32G474

Ho recentemente usato il PVD su un controllore motori basato su G474 per salvare la posizione del rotore e lo stato PWM durante un brownout. Il sistema opera a 3.3 V; sotto 2.9 V i gate driver diventano inaffidabili, ma l'MCU continua a funzionare fino a circa 1.7 V. Questo ci dà una finestra di ~1.2 V — circa 15 ms a una velocità di scarica moderata — per salvare lo stato critico.

Configurazione

#define SOGLIA_PVD_LEV6  PWR_CR2_PLS_LEV6  // 2.95 V salita, 2.84 V discesa

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

    PWR->CR2 &= ~PWR_CR2_PLS_Msk;
    PWR->CR2 |= SOGLIA_PVD_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: salva angolo rotore in 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;

        // Disabilita immediatamente le uscite PWM
        HRTIM1->CR2 &= ~HRTIM_CR2_MEN;
        GPIO_WriteLow(GPIOB, PIN_FRENO_HRTIM); // Attiva freno

        __WFI();  // Attendi POR o reset esterno
    }
    else
    {
        // Alimentazione ripristinata
        if (BACKUP_SRAM->magic == 0xA5A5A5A5)
            ripristina_da_backup();
    }
}

Il tempo di scarica dipende dal banco di condensatori di decoupling. Un 10 µF + 100 nF su VDD fornisce circa 10 ms dalla soglia LEV6 (2.84 V) fino a 1.7 V POR con un carico di 50 mA. Se serve più tempo, aggiungi capacità bulk — 100 µF danno ~100 ms, abbastanza per scrivere multiple pagine flash.

Checklist Pratica

Come Lo Affronterei su un Progetto Cliente

Su un progetto di produzione userei la modalità interrupt PVD con la seguente architettura:

Fonti

📬 Commenti / discussione

Preferisci email: comments@carrese.eu — includi l'URL dell'articolo così posso seguire. Per correzioni o domande approfondite, rispondo tipicamente entro 48 ore.