Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
IT
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>
ELECTROYOU.IT
// 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)
ELECTROYOU.IT
{ PORTB ^= 0x01; SoftTimer1 = 25; } } } // Commuta l' uscita PB0 // Carica il timer software per intervallo 500ms.
ELECTROYOU.IT
//-------------------------------------------------------------------------// 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.
ELECTROYOU.IT
#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); }
ELECTROYOU.IT
//-------------------------------------------------------------------------// 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.
ELECTROYOU.IT
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();
ELECTROYOU.IT
SoftTimer1 = 25; } } }
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); }
ELECTROYOU.IT
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>
ELECTROYOU.IT
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!
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);
10
ELECTROYOU.IT
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; }
11
ELECTROYOU.IT
//-------------------------------------------------------------------------// 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
12
ELECTROYOU.IT
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); }
13
ELECTROYOU.IT
//-------------------------------------------------------------------------// 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); } } }
14
ELECTROYOU.IT
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.
15
ELECTROYOU.IT
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"
16