I più recenti microcontrollori ATMEL della serie megaAVR® incorporano avanzate tecnologie che consentono il “Self-Programming” della memoria. Ecco cosa si può fare e come farlo.
Esistono diversi approcci alternativi per la realizzazione di microcontrollori con la possibilità di eseguire l’auto-scrittura delle memoria programma e le differenza stanno sostanzialmente nella dimensione dei blocchi della memoria codice e di boot, la dimensione del settore che può essere cancellato e riprogrammato, la gestione e la configurazione del settore di boot e l’implementazione di tecniche di sicurezza per la protezione del codice contro le programmazioni accidentali. La scelta della dimensione del settore che può essere riprogrammato, è un compromesso tra tempo di programmazione e flessibilità: un settore più ampio consente di accorciare sensibilmente il tempo di programmazione e cancellazione ma risulta una soluzione poco flessibile qualora sia necessario aggiornare solo piccole parti della memoria. Oltre a questo non è da trascurare il fatto che se il singolo settore è grande sarà necessario disporre di una sufficiente quantità di memoria RAM per contenere i dati da scrivere. Una RAM più ampia comporta una più ampia superficie del die con conseguente incremento del costo del microcontrollore.
La soluzione ATMEL
Per la gestione del settore di boot della memoria esistono tipicamente due approcci:
Flash e ROM. Nel caso della tecnologia ROM, il settore di boot viene scritto in fase di fabbricazione del wafer e può essere indirizzato attraverso dei pin esterni. Questa tecnologia è particolarmente sicura in quanto evita la possibilità di riprogrammare accidentalmente il settore di boot, ma ha l’evidente svantaggio che il contenuto del settore di boot non può essere modificato in alcun modo. A questo inconveniente si ovvia utilizzando la tecnologia Flash. In questo caso si deve prevedere un algoritmo di sicurezza in modo da evitare la scrittura accidentale del boot sector visto che questo è ora modificabile. Le soluzioni si basano generalmente sull’uso di un lock bit ed eventualmente sull’implementazione di algoritmi di encription/decription che innalzano ulteriormente il grado di protezione. La soluzione di ATMEL implementata sui microcontrollori della famiglia megaAVR® prevede un blocco di boot programmabile le cui dimensioni vanno da 256 byte fino a 4Kbytes a seconda del dispositivo. Usando micro con settore di boot piuttosto ampio consente di implementare l’algoritmo DES in 2Kbytes. La struttura è quella di figura 1.
Tutti i dispositivi della famiglia megaAVR® offrono all’utilizzatore la possibilità di eseguire le istruzioni nel settore di boot durante la cancellazione/scrittura dei blocchi della memoria programma. Il vettore di interruzione può essere trasferito dalla memoria programma al settore di boot consentendo al microcontrollore di rispondere ad interruzioni critiche anche in fase di scrittura della memoria programma.
Read-While-Write
Come mostra la figura 1, la memoria Flash è suddivisa in due sezioni: la sezione programma e la sezione si boot. La dimensione delle sezioni dipende dalla configurazione dei fuses BOOTSZ come mostrato nella figura 2.
La sezione programma è dedicata ad accogliere il codice dell’applicazione vera e propria ed il livello di protezione per questa sezione può essere impostato mediate i bit lock in accordo alla tabella 1.
BLB0 MODE | BLB02 | BLB01 | PROTEZIONE |
1 | 1 | 1 | Nessuna |
2 | 1 | 0 | SPM non può scrivere nella sezione programma |
3 | 0 | 0 | SPM non può scrivere nella sezione programma e LPM, eseguita dalla sezione di boot, non può leggere dalla sezione programma. Se il vettore di interruzione è mappato nella sezione di boot, le interruzioni vengono disabilitate durante l’esecuzione del codice dalla sezione programma. |
4 | 0 | 1 | L’esecuzione dell’istruzione LPM dalla sezione di boot non può leggere dalla sezione programma. Se il vettore di interruzione è mappato nella sezione di boot, le interruzioni vengono disabilitate durante l’esecuzione del codice dalla sezione programma. |
▲ Tabella 1. I livelli di protezione per la sezione codice |
Le restrizioni coinvolgono ovviamente le istruzioni SPM (Store Program Memomry) ed LPM (Load Program Memory). L’eventuale bootloader dovrà essere necessariamente collocato nella sezione di boot in modo che mediante l’istruzione SPM sia possibile iniziare la programmazione della sezione programma. I livelli di protezione per la sezione di boot sono impostabili mediante i flag BLB11 e BLB12 in accordo alla tabella 2.
BLB1 MODE | BLB12 | BLB11 | PROTEZIONE |
1 | 1 | 1 | Nessuna |
2 | 1 | 0 | SPM non può scrivere nella sezione di boot |
3 | 0 | 0 | SPM non può scrivere nella sezione di boot e LPM, eseguita dalla sezione programma, non può leggere dalla sezione di boot. Se il vettore di interruzione è mappato nella sezione programma, le interruzioni vengono disabilitate durante l’esecuzione del codice dalla sezione di boot. |
4 | 0 | 1 | L’esecuzione dell’istruzione LPM dalla sezione programma non può leggere dalla sezione di boot. Se il vettore di interruzione è mappato nella sezione programma, le interruzioni vengono disabilitate durante l’esecuzione del codice dalla sezione di boot. |
▲ Tabella 2. I livelli di protezione per la sezione boot |
Oltre alla suddivisione appena vista, la sezione programma è a sua volta suddivisa in due zone: la zona Read-While-Write (RWW) e la zona No-Read-While-Write (NRWW). Mentre è possibile leggere dalla zona NRWW mentre si effettua una scrittura/cancellazione di dati dalla zona RWW, la CPU risulta bloccata durante la scrittura/cancellazione di dati all’interno della zona NRWW.
Il Bootloader
L’entrata nella sezione del bootloader avviene a seguito di un salto o di una chiamata dal programma principale o a seguito del verificarsi di un evento esterno come ad esempio un codice ricevuto via UART o via SPI. Come alternativa si possono configurare i fuses in modo da far puntare il reset vector all’indirizzo della sezione boot avviando in questo modo il bootloader al reset del microcontrollore. Le operazioni del bootloader vengono configurate attraverso il registro SPMCSR (Store Program Memory Control and Status Register) la cui struttura è riportata in figura 3.
Ecco in dettaglio la funzione di ogni singolo bit:
SPMIE (SPM Interrupt Enable): se ad 1 viene generata una interruzione al termine dell’esecuzione della istruzione SPM.
RWWSB (Read-Wile-Write Section Busy): viene messo ad 1 ogni volta che viene eseguita una operazione (scrittura/cancellazione) nella sezione RWW. Quando è ad 1 non è possibile accedere all’area RWW.
RWWSRE (Read-While-Write Sectione Read Enable): Agendo sulla sezione RWW tale sezione viene bloccata automaticamente tramite il flag RWWSB e viene riabilitata solo al termie dell’operazione in corso. Se RWWSRE è ad 1 l’istruzione SPM riabilita la zona RWW entro quattro cicli istruzione.
BLBSET (Boot Lock Bit Set): una volta messo ad 1 la prima istruzione (entro quattro cicli di clock) SPM aggiorna i Lock bit in accordo a quanto scritto nel registro R0.
La prima istruzione (entro tre cicli di clock) LPM leggerà il valore dei Lock bit.
PGWRT (Page Write): una volta messo ad 1, la prima istruzione SPM entro quattro cicli clock, scrive in memoria il contenuto del buffer. Al termine della scrittura o se non viene eseguita alcuna istruzione SPM antro i quattro cicli clock, tale bit viene posto automaticamente a zero. Durante la scrittura dei dati la CPU risulta bloccata.
PGRES (Page Erase): una volta messo ad 1, la prima istruzione SPM entro quattro cicli clock provoca la cancellazione di un blocco di memoria.
SPMEN (Store Program Memory Eanble): abilita l’istruzione SPM alla scrittura in memoria.
Prevenire la perdita di dati
Se la tensione di alimentazione scende al di sotto dei valori nominali, la memoria flash viene trovarsi a lavorare in condizioni non ottimali con la conseguente perdita dei dati memorizzati. La perdita dei dati a causa della bassa tensione di alimentazione può avvenire per due effetti: l’operazione di scrittura richiede specifici valori di alimentazione per cui con tensioni troppo basse i dati possono essere scritti in modo errato (un “1” logico può essere erroneamente interpretato come uno “0” logico); l’istruzione di scrittura non viene eseguita correttamente in quanto la CPU non opera nelle condizioni nominali. Per evitare questi problemi è necessario attenersi a semplici regole di progettazione quali:
- Se nell’applicazione non è necessario un bootloader è consigliato proteggere la sezione di boot per evitare aggiornamenti non voluti;
- Mantenere attivo il reset fintanto che la tensione di alimentazione non raggiunge i valori nominali;
- Mantenere in power-down il core del microcontrollore per tutto il tempo in cui la tensione di alimentazione si mantiene a livello basso.
Un esempio di bootloader
Nel listato 1 è riportato un esempio di codice Assembler per un bootloader scritto per ATmega128. La routine scrive una pagina di dati dalla memoria RAM alla memoria Flash. I dati sono memorizzati in RAM a partire dalla locazione indicata dal puntatore Y e scritti in Flash a partire dall’indirizzo indicato nel puntatore Z. La routine dovrà essere collocata nella sezione di boot.
Boot .equ PAGESIZEB = PAGESIZE*2 ;dimensione della pagina in byte .org SMALLBOOTSTART Scrivi_pagina: ;cancellazione della pagina ldi spmcsrval, (1<<PGERS) | (1<<SPMEN) call Do_spm ; abilitazione della sezione RWW ldi spmcsrval, (1<<RWWSRE) | (1<<SPMEN) call Do_spm ; trasferimento dati dalla RAM alla Flash ldi looplo, low(PAGESIZEB) ldi loophi, high(PAGESIZEB);non necessario se PAGESIZEB<=256 Ciclo_Wr: ld r0, Y+ ld r1, Y+ ldi spmcsrval, (1<<SPMEN) call Do_spm adiw ZH:ZL, 2 sbiw loophi:looplo, 2 ;usare subi se PAGESIZEB<=256 brne Ciclo_Wr ; scrittura della pagina subi ZL, low(PAGESIZEB) ;ripristino del puntatore sbci ZH, high(PAGESIZEB) ;non necessario se PAGESIZEB<=256 ldi spmcsrval, (1<<PGWRT) | (1<<SPMEN) call Do_spm ; abilitazione della sezione RWW ldi spmcsrval, (1<<RWWSRE) | (1<<SPMEN) call Do_spm ; Rilettura e controllo (opzionale) ldi looplo, low(PAGESIZEB) ldi loophi, high(PAGESIZEB);non necessario se PAGESIZEB<=256 subi YL, low(PAGESIZEB) ;ripristino del puntatore sbci YH, high(PAGESIZEB) Coclo_Rd: lpm r0, Z+ ld r1, Y+ cpse r0, r1 jmp Errore sbiw loophi:looplo, 1 ;usare subi se PAGESIZEB<=256 brne Ciclo_Rd ; ritorno alla sezione RWW ; verifica che la sezione RWW sia pronta per la lettura Ritorna: lds temp1, SPMCSR sbrs temp1, RWWSB ; Se RWWSB=1, la sezione RWW non è pronta ret ; abilitazione della sezione RWW ldi spmcsrval, (1<<RWWSRE) | (1<<SPMEN) call esegui_spm rjmp Ritorna esegui_spm: ; attesa del completamento dell’istruzione SPM Attendi: lds temp1, SPMCSR sbrc temp1, SPMEN rjmp Attendi ; disabilitazioni interrupt e salvataggio di stato in temp2, SREG cli ; verifica che non vi siano accessi in EEPROM Attendi_eeprom: sbic EECR, EEWE rjmp Attendi_eeprom sts SPMCSR, spmcsrval spm ; rispristino di SREG out SREG, temp2 ret
Listato 1 |
Indirizzamento della memoria
Poiché la memoria è organizzata in pagine, il Program Counter può essere considerato suddiviso in due sezioni: gli 8 bit meno significativi indirizzano la word all’interno di una pagina di memoria, mentre gli 8 bit più significativi indirizzano la pagina. Le istruzioni di cancellazione e scrittura di un cella di memoria, vengono indirizzate in maniera indipendente pertanto è necessario che il bootloader si riferisca alla stessa pagina di memoria durante le suddette operazioni. La figura 4 mostra l’indirizzamento della memoria.
Una volta iniziata l’operazione di programmazione, i registri Z e RAMPZ possono essere utilizzati nuovamente in quanto l’indirizzo coinvolto nella fase di programmazione viene bufferizzato. L’operazione SPM è l’unica che non usa i registri Z e RAMPZ, ma si avvale dei Boot Loader Lock Bits. Nell’operazione di Self-Programming, la memoria viene aggiornata pagina per pagina e, prima di iniziare la programmazione, la pagina deve essere cancellata. Il buffer contenente i nuovi dati da scrivere nella pagina, viene invece aggiornato per singole word e l’aggiornamento del buffer può avvenire sia prima della fase di cancellazione che prima della fase di scrittura della pagina. Qualora sia necessario aggiornare solo parte del contenuto della pagina, sarà quindi necessario copiare l’intera pagina nel buffer, aggiornare le singole word all’interno del buffer, quindi trasferire il contenuto del buffer così aggiornato nella relativa pagina di memoria. È ovvio che in questo caso è strettamente necessario procedere con la cancellazione della pagina solo prima di eseguire l’operazione di scrittura.
Riferimenti
Informazioni generali su AVR: http://www.atmel.com/products/avr
AVR230: DES Bootloader – Application note http://www.atmel.com/dyn/resources/prod_documents/doc2541.pdf
AVR109 Self-Programming: http://www.atmel.com/dyn/resources/prod_docu- ments/doc1644.pdf
Le soluzioni flash ormai si stanno affermando sempre di più proprio per la “facilità” di programmazione.