Cosa Sono gli Option Byte?

Gli option byte risiedono in un banco di memoria flash dedicato, fisicamente separato dalla flash del programma principale. Sono organizzati come un insieme di 8–24 byte (a seconda della famiglia) che configurano il comportamento hardware prima che la CPU esegua la prima istruzione. Su STM32F4, gli option byte corrispondono ai registri FLASH_OPTCR e FLASH_OPTCR1. Su STM32L4/G4/U5, si accede tramite FLASH_OPTR, FLASH_PCROP1ER, FLASH_WRP1AR e registri simili.

La differenza fondamentale: le modifiche agli option byte hanno effetto solo dopo un reset di sistema o un ciclo di accensione. Scrivere il byte è solo il primo passo — bisogna anche attivare un ricaricamento.

RDP: Livelli di Protezione dalla Lettura

Il byte RDP (Read Protection) è l'option byte più critico. Controlla l'accesso alla memoria flash attraverso l'interfaccia di debug (SWD/JTAG) e il bootloader di sistema. Esistono tre livelli su tutte le famiglie STM32 moderne:

LivelloValore RDPComportamento
Level 00xAANessuna protezione. Accesso completo al debug, lettura dal bootloader, tutti i settori flash accessibili.
Level 10xBB (qualsiasi valore eccetto 0xAA e 0xCC)Memoria flash inaccessibile via debug o bootloader a meno di un mass erase. La CPU può ancora eseguire e leggere la flash. Usalo per i prodotti in spedizione per prevenire l'estrazione del firmware.
Level 20xCCProtezione permanente dalla lettura. L'interfaccia di debug è disabilitata in modo permanente. Irreversibile. Il chip diventa una scatola nera — niente debug, niente bootloader, nessun mass erase per tornare indietro. Usalo solo quando l'accesso fisico al dispositivo non è affidabile.

Programmazione RDP a Livello di Registro (STM32F4)

/* Sequenza di programmazione option byte su STM32F4 */
FLASH->OPTKEYR = 0x08192A3B;         /* sblocca OPTKEY1 */
FLASH->OPTKEYR = 0x4C5D6E7F;         /* sblocca OPTKEY2 */

/* Attendi lo sblocco degli option byte */
while (!(FLASH->SR & FLASH_SR_BSY));  /* attendi se un'operazione precedente è in corso */
FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_RDP_Msk)  /* pulisci campo RDP */
              | (0xBB << FLASH_OPTCR_RDP_Pos);          /* Level 1 */

/* Avvia il caricamento degli option byte */
FLASH->OPTCR |= FLASH_OPTCR_OPTSTRT;  /* avvia programmazione option byte */
while (FLASH->SR & FLASH_SR_BSY);     /* attendi il completamento */

/* Richiesto: reset di sistema per applicare il nuovo option byte */
NVIC_SystemReset();

Programmazione RDP a Livello di Registro (STM32L4/G4/U5)

/* Su L4/G4/U5, gli option byte usano un set di registri dedicato */
/* Step 1: Sblocca il registro di controllo della flash */
FLASH->KEYR = 0x45670123;             /* sblocca KEY1 */
FLASH->KEYR = 0xCDEF89AB;             /* sblocca KEY2 */

/* Step 2: Sblocca gli option byte */
FLASH->OPTKEYR = 0x08192A3B;          /* sblocca OPTKEY1 */
FLASH->OPTKEYR = 0x4C5D6E7F;          /* sblocca OPTKEY2 */

/* Step 3: Imposta RDP a Level 1 (valore 0xBB) */
FLASH->OPTR = (FLASH->OPTR & ~FLASH_OPTR_RDP_Msk) | (0xBB << FLASH_OPTR_RDP_Pos);

/* Step 4: Avvia la programmazione degli option byte */
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);

/* Step 5: Reset di sistema */
NVIC_SystemReset();
Avvertenza critica: Se programmi RDP Level 1 senza avere un percorso di aggiornamento firmware funzionante, dovrai fare un mass erase tramite bootloader per apportare modifiche — che cancella tutti i contenuti della flash, compresi gli option byte che l'hanno bloccata. Il Level 2 deve essere programmato alla fine della produzione e testato prima su unità sacrificiali. Una volta impostato il Level 2, nessun debugger parlerà mai più con quel chip.

BOR: Livello di Brown-Out Reset

Il livello BOR (Brown-Out Reset) imposta la soglia di tensione al di sotto della quale lo STM32 viene tenuto in reset. Questo impedisce alla CPU di eseguire codice a tensioni dove le letture dalla flash non sono affidabili. Su F4, il livello BOR si configura tramite gli option bit in FLASH_OPTCR. Su L4/G4/U5, si trova in FLASH_OPTR.

Livello BORSoglia (F4)Soglia (L4)Uso
Level 0 (spento)Nessun BOR. L'alimentazione deve essere supervisionata esternamente.
Level 1~2,10 V~1,80 VOperazione sicura con batteria 2,4–2,7 V
Level 2~2,30 V~2,00 VOperazione standard a 3,3 V con margine
Level 3~2,55 V~2,20 VConservativo; per alimentazioni rumorose o automotive
Level 4~2,80 V~2,50 VSoglia alta per alimentazioni 3,3 V ± 5%
/* Imposta BOR Level 2 su STM32F4 (operazione a 3,3 V) */
FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_BOR_LEV_Msk)
              | (2 << FLASH_OPTCR_BOR_LEV_Pos)
              | FLASH_OPTCR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);

/* Su STM32L4/U5 */
FLASH->OPTR = (FLASH->OPTR & ~FLASH_OPTR_BOR_LEV_Msk)
              | (2 << FLASH_OPTR_BOR_LEV_Pos);
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);
Il BOR resetta il chip quando VDD scende sotto la soglia e impedisce l'avvio finché VDD non risale sopra di essa (con isteresi di circa 100 mV). Su prodotti a batteria, impostare il BOR troppo alto causa spegnimento prematuro; impostarlo troppo basso rischia errori di lettura flash in condizioni di VDD parziale.

Configurazione del Boot e dei Pin

I pin di boot (BOOT0, BOOT1/nBOOT1) determinano dove la CPU preleva la prima istruzione dopo il reset. Gli option byte permettono di sovrascrivere lo stato del pin senza cambiare l'hardware. Questo è essenziale per i prodotti di produzione che devono avviarsi dalla flash principale ma necessitano di un bootloader di fallback per gli aggiornamenti in campo.

Option bit chiave per la configurazione del boot:

  • nBOOT0 — copia invertita del valore del pin BOOT0. Quando nBOOT0 è programmato a 0, il pin BOOT0 è effettivamente tirato a massa (boot dalla flash principale).
  • nBOOT1 — seleziona la modalità di boot quando BOOT0 = 1. Quando 1 (default), boot dalla memoria di sistema (bootloader). Quando 0, boot dalla SRAM.
  • BOOT_LOCK (L4/G4/U5) — quando impostato, il bootloader ignora il pin BOOT0 e si avvia sempre dalla flash principale. Questo impedisce a un glitch o a una manipolazione fisica di forzare il chip in modalità bootloader.
  • nSWBOOT0 (L4/G4/U5) — quando impostato, la configurazione di boot è completamente controllata dagli option byte (nBOOT0 + nBOOT1), ignorando completamente il pin BOOT0.
/* Configura F4 per avviarsi sempre dalla flash principale, ignorando BOOT0 */
/* Questo si fa tramite l'option bit nBOOT0 */
FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_nBOOT0)
              | FLASH_OPTCR_nBOOT0;              /* nBOOT0 = 1, BOOT0 mantenuto basso */
FLASH->OPTCR |= FLASH_OPTCR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);

/* Su L4/G4/U5: ignora completamente il pin BOOT0, avvio dalla flash */
FLASH->OPTR |= FLASH_OPTR_nBOOT0                /* BOOT0 internamente tirato basso */
             | FLASH_OPTR_nSWBOOT0;              /* configurazione boot via software */
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);

Area OTP: Byte One-Time Programmable

Tutte le famiglie STM32 forniscono un'area OTP (One-Time Programmable) all'interno della memoria flash. Su F4, sono 512 byte (16 banchi da 32 bit ciascuno). Su L4/G4/U5, sono tipicamente 1024 byte organizzati come 32 parole OTP da 32 bit ciascuna, più un singolo byte di lock non riutilizzabile per parola.

Casi d'uso per l'OTP:

  • Numero di serie unico del dispositivo (bruciato durante il test di produzione)
  • Indirizzo MAC di fabbrica o seed per chiave crittografica
  • Coefficienti di calibrazione che non devono mai cambiare
  • Data di produzione e codice lotto
/* Scrittura OTP su STM32L4/G4 */
/* Indirizzo base OTP: 0x1FFF7000 su L4, 0x1FFF7200 su G4 */

#define OTP_BASE      0x1FFF7000UL
#define OTP_LOCK_BASE 0x1FFF7010UL  /* byte di lock per ogni parola OTP */

/* Scrivi un valore di calibrazione a 32 bit nella parola OTP 4 */
uint32_t *otp_word4 = (uint32_t *)(OTP_BASE + 4 * 4);
*otp_word4 = 0xDEADBEEF;  /* scrittura diretta (il controller flash gestisce tutto) */

/* Blocca la parola OTP 4 per prevenire scritture future */
volatile uint8_t *lock4 = (uint8_t *)(OTP_LOCK_BASE + 4);
*lock4 = 0x00;  /* scrivere 0 blocca la parola (non può essere annullato) */

/* La lettura è immediata */
uint32_t cal = *otp_word4;  /* sempre leggibile */
L'OTP è programmabile una sola volta per parola. Puoi scrivere ogni parola OTP esattamente una volta. Il byte di lock, quando azzerato a 0x00, protegge permanentemente quella parola dalla scrittura — nemmeno il programmatore può cambiarla. Non c'è modo di sbloccare una parola OTP. Pianifica il layout OTP prima di qualsiasi run di produzione.

Write Protection (WRP)

Gli option byte WRP (Write Protection) prevengono scritture accidentali o malevole su settori flash selezionati. In produzione, proteggi i settori del bootloader e della configurazione RDP. Su STM32F4, la protezione è impostata per settore; su L4/G4/U5, per pagine da 2–4 KB.

/* Proteggi i settori 0–3 su F4 (area bootloader, 64 KB) */
FLASH->OPTCR = (FLASH->OPTCR & ~FLASH_OPTCR_WRP_Msk)
              | (0x0F << FLASH_OPTCR_WRP_Pos);  /* settori 0–3 */
FLASH->OPTCR |= FLASH_OPTCR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);

/* Su STM32L4, proteggi le pagine 0–7 usando WRP1AR */
FLASH->WRP1AR = (0 << FLASH_WRP1AR_WRP1A_STRT_Pos)  /* pagina iniziale 0 */
              | (7 << FLASH_WRP1AR_WRP1A_END_Pos)   /* pagina finale 7 */
              | FLASH_WRP1AR_WRP1A_EN;
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);

PCROP: Protezione dalla Lettura del Codice Proprietario

PCROP (Proprietary Code Readout Protection) è un'estensione disponibile su L4/G4/U5 che protegge pagine flash selezionate dalla lettura anche da parte della CPU. Questo è diverso da RDP — RDP protegge dalle letture di debug esterne, mentre PCROP impedisce a qualsiasi bus master (CPU, DMA, acceleratore ART) di leggere le pagine protette. Il codice memorizzato nelle regioni PCROP può ancora essere eseguito dalla CPU (fetch delle istruzioni) ma non può essere letto come dati.

Usa PCROP per:

  • Tabelle di chiavi AES e primitive crittografiche
  • Routine di verifica licenze
  • Bootloader che deve rimanere confidenziale anche dal codice applicativo

Esempio Pratico: Flusso di Programmazione in Produzione

Un tipico script di programmazione in produzione (eseguito via CLI di STM32CubeProgrammer o un programmatore personalizzato sul test jig) segue questa sequenza:

/* Fase 1: Flasha il binario dell'applicazione */
/* (cancella + scrivi la flash principale) */

/* Fase 2: Programma gli option byte per la produzione */
FLASH->OPTKEYR = 0x08192A3B;
FLASH->OPTKEYR = 0x4C5D6E7F;

/* Imposta BOR Level 2 per operazione a 3,3 V */
FLASH->OPTR = (FLASH->OPTR & ~FLASH_OPTR_BOR_LEV_Msk)
              | (2 << FLASH_OPTR_BOR_LEV_Pos)
              | FLASH_OPTR_nBOOT0               /* boot dalla flash */
              | FLASH_OPTR_nSWBOOT0;             /* ignora pin BOOT0 */

/* Proteggi le pagine del bootloader da scritture erronee */
FLASH->WRP1AR = (0 << FLASH_WRP1AR_WRP1A_STRT_Pos)
              | (3 << FLASH_WRP1AR_WRP1A_END_Pos)
              | FLASH_WRP1AR_WRP1A_EN;

/* Scrivi OTP con ID univoco e calibrazione */
*(uint32_t *)(OTP_BASE + 0) = unique_id_lo;
*(uint32_t *)(OTP_BASE + 4) = unique_id_hi;
*(uint8_t *)(OTP_LOCK_BASE + 0) = 0x00;   /* blocca parola 0 */
*(uint8_t *)(OTP_LOCK_BASE + 1) = 0x00;   /* blocca parola 1 */

/* Imposta RDP Level 1 (ultimo passo — dopo questo, il debug è bloccato) */
FLASH->OPTR = (FLASH->OPTR & ~FLASH_OPTR_RDP_Msk)
              | (0xBB << FLASH_OPTR_RDP_Pos);
FLASH->CR |= FLASH_CR_OPTSTRT;
while (FLASH->SR & FLASH_SR_BSY);

/* Fase 3: Reset di sistema per applicare */
NVIC_SystemReset();
L'ordine è intenzionale: prima write protection, poi OTP, poi RDP per ultimo. Se RDP Level 2 è richiesto, deve essere assolutamente l'ultima operazione — ogni altro option byte deve essere finalizzato prima di impostare RDP2, perché non c'è recupero.

Checklist Pratica

VerificaPerché
Sequenza di sblocco option byte corretta?Chiavi sbagliate vengono ignorate silenziosamente; le scritture non hanno effetto
OPTSTRT lanciato dopo ogni modifica a OPTCR?Senza OPTSTRT, gli option byte non vengono programmati
Reset di sistema eseguito dopo la programmazione?Gli option byte si applicano solo dopo POR o reset di sistema
RDP Level 1 testato prima su unità sacrificale?Level 1 richiede mass erase per cambiare — verifica il percorso di aggiornamento
Level 2 pianificato o accidentale?Level 2 è permanente. Un'unità sacrificale per confermare, poi procedere.
Livello BOR appropriato per la tensione di alimentazione?Troppo basso = errori flash. Troppo alto = spegnimento prematuro della batteria.
nSWBOOT0 + nBOOT0 configurati per boot indipendente dal pin?Le schede di produzione possono avere BOOT0 flottante o cablato erroneamente
Layout OTP documentato nelle specifiche di produzione?Ogni parola è unica. Nessuna cancellazione, nessuna riscrittura.
I confini PCROP non sovrappongono i vettori di interrupt?VTOR punta all'inizio della flash; PCROP sul settore 0 blocca la lettura dei vettori
WRP copre il bootloader ma non la regione di aggiornamento OTA?Se il bootloader si aggiorna da solo, necessita di accesso in scrittura alle proprie pagine

Come Lo Affronterei su un Progetto Cliente

Ho lavorato con un cliente che produceva un sensore di gas alimentato a batteria basato su STM32L051. La linea di produzione bruciava un numero di serie unico e coefficienti di calibrazione nell'OTP, poi impostava RDP Level 1 e nSWBOOT0 per evitare un boot dipendente dal pin. Il primo lotto di 200 unità è tornato con dati di calibrazione corrotti dopo tre mesi.

Causa principale: lo script di produzione scriveva l'OTP dopo aver impostato RDP Level 1. Il mass erase che accompagnava la transizione RDP (Level 1 richiede mass erase quando si passa da Level 0 a Level 1, poiché le vecchie unità Level 0 necessitavano la cancellazione della flash) stava anche cancellando l'area OTP. La soluzione: scrivere OTP prima di impostare RDP, e bloccare le parole OTP immediatamente dopo la scrittura. Questo ha anche rivelato un secondo problema — il test jig leggeva l'OTP prima che il byte di lock fosse scritto, ottenendo dati obsoleti dall'unità precedente perché l'OTP manteneva il suo vecchio valore quando il mass erase della flash veniva bypassato per l'OTP.

Lezione: l'ordine delle operazioni sugli option byte in produzione è importante quanto i valori stessi. Scrivi sempre una specifica di programmazione di produzione che definisca la sequenza esatta, inclusi i passaggi che necessitano un ciclo di accensione, e verifica su unità sacrificiali prima di impegnarti in volumi.

Su un altro progetto con STM32G4 per un attuatore automotive, il cliente richiedeva RDP Level 2 per la protezione della proprietà intellettuale. Abbiamo impostato tutti gli option byte (BOR Level 3, WR protection, nSWBOOT0, PCROP per la libreria crittografica) prima del passo finale RDP2. L'ultima unità prima di RDP2 viene programmata sul test jig e usata per il debug HALT nei test di sistema. Dopo la validazione, il bit RDP2 viene scritto — e l'unità è sigillata per sempre.

Fonti consultate:

  • STM32F4 Reference Manual (RM0090) — Capitolo 3: Interfaccia memoria flash, Sezione 3.7: Option bytes
  • STM32L4 Reference Manual (RM0351) — Capitolo 4: Flash, Sezione 4.3: Option bytes
  • STM32G4 Reference Manual (RM0440) — Capitolo 4: Flash, Sezione 4.3: Option bytes
  • STM32U5 Reference Manual (RM0456) — Capitolo 6: Flash, Sezione 6.6: Option bytes
  • ST Application Note AN3241 — Programmazione option byte per STM32F4
  • ST Application Note AN4803 — Programmazione option byte STM32L4
  • ST Application Note AN2606 — Modalità di boot memoria di sistema STM32
  • ST Application Note AN4370 — Introduzione a PCROP
  • CMSIS-Core 5.6.0 — Definizioni registro stm32l4xx.h, stm32f4xx.h