2026-06-12 · Davide Carrese

Configurazione RCC dell'Orologio su STM32 a Livello di Registri:
HSI, HSE, PLL e Commutazione del Clock di Sistema su STM32F4

STM32 · RCC · Orologio · PLL · Registri · 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:

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.

⚠️ Compromesso HSI vs HSE

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:

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:

CristalloMNPQSYSCLKUSB
8 MHz83364784 MHz48.0 MHz ✓
8 MHz833627168 MHz48.0 MHz ✓
12 MHz123364784 MHz48.0 MHz ✓
25 MHz253364784 MHz48.0 MHz ✓
8 MHz8300475 MHz37.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.

⚠️ Gestore NMI CSS obbligatorio

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

PassoRegistroCosa verificare
Avvio HSERCC->CR (HSERDY)Bit impostato dopo HSEON — se bloccato, controlla cristallo/carico
Latenza flashFLASH->ACRLatenza impostata PRIMA di commutare il clock
Prescaler AHBRCC->CFGR (HPRE)HCLK ≤ frequenza massima del dispositivo
Prescaler APB1RCC->CFGR (PPRE1)PCLK1 ≤ 42 MHz (F401); clock timer = 2× se > 1
Range VCO PLLRCC->PLLCFGR192 ≤ VCO ≤ 432 MHz (F401)
Aggancio PLLRCC->CR (PLLRDY)Bit impostato dopo PLLON
Commutazione clockRCC->CFGR (SWS)Deve leggere 0b10 (PLL) dopo la commutazione
Clock USBRCC->PLLCFGR (PLLQ)Deve essere esattamente 48 MHz per USB
CSSRCC->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

📬 Lascia un commento

Domande o correzioni? Scrivimi — rispondo a ogni messaggio.