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 |
|---|---|---|---|
| 000 | 2.00 | 1.90 | LEV0 |
| 001 | 2.10 | 2.00 | LEV1 |
| 010 | 2.30 | 2.19 | LEV2 |
| 011 | 2.50 | 2.39 | LEV3 |
| 100 | 2.65 | 2.54 | LEV4 |
| 101 | 2.80 | 2.69 | LEV5 |
| 110 | 2.95 | 2.84 | LEV6 |
| 111 | 3.05 | 2.94 | LEV7 |
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
- Scelta della soglia: scegli la soglia più alta che non scatta durante il ripple normale dell'alimentazione. Per una 3.3 V con tolleranza ±5%, LEV7 (3.05 V) funziona. Per LDO a 3.0 V, scendi a LEV6 (2.95 V) o LEV5 (2.80 V).
- L'isteresi è tua amica: il PVD ha ~100 mV di isteresi incorporata. Non aggiungere mai debounce software a meno che l'alimentazione non sia estremamente rumorosa — il filtro analogico è migliore di qualsiasi workaround digitale.
- Polarità fronti EXTI: abilita entrambi i fronti di salita e discesa, poi leggi PVDO dentro la ISR. Un solo fronte perderà metà delle transizioni.
- Abilitazione clock PWR: i registri del periferico PWR sono nel dominio APB1. Leggi
PWR->CR2dopo aver abilitato il clock per inserire il wait state richiesto prima della prima scrittura. - Backup SRAM vs registri backup RTC: la backup SRAM (4 KB sul G4) sopravvive a Standby e VBAT. I 20 registri backup RTC sono limitati — usali solo per magic/checksum, non per lo stato completo.
- Gestione reset post-brownout: controlla
RCC->RSR(oRCC->CSRsulle serie vecchie) all'avvio per distinguere un reset causato da PVD da un'accensione normale. Registralo per copertura nei test di produzione.
Come Lo Affronterei su un Progetto Cliente
Su un progetto di produzione userei la modalità interrupt PVD con la seguente architettura:
- Imposta PLS a LEV6 (2.95 V salita) su una 3.3 V — dà ~250 mV di margine dopo la tolleranza del regolatore.
- Abilita entrambi i fronti EXTI; usa PVDO per distinguere brownout da ripristino.
- Salva lo stato critico in backup SRAM con CRC32 e magic word. All'ingresso brownout, disabilita tutte le uscite PWM/comunicazioni, attiva una linea di freno hardware, poi entra in Stop mode.
- Al ripristino (evento alimentazione tornata), controlla la magic word in backup SRAM. Se valida, ripristina lo stato e riprendi. Se non valida, fai una re-inizializzazione completa — i contenuti SRAM sono degradati durante il brownout.
- Registra la causa del reset PVD in un contatore dedicato della backup SRAM in modo che il firmware possa riportare "3 eventi brownout dall'ultimo power cycle" sulla UART di debug o tramite tool diagnostico.
- _Attenzione al trabocchetto del dominio VBAT_: il PVD monitora VDD, non VBAT. Se il tuo RTC funziona a batteria mentre VDD è spento, il PVD non rileverà un calo di VBAT. Usa un PVM (Peripheral Voltage Monitor) separato sul G4/U5 se devi rilevare batteria scarica su una seconda linea.
Fonti
- Manuale di Riferimento STM32G4 (RM0440) — capitolo PWR, descrizione registri PVD (PWR_CR2, PWR_SR2, EXTI linea 16)
- Manuale di Riferimento STM32U5 (RM0456) — modalità reset PVDR e funzionalità PVD avanzate
- Nota Applicativa ST AN3109 — PVD STM32
- Nota Applicativa ST AN3309 — Uso del PVD su STM32F2/F4 (legacy ma ancora valida per modalità interrupt/event)
- Header CMSIS STM32G4 (stm32g431xx.h) — definizioni bit registri PWR
- Manuale Tecnico Arm Cortex-M3/M4/M7 — gestione eventi EXTI per wake da low-power
📬 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.