STM32U5 STOP2 con FreeRTOS: trattare il tempo di wake-up come un budget firmware
Nei progetti STM32U5, il low-power non è solo una checkbox CubeMX chiamata STOP2. È un contratto di sistema tra clock, stati GPIO, periferiche, priorità interrupt, temporizzazione RTOS e requisito di prodotto che dice quanto rapidamente il dispositivo deve tornare utile dopo un evento. Se questo contratto non è esplicito, il firmware può sembrare corretto in laboratorio e poi perdere interrupt, campionare in ritardo o consumare in campo diverse volte la corrente prevista.
Perché STOP2 è interessante, e perché si usa male facilmente
STOP2 è spesso il primo modo low-power serio considerato per prodotti STM32U5 a batteria, perché conserva abbastanza stato da riprendere il firmware senza un cold boot, fermando però il core e molti clock. Per un nodo sensore, un prodotto di misura, uno strumento portatile o un dispositivo industriale che si sveglia da GPIO, RTC, LPUART o da una deadline periodica, è spesso il compromesso giusto. L'applicazione conserva il contesto RAM e si sveglia molto più velocemente che da standby, con una corrente molto più bassa rispetto a un semplice loop in sleep.
L'errore che vedo nel codice prodotto è trattare STOP2 come una chiamata isolata a HAL_PWREx_EnterSTOP2Mode(). Quella funzione è solo l'istruzione finale. Il comportamento del prodotto dipende da cosa è successo prima e da cosa succede subito dopo il wake-up: quali pin sono rimasti flottanti, se SysTick è stato sospeso, se FreeRTOS si aspettava tickless idle, quale clock tree viene ripristinato, se DMA o una periferica erano ancora attivi, e se l'interrupt di wake-up può pre-emptare il percorso giusto.
Partire dal budget di wake-up
La domanda utile non è “l'MCU può entrare in STOP2?”, ma “quanti millisecondi abbiamo tra l'evento esterno e la prima azione valida del prodotto?”. Questo è il budget di wake-up. Un reed switch può concedere decine di millisecondi. Un preambolo radio, un evento di commutazione motore o una pressione breve su un pulsante potrebbero no. Un impulso di metering può richiedere cattura deterministica anche se il task applicativo lo processerà dopo.
Scrivi il budget con quattro numeri: latenza della sorgente di wake-up, tempo di ripristino clock, tempo di reinizializzazione periferiche e tempo di scheduling applicativo. Poi misura quei numeri con un GPIO e un analizzatore logico. Senza misura, il firmware low-power tende a diventare una pila di assunzioni del tipo “dovrebbe andare”.
Il tickless idle FreeRTOS non è tutto il progetto
Il tickless idle di FreeRTOS può sopprimere i tick periodici quando il sistema è idle, e il suo hook low-power è un buon punto per entrare in uno stato più profondo. Ma il kernel non conosce lo stato elettrico della scheda, il tempo di assestamento del front-end analogico, i vincoli esatti di wake-up UART o se la prossima acquisizione può tollerare un ripristino clock più lento. Queste sono decisioni di scheda e di prodotto.
Su STM32, uno schema comune è lasciare che FreeRTOS decida che il sistema può dormire per un certo idle time previsto, poi applicare una policy specifica: entrare in STOP2 solo se l'idle time è abbastanza lungo da ripagare overhead di ingresso e uscita; non entrarci mentre un driver possiede una transazione periferica; configurare le sorgenti di wake-up prima dell'ingresso; sospendere l'HAL tick se si usa SysTick; ripristinare il clock di sistema prima di tornare ai task normali.
Una forma minima per entrare in STOP2
Le chiamate HAL e i registri esatti variano in base alla versione Cube e alla scheda, ma la struttura seguente è la parte importante. Il percorso low-power è esplicito, protetto e misurabile.
#include "stm32u5xx_hal.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdbool.h>
static volatile bool lp_forbidden;
void board_low_power_lock(void) { lp_forbidden = true; }
void board_low_power_unlock(void) { lp_forbidden = false; }
static bool product_can_enter_stop2(TickType_t expected_idle_ticks)
{
const TickType_t min_stop2_ticks = pdMS_TO_TICKS(25);
if (lp_forbidden) {
return false;
}
if (expected_idle_ticks < min_stop2_ticks) {
return false;
}
/* Aggiungere qui le guardie di prodotto: DMA pendente,
conversione sensore, scrittura flash, radio, debug, UART TX, ecc. */
return true;
}
void vPortSuppressTicksAndSleep(TickType_t expected_idle_ticks)
{
if (!product_can_enter_stop2(expected_idle_ticks)) {
__WFI();
return;
}
taskENTER_CRITICAL();
/* Controllo race: un interrupt può aver reso pronto un task. */
if (eTaskConfirmSleepModeStatus() == eAbortSleep) {
taskEXIT_CRITICAL();
return;
}
HAL_SuspendTick();
HAL_GPIO_WritePin(LP_TRACE_GPIO_Port, LP_TRACE_Pin, GPIO_PIN_SET);
board_prepare_for_stop2();
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
SystemClock_Config();
board_restore_after_stop2();
HAL_GPIO_WritePin(LP_TRACE_GPIO_Port, LP_TRACE_Pin, GPIO_PIN_RESET);
HAL_ResumeTick();
taskEXIT_CRITICAL();
}
L'esempio contiene volutamente un lock lp_forbidden. Nei prodotti reali, i bug low-power compaiono spesso perché un nuovo driver avvia DMA, un'operazione flash o una conversione sensore e nessuno informa la policy energetica. Un lock semplice non è architettura elegante da solo, ma rende visibile il contratto. Progetti più maturi possono sostituirlo con vincoli reference-counted o con un power manager centrale.
Anche il leakage GPIO è un problema firmware
La documentazione ST sul low-power riporta spesso l'attenzione alla configurazione GPIO, per un buon motivo. Un ingresso flottante, un pull abilitato che combatte una resistenza esterna, o un pin periferico lasciato in una alternate function non voluta possono dominare il budget di corrente. I team firmware a volte lo trattano come un problema solo hardware, ma lo stato finale dei pin prima di STOP2 è controllato dal firmware.
Per ogni revisione scheda, mantieni una tabella pin low-power: nome pin, circuito esterno, modo in STOP2, pull, ruolo di wake-up e livello atteso. Poi implementa quella tabella in una funzione board unica invece di distribuire cambi pin nei driver. Durante la misura corrente, confronta la scheda con quella tabella prima di cambiare impostazioni CubeMX a caso.
Esempio pratico: sensore a batteria con wake-up RTC ed EXTI
Consideriamo un sensore industriale a batteria che campiona ogni minuto, ma deve svegliarsi subito quando cambia un ingresso tamper. Il requisito dice: svegliarsi e timestampare il fronte tamper entro 5 ms, campionare e pubblicare il dato normale entro 100 ms dal tick minuto, e mantenere la corrente media abbastanza bassa per una batteria pluriennale.
Userei RTC wake-up per l'acquisizione periodica ed EXTI per il pin tamper. L'ISR EXTI deve fare il minimo: catturare un riferimento temporale compatibile con la scelta di low-power, impostare un event flag e svegliare il task interessato. Il task poi riattiva l'alimentazione sensore se serve, attende il tempo di assestamento analogico documentato, legge il sensore e logga se il wake è stato RTC, EXTI o entrambi. L'ingresso in STOP2 è permesso solo quando non sono attivi acquisizione, scrittura log in flash o comunicazione. Un GPIO di trace incapsula ingresso e uscita da STOP2 durante la validazione, così il budget viene misurato e non intuito.
Checklist pratica
- Definire il budget di wake-up in millisecondi prima di scegliere il modo sleep.
- Elencare ogni sorgente di wake e verificare che funzioni da STOP2 sullo specifico STM32U5 scelto.
- Ripristinare subito il clock di sistema dopo STOP2 prima di usare periferiche sensibili al timing.
- Usare una guardia o un power manager per bloccare STOP2 durante DMA, flash, radio o operazioni sensore.
- Creare una tabella GPIO low-power a livello board e testare la corrente rispetto a quella tabella.
- Misurare ingresso, wake-up e prima azione utile con un GPIO di trace.
- Testare senza debug collegato; le funzioni di debug possono cambiare comportamento low-power e corrente.
- Loggare ragioni di wake e abort inattesi durante i trial sul campo.
Come lo affronterei su un progetto cliente
Separerei prima tre artefatti: un budget di wake-up di prodotto, una tabella pin low-power di scheda e una policy firmware degli stati energetici. Poi implementerei il percorso STOP2 più piccolo con una sola sorgente di wake-up e un GPIO di trace, misurerei corrente e latenza, e solo dopo aggiungerei seconda e terza sorgente. Non partirei tarando tutte le opzioni CubeMX insieme.
In review del codice cercherei i blocker nascosti: driver che avviano DMA senza un vincolo energetico, logging UART che tiene vivi i clock, assunzioni su SysTick dopo STOP2, priorità ISR incompatibili con le regole FreeRTOS e reinizializzazioni periferiche sepolte nei task applicativi. L'obiettivo non è il numero di corrente più basso in demo. L'obiettivo è un prodotto che si sveglia sull'evento giusto, al momento giusto, con consumo spiegabile e log utili per diagnosticare problemi di campo.