Sei sulla pagina 1di 16

ELECTROYOU.

IT

Steve Blackbird (TardoFreak)

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.


19 dicembre 2012 La prima caratteristica necessaria che un programma deve avere quella di funzionare, questo vero. Ma non affatto vero che un programma funzionante sia un buon programma. In questo breve articolo vorrei introdurre un paio di concetti: l' astrazione dell'hardware (hardware abstraction) e la tecnica per nascondere le informazioni (information hiding). Sappiamo tutti che le cattive abitudini sono quelle pi difficili da perdere, quindi abituarsi a seguire quelle buone aiuter, sopratutto chi si avvicinato da poco al mondo dei microcontrollori, a scrivere da subito buoni programmi. L'articolo non vuole essere affatto una lezione ma semplicemente un' introduzione ad un modo di programmare che ritengo molto valido. Non me ne vogliano quindi gli informatici se mi prender alcune licenze e ricorrer ad approssimazioni. Per illustrare questi due semplici ma utilissime tecniche utilizzer un programma molto semplice: il classico LED lampeggiante, la versione da microcontrollore del programma "Hello World".

Un programma funzionante
Come esempio prendo il programma di prova che ho utilizzato in questo articolo Una cosa che salta subito all' occhio guardano le prime #include che utilizza un AVR, quindi un programma specifico per questa famiglia di micro. Scendendo pi in basso troviamo la routine di servizo dell'interrupt ciclica utilizzata per implementare un timer software e, all'interno del main riusciamo a capire che il LED collegato al pin 0 della porta B. Quello che faremo organizzare il programma in modo che sia un programma di tipo generale, adattabile a qualsiasi micro e con la possibilit di essere svincolati, oltre che dal micr,o anche dall'hardware di questo. //-------------------------------------------------------------------------// Lampeggio.c // // Created: Tanto tempo fa in una galassia molto lontana // Author: TardoFreak //-------------------------------------------------------------------------#include #include #include #include <avr/io.h> <avr/interrupt.h> <avr/wdt.h> <avr/pgmspace.h>

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

// Timer software. // Nota: vanno dichiarate come "volatile" per fare in modo che il // valore sia sempre e comunque letto evitando che l' ottimizzazione // non lo faccia. volatile unsigned short SoftTimer1; // Routine di servizio chiamata quando il contenuto del comparatore A // corrisponde al valore del timer. Quando raggiunge tale valore il // timer viene resettato e viene invocata questa routine. ISR(TIMER1_COMPA_vect) { if(SoftTimer1) SoftTimer1--; } //-------------------------------------------------------------------------int main(void) { // disabilita watchdog MCUSR &= ~(1 << WDRF); wdt_disable(); // Inizializza timer 1 per timeout 10ms // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato PRR0 &= ~(1<<PRTIM1); // Carica il registro di comparazione per ottenere 20ms OCR1A = 3124; // Enable output compare A match TIMSK1 = (1<<OCIE1A)|(0<<TOIE1); // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10); // Predispone la porta B come uscita DDRB = 0xff; // Abilita le interrupt sei(); while(1) { if(!SoftTimer1)

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

{ PORTB ^= 0x01; SoftTimer1 = 25; } } } // Commuta l' uscita PB0 // Carica il timer software per intervallo 500ms.

L' astrazione dell' hardware


Svincolarsi dall' hardware significa evitare di avere a che fare con le operazioni a basso livello. Per fare questo modifichiamo il programma dapprima definendo dei simboli per identificare la porta del LED ed il bit a cui collegato: #define LED_PORT PORTB #define LED_DDR DDRB #define LED_MASK 0x01 Questo gi un primo passo per svincolarsi dall' hardware perch, se un giorno noi decidessimo che il LED dovr essere collegato ad un altro pin, ci baster modificare queste defines per assegnare l'uscita dove meglio crediamo. L'uscita del LED deve essere inizializzata. Nel programma originale l'inizializzazione viene fatta con una semplice istruzione, trasformiamola in funzione e, per non farci mancare niente scriviamo anche due funzioni: una per accendere il LED e l' altra per spegnerlo. In questo modo abbiamo tutte le funzioni per controllare il LED come meglio ci aggrada. //-------------------------------------------------------------------------// void HAL_LEDon(void) // accende il LED void HAL_LEDon(void) { LED_PORT |= LED_MASK; } //-------------------------------------------------------------------------// void HAL_LEDoff(void) // spegne il LED void HAL_LEDoff(void) { LED_PORT &= (LED_MASK ^ 0xff); }

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

//-------------------------------------------------------------------------// void HAL_initlED(void) // inizializza l' uscita che pilota il LED void HAL_initLED(void) { // Predispone il pin 0 della la porta B come uscita LED_DDR |= LED_MASK; HAL_LEDoff(); } Ora scriviamo anche la funzione che fa commutare il LED. //-------------------------------------------------------------------------// // void HAL_toggleLED(void) cambia lo stato del LED

void HAL_toggleLED(void) { LED_PORT ^= LED_MASK; } Domanda: ma necessario tutto questo casino, questa complicazione? Non indispensabile per scrivere un programma che funzioni ma lo per scrivere un buon programma, pi avanti sar facile caipre il perch. Una piccola nota: il suffisso HAL messo davanti al nome delle funzioni un acronimo che significa "Hardware Abstraction Layer". Anche questo ha ragione di esistere ed anche la sua presenza sar pi chiara pi avanti.

Un modulo solo per l' hardware


Siamo gi ad un buon punto. Per fare le cose ben fatte la cosa migliore prendere queste funzioni e metterle all' interno di un modulo a parte. Cos facendo non impesteremo il programma principale con le funzioni relative all' hardware rendendolo pi semplice da leggere. Inoltre l' avere tutte le funzioni per la gestione dell' hardware in un solo modulo ci premetter una facile manutenzione del programma. In buona sostanza un modulo costituito da due files: un file sorgente dove risiedono le funzioni ed un file header da includere nel programma che usa questo modulo. Dobbiamo quindi creare questi due files che chiamiamo HAL_pierin.c (dove ci sono le funzioni) e HAL_pierin.h dove ci sono i prototipi di queste funzioni. Incominciamo con il file header #ifndef HAL_PIERIN_H #define HAL_PIERIN_H

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

#define LED_PORT PORTB #define LED_DDR DDRB #define LED_MASK 0x01 extern extern extern extern #endif // Fine file header Nota: la prima e la seconda linea del file header servono per evitare inclusioni multiple. Una volta incluso il file il simbolo HAL_PIERIN_H sar definito quindi, in caso di inclusioni annidate i simboli seguenti non verranno ridefiniti e non si generer nessun errore. E questo il file del modulo #include #include #include #include <avr/io.h> <avr/interrupt.h> <avr/wdt.h> <avr/pgmspace.h> void void void void HAL_LEDon(void); HAL_LEDoff(void); HAL_initLED(void); HAL_toggleLED(void);

#include "HAL_pierin.h" //-------------------------------------------------------------------------// void HAL_LEDon(void) // accende il LED void HAL_LEDon(void) { LED_PORT |= LED_MASK; } //-------------------------------------------------------------------------// void HAL_LEDoff(void) // spegne il LED void HAL_LEDoff(void) { LED_PORT &= (LED_MASK ^ 0xff); }

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

//-------------------------------------------------------------------------// void HAL_initlED(void) // inizializza l' uscita che pilota il LED void HAL_initLED(void) { // Predispone il pin 0 della la porta B come uscita LED_DDR |= LED_MASK; HAL_LEDoff(); } //-------------------------------------------------------------------------// void HAL_toggleLED(void) // cambia lo stato del LED void HAL_toggleLED(void) { LED_PORT ^= LED_MASK; } // Fine modulo Ora basta includere il file header nel programma principale e le funzioni per la gestione dell' hardware saranno disponibili. Il programma principale sar ora questo: //-------------------------------------------------------------------------// Lampeggio.c // // Created: Tanto tempo fa in una galassia molto lontana // Author: TardoFreak //-------------------------------------------------------------------------#include #include #include #include <avr/io.h> <avr/interrupt.h> <avr/wdt.h> <avr/pgmspace.h>

#include "HAL_pierin.h" // // // // Timer software. Nota: vanno dichiarate come "volatile" per fare in modo che il valore sia sempre e comunque letto evitando che l' ottimizzazione non lo faccia.

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

volatile unsigned short SoftTimer1; // Routine di servizio chiamata quando il contenuto del comparatore A // corrisponde al valore del timer. Quando raggiunge tale valore il // timer viene resettato e viene invocata questa routine. ISR(TIMER1_COMPA_vect) { if(SoftTimer1) SoftTimer1--; }

//-------------------------------------------------------------------------int main(void) { // disabilita watchdog MCUSR &= ~(1 << WDRF); wdt_disable(); // Inizializza timer 1 per timeout 10ms // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato PRR0 &= ~(1<<PRTIM1); // Carica il registro di comparazione per ottenere 20ms OCR1A = 3124; // Enable output compare A match TIMSK1 = (1<<OCIE1A)|(0<<TOIE1); // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10); HAL_initLED(); // Abilita le interrupt sei(); while(1) { if(!SoftTimer1) { //PORTB ^= 0x01; // Commuta l' uscita PB0 HAL_toggleLED();

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

SoftTimer1 = 25; } } }

// Carica il timer software per intervallo 500ms.

Avanti cos!
Come si dice "abbiamo fatto trenta, facciamo trentuno". E' ora di includere nel modulo di astrazione dell' hardware anche tutte le altre funzioni ed inizializzazioni della macchina compresa la funzione di servizio dell' interrupt ciclica. In questo modo il programma principale sar ridotto all' essenziale e non ci sar traccia di qualsiasi cosa dipendente dall' hardware. Aggiungiamo quindi al nostro modulo le funzione di inizializzazione del microcontrollore e dell' interrupt ciclica. //-------------------------------------------------------------------------// void initMicro(void) // inizializza il microcontrollore void HAL_initMicro(void) { // disabilita watchdog MCUSR &= ~(1 << WDRF); wdt_disable(); } //-------------------------------------------------------------------------// void initCyclicInt(void) // Inizializza l' interrupt ciclica void HAL_initCyclicInt(void) { // Inizializza timer 1 per timeout 10ms // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato PRR0 &= ~(1<<PRTIM1); // Carica il registro di comparazione per ottenere 20ms OCR1A = 3124; // Enable output compare A match TIMSK1 = (1<<OCIE1A)|(0<<TOIE1); // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10); }

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

E quindi facciamo che spostare anche la funzione di servizio dell' interrupt. Questa funzione utilizza per una variabile chiamata SoftTimer1 che dichiarata come variabile globale. E' buona cosa utilizzare per leggere il suo valore e per impostarlo delle funzioni a posta in modo da essere sicuri che nessuno possa accedervi erroneamente. Quindi dichiariamo la variabile all' interno del modulo HAL e scriviamo le due funzioni per leggerne il valore e per impostarlo. //-------------------------------------------------------------------------// void HAL_setTimer1(unsigned int valore) // imposta il valore del timer software 1 void HAL_setTimer1(unsigned int valore) { SoftTimer1 = valore; } //-------------------------------------------------------------------------// unsigned int HAL_getTimer1(void) // legge il valore del timer software 1 unsigned int HAL_getTimer1(void) { return(SoftTimer1); } Ora il nostro programma principale sar ridotto a questo. //-------------------------------------------------------------------------// Lampeggio.c // // Created: Tanto tempo fa in una galassia molto lontana // Author: TardoFreak //-------------------------------------------------------------------------#include #include #include #include <avr/io.h> <avr/interrupt.h> <avr/wdt.h> <avr/pgmspace.h>

#include "HAL_pierin.h" //-------------------------------------------------------------------------int main(void) { HAL_initMicro();

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

HAL_initCyclicInt(); HAL_initLED(); // Abilita le interrupt sei(); while(1) { if(!HAL_getTimer1()) { HAL_toggleLED(); HAL_setTimer1(25); } } } Come si pu notare non vi pi traccia di operazioni a basso livello ma non abbiamo ancora finito!

// Carica il timer software per intervallo 500ms.

Il tocco finale
E' arrivato finalmente il momento di dare il tocco finale all' operazione. Come potete notare ci sono ancora alcune funzioni che sono specifiche del microcontrollore utilizzato come l' istruzione di abilitazione delle interrupt e le inizializzazioni. Vogliamo che queste non solo siano incluse nel modulo relativo all' hardware ma che siano raggruppate in un unica funzione di inizializzazione del sistema. In questo modo potremo anche togliere dal main l' inclusione dei files del micro e lasciare solo l' inclusione del modulo che fa riferimento al microcontrollore ed al suo hardware. Dobbiamo nascondere al programma principale alcune informazioni come le defines del pin del LED e della variabile volatile utilizzata per il timer software. Scrivendo un unica funzione di inizializzazione potremo anche nascondere le inizializzazioni specifiche della interrupt ciclica, dei LED e del micro. In questo modo siamo sicuri che nessuna parte del programma principale possa fare casini aumentandone l' affidabilit. Inoltre possiamo rendere queste funzioni visibili solo a livello di modulo togliendo i prototipi dal file header ed assicurarci che non possano essere in qualsiasi modo richiamate. Per fare questo useremo la parolina magica static che metteremo davanti alla dichiarazione delle funzioni che vogliamo nascondere. I tre files che compongono il progetto, alla fine di tutto questo lavoro saranno questi. // File HAL_pierin.h #ifndef HAL_PIERIN_H #define HAL_PIERIN_H extern void HAL_LEDon(void); extern void HAL_LEDoff(void);

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

10

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

extern void HAL_initLED(void); extern void HAL_toggleLED(void); extern void HAL_setTimer1(unsigned int valore); extern unsigned int HAL_getTimer1(void); extern void HAL_initSystem(void); #endif // Fine file header // File HAL_pierin.c #include #include #include #include <avr/io.h> <avr/interrupt.h> <avr/wdt.h> <avr/pgmspace.h>

#include "HAL_pierin.h" // Defines per il pin di uscita per il pilotaggio del LED #define LED_PORT PORTB #define LED_DDR DDRB #define LED_MASK 0x01 static void HAL_initMicro(void); static void HAL_initCyclicInt(void); // Timer software. // Nota: vanno dichiarate come "volatile" per fare in modo che il // valore sia sempre e comunque letto evitando che l' ottimizzazione // non lo faccia. volatile unsigned short SoftTimer1; //-------------------------------------------------------------------------// void HAL_LEDon(void) // accende il LED void HAL_LEDon(void) { LED_PORT |= LED_MASK; }

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

11

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

//-------------------------------------------------------------------------// void HAL_LEDoff(void) // spegne il LED void HAL_LEDoff(void) { LED_PORT &= (LED_MASK ^ 0xff); } //-------------------------------------------------------------------------// void HAL_initlED(void) // inizializza l' uscita che pilota il LED void HAL_initLED(void) { // Predispone il pin 0 della la porta B come uscita LED_DDR |= LED_MASK; HAL_LEDoff(); } //-------------------------------------------------------------------------// void HAL_toggleLED(void) // cambia lo stato del LED void HAL_toggleLED(void) { LED_PORT ^= LED_MASK; } //-------------------------------------------------------------------------// void HAL_initMicro(void) // inizializza il microcontrollore static void HAL_initMicro(void) { // disabilita watchdog MCUSR &= ~(1 << WDRF); wdt_disable(); } //-------------------------------------------------------------------------// void HAL_initCyclicInt(void) // Inizializza l' interrupt ciclica

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

12

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

static void HAL_initCyclicInt(void) { // Inizializza timer 1 per timeout 10ms // Questa istruzione assicura che l' I/O clock per il timer1 sia abilitato PRR0 &= ~(1<<PRTIM1); // Carica il registro di comparazione per ottenere 20ms OCR1A = 3124; // Enable output compare A match TIMSK1 = (1<<OCIE1A)|(0<<TOIE1); // Avvia il timer1, prescaler 1/64 modo operativo Clear Top Count TCCR1B = (0<<WGM13)|(1<<WGM12)|(0<<CS12)|(1<<CS11)|(1<<CS10); } //-------------------------------------------------------------------------// Routine di servizio chiamata quando il contenuto del comparatore A // corrisponde al valore del timer. Quando raggiunge tale valore il // timer viene resettato e viene invocata questa routine. ISR(TIMER1_COMPA_vect) { if(SoftTimer1) SoftTimer1--; } //-------------------------------------------------------------------------// void HAL_setTimer1(unsigned int valore) // imposta il valore del timer software 1 void HAL_setTimer1(unsigned int valore) { SoftTimer1 = valore; } //-------------------------------------------------------------------------// unsigned int HAL_getTimer1(void) // legge il valore del timer software 1 unsigned int HAL_getTimer1(void) { return(SoftTimer1); }

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

13

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

//-------------------------------------------------------------------------// void HAL_initSystem(void) // inizializza tutto il sistema void HAL_initSystem(void) { HAL_initMicro(); HAL_initCyclicInt(); HAL_initLED(); // Abilita le interrupt sei(); } // Fine modulo //-------------------------------------------------------------------------// Lampeggio.c // // Created: Tanto tempo fa in una galassia molto lontana // Author: TardoFreak //-------------------------------------------------------------------------#include "HAL_pierin.h" //-------------------------------------------------------------------------int main(void) { HAL_initSystem(); while(1) { if(!HAL_getTimer1()) { HAL_toggleLED(); HAL_setTimer1(25); } } }

// Carica il timer software per intervallo 500ms.

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

14

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

Cambiare pin e microcontrollore


E' facile intuire come si pu cambiare il pin che pilota il LED infatti basta modificare le defines poste all' inizio del modulo di astrazione dell' hardware ma non solo, anche possibile, modificando opportunamente le funzioni di accensione/spegnimento/commutazione del LED utilizzare anche una circuiteria diversa. In queso esempio il pin collegato al LED con una semplice resistenza ed il LED collegato a 0V. Nel caso dovessimo per forza, comodit o altri motivi utilizzare un LED collegato verso l' alimentazione sar sufficiente modificare le funzioni. Ora evidente che il programma principale una boiata ma supponiamo di avere un programma grande e complesso dove in pi punti necessario comandare il LED. Se si usassero operazioni a basso livello bisognerebbe modificarle tutte mentre cos basta modificare solo le tre funzioni. E se invece il LED deve essere sostituito, ad esempio, con un pallino su uno schermo LCD? Anche qui il problema non esiste perch sempre solo tre sono le funzioni da modificare ma non si toccherebbe affatto il programma principale. E se dobbiamo cambiare il microcontrollore sar sufficiente sviluppare il modulo specifico per il nuovo microcontrollore lasciando cos inalterato il programma principale. Se, ad esempio, vorr utilizzare un PIC o un ARM mi baster riscrivere le funzioni di gestione dell' hardware per levarmi la paura.

Conclusioni
Ho preso come esempio un programma semplice ma mi pare che oramai sia chiaro che questo modo di scrivere i programmi raggiunge gli obiettivi principali: ordine, facile manutenibilt, affidabilit e flessibilit. Si potrebbe obiettare che il codice ottenuto pi lento e corposo. Questo potrebbe essere vero se i compilatori fossero stupidi ma i compilatori di oggi sono mooolto furbi. Se si accorgono che una funzione corta scrivono il codice direttamente senza chiamarla. I compilatori della Mikroelektronika (MikroC), seppur economici, gi lo fanno. Per non parlare poi di compilatori come quello della KEIL. Sono talmente furbi che queste cose le fanno senza dire niente a nessuno. In questo breve articolo ho illustrato solo il layer di astrazione dell' hardware ma non l' unico layer che di solito si implementa. Una altro layer il cosiddetto API (Application Program Interface) che solitamente racchiude anche quello di astrazione dell' hardware. I prefissi HAL che ho messo davanti alle funzioni servono appunto per indicare il layer a cui si fa riferimento proprio per evitare intrusioni accidentali a livelli pi bassi. Un' altra piccola considerazione: la pratica di nascondere informazioni e di incapsulare i moduli dentro altri moduli quello che poi ha determinato lo sviluppo dei linguaggio orientati a gli oggetti (come il C++) e, di conseguenza, anche quelli fortemente orientati agli oggetti come il Java per sempio. In C++, ad esempio, quando si crea un oggetto viene eseguito subito il metodo chiamato "costruttore" che poi altro non che la onnipresente funzione di inizializzazione di un modulo scritto in C e l' ereditarit resa pi semplice mentre in C passa attraverso i files header. Che altro dire? Divertitevi con i micro e, se potete, evitate di prendere cattive abitudini di programmazione in modo da poter scrivere buoni programmi.

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

15

ELECTROYOU.IT

STEVE BLACKBIRD (TARDOFREAK)

La fine del mondo non arrivata, il Natale alle porte quindi non mi resta che auguravi BUONE FESTE e buona sperimentazione. Estratto da "http://www.electroyou.it/tardofreak/wiki/e"

MICROCONTROLLORI: COME SVINCOLARSI DALL'HARDWARE.

16