Configurazione RCC dell'Orologio su STM32 a Livello di Registri:
HSI, HSE, PLL e Commutazione del Clock di Sistema su STM32F4
Hai passato la mattina a debuggarre perché il baud rate della UART è esattamente la metà di quello configurato? Nove volte su dieci, l'albero dell'orologio è il colpevole. Lo STM32F4 ha sei sorgenti di clock, tre PLL e una cascata di prescaler — sbaglia un rapporto e ogni periferica che dipende dal bus clock si rompe silenziosamente. Vediamo l'albero dell'orologio registro per registro.
L'RCC (Reset and Clock Control) è il blocco più importante su qualsiasi microcontrollore STM32. Genera il clock di sistema (SYSCLK) che guida la CPU, i bus AHB e APB, e tutti i clock delle periferiche che ne derivano. Configura l'RCC correttamente e tutto funziona; sbaglia e i timer girano a metà velocità, la USART produce caratteri errati, e l'ADC campiona alla frequenza sbagliata.
Questo articolo copre la configurazione a livello di registri su STM32F401 (ARM Cortex-M4), ma la stessa struttura RCC si applica a tutta la famiglia STM32F4. Gli indirizzi dei registri e le posizioni dei campi sono identici; solo le frequenze massime e i vincoli del PLL variano leggermente tra i dispositivi.
Panoramica dell'Albero dell'Orologio
Lo STM32F4 ha tre sorgenti di clock configurabili per SYSCLK:
- HSI (High-Speed Internal) — oscillatore RC da 16 MHz. Sempre disponibile dopo il reset. Meno preciso (±1%) ma non richiede componenti esterni.
- HSE (High-Speed External) — cristallo o oscillatore da 4–26 MHz. Richiede un cristallo esterno (o ingresso di clock) ma offre una precisione molto migliore (±50 ppm con cristallo).
- PLL (Phase-Locked Loop) — Moltiplica HSI o HSE per generare frequenze più alte (fino a 84 MHz su STM32F401, 168/180 MHz su F405/F407, 216 MHz su F7).
Inoltre, ci sono due oscillatori a bassa velocità per RTC e watchdog indipendente: LSI (32 kHz RC) e LSE (32.768 kHz a cristallo).
Il PLL è suddiviso in tre rami di uscita — PLLP (clock di sistema principale), PLLQ (48 MHz per USB OTG/SDIO) e PLLR (presente su alcuni F4 per I2S) — ma sull'F401 sono disponibili solo PLLP e PLLQ.
Se ti serve solo una prestazione moderata della CPU (≤ 16 MHz), HSI è sufficiente. Ma se spingi l'uscita del PLL oltre ~120 MHz, usa HSE come sorgente del PLL. La precisione dell'oscillatore HSI degrada con la temperatura e potrebbe spingere il PLL oltre il suo range di aggancio VCO a fattori di moltiplicazione elevati, producendo un clock instabile o con jitter.
Guida Passo-Passo ai Registri: 84 MHz da un HSE da 8 MHz
Configuriamo lo STM32F401 per funzionare al massimo SYSCLK di 84 MHz (o 168 MHz su parti dual-core e F405/7). Useremo un cristallo esterno da 8 MHz come sorgente HSE, lo moltiplicheremo attraverso il PLL, e routeremo l'uscita attraverso i prescaler appropriati.
La formula di uscita del PLL è:
PLL_output = (HSE / M) × N / P dove: M = divisore d'ingresso PLL (2..63) N = moltiplicatore PLL (192..432 su F401) P = divisore clock di sistema PLL (2, 4, 6, 8)
Per 84 MHz da 8 MHz, la configurazione comune è M = 8, N = 336, P = 4:
(8 MHz / 8) × 336 / 4 = 1 × 336 / 4 = 84 MHz
Passo 1: Abilita HSE e attendi il ready
Prima di toccare il PLL, dobbiamo abilitare e stabilizzare l'oscillatore HSE. Scrivere HSEON in RCC->CR avvia l'oscillatore; pollare HSERDY conferma che è stabile:
RCC->CR |= RCC_CR_HSEON; /* avvia oscillatore HSE */
while (!(RCC->CR & RCC_CR_HSERDY)) /* attendi ready */
; /* tipicamente ~1 ms */
Verifica sempre il flag di ready. Ho visto schede in cui un cristallo danneggiato o condensatori di carico errati impedivano a HSERDY di impostarsi — il codice si blocca silenziosamente qui, il che è infinitamente meglio di una periferica che gira a una frequenza errata.
Passo 2: Configura gli wait states della Flash
Questo è il passaggio che la maggior parte dei tutorial sul clock salta. La memoria flash dello STM32F4 ha una velocità di accesso massima: a 84 MHz SYSCLK servono 5 wait states sull'F401. Far girare la CPU ad alta velocità senza abbastanza wait states causa errori di lettura della flash — che si manifestano tipicamente come hard fault o corruzione casuale delle istruzioni. Imposta questo prima di commutare il clock di sistema:
/* FLASH_ACR: 5 wait states per 84 MHz su F401 */ FLASH->ACR = FLASH_ACR_LATENCY_5WS | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;
Il buffer di prefetch (PRFTEN), la cache delle istruzioni (ICEN) e la cache dei dati (DCEN) dovrebbero essere tutti abilitati per le massime prestazioni di esecuzione dalla flash.
Passo 3: Configura i prescaler AHB e APB
Questi prescaler dividono SYSCLK per i domini dei bus periferici. Su STM32F4:
- AHB (HCLK) — la maggior parte delle periferiche e il bus memoria della CPU. Max 84 MHz su F401.
- APB1 (PCLK1) — max 42 MHz.
- APB2 (PCLK2) — max 84 MHz.
Vogliamo HCLK = 84 MHz (÷1), PCLK1 = 42 MHz (÷2), PCLK2 = 84 MHz (÷1):
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; /* prescaler AHB = 1 */ RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; /* prescaler APB1 = 2 — 42 MHz */ RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; /* prescaler APB2 = 1 — 84 MHz */
Nota critica per gli utenti di timer: I timer su APB1 ricevono 2 × PCLK1 quando il prescaler è > 1. Quindi con PPRE1 = 2, i clock dei timer su APB1 girano a 84 MHz, non 42 MHz. Questa è una fonte comune di confusione quando si configurano i periodi dei timer.
Passo 4: Configura il PLL
Imposta la sorgente del PLL su HSE, configura i divisori M, N, P e Q, poi abilita il PLL:
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLSRC; /* cancella bit sorgente */
RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; /* sorgente = HSE */
RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLM_Pos) /* M = 8 — divide 8 MHz → 1 MHz */
| (336 << RCC_PLLCFGR_PLLN_Pos) /* N = 336 — moltiplica → VCO 336 MHz */
| (4 << RCC_PLLCFGR_PLLP_Pos) /* P = 4 — VCO / 4 → 84 MHz SYSCLK */
| (7 << RCC_PLLCFGR_PLLQ_Pos) /* Q = 7 — 336 / 7 → 48 MHz per USB */
| RCC_PLLCFGR_PLLSRC_HSE;
RCC->CR |= RCC_CR_PLLON; /* abilita PLL */
while (!(RCC->CR & RCC_CR_PLLRDY)) /* attendi aggancio PLL */
; /* tipicamente pochi µs */
La frequenza VCO (HSE/M × N) deve rimanere tra 192 e 432 MHz sull'F401. Il nostro VCO è 336 MHz, ben dentro il range. L'uscita PLLQ punta a 48 MHz per USB OTG — il divisore Q deve essere scelto in modo che 336 MHz / Q = 48 MHz esatti. Q = 7 dà 48 MHz. Per SDIO, viene usato lo stesso clock da 48 MHz.
Passo 5: Commuta il clock di sistema sul PLL
Ora commutiamo SYSCLK da HSI (il default dopo il reset) al PLL. Usa i bit SW in RCC->CFGR e verifica SWS per confermare la commutazione:
RCC->CFGR |= RCC_CFGR_SW_PLL; /* SYSCLK = uscita PLL */
while ((RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL)
; /* attendi commutazione */
La commutazione è glitch-free: il mux del clock attende un fronte di clock sicuro dalla nuova sorgente prima di commutare. Non c'è rischio di un glitch del clock a metà istruzione durante la transizione.
Passo 6: Verifica il risultato
Puoi leggere lo stato corrente del clock di sistema da SWS per confermare che la commutazione sia avvenuta. Per verificare la frequenza effettiva, toggla un pin GPIO nel loop principale e misuralo con un oscilloscopio, o configura un timer per generare una frequenza nota e confrontala con un riferimento:
/* Leggi lo stato SYSCLK corrente */ uint32_t sws = (RCC->CFGR & RCC_CFGR_SWS_Msk) >> RCC_CFGR_SWS_Pos; /* 0b00 = HSI, 0b01 = HSE, 0b10 = PLL, 0b11 = non usato */
PLLQ e Vincoli del Clock USB
La periferica USB OTG richiede un clock esatto di 48 MHz. Sullo STM32F4, questo proviene dall'uscita PLLQ o da un clock esterno dedicato. Se modifichi la configurazione del PLL per ottimizzare i consumi o per un SYSCLK diverso, devi ricalcolare Q in modo che:
PLLQ = (HSE / M) × N / Q = 48 MHz (esatto)
Non tutte le combinazioni producono 48 MHz esatti. Ecco combinazioni funzionanti per cristalli comuni:
| Cristallo | M | N | P | Q | SYSCLK | USB |
|---|---|---|---|---|---|---|
| 8 MHz | 8 | 336 | 4 | 7 | 84 MHz | 48.0 MHz ✓ |
| 8 MHz | 8 | 336 | 2 | 7 | 168 MHz | 48.0 MHz ✓ |
| 12 MHz | 12 | 336 | 4 | 7 | 84 MHz | 48.0 MHz ✓ |
| 25 MHz | 25 | 336 | 4 | 7 | 84 MHz | 48.0 MHz ✓ |
| 8 MHz | 8 | 300 | 4 | — | 75 MHz | 37.5 MHz ✗ |
Conferma sempre che Q produca una divisione esatta di 48 MHz. Uno scostamento frazionario di µs nel clock del frame USB causa errori di enumerazione o disconnessioni intermittenti.
Sistema di Sicurezza dell'Orologio (CSS)
Lo STM32F4 include un Clock Security System che commuta automaticamente su HSI se l'oscillatore HSE si guasta. Abilitalo dopo che HSE si è stabilizzato:
RCC->CR |= RCC_CR_CSSON; /* abilita clock security */
Quando il CSS rileva un guasto, genera un NMI o un reset a seconda della configurazione. Su hardware di produzione, abilito sempre il CSS — un cristallo che si rompe in campo è raro ma catastrofico se causa un blocco silenzioso. Con il CSS abilitato, il sistema degrada gradualmente a HSI (16 MHz) invece di bloccarsi.
Se abiliti il CSS, DEVI fornire un NMI_Handler o HardFault_Handler che rilevi il flag CSS (RCC->CIR & RCC_CIR_CSSF) e intraprenda le azioni appropriate — tipicamente re-inizializzare l'albero del clock da HSI e avvisare il livello applicativo. Senza un gestore, il NMI causa un hard fault immediato senza possibilità di recupero.
Esempio pratico: init clock minimale per STM32F401 da HSE
Ecco una funzione di inizializzazione del clock completa e autosufficiente che configura 84 MHz da un cristallo HSE da 8 MHz:
void clock_init_84mhz(void)
{
/* 1. Abilita HSE */
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
/* 2. Wait states flash per 84 MHz */
FLASH->ACR = FLASH_ACR_LATENCY_5WS
| FLASH_ACR_PRFTEN
| FLASH_ACR_ICEN
| FLASH_ACR_DCEN;
/* 3. Prescaler AHB/APB */
RCC->CFGR |= RCC_CFGR_HPRE_DIV1; /* AHB = SYSCLK / 1 */
RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; /* APB1 = HCLK / 2 */
RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; /* APB2 = HCLK / 1 */
/* 4. PLL: 8 MHz / 8 * 336 / 4 = 84 MHz */
RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLM_Pos)
| (336 << RCC_PLLCFGR_PLLN_Pos)
| (4 << RCC_PLLCFGR_PLLP_Pos)
| (7 << RCC_PLLCFGR_PLLQ_Pos)
| RCC_PLLCFGR_PLLSRC_HSE;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
/* 5. Commuta SYSCLK sul PLL */
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL);
/* 6. Opzionale: abilita Clock Security System */
RCC->CR |= RCC_CR_CSSON; /* CSS — richiede NMI handler */
}
Checklist pratica
| Passo | Registro | Cosa verificare |
|---|---|---|
| Avvio HSE | RCC->CR (HSERDY) | Bit impostato dopo HSEON — se bloccato, controlla cristallo/carico |
| Latenza flash | FLASH->ACR | Latenza impostata PRIMA di commutare il clock |
| Prescaler AHB | RCC->CFGR (HPRE) | HCLK ≤ frequenza massima del dispositivo |
| Prescaler APB1 | RCC->CFGR (PPRE1) | PCLK1 ≤ 42 MHz (F401); clock timer = 2× se > 1 |
| Range VCO PLL | RCC->PLLCFGR | 192 ≤ VCO ≤ 432 MHz (F401) |
| Aggancio PLL | RCC->CR (PLLRDY) | Bit impostato dopo PLLON |
| Commutazione clock | RCC->CFGR (SWS) | Deve leggere 0b10 (PLL) dopo la commutazione |
| Clock USB | RCC->PLLCFGR (PLLQ) | Deve essere esattamente 48 MHz per USB |
| CSS | RCC->CR (CSSON) | Gestore NMI presente |
Come lo affronterei su un progetto cliente
Su un codice di produzione, uso una tabella strutturata di configurazione del clock invece di spargere i numeri magici del PLL attraverso più file sorgente. Un singolo header clock_cfg.h definisce le frequenze target e calcola i valori dei registri in fase di compilazione quando possibile:
/* clock_cfg.h — singola fonte di verità */ #define HSE_FREQ_HZ 8000000UL #define SYSCLK_FREQ_HZ 84000000UL /* Parametri PLL calcolati — verifica con il datasheet */ #define PLL_M 8 #define PLL_N 336 #define PLL_P 4 /* 2, 4, 6, o 8 */ #define PLL_Q 7 /* N/PLL_Q deve dare 48 MHz */
Convalido anche i parametri del PLL in fase di build con asserzioni statiche (_Static_assert) che verificano che il VCO sia nel range e che l'USB riceva esattamente 48 MHz. Questo cattura la deriva di configurazione quando qualcuno cambia la frequenza del cristallo e dimentica di aggiornare i divisori del PLL.
Per il gestore NMI, alloco una piccola funzione di recupero nel vettore NMI del file di startup che legge il flag CSS, re-inizializza il clock a HSI e imposta un flag di errore globale che il logger dei guasti raccoglie alla prossima iterazione del loop principale. Questo dà al firmware una possibilità di registrare il guasto e entrare in uno stato sicuro invece di bloccarsi silenziosamente.
Fonti
- Manuale di Riferimento STM32F401 (RM0368), Capitolo 6 — Reset and Clock Control (RCC)
- Manuale di Riferimento STM32F4xx (RM0090) — Descrizioni dei registri RCC
- Nota Applicativa ST AN3988 — Configurazione del clock per la serie STM32F4
- Nota Applicativa ST AN2867 — Guida alla progettazione degli oscillatori per microcontrolleri STM32
- Documentazione CMSIS-Core — SystemInit() e framework di configurazione del clock

Domande o correzioni? Scrivimi — rispondo a ogni messaggio.