Configurazione RCC su STM32: HSE, PLL e Flash Latency dai registri
Ogni progetto STM32 che opera al di sopra della frequenza HSI predefinita deve configurare il clock tree RCC. Sbagliare la sequenza — impostare i divisori del PLL prima che l'oscillatore sia pronto, commutare il clock di sistema prima di aver configurato i wait-state della flash, o dimenticare di abilitare il PLL — provoca un hard fault silenzioso o un chip che resta a 16 MHz invece di 168. Questo articolo descrive la sequenza a livello di registri per la famiglia STM32F4, con note sulle differenze per STM32G4, L4 e U5.
Perché configurare l'RCC dai registri
CubeMX e il template HAL SystemClock_Config() vanno bene per il prototipo. Ma in produzione spesso serve:
- Riconfigurare i clock dopo un wakeup da STOP o STANDBY senza i 50 ms di warm-up HAL.
- Passare a una sorgente HSE in bypass (iniezione di clock esterno durante il collaudo scheda).
- Regolare i divisori PLL a runtime in base alla temperatura o al budget di potenza.
- Capire perché una sequenza generata da CubeMX a volte genera hard fault su una scheda custom.
Conoscere la sequenza dei registri permette di leggere lo stato RCC e diagnosticare i guasti in minuti anziché per tentativi.
Clock tree RCC: mappa di alto livello
Tutte le famiglie STM32 condividono lo stesso albero concettuale. Al reset il chip parte da HSI (High-Speed Internal, tipicamente 8 MHz su F4/L4, 16 MHz su G4/U5). Da lì si può:
- HSE — cristallo esterno o clock (4–26 MHz tipico).
- PLL — alimentato da HSE o HSI, con divisore d'ingresso (M), moltiplicatore VCO (N) e divisori d'uscita (P, Q, R).
- Commutazione SYSCLK — seleziona HSI, HSE o PLL come clock di sistema.
- Prescaler AHB/APB — dividono SYSCLK per i bus AHB (HCLK), APB1 (max 42 MHz su F4) e APB2 (max 84 MHz su F4).
Sequenza di avvio corretta (a livello di registri)
La sequenza va seguita in quest'ordine esatto. Qualsiasi deviazione rischia un hard fault quando la CPU cerca di eseguire la prossima istruzione a una frequenza che la flash non può sostenere.
Passo 1: Abilitare HSE e attendere il ready
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY)) { /* attendi */ }
L'oscillatore HSE impiega qualche centinaio di microsecondi a stabilizzarsi, a seconda della capacità di carico del cristallo. Il bit ready viene impostato dall'hardware. Non procedere finché non è attivo. Se la scheda non ha un cristallo esterno, HSE non diventerà mai ready e dovrai usare HSI come sorgente PLL.
Passo 2: Configurare i flash wait-state
Questo va fatto prima di aumentare la frequenza del clock di sistema. La memoria flash ha una velocità massima di accesso. Su STM32F401/411 a 3.3 V:
- 0 wait-state: ≤ 30 MHz
- 1 wait-state: 30–60 MHz
- 2 wait-state: 60–90 MHz
- 3 wait-state: 90–120 MHz
- 4 wait-state: 120–150 MHz
- 5 wait-state: > 150 MHz (massimo 168 MHz)
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
Il prefetch buffer, la cache istruzioni e la cache dati sono indipendenti dalla latenza ma vanno abilitati dopo aver impostato i wait-state. Su STM32G4 e STM32U5, l'ART accelerator sostituisce la cache legacy — stesso principio: configura prima la latenza, poi abilita gli acceleratori.
Passo 3: Configurare i divisori PLL
Su un STM32F401 con HSE da 8 MHz, target 84 MHz SYSCLK:
// PLLM = 8, PLLN = 336, PLLP = 4 → SYSCLK = 8 / 8 * 336 / 4 = 84 MHz
RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLM_Pos) // M = 8
| (336 << RCC_PLLCFGR_PLLN_Pos) // N = 336
| (0 << RCC_PLLCFGR_PLLP_Pos) // P = 4 (0b00 mappa a P=4)
| RCC_PLLCFGR_PLLSRC_HSE; // Sorgente = HSE
Il registro PLLCFGR può essere modificato solo quando il PLL è disabilitato. Scriverci mentre il PLL è attivo non ha effetto sulla maggior parte delle famiglie STM32.
Passo 4: Abilitare il PLL e attendere il lock
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY)) { /* attendi */ }
Il tempo di lock del PLL è tipicamente 50–200 µs a seconda della frequenza d'ingresso e del range VCO. Esegui il polling del bit ready — non usare un ritardo fisso.
Passo 5: Configurare i prescaler AHB/APB
Imposta i prescaler prima di commutare SYSCLK in modo che le frequenze dei bus siano definite al momento della commutazione.
// HPRE = 1 (nessuna divisione), PPRE1 = 2 (APB1 = HCLK/2), PPRE2 = 1 (APB2 = HCLK)
RCC->CFGR = RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_PPRE2_DIV1;
Passo 6: Commutare il clock di sistema sul PLL
RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW_Msk) | RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL) { /* attendi */ }
Controlla i bit di stato (SWS), non solo i bit di selezione (SW). L'hardware impiega alcuni cicli per migrare la sorgente del clock. Leggere SWS conferma che la commutazione è completata con successo.
Esempio pratico: STM32F411 da HSI a 100 MHz
Alcune schede non hanno un cristallo HSE (es. WeAct STM32F411CEU6 "Black Pill"). Bisogna usare il PLL alimentato da HSI (16 MHz su F411):
void SystemClock_HSI_100MHz(void) {
// 1. Flash: 3 wait-state per 100 MHz
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN
| FLASH_ACR_LATENCY_3WS;
// 2. PLL: HSI16 / 8 * 100 / 2 = 100 MHz
// Su STM32F411 PLLP=0b00 mappa a P=2
RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLM_Pos)
| (100 << RCC_PLLCFGR_PLLN_Pos)
| (0 << RCC_PLLCFGR_PLLP_Pos)
| RCC_PLLCFGR_PLLSRC_HSI;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
// 3. Prescaler AHB/APB
RCC->CFGR = RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE1_DIV2 | RCC_CFGR_PPRE2_DIV1;
// 4. Commutazione
RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW_Msk) | RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL);
}
Importante: La codifica di PLLP cambia tra famiglie. Su F4, PLLP=0b00 → P=4 (minimo), mentre su F411 la stessa codifica dà P=2. Verifica sempre la mappa dei divisori PLLP nel reference manual del tuo target. Un valore PLLP sbagliato produce la frequenza errata ma il PLL si aggancia lo stesso — il bug si nasconde finché non misuri l'uscita.
Flash latency per famiglia: cosa cambia
Le tabelle di latenza flash differiscono tra famiglie STM32. Ecco le sezioni chiave dei reference manual:
- STM32F401/411: RM0368 / RM0383 — 0 WS fino a 30 MHz, +1 WS ogni 30 MHz, max 5 WS a 168 MHz.
- STM32F4 alta densità (F429, F439): RM0090 — stesso passo da 30 MHz, max 5 WS.
- STM32G4: RM0440 — 0 WS fino a 32 MHz, +1 WS ogni 32 MHz, max 4 WS a 170 MHz. Ha anche CCM-SRAM per esecuzione a 0 WS.
- STM32L4/L4+: RM0351 — 0 WS fino a 16 MHz a 1.8 V, fino a 4 WS a 120 MHz. Il voltage scaling (VOS) modifica la frequenza massima per ogni livello di WS.
- STM32U5: — Flash controller TrustZone-aware con 0 WS fino a 16 MHz e fino a 8 WS a 160 MHz a seconda del range VOS.
- STM32H7: RM0433 — latenza separata per ITCM/DTCM rispetto all'accesso AXI flash. I bus TCM gestiscono i fetch con latenza minore, ma il banco flash necessita comunque di wait-state.
Modalità di guasto comuni
Hard fault immediatamente dopo la commutazione
Quasi sempre causato da wait-state flash insufficienti. La CPU esegue un fetch a 168 MHz, la flash consegna dati a 30 MHz, e il bus riceve dati errati — tipicamente un HardFault alla prima istruzione dopo il SWS check. Soluzione: aumentare LATENCY prima di commutare.
Il PLL non si aggancia mai
Verifica che la frequenza VCO del PLL sia nel range valido (tipicamente 100–432 MHz per F4, 64–344 MHz per G4). Se N × HSE / M è fuori dal range VCO, il PLL non attiverà mai RDY. Leggi RCC->PLLCFGR e calcola la frequenza VCO manualmente.
Periferica APB1 alla frequenza sbagliata
I timer alimentati da APB1 (TIM2–TIM7 su F4) contano al doppio della frequenza del bus APB1 quando il prescaler APB1 non è 1. Se PPRE1 = 2, il clock del timer è 2 × APB1. Un errore comune con CubeMX: l'utente imposta il prescaler del TIM assumendo APB1 = 42 MHz, ma PPRE1 = 4 dimezza il bus a 21 MHz e il timer conta comunque a 42 MHz. Calcola sempre: APB1_timer_clock = HCLK / ppres1 * (ppres1 == 1 ? 1 : 2).
Checklist pratica
- ☐ HSE ready bit verificato prima di programmare PLLCFGR.
- ☐ PLL disabilitato prima di scrivere PLLCFGR (sulle famiglie che lo richiedono).
- ☐ Latenza flash impostata al numero corretto di WS per la frequenza target prima della commutazione.
- ☐ Frequenza VCO del PLL nel range valido (N·f_in / M).
- ☐ SWS verificato dopo la commutazione — non solo SW scritto.
- ☐ Prescaler APB1/APB2 rispettano la frequenza massima di bus (42 MHz APB1, 84 MHz APB2 su F4).
- ☐ Calibrazione HSI (
RCC->CSRoRCC->ICSCR) verificata se si usa HSI come sorgente PLL. - ☐ Overdrive (se applicabile, su L4+/U5/H7) abilitato quando si supera la frequenza del range di tensione standard.
- ☐ Mappatura del divisore PLLP confermata dal reference manual (P=2 vs P=4).
Come lo affronterei su un progetto cliente
Su un progetto firmware in produzione, non incorporo mai un singolo SystemClock_Config() hardcodato. Scrivo invece una struttura di configurazione che trasporta frequenza target, WS flash e divisori PLL come macro compile-time:
typedef struct {
uint32_t pll_m;
uint32_t pll_n;
uint32_t pll_p;
uint32_t pll_q;
uint8_t flash_latency;
uint8_t hpre, ppre1, ppre2;
} rcc_config_t;
static const rcc_config_t RCC_CFG_84MHZ = {
.pll_m = 8, .pll_n = 336, .pll_p = 4, .pll_q = 7,
.flash_latency = FLASH_ACR_LATENCY_3WS,
.hpre = RCC_CFGR_HPRE_DIV1,
.ppre1 = RCC_CFGR_PPRE1_DIV2,
.ppre2 = RCC_CFGR_PPRE2_DIV1,
};
int rcc_apply(const rcc_config_t *cfg); // restituisce 0 in caso di successo
Questa struttura vive in un modulo rcc.c dedicato con i suoi test unitari (verificati contro la tabella del reference manual). Quando il cliente cambia il cristallo o modifica la frequenza target, modifica un header, ricompila e verifica con un analizzatore logico su MCO — niente CubeMX, niente errori di copia-incolla.
Fonti e approfondimenti
- STM32F401 Reference Manual (RM0368) — Capitolo 6: Reset and Clock Control (RCC).
- STM32F411 Reference Manual (RM0383) — Mappa registri RCC ed esempi di configurazione PLL.
- STM32G4 Reference Manual (RM0440) — Sezione 7: RCC con HSI16 e tabelle PLL.
- STM32L4 Reference Manual (RM0351) — Sezione 3: RCC con voltage scaling e tabelle WS flash.
- ARM Cortex-M4 Generic User Guide — descrizione dei registri NVIC e system control block.
- ST Application Note AN5027 — Uso dei clock HSI16 per migliorare l'EMC sulle serie STM32G0.
- ST Application Note AN2867 — Guida al progetto dell'oscillatore (layout cristallo HSE e tempi di avvio).

Commenti
Hai commenti? Scrivimi un'email.