Note sulla programmazione dei micro AVR

Condividi:

Questo articolo raccoglie alcune note sulla programmazione degli avr 8-bit Atmel con la toolchain avr-gcc. Sono note apprese da vari datasheet ed application notes che è sempre utile ricordare.

I/O Headers

Ogni buon programma inizia con l’inclusione dell’header file che contiene le define e le macro relative al micro da programmare, in questo modo avremo a disposizione le dichiarazioni di tutti i registri, i vettori di interrupt, i singoli bit, i flag e tutto quello che riguarda il microcontrollore. Poichè esistono dozzine di micro diversi, per rendere le cose semplici è stato previsto un unico header file che, in fase di precompilazione tramite il flag -mmcu,  si occupa di selezionare ed includere l’header specifico per la mcu.

Tutto quello che va fatto quindi, è includere l’header file avr/io.h e passare l’informazione corretta a avr-gcc

In questo modo in fase di precompilazione viene incluso l’header file avr/iom328p.h che non sarebbe possibile includere direttamente a causa di un controllo espressamente inserito in cima al file.

Interrupt Service Routine

Per lavorare con le interrupt service routine è necessario includere un header file avr/interrupt.h.

Macro principali

Questo include definisce tutte le macro necessarie a lavorare con gli interrupt della mcu, tra le principali troviamo:

      • ISR(vector, [attributes])
        Questa macro associa il codice da eseguire con uno specifico vettore di interrupt. Poichè ogni mcu ha i suoi vettori di interrupt, le #define dei vettori utilizzabili si trovano all’interno dell’header file specifico per la mcu. E’ così possibile definire più interrupt service routine all’interno di un unico main file.
      • sei()
        Questa macro si traduce in un’unica riga di codice assembler ed abilita globalmente le interrupt settanto il global interrupt flag
      • cli()
        Come sei() ma disabilita globalmente le macro. E’ indispensabile ad esempio quando una porzione di codice nel flusso del programma deve essere completata perchè opera su registri che potrebbero essere alterati dall’esecuzione di una ISR

Decompilando il binario del codice di esempio, troviamo due entry nel vettore di interrupt dove normalmente sarebbe presente una jump all’indirizzo 0x7c da dove il programma salta all’indirizzo 0x00

    • 0x04: jump all’indirizzo 0x80 che vene eseguita a seguito di External Interrupt Request 0
    • 0x0c: jump all’indirizzo 0xa4 che vene eseguita a seguito di Pin Change Interrupt Request 0
    • 0x44: jump all’indirizzo 0xc8 che vene eseguita a seguito di SPI Serial Transfer Complete
Variabili modificate all’interno di Interrupt Service Routines

Le variabili globali modificate all’interno di ISR vanno dichiarate come volatili.

In situazioni come in questo esempio, le ottimizzazioni del compilatore potrebbero provocare un effetto indesiderato sul codice compilato. All’interno del ciclo while il valore di gc_flag non viene mai modificato quindi il compilatore ritiene inutile inserire il codice relativo al test gc_flag==0, pertanto il ciclo while verrà tradotto come un semplice loop infinito. Per dire al compilatore che il valore di quella variabile deve essere sempre testato, perchè potrebbe essere alterato esternamente al ciclo, è necessario dichiarare la variabile come volatile

Sleep mode

Il power management è gestito dall’ include file avr/sleep.h e viene attivato eseguendo le due principali macro set_sleep_mode(mode) e sleep_mode()

    • set_sleep_mode(mode): Alcune mcu dispongono di diversi sleep mode che possono essere passati come argomento. Per avere un elenco delle modalità disponibili va fatto riferimento al datasheet della mcu. Ad esempio per la m328p troviamo queste costanti:
    • sleep_mode(): Questa macro mette la mcu in modalità sleep, esegue in sequenza altre 3 macro che sono sleep_enable(), sleep_cpu() e sleep_disable()

Se la mcu deve essere risvegliata dall’evento di un’interrupt, ovviamente questi devono essere attivati prima di mettere la mcu in power save

Routines di ritardo

I cicli di ritardo vengono gestiti con l’include file util/delay.h. La compilazione di questa libreria genera dei warning se non vengono utilizzate ottimizzazioni del codice, quindi va aggiunto il flag relativo quando viene invocato il compilatore

Le varie macro dichiarate in questo include contengono delle porzioni di codice in cui vengono effettuati dei calcoli per ricavare i tempi di ritardo, che sono funzioni della frequenza di clock della mcu. Ci sono due modi per dichiarare la frequenza di clock da utilizzare come parametro

    • In fase di compilazione: passando al compilatore avr-gcc l’opzione -DF_CPU passiamo il valore al precompilatore e l’include troverà la variabile F_CPU valorizzata

      In questo modo diciamo di utilizzare come frequenza di clock 16MHz per effettuare tutti i calcoli
    • Tramite #define: E’ possibile dichiarare il valore da utilizzare per la variabile F_CPU tramite define prima di includere il file delay.h

Definita la frequenza della mcu, possiamo utilizzare le due macro fornite nell’include

    • _delay_ms(double __ms): Il ritardo limite per ottenere la massima risoluzione è T = \frac{ 262.14 ms }{ F\_CPU in MHz }. Oltre questo valore la funzione utilizza una risoluzione pari a 1/10
    • _delay_us(double __us): Il ritardo limite per ottenere la massima risoluzione è T = \frac{ 768 \mu s }{ F\_CPU in MHz }. Oltre questo valore la funzione utilizza una risoluzione pari a 1/10

Il valore massimo di ritardo che è possibile ottenere tramite queste funzioni è di T_{MAX} = \frac{ 4294967.295 ms }{ F\_CPU in MHz }. Oltre questo valore si produrrà un overlap e non si otterrà alcun ritardo ( 0 ms )

Inoltre la conversione tra ritardo e cicli di clock potrebbe non essere un numero intero, in questo caso è possibile scegliere quale tipo di arrotondamento utilizzare dichiarando tramite #define una delle due variabili __DELAY_ROUND_DOWN__ e __DELAY_ROUND_CLOSEST__ prima di includere delay.h