C18 Step by Step
C18 Step by Step
C18 Step by Step
1a
www.LaurTec.com
C18 step by step
1/93
INFORMATIVA Come prescritto dall'art. 1, comma 1, della legge 21 maggio 2004 n.128, l'autore avvisa di aver assolto, per la seguente opera dell'ingegno, a tutti gli obblighi della legge 22 Aprile del 1941 n. 633, sulla tutela del diritto d'autore. Tutti i diritti di questa opera sono riservati. Ogni riproduzione ed ogni altra forma di diffusione al pubblico dell'opera, o parte di essa, senza un'autorizzazione scritta dell'autore, rappresenta una violazione della legge che tutela il diritto d'autore, in particolare non ne consentito un utilizzo per trarne profitto. La mancata osservanza della legge 22 Aprile del 1941 n. 633 perseguibile con la reclusione o sanzione pecuniaria, come descritto al Titolo III, Capo III, Sezione II. A norma dell'art. 70 comunque consentito, per scopi di critica o discussione, il riassunto e la citazione, accompagnati dalla menzione del titolo dell'opera e dal nome dell'autore.
AVVERTENZE I progetti presentati non hanno la certificazione CE, quindi non possono essere utilizzati per scopi commerciali nella Comunit Economica Europea. Chiunque decida di far uso delle nozioni riportate nella seguente opera o decida di realizzare i circuiti proposti, tenuto pertanto a prestare la massima attenzione in osservanza alle normative in vigore sulla sicurezza. L'autore declina ogni responsabilit per eventuali danni causati a persone, animali o cose derivante dall'utilizzo diretto o indiretto del materiale, dei dispositivi o del software presentati nella seguente opera. Si fa inoltre presente che quanto riportato viene fornito cosi com', a solo scopo didattico e formativo, senza garanzia alcuna della sua correttezza. L'autore ringrazia anticipatamente per la segnalazione di ogni errore.
2/93
www.LaurTec.com
Introduzione
In questo Tutorial si spiegano le basi per programmare in MPLAB C18 Student version permettendo di raggiungere un livello di esperienza sufficiente per affrontare ogni tipo di problema. Una conoscenza base del C richiesta per una pi veloce comprensione del testo ma non fondamentale. Ogni programma d'esempio spiegato passo passo illustrando anche la sintassi del C stesso mettendo inoltre in evidenza eventuali differenze tra il C18 e l'ANSI C. Per agevolare anche i pi inesperti vengono illustrati anche i passi che bisogna seguire per ottenere un progetto completo. Come demo board viene utilizzato il sistema embedded Freedom con il PIC 18F4580.
L'unico limite la memoria del PIC che si sta utilizzando. Il linguaggio assembly rappresenta il primo livello di astrazione tra il mondo del microcontrollore, che ragiona in binario, e il mondo umano...che non ragiona! Una tipica periferica che ritorna utile e l'USART presente nel PIC16F877, questa permette una facile connessione del PIC al computer senza preoccuparsi della gestione del protocollo seriale. Per ulteriori informazioni sulla trasmissione seriale RS232 si rimanda al Tutorial Il Protocollo RS232. Kw o KB. KB significa KiloByte, ovvero 1000 locazioni di memoria da 1 Byte. Il set di istruzioni dei PIC per a 14 bit quindi pi di un Byte. I questi casi si parla pi propriamente di Word (parola) da cui Kw. In futuro si user solamente la K ma il lettore sapr di cosa sto parlando.
3/93
www.LaurTec.com
quelle applicazioni in cui richiesto un controllo totale dell'Hardware. In questo Tutorial accenner solamente a questi casi permettendo al lettore di avere una visione d'insieme. Tra poco iniziamo...ma...perch non il PIC16F877 ? La ragione per cui ho scelto il PIC18F4580 risiede nella natura umana della sete di conoscenza e di potere! Infatti molti hanno iniziato con il PIC16F84 per poi inevitabilmente passare al PIC16F877 o PIC16F876...chi si fermato qui...sogna i PIC18F i brave hart sono passati alla famiglia 18F. La differenza di costo tra un PIC16F e un PIC18F non elevata se non inesistente, dunque lo sforzo di un passo un po' pi lungo ricompensato dal fatto che ogni possibile applicazione potr essere svolta dal vostro PIC...considerate che sulla luna son andati con una potenza di calcolo molto inferiore ad un PIC18F...quindi potete sognare anche come programmare un viaggetto sulla luna!
4/93
www.LaurTec.com
Installazione del software
Ancora qualche piccolo passo prima d'iniziare...tutto il software di cui si parler possibile scaricarlo gratuitamente dal sito della Microchip www.microchip.com . Come prima cosa dobbiamo scaricare i seguenti programmi MPLAB IDE e MPLAB C18. Prima di installare MPLAB C18 bisogna installare MPLAB IDE. La versione a cui si fa riferimento la 7.4. All'inizio dell'installazione di MPLAB IDE si ha la Figura 1.
Premendo Next comparir la Licenza di Figura 2...leggete la Licenza! Se non l'accettate non avete ragione di continuare a leggere questo Tutorial!
5/93
www.LaurTec.com
Bene avete accettato la licenza...possiamo continuare. Premendo nuovamente Next selezionare sulla nuova finestra la voce Complete come riportato in Figura 3.
Premendo nuovamente il tasto Next possibile impostare il percorso di installazione come riportato in Figura 4. Se non si hanno particolari esigenze non c' ragione di cambiare il percorso di Default, per cui premete nuovamente Next.
6/93
www.LaurTec.com
Come riportato in Figura 5 c' una nuova licenza che bisogna accettare o meno. Il consiglio di accettarla anche se probabilmente non ne farete uso. L'applicazione Maestro alla quale questa Licenza fa riferimento una particolare applicazione che raccoglie un certo numero di librerie da utilizzare nel caso si programmi in Assembly. In questo Tutorial non se ne far uso ma la sua presenza non nuocer!...dunque premere nuovamente Next...se si accettata la Licenza!
A questo punto il software ha le informazioni necessarie per iniziare l'installazione, queste vengono riassunte come riportato in Figura 6. Premendo nuovamente Next si avvia l'installazione.
7/93
www.LaurTec.com
Durante la fase d'installazione verr richiesto d'installare il driver per USB (Figura 8) in questo Tutorial non si utilizzer questo driver ma potrebbe ritornare utile in futuro se si pensa di utilizzare Tools Microchip, dunque installate il driver premendo Next.
8/93
www.LaurTec.com
Dopo l'installazione del driver, MPLAB IDE installato. La conferma di fine installazione avviene per mezzo della finestra riportata in Figura 9. Per completare l'installazione per necessario riavviare il Computer. Premendo Finish il sistema operativo viene automaticamente riavviato.
Al riavvio del Computer MPLAB IDE completamente installato. A questo punto possibile iniziare l'installazione di MPLAB C18. La versione alla quale si fa riferimento la 3.02. In Figura 10 riportata la prima finestra di dialogo premendo Next si avvia l'installazione.
9/93
www.LaurTec.com
10/93
www.LaurTec.com
Il programma quasi pronto per installare l'applicazione. In Figura 12 riportata la lista con una breve descrizione dei vari punti riportati in Table of Contets. Premere Next per continuare.
In Figura 13 riportata la finestra per mezzo della quale possibile cambiare il percorso d'installazione. Se non si hanno particolari esigenze bene lasciare il percorso di Default. Premere Next per continuare.
11/93
www.LaurTec.com
In Figura 14 riportata la lista di ci che verr installato. E' possibile rimuovere alcune parti a seconda delle proprie esigenze ma bene selezionare quello riportato in in Figura 14. Premere Next per continuare.
In Figura 15 sono riportate alcune alcune impostazione per configurare il sistema operativo avvertendolo della presenza di C18. Selezionare le opzioni come riportato in Figura 15. Premere Next per continuare.
12/93
www.LaurTec.com
In Figura 16 riportata la finestra che avvisa che C18 pronto per essere installato. La Warning della finestra avvisa che l'installazione scriver sopra i vecchi file cancellando il contenuto di precedenti installazioni. Se questa installazione non dovesse essere la prima e si dovesse avere qualche file all'interno delle directory d'installazione bene salvarne una copia prima di proseguire. Fatto questo si pu premere Next per avviare l'installazione. Se questa la prima installazione si pu tranquillamente installare il programma.
13/93
www.LaurTec.com
14/93
www.LaurTec.com
Il nostro primo progetto
...Ci siamo quasi. Per poter scrivere un programma e compilarlo necessario creare un progetto. Un progetto non altro che una collezione di files che contengono tutte le informazioni sul nostro lavoro. In particolare sar sempre presente il programma sorgente e files di libreria. L'ambiente di lavoro aggiunger poi altri files per mantenere anche altre informazioni, ma di questi si parler in seguito. Quando si eseguir MPLAB per la prima volta si avr la finestra riportata in Figura 19.
Per creare un nuovo progetto andare sul men Project e selezionare Project wizard . Si aprir una finestra che guider la creazione di un nuovo progetto, come riportato in Figura 20. Cliccando sul pulsante Next si avr una nuova finestra di Figura 22, dove bisogner selezionare il PIC della famiglia PIC18 che si vuole utilizzare. Negli esempi che seguiranno si far uso del PIC18F4580 montato sul sistema embedded Freedom5. Dopo aver selezionato il PIC si pu premere nuovamente il pulsante Next. Il PCB di Freedom puo' essere richiesto alla sezione servizi del sito www.LaurTec.com per mezzo di semplice donazione di supporto. Tutta la documentazione della scheda stessa sono gratuitamente scaricabili dal sito.
Un qualunque altro sistema di sviluppo o PIC della famiglia PIC18 in generale utilizzabile.
15/93
www.LaurTec.com
La nuova finestra di dialogo che appare (Figura 22) quella per mezzo della quale si imposta MPLAB affinch lavori con C18. Infatti MPLAB un IDE che pu lavorare anche con altri compilatori, come per esempio MPASM. Nel nostro caso bisogna selezionare come Active Toolsuite la voce Microchip C18 Toolsuite, come riportato in Figura 22. Dopo aver impostato il Toolsuite si pu premere il tasto Next.
16/93
www.LaurTec.com
Nella nuova finestra di dialogo, riportata in Figura 23 possibile impostare il nome del progetto, ovvero del lavoro che si sta facendo, e il percorso dove salvarlo. E' buona abitudine avere una cartella dove salvare tutti i progetti, in questo esempio la cartella situata in C:\Programmi\ dove si poi creata la sottocartella HelloWorld
Figura 23: Finestra di dialogo per l'impostazione del nome del progetto
17/93
www.LaurTec.com
Una volta impostato il nome e il percorso dove salvare il progetto si pu premere il tasto Next. Per mezzo della nuova finestra di dialogo riportata in Figura 24 possibile inserire files all'interno del nostro progetto. I file che si inseriscono in questo punto possono comunque essere aggiunti in un secondo momento dunque questa fase potrebbe anche essere saltata.
Per far vedere come aggiungere i file nel secondo modo si salter questa fase premendo semplicemente Next. Fatto questo la creazione del progetto terminata e si ottiene una finestra di riepilogo come in Figura 25.
Figura 25: Finestra di riepilogo alla fine della creazione del Progetto
18/93
www.LaurTec.com
L'interfaccia IDE apparir ora come in Figura 26.
A questo punto bisogner aggiungere i files che non si sono inseriti precedentemente. Un file che sempre necessario aggiungere il file per il linker con estensione .lkr. Ogni PIC possiede pi file di .lkr a seconda della modalit con cui si sta programmando il PIC stesso. In questo Tutorial, come detto, la modalit estesa non verr trattata dunque il file .lkr che bisogner inserire sar 18F4580.lkr. Per inserire questo file bisogna selezionare la cartella Linker Script dalla finestra all'angolo sinistro della finestra principale e premere il tasto destro del mouse. Fatto questo bisogna selezionare la voce Add File... e cercare il file nella directory C:\MCC18\lkr6. Aggiunto il nostro file possiamo ora aggiungere il file principale dove scrivere il nostro programma. In C tipico scrivere il programma principale all'interno di un file nominato main.c dove main significa principale7. Per inserire il nostro file bisogna creare una nuova finestra di testo dal men File e selezionando poi New. Il file di testo cosi creato non appartiene per ancora al nostro progetto. Per poter integrare il file al progetto bisogna salvare il file da qualche parte e poi aggiungerlo alla cartella Source Files, come si fatto per lo script precedente. Ragionevolmente il file va salvato all'interno della directory in cui stiamo lavorando, ovvero C:\Programmi\HelloWorld in modo da avere tutti i file
6
Questa directory potrebbe essere diversa se durante la fase di installazione del programma si scelto di installare C18 in un'altra directory. Altra abitudine anche chiamare il file principale con un nome che descriva l'applicazione, quindi in questo caso potrebbe essere chiamato HelloWorld.
19/93
www.LaurTec.com
in un solo punto. Dopo aver salvato il file di testo con il nome main.c ed inserito nella cartella Source Files, l'IDE avr il nuovo aspetto riportato in Figura 27.
Figura 27: Nuovo aspetto dell'IDE dopo l'inserimento dei vari files e la creazione del main.c
A questo punto il pi fatto. Ultima cosa che bene fare accertarsi che la finestra di dialogo di Figura 28 sia impostata con i percorsi corretti. Per qualche strana ragione ogni volta che si crea un nuovo progetto i percorsi non vengono memorizzati. Per richiamare tale finestra basta cliccare sulla piccola icona con la cartella verde e l'ingranaggio, presente sulla Toolbar. Fatto questo possibile iniziare a scrivere all'interno della finestra di testo, che si precedentemente creata, il nostro primo programma chiamato...in maniera molto originale...Hello World...
20/93
www.LaurTec.com
Il primo programma non molto complicato o quantomeno non molto utile, dal momento che permette di accendere solo un LED. Ci nonostante verr spiegato riga per riga in modo da poter comprendere la sintassi del C e avere una panoramica anche del funzionamento del PIC. Il programma che ci permette di accendere il LED il seguente8 :
1 #include <p18f4580.h> 2 3 #pragma config OSC = HS // 20Mhz 4 #pragma config WDT = OFF // disabilito il watchdog timer 5 #pragma config LVP = OFF // disabilito programmazione LVP 6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB
8
Si fa notare che i programmi completi pronti per essere testati sono solo quelli in cui riportata la numerazione delle righe. Gli altri programmi di esempio sono solo segmenti di programma che richiedono in generale di altre righe di codice (inizializzazioni) per poter funzionare. Copiando e incollando il programma bisogna cancellare il numero delle righe.
21/93
www.LaurTec.com
7 8 9 void main (void) 10 { TRISA = 0xFF; // Tutti 11 PORTA = 0x00; 12 13 TRISB = 0xFF; // Tutti 14 PORTB = 0x00; 15 16 TRISC = 0xFF; // Tutti 17 PORTC = 0x00; 18 19 TRISD = 0xFF; // Tutti 20 PORTD = 0x00; 21 22 TRISE = 0b11111101; // 23 PORTE = 0x00; 24 25 PORTEbits.RE1 = 1; 26 27 28 while (1) 29 { 30 } 31 32 }
ingressi
ingressi
ingressi
ingressi
RE1 un'uscita
Per poterlo eseguire su un microcontrollore necessario prima compilarlo per accertarsi che non siano presenti errori di sintassi. Per compilare il programma si pu premere Control+F10 o andare sul men Project e selezionare Build All o premere l'icona bianca con le due freccette blu in basso presente nella Toolbar. Se non si hanno errori si avr la finestra di dialogo riportata in Figura 29 in cui si viene avvisati che la compilazione avvenuta correttamente.
22/93
www.LaurTec.com
Se sono presenti errori si avr il numero di errori e la riga dove stato commesso l'errore. Per correggere l'errore bene procedere dalla correzione del primo errore e poi ricompilare. Infatti capita spesso che errori multipli non siano altro che gli effetti collaterali di un singolo errore. Dopo la compilazione del programma possibile vedere che all'interno della directory del nostro progetto C:\Programmi\HelloWorld\ sono presenti altri files. In particolare pu risultare interessante il file .map che contiene informazioni sulle variabili e l'uso della memoria. Lo scopo della compilazione per quella di ottenere il file .hex in cui presente il codice in esadecimale del programma da noi scritto. Questo il file che viene fisicamente caricato all'interno del PIC affinch possa eseguire quanto scritto nel file main.c. Per caricare il programma all'interno del PIC necessario un programmatore per PIC e il programma che gestisca il programmatore stesso. Vediamo ora di comprendere il programma precedentemente scritto. Alla riga 1 troviamo la direttiva #include che permette di includere un file al nostro programma sorgente. Le direttive in C iniziano sempre con # e rappresentano delle indicazioni che il preprocessore (e non il compilatore) utilizzer prima di avviare la compilazione del programma. Le direttive non appartengono al programma che il PIC fisicamente eseguir; questo significa che una direttiva non un'istruzione eseguibile dal PIC e non verr dunque tradotta in codice eseguibile. Con la direttiva #include nella riga 1 viene incluso il file p18f4580.h, questo file contiene le informazioni del PIC che si sta utilizzando9. Ci significa che a seconda del PIC che si utilizza il file da includere sar diverso.
9
Tra le informazioni presenti vi il nome dei registri che in generale hanno lo stesso nome utilizzato anche sul data sheet del PIC che si sta utilizzando. L'utilizzo del nome dei registri permette di non considerare la locazione di memoria in cui sono fisicamente presenti permettendo dunque di raggiungere un livello di astrazione che semplifica il programma stesso.
23/93
www.LaurTec.com
Dalla riga 3 alla 6 presente la direttiva #pragma, a differenza della direttiva precedente che appartiene all'ANSI C, questa direttiva stata introdotta da Microchip e dunque non ANSI C. In questo caso la direttiva #pragma utilizzata per modificare i registri di configurazione del PIC. Ogni PIC ha uno o pi registri di configurazione il cui scopo settare l'hardware di base del PIC stesso in modo che possa funzionare correttamente ogni volta che viene alimentato il PIC. Alla riga 3 si configura l'oscillatore ad HS, ovvero ad alta velocit questo poich sul sistema Freedom si deciso di montare un quarzo da 20MHz. Ogni PIC ha pi modalit di oscillazione, in particolare il PIC18F4580 possiede anche un quarzo interno, dunque i potrebbe utilizzare anche questo10. Alla riga 4 viene configurato il WDT ovvero il watch dog (cane da guardia) in particolare questo viene disattivato avendo scritto OFF11. Il watch dog rappresenta un contatore interno al PIC che deve essere, se attivato, ricaricato via software prima che si scarichi e resetti il sistema. Questa apparente spina sul fianco risulta in realt molto utile poich grazie a questo contatore si riesce a resettare il sistema automaticamente qualora sia entrato in stallo. Infatti un programma in stallo, in generale, non ricaricher il registro WDT dunque quando questo si scarica resetter il sistema permettendo al programma di riprendere le normali operazioni12. Alla riga 5 viene disattivata la modalit di programmazione LVP visto che non verr utilizzata13. Alla riga 6 viene posto a OFF il bit PBADEN in modo da utilizzare gli ingressi analogici presenti sulla PORTB come normali ingressi o uscite digitali. Si fa presente che questo bit non sempre presente su tutti PIC poich non tutti hanno ingressi analogici anche sulla PORTB. Quanto appena descritto in realt stato descritto parzialmente come commento alla destra delle righe di codice. I commenti in C devono essere preceduti dal doppio slash //. I commenti cosi scritti devono per stare su di una sola riga. Se si volessero scrivere pi righe di commento bisogna scrivere // all'inizio di ogni riga o scrivere /* alla prima riga e scrivere */ alla fine dell'ultima riga. Un possibile esempio di commento a riga multipla il seguente:
// questo un commento // che non entrerebbe su una sola riga
o anche
/* questo un commento che non entrerebbe su una sola riga */
o ancora
/* questo un commento che non entrerebbe su una sola riga */
Vediamo ora il programma vero e proprio. Ogni programma C una collezione di funzioni 14 che possono o meno essere scritte sullo stesso file. Il numero di funzioni presente in ogni programma viene a dipendere dal programmatore e dal modo con cui ha organizzato la soluzione del suo problema. In ogni modo all'interno di ogni programma C deve essere sempre presente una funzione nominata main. La funzione main quella che il compilatore andr a cercare per prima e dalla quale organizzer la
10 11 12 13 14
Per le varie modalit di oscillazione disponibili sul PIC che si sta utilizzando si rimanda al relativo data sheet. Per abilitare il WDT bisogna scrivere ON. Per ulteriori informazioni su tale registro si rimanda al data sheet del PIC utilizzato. Per ulteriori informazioni sulla programmazione LVP si rimanda al data sheet del PIC utilizzato. Ogni funzione contiene un certo numero d'istruzioni per mezzo delle quali la funzione svolge il compito per cui stata creata.
24/93
www.LaurTec.com
compilazione delle altre funzioni che saranno ragionevolmente collegate direttamente o indirettamente alla funzione main. Ogni funzione deve essere dichiarata nel modo seguente :
tipo_var_di_ritorno NomeFunzione (tipo_var_in_ingresso1)
in particolare la funzione main non ritorna nessun valore dunque viene scritto void, che sta ad indicare nessun valore. Inoltre la funzione main almeno per i PIC non accetta variabili in ingresso e dunque viene riscritto void15. Ogni funzione svolger qualche operazione che verr poi tradotta dal compilatore in istruzioni eseguibili dal PIC. Queste istruzioni devono essere contenute all'interno di due parentesi graffe una aperta e una chiusa, che stanno ad indicare l'inizio della funzione e la sua fine. Per scrivere le parentesi graffe con tastiere italiane pu esser un problema, questo problema non sentito dagli americani poich le parentesi graffe, come anche le quadre sono apparecchiate sulla tastiera. Un modo per scrivere le parentisi graffe per mezzo dei codici ASCII 123 ({) e 125 (}). Per scrivere tali caratteri sulla tastiera bisogna tenere premuto il tasto ALT e digitare il codice 123 o 125, e poi rilasciare il tasto ALT. Un secondo modo, utilizzabile solo se si hanno le parentesi quadre sulla tastiera quello di premere freccia maiuscole + Alt Gr + parentesi quadra. Dunque un programma che non fa nulla potrebbe essere scritto nel seguente modo:
void main (void) { //non faccio assolutamente nulla }
Vediamo ora il programma che abbiamo scritto all'interno delle nostre parentesi graffe. Alla riga 10 e 11 troviamo le nostre prime due istruzioni in C. E' possibile vedere che ogni istruzione termina con un punto e virgola 16. Le istruzioni alle righe 10 e 11 sono di assegnazione del numero esadecimale 0xFF ovvero del valore FF (255 in decimale) nelle variabili TRISA e PORTA. Queste due variabili non sono state dichiarate direttamente da noi ma sono presenti all'interno del file p18f4580.h che abbiamo incluso. Come visibile queste due variabili sono state scritte in maiuscolo, questo non stato fatto per metterle in evidenza ma poich sono state dichiarate maiuscole. Infatti il compilatore C case sensitive ovvero distingue tra maiuscole e minuscole. Dunque TRISA per il compilatore diverso da TrisA. Vediamo cosa sono queste due variabili. Ogni PIC possiede vari pin che possono essere in generale sia ingressi che uscite. I vari pin vengono raggruppati in porte che al massimo hanno 8 bits. Ogni porta ha un nome e singoli pin ereditano il nome dalla porta a cui appartengono. Ad ogni porta sono associati due registri17 il registro TRISx e PORTx con x il nome della porta. Il registro TRISx permette di settare individualmente come ingresso o come uscita ogni pin della porta. Il registro TRISx un registro a 8 bit in cui ogni bit corrisponde la configurazione ingresso uscita del bit corrispondente della porta. In particolare 1 viene utilizzato per indicare ingresso (Input) e 0 per indicare uscita (Output). Se per esempio si volesse configurare la PORTD met come ingressi e met come uscite si dovr impostare la variabile TRISD ad 00001111 che imposter i quattro bit meno significativi come ingressi e i 4 bit pi significativi come uscite 18. Il valore precedentemente scritto in binario corrisponde al valore esadecimale 0F o al valore decimale 1519. A seconda di come ci si trova meglio si pu scrivere nel registro TRISD un valore o l'altro. Per
15 16
17
18
19
Si comprenderanno meglio le funzioni quando verranno utilizzate altre funzioni oltre al main. Per chi ha esperienza di programmazione in Basic...e non, un errore tipico di sintassi scordarsi il punto e virgola. Il compilatore in questo caso individua l'errore alla riga successiva a quella in cui manca effettivamente il punto e virgola. In realt presente anche il registro LATCH la cui trattazione esula dagli scopi di questo Tutorial. Per ulteriori informazioni si rimanda al data sheet del PIC utilizzato. E' bene subito notare che alcune volte ci sono pin che hanno funzioni multiple e per poterli utilizzare come ingressi o come uscite digitali non basta semplicemente impostare TRISx. Un caso di questo tipo lo si gi incontrato con PORTB in cui necessario, se non si vogliono utilizzare gli ingressi analogici, configurare ad OFF il bit PBADEN. Per una facile conversione nei vari sistemi a base differenti si pu utilizzare la calcolatrice di Windows.
25/93
www.LaurTec.com
fare questo bisogna per rispettare la sintassi per far riconoscere al compilatore che stiamo utilizzando una base 2,10 o 16. I differenti modi per scrivere il numero precedente nelle varie basi sono:
TRISD = 0b00001111; TRISD = 0x0F; TRISD = 15; //numero binario //numero esadecimale //numero decimale
Il registro PORTx permette invece di leggere/scrivere il valore dalla/sulla rispettiva porta. Quando si vuole scrivere un valore su una porta la variabile PORTx sar alla sinistra dell'uguale, mentre se si vuole leggere un valore da una porta la variabile PORTx sar sulla destra dell'uguale. E' buon uso, ma non obbligatorio porre come ingressi i pin non utilizzati, in modo da evitare che cortocircuiti accidentali possano danneggiare il PIC. Per questa ragione la variabile TRISA in riga 10 stata posta ad 0xFF. Inoltre sempre buon uso porre a 0x00 tutte le eventuali uscite. In questo caso dal momento che si sono impostati tutti i bit come ingressi PORTA non verr in realt trasferita in uscita. Quanto appena detto per TRISA e PORTA valido anche per le altre porte. Le impostazioni sulle porte PORTA, PORTB, PORTC, PORTD, dal momento che non sono utilizzate si potrebbero anche evitare, ma per chiarezza e soprattuto per poter proteggere i pin non utilizzati sempre bene configurarle come si fatto in questo esempio. Alla riga 19, dal momento che si fa uso del LED o cicalino presente all'uscita RE1 del PIC del sistema embedded Freedom, si impostato il registro TRISE in modo che questo pin sia un'uscita20. Alla riga 25 presente una variabile particolare che permette di accedere al singolo pin della porta di interesse senza interferire con gli altri; questa operazione risulta utile in molte applicazioni. Ogni porta oltre ad avere la variabile PORTx possiede una variabile particolare (una struttura) nominata PORTxbits che permette di accedere ogni singolo pin della porta stessa con il suo nome. Questa particolare variabile associata alla PORTx presente anche per altri registri interni al PIC in cui si ha la necessit di leggere o scrivere singoli bit. Dunque l'istruzione alla riga 25, permette di accendere il LED o cicalino lasciando invariati gli altri bit della PORTE. Tra la riga 28 e 30 presente un ciclo while infinito che blocca il programma in un dolce far nulla21.
20 21
La PORTE come anche altre porte non ha 8 pin, dunque i bit non utilizzati di TRISE e PORTE vengono ignorati. L'istruzione while verr spiegata successivamente.
26/93
www.LaurTec.com
Tipi di variabili
Ogni volta che si scrive un programma, salvo il caso precedente, necessario svolgere delle operazioni che richiedono dei registri per memorizzare il risultato o i termini per l'operazione stessa. I registri non sono altro che locazioni di memoria RAM22 interna al microcontrollore che permettono di raggiungere il seguente scopo. Il numero di registri necessari per rappresentare un tipo di variabile varia a seconda del tipo di variabile. I registri all'interno dei microcontrollori PIC18F sono a 8 bits ovvero un byte. Il tipo di variabili disponibili in C che possibile utilizzare nella programmazione dei PIC sono riportate in Tabella 1 e Tabella 2. Tipo char signed char unsigned char int unsigned int short unsigned short short long unsigned short long long unsigned long 8 bits 8 bits 8 bits 16 bits 16 bits 16 bits 16 bits 24 bits 24 bits 32 bits 32 bits Dimensione -128 -128 0 -32.768 0 -32.768 0 -8.388.608 0 -2.147.483.648 0 Minimo Massimo +127 +127 255 +32.767 65.535 +32.767 65.535 +8.388.607 16.777.215 -2.147.483.647 4.294.967.295
Come possibile osservare a seconda del tipo di variabile lo spazio di memoria richiesto diverso. A seconda del tipo di dato che bisogna memorizzare e del valore numerico minimo o massimo, bisogna scegliere un tipo di variabile piuttosto che un'altra. Una scelta attenta permetter di risparmiare locazioni di memoria RAM e anche il tempo necessario per lo svolgimento delle operazioni. Infatti fare una somma tra due interi contenuti in una variabile char sar pi' veloce che la somma effettuata tra due interi di tipo int. La variabile di tipo char anche se formalmente pensata per contenere il valore numerico di un carattere ASCII pu risultare utile in molti altri casi. Basti infatti pensare che le porte di uscita del PIC sono a 8 bits. Per mezzo del C utilizzato per i PIC anche possibile effetture divisioni. Per poter memorizzare il risultato in generale richiesto un numero decimale. Per questa esigenza sono presenti altri due tipi di variabili floating point come riportato in Tabella 223. Le variabili floating point a differenza delle variabili intere sono caratterizzate da una mantissa e da un esponente e dal fatto che non tutti i valori compresi nell'intervallo minimo e massimo sono in realt possibili. Questo significa un risultato floating point in generale il valore pi prossimo al risultato reale.
22
23
Nel caso dei microcontrollori si ha in generale anche la memoria flash, ma questa viene generalmente utilizzata per contenere delle costanti. E' possibile osservare che in realt le due variabili, come anche per alcune variabili intere non esiste. Eventuali differenze sono generalmente legate al compilatore utilizzato.
27/93
www.LaurTec.com
Tipo float Dimensione Exp. min Exp. max. 32 bits -126 -126 +128 +128 Min. normalizzato
Max. normalizzato
2126 1.17549435e - 38 2128 * (2-215) 6.80564693e + 38 2126 1.17549435e - 38 2128 * (2-215) 6.80564693e + 38
double 32 bits
Ogni volta che si dichiara una variabile molto importante inizializzarla ovvero dargli un valore iniziale, che generalmente il valore nullo. Questo pu essere fatto sia in fase di dichiarazione della variabile che successivamente. L'esempio di seguito riportato mostra come inizializzare nei due modi le variabili i e x.
void main (void) { int i; int x = 0; // inizializzazione durante la dichiarazione della var. i = 0; // inizializzazione della variabile nel programma }
Le due modalit d'inizializzazione delle variabili sono del tutto equivalenti ai fini pratici. Nell'esempio precedente anche possibile osservare che per dichiarare una variabile di un certo tipo necessario anteporre al nome della variabile il tipo. Le variabili fin ora introdotte sono sufficienti per organizzare ogni tipo di programma ma spesso ci sono grandezze per le quali utile avere pi variabili dello stesso tipo. Si pensi ad esempio ad un programma che debba memorizzare la temperatura ogni ora. Piuttosto che dichiarare 24 variabili dello stesso tipo risulta utile dichiarare una sola variabile che permetta di contenerle tutte. Questi tipi di variabili si chiamano Array o vettori, e possono essere anche multidimensionali. Nel caso preso in esame si ha a che fare con un Array monodimensionale poich un indice sufficiente ad individuare tutti i suoi elementi. In C18 per dichiarare un Array si procede come per il C:
void main (void) { char mioArray[10]; // Array di caratteri con 10 elementi }
nell'esempio appena scritto si dichiarato un Array di caratteri di 10 elementi. Per richiamare un elemento di un Array sufficiente scrivere l'indice dell'elemento che si vuole richiamare all'interno delle parentesi quadre. Ci rimane valido anche se si vuole scrivere all'interno di un elemento dell'Array stesso. Quanto detto fino ad ora nasconde per qualcosa di pericoloso...se non noto. Si affermato che mioArray un Array di 10 interi il primo elemento per mioArray[0] mentre l'ultimo mioArray[9] e non mioArray[10] come si potrebbe pensare. Altra pericolosit quando si lavora con gli Array che bisogna essere sempre certi che il programma non vada a leggere indici maggiori di 9. Infatti sar possibile leggere anche mioArray[28] ma questo elemento non in realt parte del nostro Array. Come esempio riportiamo questo segmento di programma:
void main (void) { char mioArray[10]; // Array di caratteri con 10 elementi
28/93
www.LaurTec.com
}
mioArray[0] = 23; //scrivo 23 nel primo elemento dell'array mioArray[2] = mioArray[0]; // copio l'elemento 0 nell'elemento 2
Con l'introduzione degli Array si hanno tutti i tipi di variabili sufficienti per gestire ogni tipo di problema. In realt manca un tipo particolare che definibile dal programmatore e per mezzo del quale possibile gestire in maniera pi snella strutture dati pi complesse. Questo tipo di dato va sotto il nome di struttura, la sua sintassi del tutto simile all'ANSI C. Per esempio un rettangolo avr sempre un'altezza e una larghezza. Dal momento che questi parametri caratterizzano ogni rettangolo, possibile dichiarare un nostro tipo di variabile chiamata rettangolo e che sia caratterizzata dai parametri precedentemente scritti, larghezza e altezza. Per dichiarare una variabile rettangolo si deve procedere come segue:
typedef struct { unsigned char larghezza; unsigned char altezza; } rettangolo;
Dunque bisogna scrivere typedef struct per poi scrivere all'interno delle parentesi graffe tutti i campi che caratterizzano la nostra variabile. I tipi di variabili interni devono essere tipi primitivi, come int, char...o tipi che abbiamo precedentemente dichiarato con un'altra struttura. Alla fine delle parentisi graffe va scritto il nome della nostra struttura, ovvero il tipo della nostra variabile. Una volta creato il nostro tipo possiamo utilizzarlo per dichiarare delle variabili come si farebbe per una variabile intera. Vediamo il seguente esempio:
1 #include <p18f4580.h> 2 3 4 #pragma config OSC = HS // 20Mhz 5 #pragma config WDT = OFF // disattivo il watchdog timer 6 #pragma config LVP = OFF // disattivo la programmazione LVP 7 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 8 9 typedef struct 10 { 11 unsigned char larghezza; 12 unsigned char altezza; 13 14 } rettangolo; 15 16 17 18 void main (void) 19 { rettangolo figura; 20 21 TRISA = 0xFF; // Tutti input 22 PORTA = 0x00; 23 24 TRISB = 0x80 ; // RB7 input 25 PORTB = 0x00 ; 26
29/93
www.LaurTec.com
27 TRISC = 0xFF; // Tutti input 28 PORTC = 0x00; 29 30 TRISD = 0x00; // Tutte uscite 31 PORTD = 0x00; 32 33 TRISE = 0xFF; // Tutti input 34 PORTE = 0x00; 35 36 figura.altezza = 10; //assego l'altezza 37 figura.larghezza = 3; //assegno la larghezza 38 39 PORTD = figura.altezza; //scrivo su PORTD l'altezza 40 41 42 while (1); //ciclo infinito 43 44 }
E' possibile vedere che la dichiarazione del nostro tipo stata fatta fuori dalla funzione main, questo non obbligatorio ma potrebbe essere utile poich in questo modo posso utilizzare questa dichiarazione anche per altre funzioni e non solo nella funzione main 24. Alla riga 19 viene dichiarata la variabile figura e si dichiara che di tipo rettangolo. Questa dichiarazione del tutto simile a quella che si era fatta per la variabile i dicendo che era una variabile intera. Per accedere ai campi della nostra variabile figura bisogna utilizzare il punto. Come riportato alla riga 36 e 37. In particolare bisogna scrivere il nome della nostra variabile seguita dal punto e il campo che vogliamo accedere per una lettura o scrittura. Da quanto detto si capisce che dietro l'istruzione PORTBbits.RB0 c' una dichiarazione di una struttura. Alla riga 39 viene visualizzata sulla PORTD il valore dell'altezza. Attaccando in uscita la scheda con i LED verr visualizzato il valore 00001010 ovvero il valore decimale 10. Con la struttura si ha a disposizione ogni tipo di variabile per affrontare qualunque problema. Si fa presente che in C non presente il tipo stringa. La stringa in C viene realizzata per mezzo di un Array di caratteri, dunque un insieme di caratteri rappresenta una stringa. La particolarit delle stringhe che l'ultimo carattere della stringa deve essere il carattere speciale '\0'. Dunque se si ha un Array di 10 elementi e si volesse scrivere Mauro, all'elemento 5 dell'Array bisogna caricare il carattere speciale '\0' che indica la fine della stringa, che in questo caso pi corta di dieci elementi. Nel dichiarare un Array che conterr una stringa bisogner sempre aggiungere un elemento rispetto al nome o frase pi lunga, in modo da poter inserire il carattere speciale anche nel caso di frase di lunghezza massima. Per manipolare le stringhe presente la libreria string.h che dovr essere inclusa con la direttiva include, ovvero #include <string.h>. Ulteriori dettagli sulle stringhe verranno dati nel paragrafo in cui si parler su come utilizzare un display alfanumerico LCD. Alcune volte si ha l'esigenza di avere una specie di variabile che conterr lo stesso valore durante tutto il programma. Questo tipo di variabile pi propriamente detta costante poich a differenza di una variabile non possibile variare il suo valore per mezzo del programma in esecuzione, cio possibile cambiare il loro valore solo prima della compilazione. Normalmente il nome delle costanti viene scritto in maiuscolo ma questa solo una convenzione. In C per poter definire una costante si usa la direttiva #define25per mezzo della quale si definisce una corrispondenza tra un nome e un valore. In questo modo nel programma ogni volta che bisogner scrivere questo valore baster scrivere
24 25
Per ulteriori informazioni a riguardo si rimanda al paragrafo sulla visibilit delle variabili (scope). La direttiva #define pu essere utilizzata anche per la definizione di macro ovvero blocchi di codice che possibile riscrivere facendo riferimento al nome con cui vengono definiti. La macro non una funzione poich al posto del nome da dove viene richiamata viene sostituito l'intero codice e non un salto.
30/93
www.LaurTec.com
il nome che gli si assegnato. Questo un tipico esempio:
#define MAX_VALUE 56
L'utilit dell'aver definito la costante MAX_VALUE che se si dovesse variare questo valore non sar necessario cambiarlo in ogni punto del programma ma baster cambiare la riga precedente con il nuovo valore. Come ultima nota si ricorda che dal momento che il C case sensitive, ovvero distingue le maiuscole dalle minuscole, la variabile dichiarata come mioNumero diversa dalla variabile MioNumero, anche se sono dello stesso tipo.
31/93
www.LaurTec.com
Operatori matematici, logici e bitwise
Gli operatori matematici, logici e bitwise permettono di manipolare dei registri, ovvero variabili. Gli operatori matematici standard che possibile utilizzare sono: + : operatore somma - : operatore sottrazione / : operatore divisione * : operatore moltiplicazione a questi quattro operatori si aggiungono in realt altre funzioni matematiche particolari, quali i sen(x), cos(x), log(x)... e relative funzioni inverse. Per poter per utilizzare questi ulteriori operatori bisogna includere, per mezzo della direttiva #include, la libreria math.h26. Come possibile operatore di somma alcune volte viene utilizzato il doppio ++, che ha lo scopo di incrementare la variabile di uno. Consideriamo il seguente segmento di codice:
void main (void) { int i=0; i = i + 1; } // dopo la somma i vale 1
un altro modo per effetture questo tipo di somma in maniera snella per mezzo dell'operatore incremento ++, come riportato nel seguente codice:
void main (void) { int i=0; } i++; // dopo la somma i vale 1
in maniera analoga all'operatore ++ esiste anche l'operatore di decremento --. Un esempio riportato nel seguente codice. void main (void) { int i=0; i++; i--; } // dopo la somma i vale 1 // i vale nuovamente 0
questi operatori vengono utilizzati per ottimizzare il codice assembly durante la fase di compilazione. Infatti il microcontrollore possiede come istruzioni base l'operazione d'incremento e decremento di un registro senza far uso del registro speciale accumulatore27.
26
27
Per ulteriori informazioni su tale libreria si rimanda alla documentazione ufficiale della Microchip che possibile trovare nella directory doc della cartella principale dove stato installato C18. L'accumulatore un registro particolare presente in ogni microcontrollore e microprocessore e permette di svolgere le varie operazioni matematiche tra i registri. In molti microprocessori sono spesso presenti pi accumulatori in modo da
32/93
www.LaurTec.com
L'operazione di divisione e moltiplicazione sono operazioni molto complesse e per la loro esecuzione richiedono molto tempo e memoria del PIC. In alcuni casi queste operazioni possono essere sostituite con shift a destra (divisione) o shift a sinistra (moltiplicazione) ma solo qualora l'operazione sia una potenza di due. Gli operatori logici rappresentano quegli operatori che permettono al programma di rispondere a delle domande con una risposta positiva, ovvero 1 logico, o negativa, ovvero 0 logico. Tali operatori sono: || : operatore logico OR && : operatore logico AND = = : operatore logico di uguaglianza != : operatore logico diverso <= : operatore logico minore o uguale >= : operatore logico maggiore o uguale Come verr messo con maggior evidenza parlando dell'istruzione if (...) la domanda A uguale a B si pone scrivendo if (A= =B) e non if (A=B). Gli operatori bitwise permettono di manipolare un registro (variabile) variandone i singoli bit. Gli operatori bitwise sono: & : operatore binario AND | : operatore binario OR ^ : operatore binario XOR ~ : operatore complemento a 1 (i bit vengono invertiti)28 << : shift a sinistra >> : shift a destra Gli operatori di shift intervengono sulla variabile con uno spostamento di un bit verso sinistra o verso destra dei bit che compongono il valore numerico originale. Il bit che viene inserito uno zero mentre il bit che esce viene perduto. Spostare verso sinistra di un bit equivale a moltiplicare per due, mentre spostare verso destra di un bit equivale a dividere per due. Si capisce dunque che se l'operazione di divisione o moltiplicazione deve essere fatta per una potenza di due bene utilizzare gli operatori di shift visto che richiedono ognuno un solo ciclo di clock ( a livello di linguaggio macchina). In questo modo si riesce a risparmiare tempo e spazio in memoria. Vediamo un esempio; si supponga di dover dividere per 4 il numero presente nella variabile i.
void main (void) { int i=9; //9 in binario 00001001 int ris = 0; ris = i >> 2; // ris varr 00000010 ovvero 2 }
Per fare la divisione per 4 si effettuato uno shift a destra di due posti ottenendo come risultato 2. Si capisce dunque che il resto della divisione viene perduto.
velocizzare i tempi di calcolo. L'accumulatore direttamente connesso con l'ALU, ovvero Arithmetic Logic Unit. Il simbolo ~ possibile inserirlo come carattere ASCII numero 126. Tenere premuto ALT, digitare 126 e poi rilasciare ALT.
28
33/93
www.LaurTec.com
Il ciclo for (...)
Il ciclo for (...) permette di eseguire un numero definito di volte una certa operazione o insieme di operazioni. La sua sintassi :
for (espressione1; espressione2; espressione3) { //istruzioni da ripetere }
Fino alla riga 23 il programma non richiede particolari commenti, vediamo dunque come funziona il ciclo for. E' possibile vedere che le espressioni per far funzionare il ciclo for vanno inserite all'interno di parentesi tonde e separate da un punto e virgola. La prima espressione inizializza la variabile, precedentemente dichiarata, che verr utilizzata nel conteggio. In questo caso si fissato il punto iniziale a 0. Se si hanno particolari esigenze possibile iniziare il conteggio da un valore differente. La seconda espressione effettua ad ogni ciclo un controllo sullo stato della variabile per 34/93
www.LaurTec.com
vedere se la condizione verificata. In questo caso volendo fare un ciclo con 10 iterazioni si posto i=0 come inizio e i<10 come fine. La terza espressione del ciclo for il tipo di conteggio che si vuole avere, in questo caso, partendo da 0 e volendo raggiungere 9 bisogna incrementare di 1, dunque si scritto i++. Se il punto di partenza fosse stato 10 e quello di arrivo fosse stato >0 si sarebbe dovuto scrivere i--29. Le istruzioni che si vogliono ripetere per 10 volte sono contenute all'interno delle parentesi graffe. In questo caso si ha la sola istruzione di riga 26 che scrive il valore di i sulla PORTD. Collegando la scheda di espansione di Freedom con 8 LED ed eseguendo il programma possibile vedere...che non si vede nulla oltre allo stato finale 00001001. Infatti il PIC a 20MHz troppo veloce per i nostri occhi. Per poter vedere il conteggio necessario rallentare il tutto con un ciclo di ritardo ottenibile con un conteggio a vuoto di un ciclo for. Vediamo un altro semplice esempio di ciclo for in cui vengano utilizzati anche gli Array in modo da prendere dimestichezza con entrambi e vedere come inserire anche un ritardo.
1 #include <p18f4580.h> 2 3 4 #pragma config OSC = HS // 20Mhz 5 #pragma config WDT = OFF // disabilito il watchdog timer 6 #pragma config LVP = OFF // disabilito programmazione LVP 7 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 8 9 10 void main (void) 11 { unsigned int i; 12 char j; 13 unsigned int mioArray[3]; 14 15 TRISA = 0xFF; // Tutti ingressi 16 PORTA = 0x00; 17 18 TRISB = 0xFF ; // Tutti ingressi 19 PORTB = 0x00 ; 20 21 TRISC = 0xFF; // Tutti ingressi 22 PORTC = 0x00; 23 24 TRISD = 0x00; // Tutte uscite 25 PORTD = 0x00; 26 27 mioArray[0] = 1; 28 mioArray[1] = 2; 29 mioArray[2] = 3; 30 31 32 for (j=0; j<3; j++) 33 { 34 PORTD = mioArray[j]; //visualizzo il contenuto su PORTD 35 36 for (i=0; i<64000; i++) //ciclo di ritardo 37 {
29
In questo caso si parlato solo di i++ e i-- ma in realt si possono scrivere anche altre espressioni.
35/93
www.LaurTec.com
38 39 40 41 42 43 44 45 } } } while (1) { }
Anche in questo caso fino a riga 25 il programma piuttosto standard. Tra la riga 27 e la riga 29 vengono caricati i valori interi positivi 1, 2 ,3 all'interno dell'Array di interi precedentemente dichiarato. Tra la riga 32 e 39 viene visualizzato il contenuto dell'Array mioArray sulla PORTD. Per ottenere questo si fatto uso di due cicli for. Il primo ciclo for con la variabile j viene utilizzato per cambiare l'indice dell'Array che si vuole visualizzare, mentre il ciclo for di riga 36 viene utilizzato per un conteggio inutile solo per far perdere tempo al microcontrollore e permettere all'occhio di vedere il cambio dei dati visualizzato sulla PORTD. Si osservi che la variabile j stata dichiarata come char mentre la variabile i stata dichiarata come unsigned int. In quest'ultimo caso l'indice deve infatti raggiungere un conteggio pi elevato ed un solo byte non pi sufficiente per lo scopo. Si fa notare come in questo caso una corretta spaziatura permette una pi facile lettura del programma stesso. Questa accortezza sempre una buona abitudine che torna utile in programmi pi complessi. Un'ultima nota sul ciclo for riguarda il caso particolare in cui venga scritto nessuna espressione, come sotto riportato:
for (; ;) // ciclo infinito { PORTD =5; }
quello che si ottiene un ciclo infinito, ovvero il PIC caricher in continuazione il numero 5 sul registro di uscita PORTD, che varr dunque sempre 5. Il ciclo risulta infinito poich non c' nessuna condizione da verificare.
36/93
www.LaurTec.com
L'istruzione condizionale if (...)
In ogni programma di fondamentale importanza poter controllare una variabile, o un particolare stato, e decidere se fare o meno una determinata operazione. In C possibile porre domande e decidere, facendo uso dell'istruzione if (...). Per questa istruzione sono presenti due diverse sintassi, la prima :
if (condizione_logica) { //parte di programma da eseguire se la condizione verificata }
la seconda sintassi e:
if (espressione_logica) { //parte di programma da eseguire se la condizione verificata } else { //parte di programma da eseguire se la condizione non verificata }
Per mezzo della prima sintassi possibile eseguire una parte di programma se l'espressione logica all'interno delle parentesi tonde verificata. Per espressione logica si intende una qualunque espressione ottenuta per mezzo degli operatori logici mentre per espressione logica verificata si intende un qualunque valore maggiore o uguale a 1, mentre per espressione logica non verificata si intende 0. Come per il ciclo for (...) anche per l'istruzione if (...) il programma da eseguire contenuto all'interno di parentesi graffe. Per mezzo della seconda sintassi, in cui presente anche l'istruzione else, possibile eseguire un secondo blocco d'istruzioni qualora l'espressione logica non sia verificata. Vediamo un semplice esempio con la prima sintassi:
if (i==3) { PORTD = i; }
In questo esempio PORTD avr in uscita il valore di i solo se i uguale a tre. Dunque se i, nel momento in cui viene effettuato il controllo vale 2, il blocco d'istruzioni all'interno delle parentisi graffe non verr eseguito. Si fa notare che per verificare che i sia uguale a 3 bisogna scrivere i = =3 e non i=3. Questo tipo di errore tipico se si ha esperienza di programmazione con il Basic o il Pascal; il problema che il C non ci viene in generale in aiuto nella risoluzione di questi problemi. Il C infatti permette al programmatore di scrivere anche i=3 come nel seguente esempio:
if (i=3) { PORTD = i; }
il compilatore non segnaler nessun errore e il programma verr anche eseguito dal PIC, il problema sta nel fatto che quando viene eseguita l'operazione if (...) l'espressione logica sar sempre verificata poich sar maggiore di 1, ovvero 3, dunque per il C sar vera. Dunque come effetto collaterale si avr che PORTD verr impostata a 3 ogni volta che verr eseguito l'if(i=3). 37/93
www.LaurTec.com
Vediamo ora un esempio completo in cui si effettua la lettura di un pulsante collegato tra massa e RB0 e si pilotano dei LED sulla PORTD.
1 #include <p18f4580.h> 2 #include <portb.h> 3 4 5 #pragma config OSC = HS // 20Mhz 6 #pragma config WDT = OFF // disabilito il watchdog timer 7 #pragma config LVP = OFF // disabilito programmazione LVP 8 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 9 10 11 void main (void) 12 { 13 TRISA = 0xFF; // Tutti ingressi 14 PORTA = 0x00; 15 16 TRISB = 0xFF ; // Tutti ingressi 17 PORTB = 0x00 ; 18 19 TRISC = 0xFF; // Tutti ingressi 20 PORTC = 0x00; 21 22 TRISD = 0x00; // Tutte uscite 23 PORTD = 0x00; 24 25 EnablePullups(); // abilita i resistori di pull-up sulla PORTB 26 27 28 for (;;) //ciclo infinito 29 { 30 if (PORTBbits.RB0 == 0) 31 { 32 PORTD = 0xF0; //ho premuto il pulsante su RB0 33 } 34 else 35 { 36 PORTD = 0x0F; //il pulsante aperto 37 } 38 } 39 40 }
Questa volta il programma diventa interessante...comincia ad essere un po' pi utile. Alla riga 2 si pu vedere che stato incluso un nuovo file portb.h. Questo file una libreria fornita da Microchip per mezzo della quale possibile modificare alcune propriet della PORTB30. Questa porta ha infatti diverse linee di interrupt31 e dei resistori di pull-up interni. In questo programma dal momento che si
30
31
Microchip fornisce anche altre librerie pronte per l'uso con tanto del sorgente. Per ulteriori informazioni si rimanda alla documentazione ufficiale che possibile trovare nella cartella doc presente nella cartella d'installazione di C18. In particolare i 4 bits pi significativi del PIC possiedono un iterrupt sul cambio di livello logico del pin, quindi viene
38/93
www.LaurTec.com
vuole leggere un pulsante collegato tra massa e RB0 si far uso dei resistori di pull-up. Quando si legge lo stato logico di un pulsante o un interruttore sempre necessario avere un resistore di pull-up o di pull-down32. In questo caso dal momento che la PORTB possiede al suo interno dei resistori di pullup...perch non sfruttarli?! Per poter attivare i resistori di pull-up bisogna richiamare la funzione EnablePullups();33 che setta un bit particolare all'interno dei registri del PIC. Per poter leggere il pulsante si provveduto a realizzare un numero infinite di letture34 sulla RB0 di PORTB. Per fare questo si fatto uso del ciclo for senza parametri, come riportato alla riga 28. All'interno del ciclo infinito viene effettuato il controllo del bit RB0 di PORTB per mezzo della variabile PORTBbits.RB0. Poiche' sono stati attivati i resistori di pull-up e il pulsante collegato verso massa si ha che normalmente il valore di RB0 pari a 1 logico, dunque il controllo effettuato dall'if a riga 30 vale 0 e viene dunque eseguito il blocco di istruzioni dell'else, dunque PORTD viene caricato con il valore o 0x0F, ovvero i LED collegati sui quattro bit meno significativi saranno accesi. Tali LED rimarranno accesi fin a quando non si premer il pulsante. Quando si premer il pulsante RB0 varr infatti 0 logico, dunque la condizione if verr verificata e PORTD varr dunque 0xF0, ovvero si accenderanno i LED sulla PORTD associati ai bit pi significativi. Poich il ciclo for infinito RB0 verr continuamente testato, dunque quando si rilascer il pulsante PORTD varr nuovamente 0x0F. Normalmente quando si leggono dei pulsanti la procedura ora utilizzata non sufficiente. Infatti quando si legge un pulsante sempre bene accertarsi che il pulsante sia stato effettivamente premuto e in particolare non interpretare una singola pressione come pi pressioni. Infatti quando si preme un pulsante si vengono a creare degli spikes, ovvero l'ingresso del PIC avr un treno di 0 e 1, che se non oppurtunatamente filtrati possono essere interpretati come pressioni multiple del pulsante stesso. Per filtrare l'ingresso a cui collegato il pulsante si inserisce generalmente una pausa dopo aver rilevato la pressione del pulsante stesso e si effettua poi una seconda lettura per essere certi che il pulsante sia stato effettivamente premuto. Questa tecnica un modo software per implementare un filtro antirimbalzo. Di seguito riportato l'esempio precedente modificato con un filtro antirimbalzo.
1 #include <p18f4580.h> 2 #include <portb.h> 3 4 5 #pragma config OSC = HS // 20Mhz 6 #pragma config WDT = OFF // disabilito il watchdog timer 7 #pragma config LVP = OFF // disabilito programmazione LVP 8 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 9 10 11 void main (void) 12 { int i; // variabile per il filtro antirimbalzo
32
33
34
generato un iterrupt sia quando uno di questi ingressi passa da 0 a 1 che quando passa da 1 a 0. Un resistore di pull-up collega un ingresso a Vcc, mentre un resistore di pull-down collega un ingresso a massa. Questi resistori, o l'uno o l'altro risultano indispensabili quando si deve leggere uno stato di un pulsante o un interruttore. Infatti quando il pulsante/interruttore aperto l'ingresso rimarrebbe fluttuante ovvero ad un livello logico indeterminato, mentre per mezzo del resistore l'ingresso vincolato o a Vcc o a GND. I resistori di pull-up sono attivati su tutti gli 8 bit della PORTB che siano configurati come ingressi, infatti se un bit configurato come uscita, tale resistore viene disattivato. La tecnica di leggere continuamente lo stato di un ingresso per catturarne una sua variazione di stato viene detta polling. Questa si contrappone alla tecnica dell'interrupt (interruzione) in cui il micro libero di svolgere altre cose invece di leggere continuamente un ingresso, poich verr avvisato (interrupt) quando l'ingresso ha subito una variazione.
39/93
www.LaurTec.com
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 } TRISA = 0xFF; // Tutti ingressi PORTA = 0x00; TRISB = 0xFF ; // Tutti ingressi PORTB = 0x00 ; TRISC = 0xFF; // Tutti ingressi PORTC = 0x00; TRISD = 0x00; // Tutte uscite PORTD = 0x00;
EnablePullups(); // abilita i resistori di pull-up sulla PORTB for (;;) //ciclo infinito { if (PORTBbits.RB0 == 0) //prima lettura { for (i=0;i<10000; i++); // pausa che filtra gli spikes if (PORTBbits.RB0 == 0) //seconda lettura dopo il filtro { PORTD = 0xF0; //ho premuto il pulsante su RB0 } } else { PORTD = 0x0F; //il pulsante aperto } }
In questo secondo programma si inserita la variabile intera i alla riga 12, in modo da implementare una pausa per mezzo di un ciclo for. Alla riga 29 ancor presente il ciclo infinito. Questa volta presente per una prima lettura del bit RB0 alla riga 31; se il pulsante premuto e quindi RB0 vale 0 si aspetta qualche decina di millisecondi per mezzo del ciclo for di riga 3335. Si osservi che in questo caso, dal momento che con il for non si fa altro che contare a vuoto non sono state inserite le parentesi graffe ma solo il punto e virgola, come se fosse una normale istruzione. Le parentesi graffe si sarebbero comunque potute mettere ma in questo caso si sarebbe dovuto togliere il punto e virgola. Dopo la pausa, alla riga 35 viene riletto l'ingresso RB0, se questo ancora 0 allora il pulsante stato realmente premuto e PORTD verr caricata con 0xF0. Si osservi che l'istruzione else appartiene all'if di riga 31 e non all'if di riga 35. Infatti se l'else appartenesse all'if di riga 35 si rischierebbe che con il rilascio del pulsante PORTD rimanga bloccata a 0xF0, come se il pulsante fosse ancora premuto.
35
Il numero 10000 inserito all'interno del ciclo for sufficiente per filtrare un pulsante se si sta utilizzando un quarzo da 20MHz. Con frequenze pi basse tale pausa potrebbe essere eccessiva mentre potrebbe risultare insufficiente per frequenze di quarzi maggiori.
40/93
www.LaurTec.com
L'istruzione condizionale while (...)
L'istruzione while (...) risulta concettualmente molto simile al ciclo for (...) ma pi conveniente nei casi in cui non si a priori a conoscenza del numero di cicli per cui bisogna ripetere un blocco di istruzioni. La sintassi dell'istruzione while (...) :
while (espressione) { //blocco di istruzioni interne al while }
Come per l'istruzione if (...) anche per il ciclo while all'interno delle parentesi tonde contenuta l'espressione logica che se verificata permette l'esecuzione del gruppo di istruzioni all'interno delle parentesi graffe. L'espressione logica verificata se vale 1 o assume un valore maggiore di 1, mentre risulta non verificata se vale 0 o un valore minore di 0. Una volta che le istruzioni contenute tra le parentesi graffe sono eseguite viene eseguito nuovamente il controllo dell'espressione. Se l'espressione nuovamente verificata viene nuovamente eseguito il blocco di istruzioni tra le parentesi graffe, altrimenti il programma continua con la prima istruzione successiva alle parentesi graffe. Come per il ciclo for anche con il ciclo while possibile ottenere dei cicli infiniti se come espressione si scrive qualcosa che viene sempre verificata. Il modo pi semplice per ottenere un ciclo infinito si ha semplicemente scrivendo:
while (1) { //il blocco d'istruzioni qui presenti sono ripetute all'infinito }
in questo caso infatti il risultato dell'espressione 5 cio maggiore di 0, dunque equivale ad una condizione sempre vera. Una volta che si all'interno di un ciclo while vi sono due modi per uscirne. Il primo modo quello spiegato precedentemente, ovvero l'espressione logica non deve essere verificata. Questo tipo di uscita non permette per di uscire da un ciclo infinito come quelli precedentemente descritti. In questo caso pu risultare comoda l'istruzione break. Quando viene eseguita l'istruzione break il programma procede con la prima istruzione successiva alle parentesi graffe. L'istruzione break pu essere utilizzata anche all'interno di cicli while che non siano infiniti, permettendo al programmatore di avere altre condizioni di uscita dal ciclo while. Vediamo un semplice esempio di utilizzo di un ciclo while in sostituzione del for.
1 #include <p18f4580.h> 2
41/93
www.LaurTec.com
3 #pragma config OSC = HS // 20Mhz 4 #pragma config WDT = OFF // disabilito il watchdog timer 5 #pragma config LVP = OFF // disabilito programmazione LVP 6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 7 8 9 void main (void) 10 { unsigned int i; // variabile per la pausa 11 char numero; // variabile per il conteggio interno al while 12 13 TRISA = 0xFF; // Tutti ingressi 14 PORTA = 0x00; 15 16 TRISB = 0xFF ; // Tutti ingressi 17 PORTB = 0x00 ; 18 19 TRISC = 0xFF; // Tutti ingressi 20 PORTC = 0x00; 21 22 TRISD = 0x00; // Tutte uscite 23 PORTD = 0x00; 24 25 numero = 0; //inizializzo il numero a 0 26 27 while (numero<16) 28 { 29 PORTD = numero; // visualizzo in uscita il valore di numero 30 numero++; // incremento il numero 31 32 for (i=0;i<64000; i++); // pausa per rallentare il conteggio 33 } 34 35 while(1); 36 37 }
In questo esempio si sono dichiarate due variabile, la variabile i utilizzata per un ciclo di ritardo mentre la variabile numero utilizzata all'interno del while. L'inizializzazione della variabile i avviene all'interno del ciclo for stesso, mentre l'inizializzazione della variabile numero viene fatta alla riga 25 per mezzo dell'assegnazione del valore 0. Quando il PIC esegue la riga 27, dal momento che numero vale 0, ovvero minore di 16, esegue le istruzioni all'interno delle parentesi graffe. Le istruzioni del ciclo while consistono semplicemente nel porre sulla PORTD il valore di numero in modo da visualizzare il conteggio. Dopo la visualizzazione la variabile numero viene incrementata di uno alla riga 30. Dopo l'incremento, al fine di rallentare il conteggio presente il ciclo for che conta 64000 pecore. Alla fine del conteggio il PIC ritorna ad eseguire l'espressione dell'istruzione while per verificare se numero ancora inferiore a 16. Fin tanto che la variabile minore di 16 il conteggio va avanti e viene visualizzato sulla PORTD. Quando il conteggio giunge a 16 l'espressione del while non pi verificata, dunque il PIC continua con l'eseguire la prima istruzione dopo il ciclo while. In questo caso la prima istruzione si trova alla riga 35 e consiste in un ciclo infinito ottenuto con un while. Si noti che non sono state inserite le parentisi graffe poich in realt il while non contiene nessuna istruzione. Si osservi in ultimo che il valore finale di PORTD 15 ovvero 0x0F. Vediamo un altro esempio di ciclo while in cui si utilizza l'istruzione break. 42/93
www.LaurTec.com
1 #include <p18f4580.h> 2 3 #pragma config OSC = HS // 20Mhz 4 #pragma config WDT = OFF // disabilito il watchdog timer 5 #pragma config LVP = OFF // disabilito programmazione LVP 6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 7 8 9 void main (void) 10 { unsigned int i; // variabile per la pausa 11 char numero; // variabile per il conteggio interno al while 12 13 TRISA = 0xFF; // Tutti ingressi 14 PORTA = 0x00; 15 16 TRISB = 0xFF ; // Tutti ingressi 17 PORTB = 0x00 ; 18 19 TRISC = 0xFF; // Tutti ingressi 20 PORTC = 0x00; 21 22 TRISD = 0x00; // Tutte uscite 23 PORTD = 0x00; 24 25 numero = 0; //inizializzo il numero a 0 26 27 while (numero<16) 28 { 29 PORTD = numero; 30 numero++; // incremento il numero 31 32 if (numero==9) 33 { 34 break; // il conteggio si interrompe a 9 35 } 36 37 for (i=0;i<64000; i++); // pausa per rallentare il conteggio 38 } 39 40 while(1); 41 42 }
Il nuovo esempio praticamente identico al precedente se non per l'aggiunta di un controllo del valore della variabile numero dopo il suo incremento. Il controllo viene effettuato con l'istruzione if. In particolare se numero uguale a 9 viene eseguita l'istruzione break altrimenti il programma identico a prima. Questo significa che questa volta il conteggio si fermer a 9 e non pi a 15. Si noti che il conteggio si fermer a 9 ma PORTD var 8, infatti dopo il valore 8 la variabile PORTD non viene pi aggiornata.
43/93
www.LaurTec.com
Le funzioni
Per mezzo delle funzioni, ogni programma in C pu essere scritto in maniera molto pi snella e leggibile e al tempo stesso si ha la possibilit di riutilizzare codice gi scritto sotto forma di librerie. La funzione un insieme d'istruzioni identificate con un nome al quale possibile passare un certo insieme di variabili che dopo una certa elaborazione rilasciano un risultato (valore di ritorno). La sintassi per una funzione : tipo_di_ritorno nomeFunzione (var1,...,varN) { //blocco d'istruzioni della funzione } Fino ad ora si utilizzata solo la funzione main, che, come detto, deve sempre essere presente. Questa funzione un po' particolare poich non non ha nessun risultato di ritorno e non ha nessuna variabile in ingresso36. Per tipo di ritorno si intende il tipo di variabile che verr ritornata. Se per esempio la funzione deve svolgere una somma tra due interi il valore di ritorno sar a sua volta un intero. Gli addendi della somma devono essere dichiarati tra le parentisi tonde nella dichiarazione della funzione stessa. I due addendi sono chiamati argomenti della funzione. Nelle parentesi tonde vi dunque la dichiarazione delle variabili che la funzione ricever da parte del programma. Queste variabili non saranno le sole che la funzione potr utilizzare infatti si potranno dichiarare anche altre variabili interne alla funzione stessa. Vediamo un esempio di dichiarazione e utilizzo di una funzione che fa la somma tra due interi:
1 #include <p18f4580.h> 2 3 #pragma config OSC = HS // 20Mhz 4 #pragma config WDT = OFF // disabilito il watchdog timer 5 #pragma config LVP = OFF // disabilito programmazione LVP 6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 7 8 9 //funzione per sommare due numeri interi 10 11 int sommaInteri (int add1, int add2) 12 { 13 int somma; 14 15 somma = add1+add2; 16 17 return(somma); // ritorno la somma 18 } 19 20 21 void main (void) 22 { 23 24 TRISA = 0xFF; // Tutti ingressi 25 PORTA = 0x00;
36
Questo non vero in ANSI C, dove la funzione main pu ricevere variabili in ingresso e ritornare un valore. Le variabili in ingresso sono rappresentate dal testo che si scrive sulla linea di comando quando si lancia il programma stesso da una shell di comando. Il valore di uscita in generale un valore che segnala un eventuale codice di errore.
44/93
www.LaurTec.com
26 27 TRISB = 28 PORTB = 29 30 TRISC = 31 PORTC = 32 33 TRISD = 34 PORTD = 35 36 PORTD = 37 38 39 while(1); 40 41 } 0xFF ; // Tutti ingressi 0x00 ; 0xFF; // Tutti ingressi 0x00; 0x00; // Tutte uscite 0x00; sommaInteri (3,7); //effettuo la somma tra 3 e 7
La funzione somma dichiarata prima della funzione main, tra la riga 11 e 18, questo non obbligatorio ma su questo si ritorner a breve. E' possibile vedere che alla riga 11 si dichiarato un int come tipo di ritorno, poich come detto si sta effettuando la soma tra due interi 37 siano essi positivi o negativi. I due addendi vengono dichiarati all'interno delle parentesi tonde. Questa dichiarazione a tutti gli effetti una dichiarazione di variabile ma come si vedr nel paragrafo sulla visibilit delle variabili queste variabili verranno rilasciate non appena la funzione avr termine. Le variabili dichiarate tra parentisi ospiteranno i valori degli addendi che verranno inseriti nel momento della chiamata della funzione stessa. Il corpo della funzione praticamente identico alla funzione main, ovvero si possono dichiarare altre variabili e si pu scrivere tutto il codice che si vuole. Una differenza la presenza dell'istruzione return ( ) (in realt una funzione) che permette di ritornare il risultato della somma. Qualora la funzione non abbia nessun valore di ritorno, come potrebbe essere per una funzione di ritardo, ovvero il tipo di ritorno sia void, la funzione return ( ) non risulta necessaria. All'interno delle parentesi tonde della funzione return si deve dunque porre una variabile o costante che sia dello stesso tipo di quella che stata dichiarata per la funzione. Dopo un'istruzione return ( ) si ha l'uscita dalla funzione stessa e il programma riprende dal punto in cui la funzione era stata chiamata. All'interno di ogni funzione possono essere presenti anche pi punti di uscita, ovvero return ( ) ma solo uno sar quello che effettivamente far uscire dalla funzione stessa. Se la funzione non ha nessuna istruzione return ( ), si ha l'uscita dalla funzione quando il programma giunge alle parentesi graffe. Alla riga 36 vi la chiamata alla funzione, questa avviene semplicemente scrivendo il nome della funzione e mettendo tra parentesi, con lo stesso ordine e tipo, le variabili o costanti, che la funzione si aspetta in ingresso. In questo semplice caso gli addendi sono delle semplici costanti ma potrebbero essere anche delle variabili. Dal momento che la funzione ritorner un risultato bisogner scrivere sulla sinistra della funzione un'assegnazione, o comunque la funzione deve essere parte di una espressione il cui risultato verr assegnato ad una variabile. In questo caso, sempre a riga 36 si pu osservare che il risultato viene posto in uscita alla PORTD. Come detto ogni funzione deve essere dichiarata prima della funzione main ma questo non obbligatorio. Se si dovesse dichiarare la funzione o funzioni dopo la funzione main il compilatore dar un avviso poich compilando la funzione main si trover ad usare la funzione sommaInteri che non stata precedentemente dichiarata...ma che presente dopo la funzione main. Per evitare questo avviso, prima della funzione main bisogna scrivere il prototipo di funzione ovvero una riga in cui si avvisa il compilatore che la funzione sommaInteri che utilizzata all'interno della funzione main
37
In questo esempio non si sta considerando il caso in cui il risultato possa eccedere il valore massimo consentito da un intero.
45/93
www.LaurTec.com
dichiarata dopo la funzione main stessa. Il prototipo di funzione e relativo spostamento della funzione sommaInteri riportato nel seguente esempio:
1 #include <p18f4580.h> 2 3 #pragma config OSC = HS // 20Mhz 4 #pragma config WDT = OFF // disabilito il watchdog timer 5 #pragma config LVP = OFF // disabilito programmazione LVP 6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 7 8 int sommaInteri (int add1,int add2); 9 10 void main (void) 11 { 12 13 TRISA = 0xFF; // Tutti ingressi 14 PORTA = 0x00; 15 16 TRISB = 0xFF ; // Tutti ingressi 17 PORTB = 0x00 ; 18 19 TRISC = 0xFF; // Tutti ingressi 20 PORTC = 0x00; 21 22 TRISD = 0x00; // Tutte uscite 23 PORTD = 0x00; 24 25 PORTD = sommaInteri (3,7); //effettuo la somma tra 3 e 7 26 27 28 while(1); 29 30 } 31 32 33 //funzione per sommare due numeri interi 34 35 int sommaInteri (int add1, int add2) 36 { 37 int somma; 38 39 somma = add1+add2; 40 41 return(somma); // ritorno la somma 42 }
Alla riga 8 presente il prototipo di funzione che consiste semplicemente nello scrivere la riga 35 con un punto e virgola ovvero la dichiarazione della funzione ma senza il corpo della funzione stessa. L'utilizzo delle funzioni uno strumento molto potente che permette di risolvere applicazioni complesse concentrandosi su singoli problemi, inoltre, come detto, risulta anche un modo per riutilizzare il codice. Per fare questo si pu semplicemente copiare ed incollare una funzione da un programma ad un altro o cosa migliore creare una propria libreria che contenga una certa categoria di funzioni. Si pu per esempio fare una libreria per controllare un LCD o una libreria per controllare la comunicazione con un'integrato particolare. Per poter scrivere una libreria basta creare un altro file, 46/93
www.LaurTec.com
come si fatto per il file main.c e dargli un nome per esempio funzioniLCD.c ed aggiungerlo al progetto. Il file creato in questo modo in realt parte del progetto stesso e non una vera e propria libreria. Per trattarla come una libreria basta in realt cancellare il file dal nostro progetto ed includerlo con la direttiva #include. Quando si include un file con la direttiva #include si hanno due formati, il primo quello visto fino ad adesso, ovvero #include <nome_file> dove si scrive il file da includere tra i due simboli di minore e maggiore. Questo modo valido solo se il file di libreria insieme ai file di libreria della Microchip38. Se il file contenuto altrove bisogna utilizzare quest'altro formato #include percorso_file/nome_libreria . Nel caso particolare in cui la libreria risieda all'interno della directory del nostro progetto basta scrivere #include nome_file. Negli esempi che seguiranno si far uso di quanto appena descritto. L'utilizzo di file multipli non solo consigliato per raccogliere una certa classe di funzioni ma anche per spezzare il programma principale. Infatti, in programmi complessi si potrebbe creare un file che contenga solo la dichiarazione delle variabili ed includerlo come precedentemente detto. Inoltre buona abitudine scrivere le funzioni in altri file seguendo un criterio di appartenenza e scrivere sul file principale solo lo scheletro del programma. Per un robot si potrebbe scrivere per esempio un file per le sole variabili, un file con le funzioni di controllo del movimento, una file per la gestione degli occhi, un file per il controllo delle interfacce grafiche e via discorrendo.
38
47/93
www.LaurTec.com
Visibilit delle variabili
Per visibilit o scope di variabili si intende le parti di programma dove possibile utilizzare una variabile precedentemente dichiarata. Una variabile viene detta globale quando pu essere utilizzata in qualunque parte del programma. Generalmente questo tipo di variabili vengono dichiarate nel caso in cui l'informazione in esse contenute debba essere condivisa da pi moduli ovvero funzioni di programma. Per dichiarare una variabile globale bisogna effettuare una dichiarazione al di fuori della funzione main come nel seguente esempio:
int TemperaturaSistema; //questa variabile globale
void main (void) { . . // parte del programma . if (TemperaturaSistema>37) { attivaAllarme ( ); //chiamo la funzione per attivare l'allarme }
quando una variabile globale vuol dire che nel PIC gli verr assegnata la quantit di memoria RAM necessaria per contenerla e questa rimarr fissa e accessibile da ogni parte del programma. Quando una variabile viene dichiarata all'interno della funzione main questa sar accessibile solo da parti di programma interne alla funzione main stessa ma non da funzioni esterne. Ad una variabile interna alla funzione main, come per le variabili globali gli viene assegnata della RAM e questa rimane fissa durante tutto il tempo di esecuzione del programma da parte del PIC ma sar accessibile solo dalla funzione main. Quando una variabile dichiarata all'interno di una funzione generica, come per esempio la variabile intera somma dichiarata all'interno della funzione sommaInteri degli esempi precedenti, questa sar visibile solo all'interno della funzione stessa. In particolare la RAM che viene allocata per tale memoria viene rilasciata (resa disponibile) una volta che la funzione termina le sue operazioni. Questo significa che la variabile cessa di esistere. Poich una variabile dichiarata all'interno della funzione main non sar visibile all'interno di altre funzioni e viceversa, possibile utilizzare anche nomi di variabili uguali in funzioni diverse. Nonostante sia possibile avere nomi di variabili uguali grazie al fatto che le funzioni non riescono a vedere le variabili interne ad altre funzioni questa non una buona pratica di programmazione. Questa possibilit dovrebbe essere sfruttata solo per variabili molto semplici come le variabili di indice utilizzate nei cicli for o while ma bene sempre non eccedere. Alcune volte si pu avere l'esigenza di contare degli eventi ogni volta che si richiama una funzione. Ma come possibile contare degli eventi se le variabili interne ad una funzione vengono poi cancellate. Il modo per fare questo dichiarare una variabile globale e utilizzarla per il conteggio internamente alla funzione. Infatti tale variabile non appartenendo alla funzione non verr cancellata infatti ad ogni accesso avr sempre il valore relativo all'ultimo incremento. Un altro modo per raggiungere lo scopo dichiarare la variabile static, ovvero statica. Questo significa che la variabile pur appartenendo alla funzione non verr cancellata alla fine della funzione stessa. Dunque ogni volta 48/93
www.LaurTec.com
che la funzione verr rieseguita questa potr accedere sempre alla stessa variabile precedentemente dichiarata static. Vediamo il seguente esempio, in cui la funzione Conteggio grazie alla variabile statica conta il numero di chiamate effettuate alla funzione stessa.
1 #include <p18f4580.h> 2 3 #pragma config OSC = HS // 20Mhz 4 #pragma config WDT = OFF // disabilito il watchdog timer 5 #pragma config LVP = OFF // disabilito programmazione LVP 6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB 7 8 //************************************************************** 9 //funzione che conta le sue chiamate 10 11 int Conteggio (void) 12 { 13 static int contatore=0; //dichiarazione variabile statica 14 15 contatore++; 16 17 return (contatore) 18 } 19//************************************************************** 20 21 void main (void) 22 { unsigned int i; 23 24 TRISA = 0xFF; // Tutti ingressi 25 PORTA = 0x00; 26 27 TRISB = 0xFF ; // Tutti ingressi 28 PORTB = 0x00 ; 29 30 TRISC = 0xFF; // Tutti ingressi 31 PORTC = 0x00; 32 33 TRISD = 0x00; // Tutte uscite 34 PORTD = 0x00; 35 36 PORTD = Conteggio (); //visualizzo il valore di ritorno 37 38 for (i=0; i<64000; i++); //pausa 39 40 PORTD = Conteggio (); //visualizzo il valore di ritorno 41 42 for (i=0; i<64000; i++); //pausa 43 44 PORTD = Conteggio (); //visualizzo il valore di ritorno 45 46 while(1); 47 48 }
La variabile contatore stata dichiarata statica, questo significa che non verr cancellata alla fine della funzione e che verr inizializzata una sola volta, ovvero contatore = 0 verr eseguita solo la prima 49/93
www.LaurTec.com
volta che viene chiamata la funzione. Da questo discende che dopo le tre chiamate alla funzione Conteggio, effettuate tra la riga 36 e la riga 44, sulla PORTD verr visualizzato il valore 00000011 ovvero 3.
50/93
www.LaurTec.com
Le interruzioni
Le interruzioni rappresentano uno strumento particolarmente potente per mezzo del quale il programmatore riesce a gestire molteplici operazioni senza sprecare tempo utile39. Nonostante l'importanza delle interruzioni, in questo paragrafo, non si tratterr in maniera esaustiva l'intero argomento, dal momento che questo pu variare da microcontrollore a microcontrollore. Ci nonostante si tratteranno gli aspetti principali e grazie agli esempi si dar modo al lettore di acquisire gli strumenti necessari per affrontare gli aspetti non trattati. In particolare si rimanda alla documentazione ufficiale del PIC utilizzato per una pi approfondita trattazione. Che cose' un'interruzione? Come dice la parola stessa un'interruzione un evento che interrompe la normale esecuzione di un programma. Gli eventi che possono interrompere la normale esecuzione di un programma sono molteplici e possono differire da microcontrollore a microcontrollore, ci nonostante un'interruzione comune a tutti i microcontrollori quella che si viene a generare con il segnale di Reset. Infatti il Reset rappresenta un'interruzione che in particolare interrompe la normale esecuzione del programma facendolo iniziare nuovamente da capo. Altre interruzioni tipiche sono quelle che vengono a generarsi dalle periferiche interne, come per esempio il convertitore analogico digitale, l'USART, i timer, le linee sulla PORTB e altro ancora. Questi tipi d'interruzione, a differenza dell'interruzione generata dal Reset non fanno iniziare il programma da capo ma lo fanno continuare a partire da un punto specifico del programma stesso; questo punto viene detto vettore d'interruzione. Quando avviene un'interruzione da parte delle periferiche, prima di saltare al vettore d'interruzione, l'Hardware40 si preoccupa di salvare tutte le informazioni necessarie per poter riprendere dal punto in cui il programma stato interrotto, una volta eseguite le operazioni necessarie per gestire l'interruzione. Il PIC18F4580 come molti altri possiede due livelli d'interruzione, ovvero due vettori di interruzione. Il vettore d'interruzione ad alta priorit posizionato all'indirizzo di memoria 0x08 e il vettore d'interruzione a bassa priorit posizionato all'indirizzo di memoria 0x18. Quando si verifica un'interruzione ad alta priorit il programma viene interrotto anche se stava gestendo un'interruzione a bassa priorit. Se il programma si dovesse invece trovare a gestire un'interruzione ad alta priorit il verificarsi di una interruzione a bassa priorit non influenzerebbe l'esecuzione del programma fino al termine della gestione dell'interruzione ad alta priorit. Nel caso si dovesse premere il tasto di Reset anche la gestione di un'interruzione ad alta priorit verrebbe interrotta per far iniziare il programma nuovamente da capo. Ogni tipo d'interruzione che pu essere generata da una periferica, possiede tre bit di controllo posizionati in punti diversi nei registri utilizzati dal microcontrollore per la gestione delle interruzioni stesse. In particolare si ha un bit che funziona da flag per l'interruzione, ovvero quando vale 1 segnala che si verificata l'interruzione da parte della periferica rappresentata dal bit stesso. Un secondo bit dedicato all'Enable dell'interruzione da parte della periferica rappresentata dal bit, mentre il terzo bit serve per decidere se l'interruzione da parte della periferica deve essere considerata ad alta priorit o a bassa priorit. Per poter accettare le interruzioni a bassa priorit necessario porre ad 1 il bit GIE e PEIE del registro INTCON. Per poter poter fare questo bisogna eseguire la seguente istruzioni:
INTCONbits.GIE = 1; INTCONbits.PEIE = 1 ; // Abilito l'interrupt globale // Abilito interrupt per periferiche
Ogni volta che un'interruzione viene generata il relativo bit di flag che la segnala posta ad uno.
39
40
Dal momento che le interruzioni possono essere sfruttate per risvegliare il microcontrollore da uno stato di sleep, queste possono essere utilizzate a supporto della filosofia del risparmio di potenza. In realt anche il software interviene spesso per il salvataggio di variabili particolari che altrimenti non verrebbero salvate.
51/93
www.LaurTec.com
Controllando i flag delle periferiche possibile rendersi conto quale periferica ha generato l'interruzione. Prima di riprendere l'esecuzione del normale programma, ovvero prima di uscire dalla funzione che gestisce l'interruzione, necessario riporre a 0 il bit della periferica che ha causato l'interruzione. In questo modo si evitano interruzioni ricorsive dovuto a questo bit. Per poter gestire le interruzioni necessario dichiarare una funzione particolare o meglio bisogna specificare al compilatore dove si trova tale funzione. Infatti nel momento in cui il programma viene interrotto, questo andr alla locazione di memoria 0x08 o 0x18 a seconda del tipo. A partire da questi indirizzi non possibile scrivere l'intero programma di gestione delle interruzioni, si pensi ad esempio che tra il vettore ad alta priorit e bassa priorit sono presenti solo 16 locazioni di memoria. Per tale ragione quello che si fa posizionare degli indici, ovvero dei salti, che dai vettori d'interruzione posizionano il programma (ovvero il Program Counter) alle relative funzioni di gestione dell'interruzione. Quanto appena detto viene fatto dal seguente segmento di codice:
. . .
void low_interrupt (void) { _asm GOTO Low_Int_Event _endasm //salto per la gestione dell'interrupt } #pragma code #pragma interruptlow Low_Int_Event void Low_Int_Event (void) { //programma per la gestione dell'interruzione } void main (void) { . . //programma principale . }
Da quanto appena scritto si capisce che in C18 la gestione delle interruzioni un po' infelice, in particolare si fa uso della direttiva pragma che non ANSI C. Questo significa che se si volesse compilare il programma con un altro compilatore diverso da C18 bisogner molto probabilmente modificare la dichiarazione della funzione per la gestione delle interruzioni. Nel segmento di codice sopra scritto si fatto riferimento all'interruzione a bassa priorit ma per quella ad alta priorit vale la stessa dichiarazione a patto di cambiare il vettore d'interruzione 0x18 con 52/93
www.LaurTec.com
0x08 e la parola Low con High in modo da chiamare le funzioni in maniera differenti. Il nome delle funzioni pu essere scelto dal programmatore, quello riportato il nome di cui faccio uso per comodit. Altra modifica richiesta per lavorare con le interruzioni ad alta priorit sar diseguito descritto. Vediamo i passi che si sono seguiti in questo segmento di codice. Come prima cosa si dichiarato il prototipo di funzione per la gestione dell'interrupt. Come secondo passo si fatto uso della direttiva #pragma code per dichiarare il vettore d'interruzione a cui si sta facendo riferimento. Il terzo passo la dichiarazione della funzione low_interrupt che grazie al passo precedente posizionata proprio sul vettore d'interruzione di nostro interesse. In questa funzione si inserisce il salto alla funzione vera e propria che gestir l'interruzione. Tale salto viene effettuato con l'istruzione assembly GOTO. Per poter scrivere segmenti di codice assembly all'interno del programma principale necessario inserire tale codice tra le due linee di codice _asm e _endasm. _asm e _endasm risultano particolarmente utili per quelle parti di codice che devono essere ottimizzate ma richiedono una conoscenza del codice assembly e soprattuto una buona conoscenza del PIC che si sta utilizzando. Come quarto e quinto passo si fa nuovamente uso della direttiva pragma, in particolare #pragma code e #pragma interruptlow, per mezzo delle quali si ha poi la possibilit di dichiarare la funzione per gestire l'interruzione. Nel caso si sia specificato il vettore ad alta priorit la direttiva#pragma interruptlow deve essere sostituita con #pragma intterupt ovvero senza il low finale. Dal momento che le funzioni per la gestione delle interruzioni possono essere richiamate da periferiche differenti necessario all'interno delle funzioni stesse controllare i flag per capire quale periferica ha generato l'interruzione. In particolare si ricorda che alla fine della gestione dell'interruzione bisogna riporre a 0 il flag relativo alla periferica che ha generato l'interruzione. Per una completa trattazione dei vari flag associati alle periferiche disponibili nel PIC che si sta utilizzando si rimanda al relativo datasheet. Si ricorda che in C18 il nome di tali bit lo stesso dei data sheet ma per cambiare il valore del bit bisogna scrivere: NOME_REGISTRObits.nomeflag per esempio per modificare il bit GIE presente nel registro INTCON si scriver:
INTCONbits.GIE = 1;
53/93
www.LaurTec.com
16 #pragma config OSC = HS // 20Mhz 17 #pragma config WDT = OFF // disattivo il watchdog timer 18 #pragma config LVP = OFF // disattivo la programmazione LVP 19 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 20 21 22 void Low_Int_Event (void); // prototipo di funzione 23 24 25 #pragma code low_vector=0x18 26 27 void low_interrupt (void) 28 { 29 _asm GOTO Low_Int_Event _endasm //imposta il salto per la gestione dell'interrupt 30 } 31 32 #pragma code 33 34 #pragma interruptlow Low_Int_Event 35 36 void Low_Int_Event (void) 37 { int i; // indice per il ciclo di pausa 38 39 if (INTCONbits.RBIF == 1 ) // Controllo che l'interrupt sia stato generato da PORTB 40 { for (i=0; i<30000; i++); //pausa filtraggio spikes 41 42 if (PORTBbits.RB7==0) // Controllo la pressione di RB7 43 { 44 PORTD = 0xFF; //accendo tutti i LED 45 for (i=0; i<30000; i++); //pausa 46 PORTD = 0x00; //spengo tutti i LED 47 } 48 } 49 50 INTCONbits.RBIF = 0; //resetto il flag d'interrupt 51 } 52 53 54 void main (void) 55 { 56 TRISA = 0xFF; // Tutti input 57 PORTA = 0x00; 58 59 TRISB = 0x80 ; // RB7 input 60 PORTB = 0x00 ; 61 62 TRISC = 0xFF; // Tutti input 63 PORTC = 0x00; 64 65 TRISD = 0x00; // Tutte uscite 66 PORTD = 0x00; 67 68 TRISE = 0xFF; // Tutti input
54/93
www.LaurTec.com
69 70 71 72 73 74 75 76 77 78 79 } PORTE = 0x00; EnablePullups(); // abilito i resistori di pull-up INTCONbits.RBIE = 1; // abilito le interruzioni su PORTB INTCONbits.GIE = 1; // Abilito l'interrupt globale while (1); //ciclo infinito in attesa d'interrupt
Il vantaggio che si ottiene in questo esempio rispetto all'esempio in cui si leggeva il pulsante facendo letture infinite sull'ingresso stesso che il PIC ora libero di svolgere altre attivit ovvero calcoli infatti il ciclo infinito a riga 77 non fa null'altro che attendere per un'interruzione. Si pu vedere che a riga 39 si effettua il controllo sul flag di RBIF per accertarsi che l'interruzione sia stata generata dalla PORTB41. In questo caso dal momento che non si stanno generando altre interruzioni sicuramente l'interruzione sar generata dalla PORTB. Per essere certi che l'interruzione sia quella generata dalla pressione del tasto e non dal suo rilascio anche presente il controllo su RB7, if (PORTBbits.RB7==0). Prima di questo controllo possibile osservare a riga 40 che presente un ciclo di ritardo per filtrare eventuali spikes. Nel caso sia stato premuto il pulsante viene eseguito il codice tra riga 44 e riga 46 che permette di accendere tutti i LED su PORTD con un piccolo flash. A riga 50 si pu osservare che il flag RBIF viene posto nuovamente a 0. In realt per la PORTB, quando viene generato un'interrupt al fine di resettare il flag RBIF sufficiente effettuare una lettura sulla PORTB stessa. Qualora questo non venga fatto l'istruzione INTCONbits.RBIF = 0; risulta obbligatoria. Per evitare ogni problema e per omogeneit con gli altri flag comunque meglio resettare il flag per mezzo dell'istruzione INTCONbits.RBIF = 0;. Sulla parte principale del programma possibile vedere che alla riga 71 si abilitano i resistori di pull-up, ma solo quello su RB7 sar utilizzato poich l'unico ingresso della PORTB. Alla riga 73 viene invece abilitata l'interruzione sui bit RB4, RB5, RB6, RB7 della PORTB, mentre alla riga 75 si abilita il flag globale per le interruzioni. Si noti che in questo caso non stato necessario abilitare il flag PEIE poich PORTB non una periferica. Dopo queste impostazioni il PIC non fa null'altro che eseguire un ciclo while infinito...poich non sa cos'altro fare fino a quando non si premer il pulsante su RB7. Vediamo qualche altro dettaglio per l' utilizzo delle interruzioni ad alta priorit. Come detto ogni periferica che pu generare un'interruzione possiede un bit per mezzo del quale possibile indicare se la sua interruzione sar ad alta (bit settato ad 1) o a bassa priorit (bit settato a 0). Normalmente questi bit vengono ignorati ameno che non vengano abilitate le interruzioni ad alta priorit per mezzo del bit IPEN presente nel registro RCON bit 7. Quando questo bit abilitato (settato ad 1) bisogner impostare oppurtunatamente i bit di priorit per indicare se la periferica sar ad alta o bassa priorit. Si ricorda che una periferica ad alta priorit interromper la funzione di gestione di una qualunque periferica a bassa priorit per poi tornare alla sua esecuzione una volta eseguita la funzione ad alta priorit. L'abilitazione delle interruzioni ad alta priorit non abilita fisicamente le interruzioni. Per abilitare fisicamente le interruzioni globali, come nel caso a bassa priorit bisogna attivare GIE. In realt in questo caso non si ha pi GIE bens GIEH e GIEL. GIEH serve per abilitare le periferiche che hanno il bit di priorit pari ad 1 (periferiche ad alta priorit) mentre GIEL serve per abilitare le periferiche che hanno il bit di priorit pari a 0 (periferiche a bassa priorit). Il bit GIEH si trova nel registro INTCON bit 7; GIEH rappresenta GIE nel caso in cui IPEN pari a 042. Il bit GIEL si trova
41 42
I bit della PORTB che generano un'interruzione al variare dello stato logico 0-1 o 1-0 sono RB4, RB5, RB6, RB7. Dopo il reset del PIC IPEN vale 0.
55/93
www.LaurTec.com
nel registro INTCON bit 6; GIEL rappresenta il bit PEIE nel caso in cui IPEN vale 0. Per quanto riguarda l'attivazione dell'intterrupt delle singole periferiche non c' differenza tra alta e bassa priorit, infatti bisogner attivare il bit di enable corrispondente. Vediamo ora un segmento di codice dove viene mostrato l'utilizzo contemporaneo delle interruzioni ad alta e bassa priorit:
//****************************************************************** // Interrupt Handler //****************************************************************** void Low_Int_Event (void); // prototipo di funzione void High_Int_Event (void); // prototipo di funzione #pragma code high_vector=0x08 void high_interrupt (void) { _asm GOTO High_Int_Event _endasm //imposta il salto per la gestione } #pragma code low_vector=0x18 void low_interrupt (void) { _asm GOTO Low_Int_Event _endasm //imposta il salto per la gestione } #pragma code //************************************** // Low Priority Interrupt Handler //************************************** #pragma interruptlow Low_Int_Event void Low_Int_Event (void) { // gestione interrupt bassa priorit // in questo esempio il TMR0 }
//************************************** // High Priority Interrupt Handler //************************************** #pragma interrupt High_Int_Event void High_Int_Event (void) {
56/93
www.LaurTec.com
// Gestione interrupt ad alta priorit // in questo esempio l'USART }
//****************************************************************** //Main Program //****************************************************************** void main (void) { TRISA = 0xFF; // inizializzazione PORTA PORTA = 0x00; TRISB = 0x00; // inizializzazione PORTB PORTB = 0x00 ; TRISC = 0xFF; // inizializzazione PORTC PORTC = 0x00; TRISD = 0x00; // inizializzazione PORTD PORTD = 0x00;
//********************************************************* // Iterrupt enable setup //********************************************************* RCONbits.IPEN = 1; //abilito int alta priorit // // // // Gli enable degli interrupt delle periferiche sono attivati dalle funzioni OpenUSART (...) ed OpenTimer0 (...) Se non usate gli enable devono essere qui impostati
IPR1bits.RCIP = 1; // Ricezione usart alta priorit INTCON2bits.TMR0IP = 0; // Timer bassa priorit INTCONbits.GIEH = 1; // Abilito l'interrupt globale alta priorit INTCONbits.GIEL = 1; // Abilito l'interrupt globale bassa priorit
Per ulteriori programmi di esempio si rimanda ai paragrafi successivi e ai progetti presentati sui siti riportati in Bibliografia.
57/93
www.LaurTec.com
Come utilizzare un display alfanumerico LCD
I display alfanumerici LCD sono ormai molto popolari e permettono con modica spesa di aggiungere un pizzico di professionalit ad ogni circuito. Per mezzo del di tali display inoltre possibile realizzare un'ottima interfaccia macchina utente grazie ai men che possibile scrivere direttamente sul display. In commercio sono presenti molti tipi di display alfanumerici LCD di varie dimensioni, quelle pi tipiche sono 8x2, 16x1, 16x2, 20x2, 40x4, dove il primo numero indica il numero dei caratteri43 che possibile scrivere su ogni riga mentre il secondo rappresenta il numero di righe disponibili. Ogni LCD possiede almeno un controllore che permette la comunicazione tra il microcontrollore e il display LCD. Sono presenti diversi tipi di controllori con diversi tipi set di istruzioni necessarie per la comunicazione col controllore stesso. Il pi familiare senza dubbio il controllore HD44780 della Hitachi. Sono presenti anche altre sigle di integrati realizzate da altre case costruttrici ma che sono compatibili con questo controllore44. Ogni display possiede varie linee di controllo in particolare un bus per la trasmissione dei dati composto da 8 linee, una linea di Enable45, una linea R/W per scrivere o leggere dal/sul controllore, e una linea RS per distinguere l'invio di un comando da un carattere. Oltre a queste linee, necessarie per il controllo del display, presente il pin per il contrasto. Il controllore, al fine di risparmiare pin pu essere utilizzato in modalit 4 bit piuttosto che a 8 bit, ovvero si fa uso di sole 4 linee dati. La piedinatura dei display generalmente standard ma potrebbe variare da quella sotto riportata:
pin 1 = GND (il pin 1 generalmente indicato sul display stesso) pin 2 = Vcc (+5V) pin 3 = Contrasto pin 4 = RS pin 5 = R/W (collegato a massa in applicazioni in cui non si legge dal controllore) pin 6 = E pin 7 = DB0 pin 8 = DB1 pin 9 = DB2 pin 10 = DB3 pin 11 = DB4 pin 12 = DB5 pin 13 = DB6 pin 14 = DB7 pin 15 = LED+ pin 16 = LEDIl C18 possiede una libreria dedicata per il controllo dei display LCD ma per ragioni di semplicit si parler della libreria che ho personalmente realizzato46. Unico punto debole di questa libreria la
43
44 45 46
Ogni carattere contenuto all'interno di una piccola matrice di punti per mezzo dei quali si ottiene la forma del carattere stesso. Per una completa trattazione dei comandi disponibili per questo controllore si rimanda al relativo data sheet. Le linee di Enable possono anche essere due ma per il 16x2, 16x1 sono sempre 1. I file di libreria personale sono disponibili al sito www.LaurTec.com.
58/93
www.LaurTec.com
funzione delay che funziona correttamente con un quarzo da 20MHz. Per frequenze maggiori potrebbe essere necessario rallentarla mentre per frequenze inferiori a 20MHz potrebbe essere necessario velocizzarla. Questa libreria, con pochi semplici passi, pu essere adattata per funzionare con qualunque applicazione 4 bit. Le linee di cui si parlato sopra possono essere collegate ad un qualunque pin del PIC lasciando ampia libert. Naturalmente per semplicit sempre bene assegnare i pin con un criterio logico ma non obbligatorio. In particolare Freedom possiede un connettore per LCD in cui presente anche il trimmer per il contrasto, i pin di controllo sono collegati sulla PORTD. Oltre alla libreria di cui si parler presente la libreria LCD_44780_Freedom che contiene le stesse funzioni di seguito riportate ma ottimizzate per la PORTD. Nell'esempio si far riferimento ad un caso generico in cui il display sia collegato alla PORTB. Per poter adeguare la libreria alle proprie esigenze bisogna seguire i seguenti passi: la parte di libreria in cui sono dichiarate le costanti riportate di seguito deve essere variata con i pin di cui si sta facendo uso.
//******************************************************************* // LCD constants #define #define #define #define #define #define LCD_D0 PORTBbits.RB4 LCD_D1 PORTBbits.RB5 LCD_D2 PORTBbits.RB6 LCD_D3 PORTBbits.RB7 LCD_RS PORTBbits.RB2 LCD_E PORTBbits.RB3 // // // // // // you you you you you you must must must must must must set set set set set set this this this this this this pin pin pin pin pin pin as as as as as as output output output output output output
//*******************************************************************
Si fa notare che le linee dati partono da 0 fino a 3, questo poich si sta utilizzando una comunicazione a 4 bit corrispondono in realt alle linee DB4, DB5, DB6 e DB7 del display e non DB0, DB1, DB2 e DB3 che non vengono usate in una trasmissione a 4 bit. Nell'esempio sopra si pu vedere che i pin utilizzati sono quelli della PORTB. Una volta adeguata la libreria bisogna salvarla con le nuove impostazioni. Per poterla poi utilizzare necessario includerla con la direttiva #include all'interno del progetto47 e dichiarare come uscite (0) i pin che si stanno utilizzando. Dunque Quando si imposta con TRISx il registro x bisogna porre degli 0 sui pin usati dal display. Nella libreria LCD_44780.h sono presenti molte funzioni che permettono di semplificare la vita del programmatore, le funzioni disponibili che possibile richiamare sono: Funzioni
void OpenLCD (void) void ClearLCD (void) void CursorLCD (char,char) void HomeLCD (void) void Line2LCD (void) void ShiftLCD (char) void ShiftCursorLCD (char)
Riposiziona il cursore all'inizio del display Posiziona il cursore all'inizio della seconda riga Trasla le righe di una posizione a destra o sinistra Sposta il cursore di una posizione a destra o sinistra
47
E' mia abitudine avere una cartella Library con tutte le librerie che copio all'interno della cartella del progetto che sto realizzando.
59/93
www.LaurTec.com
void WriteCharLCD (char) void WriteVarLCD(char *) void WriteStringLCD(const char *)
Questa funzione deve essere eseguita una sola volta e sempre prima d'iniziare ad utilizzare le altre funzioni. Lo scopo della funzione inizializzare il display, pulire le righe, posizionare il cursore all'inizio e togliere il suo lampeggio. Una mancata esecuzione di tale funzione lascia la prima riga del display scura. Es.
OpenLCD (); void ClearLCD (void)
Tale funzione quando richiamata permette di ripulire il display da ogni scritta. Es.
ClearLCD ( ); void CursorLCD (char,char)
Questa funzione permette di impostare alcune caratteristiche del cursore che punta la posizione in cui sar scritto il prossimo carattere. Il primo valore tra parentesi attiva o disattiva il cursore; il valore 0 disattiva il cursore il valore 1 lo attiva. Il secondo valore attiva il lampeggio o meno del cursore, 0 lo disattiva 1 lo attiva. Es.
CursorLCD (0,0); void HomeLCD (void) //non visualizza il cursore e non effettua lampeggi
Tale funzione riposiziona il cursore alla prima riga in modo da iniziare a scrivere dall'inizio sovrascrivendo i caratteri presenti. Es.
HomeLCD ( ) ; void Line2LCD (void)
Tale funzione trasla verso destra o verso sinistra, di un carattere, le righe del display, creando l'effetto di scorrimento. Per far traslare verso destra bisogna scrivere RIGHT mentre per traslare verso sinistra bisogna scrivere LEFT. Dal momento che il display possiede una memoria interna ciclica una volta che i caratteri scompaiono dal display ritornando indietro verranno rivisualizzati. Es.
ShiftLCD (LEFT); //traslo il display di un carattere a sinistra
60/93
www.LaurTec.com
void ShiftCursorLCD (char)
Per mezzo di questa funzione possibile spostare la posizione attuale del cursore influenzando la posizione in cui verr inserito il prossimo carattere o stringa. Analogamente alla funzione ShiftLCD si possono utilizzare le costanti RIGHT e LEFT per spostare il cursore a destra e a sinistra. Es.
ShiftCursorLCD (RIGHT); void WriteCharLCD (char) //sposto il cursore un carattere a destra
Per mezzo di questa funzione possibile scrivere un carattere ASCII sul display. Es.
WriteCharLCD ('M'); void WriteVarLCD(char *) // scrivo il carattere M
Per mezzo di questa funzione, che al suo interno fa uso della funzione precedente, possibile scrivere una stringa (Array di caratteri) sul display. La stringa di caratteri deve avere come ultimo elemento il valore speciale '\0' . La variabile in ingresso alla funzione il puntatore all'inizio dell'Array ovvero il nome dell'Array.
void WriteStringLCD(const rom char *)
Per mezzo di tale funzione possibile scrivere sul display una stringa costante come potrebbero essere i messaggi per un men da visualizzare. Es.
WriteStringLCD (Hello World); // scrivo una stringa costante
Si osservi che in questo caso si fatto uso del doppio apice e non dell'accento. Per poter far uso delle funzioni descritte necessario includere la libreria LCD_44780.h presente nella cartella delle librerie personali Library. Vediamo un primo esempio di Hello World in cui sia realmente possibile leggere Hello World!
1 #include <p18f4580.h> 2 3 #include "LCD_44780.h" 4 5 #pragma config OSC = HS // 20Mhz 6 #pragma config WDT = OFF // disattivo il watchdog timer 7 #pragma config LVP = OFF // disattivo la programmazione LVP 8 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 9 10 11 void main (void) 12 { 13 14 TRISA = 0xFF; // Tutti input 15 PORTA = 0x00 ; 16
61/93
www.LaurTec.com
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 } TRISB = 0x00 ; // Tutte Uscite PORTB = 0x00 ; TRISC = 0xFF; // Tutti input PORTC = 0x00 ; TRISD = 0x00; // Tutte uscite PORTD = 0x00; TRISE = 0xFF; // Tutti input PORTE = 0x00; OpenLCD (); // Inizializzo LCD WriteStringLCD ("Hello World"); //Scrivo la mia frase ShiftLCD (RIGHT); //sposto la scritta a destra ShiftLCD (RIGHT); while (1); //ciclo infinito
Alla riga 3 possibile osservare che necessario includere la libreria LCD_44780.h presente all'interno della cartella Library. Questa cartella contiene le librerie personali e deve essere posizionata all'interno della stessa cartella di ogni progetto. L'alternativa potrebbe essere quella di posizionare una sola copia in C: e sostituire la direttiva incude di riga 3 con #include C:\Library\ LCD_44780.h. Si noti che la PORTB configurata come uscita in modo da pilotare LCD. Se si dovesse cambiare porta sar necessario impostare i relativi pin come output e cambiare la libreria LCD_44780.h come precedentemente spiegato. Alla riga 29 si pu vedere che come prima funzione da richiamare prima di poter utilizzare l'LCD la funzione OpenLCD () che permette d'inizializzare l'LCD. Si ricorda che se l'LCD non viene oppurtunatamente inizializzato la prima riga dell'LCD rimane sempre accesa. Alla riga 31 viene scritto Hello World per mezzo della funzione WriteStringLCD (). Questa funzione risulta utile in tutti i casi in cui bisogna scrivere delle stringhe che siano note a priori, come per esempio dei messaggi di errore o men. Alla riga 33 e 34 viene spostata la scritta Hello World di due posizioni (caratteri) verso destra, in modo da centrare la scrittura nell'LCD. In questo caso la centratura la si sarebbe potuta ottenere anche scrivendo Hello World invece di Hello World (si notino i due spazi che sono stati lasciati nel primo caso). Un altro modo sarebbe stato quello di spostare il cursore di due posizioni e poi scrivere il messaggio. Si capisce che il metodo pi semplice in realt inserire degli spazi vuoti. Dopo la scrittura del messaggio il programma non fa pi nulla! Vediamo un secondo esempio in cui si fa uso di una struttura per memorizzare il nome e il cognome di una persona e si effettua una piccola manipolazione di Array. Questa piccola manipolazione fa di questo programma uno dei pi complicati, visto che si introdurr il concetto di puntatore che tra gli aspetti pi complicati per chi affronta il C per la prima volta.
1 #include <p18f4580.h> 2 3 #include "LCD_44780.h" 4 5 #pragma config OSC = HS // 20Mhz
62/93
www.LaurTec.com
6 #pragma config WDT = OFF // disattivo il watchdog timer 7 #pragma config LVP = OFF // disattivo la programmazione LVP 8 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 9 10 11 void copy (char * dest, rom const char * parola); //prototipo 12 13 14 typedef struct 15 { 16 unsigned char nome [20]; 17 unsigned char cognome [20]; 18 19 } persona; 20 21 22 23 void main (void) 24 { persona tizio; // la variabile tizio di tipo persona 25 26 TRISA = 0xFF; // Tutti input 27 PORTA = 0x00 ; 28 29 TRISB = 0x00 ; // Tutte Uscite 30 PORTB = 0x00 ; 31 32 TRISC = 0xFF; // Tutti input 33 PORTC = 0x00 ; 34 35 TRISD = 0xFF; // Tutti input 36 PORTD = 0x00; 37 38 TRISE = 0xFF; // Tutti input 39 PORTE = 0x00; 40 41 OpenLCD (); // Inizializzo LCD 42 43 copy (tizio.nome, "Mauro"); //scrivo i dati nella variabile 44 copy (tizio.cognome, "Laurenti"); 45 46 WriteVarLCD (tizio.nome); //scrivo il nome sull'LCD 47 Line2LCD (); //mi sposto alla seconda linea 48 WriteVarLCD (tizio.cognome); //scrivo il cognome sull'LCD 49 50 while (1); //ciclo infinito 51 52 } 53 54// ***************************************************************** 55 void copy (char *dest, rom const char *parola) 56 { 57 while(*parola) 58 { 59 *dest = (*parola); // copio il carattere dentro l'array 60 parola++; // Incremento il puntatore
63/93
www.LaurTec.com
61 62 } 63 64 65 } dest++;
*dest = '\0';
Alla riga 3 viene inclusa la libreria per il controllo dell'LCD, fin qui nulla di nuovo. Alla riga 11 viene dichiarato il prototipo di funzione per la funzione creata per copiare due stringhe. Qui la cosa si fa complicata ma per ora pu essere saltata, l'unica cosa da tenere a mente che dopo la funzione main esiste la funzione copy che copia una stringa del tipo questo un esempio all'interno di un Array di caratteri. Tra la riga 14 e la riga 19 viene dichiarata una struttura nominata persona con i campi nome e cognome che sono rispettivamente due Array di caratteri di 20 elementi. Dal momento che un elemento dovr essere utilizzato per memorizzare il carattere di fine stringa '\0' si capisce che il nome e cognome pi lungo saranno di 19 caratteri. Alla riga 24 viene dichiarata la variabile tizio che di tipo persona ovvero un tizio una persona! La variabile tizio sar dunque caratterizzata dai campi nome e cognome. Dal momento che in C non esiste la variabile stringa se non come Array di caratteri, si ha che istruzioni del tipo nome= Piero non sono lecite. Per poter scrivere un nome o una qualunque frase all'interno di un Array necessario scrivere elemento per elemento i singoli caratteri del nome o frase. Per agevolare il programmatore presente la libreria string.h che deve essere inclusa insieme alle altre eventuali librerie. Questa contiene varie funzioni ad hoc per le stringhe. In questo programma di esempio ho preferito scrivere la funzione copy piuttosto che usare la libreria string.h in modo da comprendere come poter manipolare un Array di caratteri. Come detto la variabile tizio possiede i due campi nome e cognome. Questo significa che sar possibile accedere i singoli caratteri di questi due campi per mezzo di questa sintassi:
a = tizio.nome[2]; b = tizio.cognome[3];
che permettono di copiare nelle variabili a e b rispettivamente il terzo e il quarto carattere dei due campi nome e cognome. Questo significa che a e b devono essere due variabili dichiarate come caratteri:
unsigned char a; unsigned char b;
questa volta d non deve essere semplicemente un carattere! Infatti con questa sintassi senza parentesi quadre si intende l'indirizzo di memoria dove inizia il nostro Array48 tizio.nome. Pi propriamente si dice che d deve essere un puntatore del tipo char, ovvero servir per puntare, ovvero memorizzare, l'indirizzo di una stringa di caratteri. Per poter dichiarare un puntatore ad una variabile si fa uso del simbolo * prima del nome della variabile stessa; dunque un puntatore a char sar:
char * d;
una volta che si ha il puntatore lo si pu usare anche in sostituzione della sintassi in cui si accede
48
I questo caso si ha una struttura, ma la cosa sarebbe stata equivalente con un semplice Array di caratteri chiamato nome piuttosto che un Array nome interno alla struttura tizio; ovvero c = nome.
64/93
www.LaurTec.com
l'elemtento dell'Array con le parentesi quadre. Supponiamo di voler scrivere MAURO dentro l'Array tizio.nome. Quello che bisogna fare scrivere nel primo elemento dell'Array la 'M', nel secondo 'A', nel terzo 'U' nel quarto 'R' nel quinto 'O' e nel sesto il carattere '\0'. Questo lo si pu fare nel seguente modo:
tizio.nome tizio.nome tizio.nome tizio.nome tizio.nome tizio.nome [0] [1] [2] [3] [4] [5] = = = = = = 'M'; 'A'; 'U'; 'R'; '0'; '\0';
In questo esempio scrivere * d significa: scrivi nella variabile (elemento) puntata dall'indirizzo di memoria contenuto in d. Scrivere d++ o comunque d uguale a qualcosa significa cambiare il valore del puntatore ovvero il contenuto di d. Per mezzo di d++ si incrementa l'indirizzo e dunque come se si accedesse all'elemento successivo dell'Array. Rivediamo il tutto con l'aiuto della Figura 30 in modo da comprendere l'argomento in maniera pi chiara. 0 tizio.nome 19 0 tizio.cognome 19 d
Si consideri che ogni cella sia un Byte della memoria RAM dove sono contenute le nostre informazioni ovvero variabili. In particolare si consideri che le caselle dentro il rettangolo continuo siano i 20 bytes appartenenti all'Array tizio.nome mentre nel rettangolo tratteggiato siano presenti i 20 bytes dell'Array tizio.cognome mentre il rettangolo punto linea sia la variabile puntatore a char. Ogni casella avr un proprio indirizzo che il PIC utilizzir per sapere dove andare a leggere e dove andare a scrivere un certo dato. Quando si scrive d = tizio.nome si scrive all'interno di d l'indirizzo della prima casella dell'Array tizio.nome. L'indirizzo per solo un numero, per poter effettivamente andare a 65/93
www.LaurTec.com
leggere o scrivere nella casella di memoria puntata dall'indirizzo contenuto in d, necessario scrivere un asterisco prima di d stesso. Senza mettere l'asterisco si accede al numero, contenuto in d, come se questa fosse una variabile normale. Dopo questa breve spiegazione ritorniamo al nostro programma Alla riga 43 e 44 si richiama la funzione copy in modo da copiare il nome e il cognome all'interno dei nostri Array. Si capisce che per far funzionare la nostra funzione necessario indicare la posizione del nostro Array, dunque si passer il suo indirizzo semplicemente scrivendo tizio.nome e tizio.cognome. Come secondo campo sar necessario passare la nostra stringa costante che contiene il nostro nome (riga 43) e il nostro cognome (riga 44). Alla riga 46 si scrive il nome sul display passando guarda caso alla funzione WriteVarLCD l'indirizzo dove contenuto il primo elemento del nome. Alla riga 47 viene eseguita la funzione che permette di passare alla seconda linea mentre alla riga 48 viene scritto il cognome facendo uso della funzione WriteVarLCD ( ). Fatto questo, il programma inizia il suo bel loop infinito. Alla riga 55 inizia la dichiarazione della funzione copy che viene richiamata per copiare una qualunque parola all'interno di un'Array. E' possibile notare che la prima variabile rappresenta un tipo puntatore ad Array, questo se si seguito il ragionamento precedente spero non sorprenda. La seconda variabile che viene passata alla funzione un po' infelice poich in realt non ANSI C. Il tipo di variabile un puntatore a caratteri costanti contenuti in rom, ovvero nella memoria programma. Questa la scelta di Microchip per gestire una stringa costante che viene memorizzata all'interno della memoria usata per il programma. Si capisce che se la funzione avesse dovuto copiare un Array in un altro Array anche la seconda variabile sarebbe stato un puntatore a char; per questo caso bisogna scrivere dunque un'altra funzione. Alla riga 57 viene eseguito un ciclo while che termina quando il valore puntato dal puntatore parola vale '\0'. Fino a che tale valore diverso da tale carattere vengono eseguite le istruzioni di riga 59, 60 e 61. Alla riga 59 si copia il carattere puntato da parola nell'indirizzo puntato da dest, ovvero si copia un elemento dall'origine alla destinazione. Alla riga 60 si incrementa l'indirizzo contenuto nella variabile parola in modo da puntare il carattere successivo della parola origine. Alla riga 61 si incrementa l'indirizzo contenuto nella variabile dest in modo da poter copiare il nuovo carattere alla cella successiva. Il ciclo si ripete fino a che la parola non termina. Alla riga 64 si aggiunge, all'ultimo elemento puntato da dest, il valore '\0'. Infatti tale valore non viene trasferito poich il ciclo while termina quando questo viene trovato all'interno della parola.
66/93
www.LaurTec.com
Come utilizzare l'USART
L'utilizzo dell'USART un modo per collegare il nostro microcontrollore al computer e fare del nostro progetto un sistema professionale. In questo paragrafo si considerano gi noti i concetti introdotti nel Tutorial Il Protocollo RS232 in cui spiegato il protocollo RS232 utilizzato nei computer per le trasmissioni seriali. Freedom possiede la porta RS232 e dunque non ci si deve preoccupare dell'opportuno cambio di livello logico da TTL a RS232 svolto dal MAX23249. Per ulteriori informazioni sull'Hardware necessario si rimanda alla documentazione tecnica del Progetto Freedom, sistema embedded per PIC. Il PIC18F4580 possiede al suo interno un USART dunque non bisogna preoccuparsi di gestire via software l'intera comunicazione, quello che bisogna fare sar semplicemente impostare l'USART e dire quali informazioni trasmettere o andare a leggere. Una trasmissione seriale pu essere gestita sia in polling che per mezzo delle interruzioni. Per polling si intende che via software si deve controllare continuamente se l'USART ha ricevuto qualche dato; questa tecnica la stessa che si utilizzata nel primo esempio di lettura di un interruttore. La seconda tecnica, per mezzo delle interruzioni, permette di gestire il tutto in maniera pi snella poich il microcontrollore pu compiere altre operazioni fino a che non riceve un dato. Vediamo un riassunto delle funzioni della libreria usart.h che sono state utilizzate: Funzioni
char BusyUSART( void ) void CloseUSART( void ) char DataRdyUSART( void ) void OpenUSART( unsigned char config, unsigned int spbrg); char ReadUSART( void ) void WriteUSART( char data ) char BusyUSART( void )
Descrizione Controlla se l'USART occupata Chiude l'USART Controlla se sono presenti dati ricevuti Inizializza l'USART Legge un dato dal buffer di ricezione Trasmette un dato in uscita
Per mezzo di questa funzione possibile controllare lo stato di trasmissione dell'USART. In particolare la funzione ritorna il valore 1 se l'USART sta trasmettendo il dato altrimenti ritorna il valore 0. Questa funzione pu essere utilizzata per controllare la fine della trasmissione di un byte.
void CloseUSART( void )
Per mezzo di questa funzione possibile controllare se nel buffer di ricezione dell'USART presente almeno un byte. Se presente un dato viene ritornato il valore 1 altrimenti se non presente nessun dato viene ritornato il valore 0.
void OpenUSART( unsigned char config,unsigned int spbrg)
Per mezzo di questa funzione possibile impostare i parametri di trasmissione RS232 ed eventuali interruzioni. Per fare questo bisogna riempire i due campi della funzione OpenUSART. Il primo valore dato da un AND bitwise di varie costanti. Dal valore finale la funzione rileva le impostazione della porta interna. Il secondo valore un registro che permette d'impostare la frequenza di trasmissione.
49
Si ricorda che la porta seriale RS232 di Freedom multiplexata con la porta seriale RS485 dunque i jumper devono essere oppurtunatamente settati per la trasmissione RS232. Si rimanda alla documentazione ufficiale di Freedom per ulteriori informazioni.
67/93
www.LaurTec.com
Il primo valore della funzione viene impostato per mezzo delle seguenti costanti, unite tra loro per mezzo dell'AND bitwise &. Interruzione di Trasmissione: USART_TX_INT_ON Interruzione TX ON USART_TX_INT_OFF Interruzione TX OFF Interruzione in ricezione:
USART_RX_INT_ON USART_RX_INT_OFF Modalit USART: USART_ASYNCH_MODE USART_SYNCH_MODE
Interruzione RX ON Interruzione RX OFF Modalit Asincrona Modalit Sincrona 8-bit 9-bit Slave modalit' sincrona (si applica solo in modalit' sincrona) Master modalit' sincrona (si applica solo in modalit' sincrona) Ricezione singola Ricezione multipla baud rate alto baud rate basso
Larghezza dati:
USART_EIGHT_BIT USART_NINE_BIT
Modalit Slave/Master:
USART_SYNC_SLAVE USART_SYNC_MASTER
Modalit di ricezione:
USART_SINGLE_RX USART_CONT_RX
Baud rate:
USART_BRGH_HIGH USART_BRGH_LOW
Il secondo valore da passare alla funzione spbrg che permette di impostare la frequenza di trasmissione. Tale valore varia a seconda della frequenza del quarzo che si sta utilizzando e se si sta utilizzando un alto baud rate o meno. Alto baud rate s ha quando il flag BRGH impostato ad 1 mentre un basso baud rate si ha con BRGH impostato a 0. Questi valori sono assegnati dalla funzione OpenUSART per mezzo delle costanti USART_BRGH_HIGH e USART_BRGH_LOW. Per decidere il valore della variabile spbrg si pu far uso delle tabelle riportate sui data sheet del microcontrollore che si sta utilizzando. In Tabella 3 sono riportate quelle di maggior interesse ovvero per il caso asincrono alto baud rate e basso baud rate. In particolare le prime due tabelle fanno riferimento all'opzione basso baud rate; ogni colonna delle tabelle fa riferimento a diverse frequenze di quarzo. Le ultime due tabelle fanno riferimento al caso sia selezionata l'opzione alto baud rate; anche in questo caso le colonne fanno riferimento a diversi valori di quarzo.
68/93
www.LaurTec.com
69/93
www.LaurTec.com
Vediamo un esempio con quarzo 20MHz trasmissione asincrona alto baud rate, lunghezza dato 8 bit, 1 bit di stop, 0 bit di parit e un baud rate di 19200 bit/s senza interruzione n in trasmissione n in ricezione.
OpenUSART( USART_TX_INT_OFF & USART_RX_INT_OFF & USART_ASYNCH_MODE & USART_EIGHT_BIT & USART_CONT_RX & USART_BRGH_HIGH,
64 ); Per ulteriori informazioni sulle impostazioni sull'USART si rimanda al data sheet del PIC utilizzato.
char ReadUSART( void )
Per mezzo di questa funzione possibile leggere un byte ricevuto dalla porta seriale. Il valore letto ovvero ritornato dalla funzione, di tipo char.
void WriteUSART( char data )
Per mezzo di questa funzione possibile scrivere un dato in uscita alla porta seriale. Il dato deve essere di tipo char quindi di lunghezza non superiore a 8 bits. Per ulteriori informazioni sulle altre funzioni disponibili nella libreria usart.h si rimanda alla documentazione ufficiale della Microchip. Vediamo un esempio di trasmissione seriale in cui il microcontrollore, usando la tecnica del polling, ritrasmette ogni carattere che riceve dalla porta seriale RS232 collegata ad un computer. La lettura finisce quando il microcontrollore riceve il carattere 'c'.
1 /* 2 Autore : Mauro Laurenti 3 Versione : 1.0 4 Data : 25/5/2006 5 CopyRight 2006 6 7 la descrizione di questo programma applicativo possibile trovarla nel Tutorial 8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com 9 10 */ 11 12 #include <p18f4580.h> 13 #include <usart.h> 14 15 #include "LCD_44780_Freedom.h" 16 #include "Sponsor.h" 17 18 #pragma config OSC = HS // 20Mhz 19 #pragma config WDT = OFF // disattivo il watchdog timer 20 #pragma config LVP = OFF // disattivo la programmazione LVP 21 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 22 23
70/93
www.LaurTec.com
24 25 26 void main (void) 27 { unsigned char data; // variabile che conterr i dati ricevuti 28 29 TRISA = 0xFF; // Tutte ingressi 30 31 TRISB = 0xFF ; // Tutti ingressi 32 PORTB = 0x00 ; 33 34 TRISC = 0x80; // Tx line 0 and Rx line 1 35 PORTC = 0x00; 36 37 TRISD = 0x00; // la PORTD settata per lavorare con l'LCD 38 PORTD = 0x00; 39 40 TRISE = 0xFF; // Tutti ingressi 41 42 OpenLCD (); // inizializzo LCD 43 44 WriteSponsor (); 45 46 // Configura l'USART 47 // 8 bit 48 // 19200 bit/s 49 // 1 bit stop 50 // 0 bit parit 51 52 OpenUSART( USART_TX_INT_OFF & 53 USART_RX_INT_OFF & 54 USART_ASYNCH_MODE & 55 USART_EIGHT_BIT & 56 USART_CONT_RX & 57 USART_BRGH_HIGH, 58 64 ); 59 60 WriteStringLCD ("Ready"); //l'USART pronta per la ricezione 61 62 while(1) 63 { 64 while( !DataRdyUSART( ) ); //attendo di ricevere un dato dal PC 65 data = ReadUSART(); // leggo il dato dal buffer di ricezione 66 WriteUSART( data); //ritrasmetto il dato ricevuto 67 68 if(data == 'c') // ricevendo una 'c' chiudo l'USART 69 break; // esco dal loop di ricezione 70 71 } 72 73 CloseUSART(); // chiudo l'USART 74 75 ClearLCD (); // ripulisco LCD prima di riscrivere 76 77 WriteStringLCD ("Closed"); //l'USART stata chiusa 78 79
71/93
www.LaurTec.com
80 while (1); //ciclo infinito 81 82 }
Alla riga 13 possibile vedere che bisogna includere la libreria C18 #include <usart.h> che permette di utilizzare le funzioni per la gestione dell'USART, nulla naturalmente vieta di scrivere una propria libreria. Alla riga 15 viene inclusa la libreria per controllare l'LCD visto che nell'esempio vengono visualizzati semplici messaggi per segnalare lo stato dell'USART. Alla riga 34 la PORTC viene impostata in modo da avere il pin Tx (RC6) come uscita e il pin Rx (RC7) come ingresso. Alla riga 52, viene eseguita la funzione OpenUsart () che permette di inizializzare l'USART interna al PIC. Si fa subito notare che le impostazioni di comunicazione devono essere le stesse di quelle settate sul PC o altro sistema RS232. L'USART viene inizializzata per lavorare a: parola : 8 bit frequenza : 19200 bit/s stop bit : 1 bit parit : 0 che come detto devono essere le impostazioni anche del PC. Alla riga 60 viene visualizzato sull'LCD il messaggio Ready per indicare che il sistema pronto per la ricezione. Tra la riga 62 e 71 presente il loop infinito che come detto permette di controllare continuamente la porta seriale per eventuali dati ricevuti. Alla riga 64 presente un altro while che blocca il programma fino a che non viene ricevuto un dato. Il blocco del programma avviene poich l'argomento del ciclo while rappresenta la negazione logica (fatta con il punto esclamativo) del valore ritornato dalla funzione DataRdyUSART( ), questa ritorna 0 se non presente nessun dato. Dal momento che presente la negazione si ha che il ciclo while continua a interrogare l'USART con la funzione DataRdyUSART( ) fino all'arrivo di un dato. Quando viene ricevuto un dato il programma continua alla riga 65. Alla riga 65 viene caricato il valore ricevuto dalla porta seriale nella variabile data, questo viene fatto per mezzo della funzione ReadUSART() . Alla riga 66 il dato ricevuto viene ritrasmesso alla sorgente (PC o altro sistema con porta RS232) per mezzo della funzione WriteUSART( data); Alla riga 68 viene controllato se il dato ricevuto il carattere ASCII 'c'. Se il carattere 'c' viene eseguita l'istruzione break che fa uscire dal while infinito. Se il carattere non la 'c' il programma si riblocca alla riga 64 in attesa di un nuovo dato. Alla riga 73, dopo la ricezione del carattere 'c' viene eseguita la funzione CloseUSART(); che permette di disattivare l'USART. Alla riga 77 viene scritto il messaggio Closed per segnalare l'avvenuta chiusura dell'USART. Vediamo ora lo stesso programma implementato per facendo uso delle interruzioni:
1 /* 2 Autore : Mauro Laurenti 3 Versione : 1.0 4 Data : 25/5/2006 5 CopyRight 2006 6 7 la descrizione di questo programma applicativo possibile trovarla nel Tutorial
72/93
www.LaurTec.com
8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com 9 10 */ 11 12 13 #include <p18f4580.h> 14 #include <usart.h> 15 16 #include "LCD_44780_Freedom.h" 17 #include "Sponsor.h" 18 19 #pragma config OSC = HS // 20Mhz 20 #pragma config WDT = OFF // disattivo il watchdog timer 21 #pragma config LVP = OFF // disattivo la programmazione LVP 22 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 23 24 25 void Low_Int_Event (void); // prototipo di funzione 26 27 28 #pragma code low_vector=0x18 29 30 void low_interrupt (void) 31 { 32 _ asm GOTO Low_Int_Event _endasm //imposta il salto per la gestione dell'interrupt 33 } 34 35 #pragma code 36 37 #pragma interruptlow Low_Int_Event 38 39 void Low_Int_Event (void) 40 { unsigned char data; // variabile che conterr i dati ricevuti 41 42 43 if (PIR1bits.RCIF == 1 ) // Controllo che l'interrupt sia stato generato dall'USART 44 { 45 data = ReadUSART(); // leggo il dato dal buffer di ricezione 46 WriteUSART( data); // ritrasmetto il dato ricevuto 47 PORTB = data; // scrivo il dato ricevuto, sulla PORTB 48 while (BusyUSART()); // attendo che il dato venga trasmesso 49 50 if (data == 'c') 51 { 52 INTCONbits.GIE = 1; // abilito l'interrupt globale 53 CloseUSART(); // chiudo l'USART 54 55 ClearLCD (); // ripulisco LCD prima di riscrivere 56 57 WriteStringLCD ("Closed"); 58 } 59 60 PIR1bits.RCIF = 0;
73/93
www.LaurTec.com
61 } 62 63 } 64 65 66 67 void main (void) 68 { 69 TRISA = 0xFF; // Tutti ingressi 70 71 TRISB = 0x00 ; // Tutte uscite 72 PORTB = 0x00 ; 73 74 TRISC = 0x80; // Tx line 0 and Rx line 1 75 PORTC = 0x00; 76 77 TRISD = 0x00; // la PORTD settata per lavorare con l'LCD 78 PORTD = 0x00; 79 80 TRISE = 0xFF; // Tutti ingressi 81 82 OpenLCD (); 83 84 WriteSponsor (); 85 86 87 // Configura l'USART 88 // 8 bit 89 // 19200 bit/s 90 // 1 bit stop 91 // 0 bit parit 92 93 OpenUSART( USART_TX_INT_OFF & 94 USART_RX_INT_ON & 95 USART_ASYNCH_MODE & 96 USART_EIGHT_BIT & 97 USART_CONT_RX & 98 USART_BRGH_HIGH, 99 64 ); 100 101 102 INTCONbits.GIE = 1; // Abilito l'interrupt globale 103 INTCONbits.PEIE = 1 ; // abilito interrupt per periferiche 104 105 WriteStringLCD ("Ready"); //Usart pronta per la ricezione 106 107 while (1); //ciclo infinito in attesa d'interrupt 108 109 }
Partiamo dalla funzione main. Si pu vedere che l'inizializzazione simile al caso del polling ma alla riga 102 e 103 vengono attivati i bit GIE e PEIE per abilitare le interruzioni generali e quelle delle periferiche. Si ricorda che il bit per abilitare l'interruzione dell'USART viene settato automaticamente dalla funzione OpenUSART (...) per mezzo della costante USART_RX_INT_ON . L'interruzione in trasmissione non viene abilitata dal momento che si far uso della funzione BusyUSART() non usata nel programma precedente. 74/93
www.LaurTec.com
Dopo questa inizializzazione viene scritto sull'LCD il messaggio Ready e poi il programma inizia un loop infinito in attesa d'essere interrotto da un dato in arrivo. Durante questa attesa il PIC potrebbe essere impiegato per svolgere altre operazioni o andare in stato di sleep in modo da risparmiare energia. Si fa notare che l'USART non stata impostata come periferica interrompente ad altra priorit, dunque il vettore d'interruzione 0x18. Se si volesse impostare l'USART come periferica interrompente ad altra priorit sarebbero dovute scrivere queste altre istruzioni:
RCONbits.IPEN = 1; IPR1bits.RCIP = 1; //abilit interruzioni con priorit // imposta la ricezione ad alta priorit
Il vettore delle interruzioni deve essere posto a 0x08 mentre la direttiva #pragma interuptlow deve essere sostituita con #pragma interrupt. La gestione dell'interruzione avviene tra la riga 39 e 63. Alla riga 43 viene controllato il flag di ricezione RCIF presente nel registro PIR1. Se l'interruzione effettivamente generata dalla ricezione di un dato allora viene eseguita la lettura e la trasmissione del dato stesso. In questo caso l'interruzione sar generata sicuramente dalla ricezione di un dato, ma in un programma pi complesso si possono avere interruzioni multiple che bisogna gestire per mezzo dei flag. Il programma delle righe successive concettualmente simile al precedente scritto per il polling. Si fa notare che alla riga 60 il flag di ricezione viene resettato in modo da evitare interruzioni ricorsive dovute ad uno stesso byte ricevuto.
75/93
www.LaurTec.com
Come utilizzare il bus I2C
Il protocollo I2C risulta particolarmente utile per la comunicazione d'informazioni fra sistemi intelligenti o comunque tra microcontrollori e periferiche esterne quali memorie, orologi real time, termometri, display e molto altro. In questo paragrafo si considereranno noti le conoscenze di base sul protocollo I2C esposte nel Tutorial Bus I2C. Come per le altre periferiche presente una libreria ad hoc con la quale possibile controllare con poco sforzo l'hardware interno ai PIC dedicato all'I2C. Il file da includere per poter utilizzare tale librerie il file i2c.h, quindi in testa al programma bisogner scrivere #include <i2c.h>. Piuttosto che descrivere le funzioni contenute in questa libreria descriver alcune librerie personali che risultano comode per due applicazioni in cui frequentemente si utilizza il protocollo I2C. In particolare si descriver una semplice libreria che permette di leggere e scrivere all'interno di una EEPROM I2C quale per esempio la 24LC512 o altre per le quali sono necessari due byte per l'indirizzamento interno della cella di memoria50. La seconda libreria che descriver sar quella per il controllo dell'integrato PCF8563 ovvero il real time clock calendar della Philips. Vediamo le funzioni disponibili nella prima libreria eeprom.h per il controllo di memorie EEPROM ad alta capacit: Funzioni Descrizione
char WriteEEprom( unsigned char control, Permette di scrivere un dato all'interno unsigned char addressH,unsigned char della memoria EEPROM ad un addressL, unsigned char data ) determinato indirizzo. char ReadEEprom( unsigned char addressL ) unsigned char control, Permette di leggere un dato dalla addressH,unsigned char memoria EEPROM ad un determinato
indirizzo.
char WriteEEprom( unsigned char control, unsigned char addressH,unsigned char addressL, unsigned char data )
Per mezzo di questa funzione si ha la possibilit di scrivere un byte all'interno della memoria EEPROM ad un prefissato indirizzo. La funzione ritorna 0 se l'operazione riesce correttamente altrimenti un numero negativo se si verificato un errore. I dati che bisogna passare alla funzione sono rispettivamente: control : rappresenta l'indirizzo di scrittura della memoria. Questo cambier a seconda del valore dei pin d'indirizzo della memoria. addressH: contiene il byte pi significativo dell'indirizzo di memoria dove andare a scrivere il dato. addressL: contiene il byte meno significativo dell'indirizzo di memoria dove andare a scrivere il dato. data: contiene il byte che rappresenta il dato che bisogna scrivere all'interno dell'indirizzo di memoria precedentemente selezionato.
char ReadEEprom( unsigned char control, unsigned char addressH,unsigned char addressL )
Per mezzo di tale funzione possibile leggere un byte ad un determinato indirizzo della memoria EEPROM. Il valore che la funzione ritorna un numero negativo se si riscontrato un errore altrimenti il valore contenuto all'interno della locazione di memoria selezionata. I dati che bisogna
50
Questa libreria risulta utile poich Microchip mette a disposizione solo delle funzioni che permettono di scrivere all'interno di memorie EEPROM I2C in cui sia necessario trasmettere un solo byte d'indirizzo.
76/93
www.LaurTec.com
passare alla funzione sono rispettivamente:
control : rappresenta l'indirizzo di scrittura (non di lettura) della memoria. Questo cambier a seconda del valore dei pin d'indirizzo. addressH: contiene il byte pi significativo dell'indirizzo di memoria dove andare a leggere il dato. addressL: contiene il byte meno significativo dell'indirizzo di memoria dove andare a leggere il dato. Per poter far uso delle funzioni descritte necessario includere la libreria eeprom.h presente nella cartella delle librerie personali Library. Per poter utilizzare tale funzioni non necessario includere la libreria i2c.h poich questa viene inclusa all'interno della libreria eeprom.h. Vediamo un semplice esempio in cui si scrive un dato all'interno di una memoria EEPROM 24LC512 per poi successivamente leggerlo e visualizzarlo in uscita alla PORTD.
1 #include <p18f4580.h> 2 3 #include "eeprom.h" 4 5 #pragma config OSC = HS // 20Mhz 6 #pragma config WDT = OFF // disattivo il watchdog timer 7 #pragma config LVP = OFF // disattivo la programmazione LVP 8 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 9 10 11 //************************************************** 12 13 void main (void) 14 { int i ; 15 16 TRISA = 0xFF; //Tutti ingressi 17 PORTA = 0x00; 18 19 TRISB = 0xFF ; //Tutti ingressi 20 PORTB = 0x00 ; 21 22 TRISC = 0xFF; //Tutti ingressi 23 PORTC = 0x00; 24 25 TRISD = 0x00; //Tutte uscite 26 PORTD = 0x00; 27 28 TRISE = 0xFF; //Tutti ingressi 29 PORTE = 0x00; 30 31 32 OpenI2C(MASTER, SLEW_ON);// Initializza il modulo I2C a 100KHz 33 34 SSPADD = 14; //400kHz Baud clock @20MHz 35 36 37
77/93
www.LaurTec.com
38 WriteEEprom (0xA0,0x00,0x01,0b01010101); // scrivo il byte 55H all'indirizzo 0001H 39 40 for (i=0; i<1000; i++); //pausa 41 42 PORTD = ReadEEprom (0xA0,0x00,0x01); // leggo l'indirizzo 0001H 43 44 while(1); // ciclo infinito 45 46 }
Alla riga 32 viene richiamata la funzione OpenI2C(MASTER, SLEW_ON) che appartiene alla libreria i2c.h questa permette di inizializzare il modulo I2C del PIC. In particolare il modulo viene inizializzato come master e per lavorare alla frequenza di 400KHz. La frequenza viene in realt impostata per mezzo del registro SSPADD impostato al valore 14. Si ricorda che il master a decidere la frequenza di lavoro del bus l'importante che questa non superi la frequenza massima delle periferiche collegate al bus stesso. Se si volesse per esempio una frequenza pi bassa si potrebbe variare il valore di SSPADD. La relazione da usare per il calcolo del valore da inserire in SSPADD : SSPADD=1 F OSC 4F BUS
dove FOSC rappresenta la frequenza del quarzo mentre FBUS rappresenta il valore della frequenza che si vuole avere per il bus I2C. Alla riga 38 viene scritto il valore 0x55 all'indirizzo 0001 della memoria EEPROM presente su Freedom. Dal momento che tale memoria possiede i tre pin d'indirizzo collegati a massa si ha che l'indirizzo di scrittura 0xA0. Alla riga 40 presente un ritardo che permette al dato d'essere scritto correttamente prima di essere letto. Alla riga 42 avviene la lettura dalla memoria EEPROM dell'indirizzo 0001 e la relativa scrittura sulla PORTD del dato letto. Vediamo ora le funzioni contenute nella libreria PCF8563.h che permettono di controllare il real time clock calendar PCF8563 della Philips: Funzioni
unsigned char char Seconds) unsigned char char Minutes) WriteSeconds
Descrizione
(unsigned Scrive i secondi dell'orario
unsigned char WriteHours (unsigned char Scrive l'ora dell'orario Hours) unsigned char ReadHours (void) unsigned char* ReadTimeSeconds (void) unsigned char* ReadTime (void)
Legge l'ora dell'orario Legge l'orario comprensivo dei secondi Legge l'orario senza secondi
78/93
www.LaurTec.com
unsigned char WriteDays (unsigned char Scrive il giorno della data Days) unsigned char ReadDays (void) unsigned char WriteWeekDays char WeekDays) unsigned char char Months) WriteMonths
unsigned char WriteYears (unsigned char Scrive l'anno Years) unsigned char ReadYears (void) unsigned char* ReadDate (void)
unsigned char WriteMinutesAlarm Scrive i minuti per l'allarme (unsigned char Minutes,unsigned char AlarmEnable) unsigned char WriteHoursAlarm (unsigned Scrive l'ora per l'allarme char Hours,unsigned char AlarmEnable) unsigned char WriteDaysAlarm (unsigned Scrive il giorno per l'allarme char Days,unsigned char AlarmEnable) unsigned char WriteWeekDaysAlarm Scrive la settimana per l'allarme (unsigned char WeekDays,unsigned char AlarmEnable) unsigned char EnableInt (void) unsigned char DisableAllInt (void) unsigned char IsAlarmON (void)
Abilita l'interruzione per l'allarme Disabilita tutte le interruzioni Controlla se l'allarme stato attivato (polling)
Per mezzo di questa funzione possibile scrivere i secondi dell'orario corrente all'interno dell'integrato. Il formato dell'orario di tipo BCD, per cui i quattro bit meno significativi sono le unit mentre i quattro bit pi significativi sono le decine. Dunque per semplificarne la scrittura bene far uso di numeri esadecimali. Infatti in questo modo possibile leggere facilmente i secondi che si sono impostati. Per esempio 0x55 sono 55 secondi, 0x12 sono 12 secondi. Se si scrivesse direttamente 12 in decimale si avrebbe un valore BCD non valido.
unsigned char ReadSeconds (void)
Per mezzo di tale funzione possibile leggere i secondi dell'orario corrente. Il valore viene riportato all'interno di un byte in formato BCD.
unsigned char WriteMinutes (unsigned char Minutes)
Per mezzo di questa funzione possibile scrivere i minuti dell'orario corrente. Il formato dei minuti BCD.
unsigned char ReadMinutes (void)
Per mezzo di questa funzione possibile leggere i minuti dell'orario corrente. Il valore dei minuti viene riportato in formato BCD all'intero di un byte. 79/93
www.LaurTec.com
unsigned char WriteHours (unsigned char Hours)
Per mezzo di questa funzione possibile scrivere l'ora dell'orario corrente. Il formato dell'ora BCD.
unsigned char ReadHours (void)
Per mezzo di questa funzione possibile leggere l'ora dell'orario corrente. Il valore dell'ora viene riportato in formato BCD all'intero di un byte.
unsigned char* ReadTimeSeconds (void)
Per mezzo di questa funzione possibile leggere l'intero orario comprensivo di secondi in formato HH:MM.ss. Il valore viene riportato sotto forma di stringa ASCII direttamente visualizzabile su display alfanumerici LCD.
unsigned char* ReadTime (void)
Per mezzo di questa funzione possibile leggere l'intero orario, senza i secondi, in formato HH:MM. Il valore viene riportato sotto forma di stringa ASCII direttamente visualizzabile su un display LCD.
unsigned char WriteDays (unsigned char Days)
Per mezzo di questa funzione possibile scrivere il giorno della data odierna. Il formato del giorno tipo BCD.
unsigned char ReadDays (void)
Per mezzo di questa funzione possibile leggere la data odierna. Il valore viene riportato in un byte in formato BCD.
unsigned char WriteWeekDays (unsigned char WeekDays)
Per mezzo di questa funzione possibile scrivere il giorno della settimana della data odierna. E' possibile far uso direttamente delle seguenti costanti: DO: domenica LU: luned MA: marted GI: gioved VE: venerd SA: sabato
unsigned char ReadWeekDays (void)
Per mezzo di questa funzione possibile leggere il giorno della settimana. Il valore ritornato compreso tra 0 e 6. In particolare si pu far uso delle costanti precedenti per eventuali confronti. Es. if (ReadWeekDays ( ) = = LU) { //istruzioni per gestire inizio della settimana! }
unsigned char WriteMonths (unsigned char Months)
Per mezzo di questa funzione possibile scrivere il mese della data corrente. Il formato del mese BCD. 80/93
www.LaurTec.com
unsigned char ReadMonths (void)
Per mezzo di questa funzione possibile leggere in formato BCD il valore del mese della data odierna.
unsigned char WriteYears (unsigned char Years)
Per mezzo di questa funzione possibile scrivere l'anno della data corrente. Il formato dell'anno BCD e comprende solo le ultime due cifre dell'anno stesso.
unsigned char ReadYears (void)
Per mezzo di questa funzione possibile leggere l'anno della data corrente. Il valore dell'anno viene riportato in formato BCD e comprende solo le ultime due cifre dell'anno stesso.
unsigned char* ReadDate (void)
Per mezzo di questa funzione possibile leggere l'intera data corrente in formato GG/MM/AA. Il valore viene riportato all'interno di una stringa in formato ASCII direttamente visualizzabile su display alfanumerici LCD.
unsigned char AlarmEnable) WriteMinutesAlarm (unsigned char Minutes,unsigned char
Per mezzo di questa funzione possibile scrivere i minuti relativi all'orario di allarme. In particolare la funzione necessita di un secondo parametro per settare o meno il flag di allarme per i minuti. Il flag pu essere settato per mezzo della costante Enable_ON mentre viene disattivato per mezzo della costante Enable_OFF . Attivare o meno il flag significa abilitare o meno il confronto tra questo valore e l'orario corrente per decidere se attivare o meno l'allarme.
unsigned char AlarmEnable) WriteHoursAlarm (unsigned char Hours,unsigned char
Per mezzo di questa funzione possibile scrivere l'ora relativa all'orario di allarme. In particolare la funzione necessita di un secondo parametro per settare o meno il flag di allarme per l'ora. Il flag pu essere settato per mezzo della costante Enable_ON mentre viene disattivato per mezzo della costante Enable_OFF . Attivare o meno il flag significa abilitare o meno il confronto tra questo valore e l'orario corrente per decidere se attivare o meno l'allarme.
unsigned char WriteDaysAlarm (unsigned char Days,unsigned char AlarmEnable)
Per mezzo di questa funzione possibile scrivere il giorno relativo alla data di allarme. In particolare la funzione necessita di un secondo parametro per settare o meno il flag di allarme del giorno. Il flag pu essere settato per mezzo della costante Enable_ON mentre viene disattivato per mezzo della costante Enable_OFF . Attivare o meno il flag significa abilitare o meno il confronto tra questo valore e la data corrente per decidere se attivare o meno l'allarme.
unsigned char AlarmEnable) WriteWeekDaysAlarm (unsigned char WeekDays,unsigned char
Per mezzo di questa funzione possibile scrivere il giorno della settimana relativo alla data di allarme. In particolare la funzione necessita di un secondo parametro per settare o meno il flag di allarme del giorno della settimana. Il flag pu essere settato per mezzo della costante Enable_ON mentre viene disattivato per mezzo della costante Enable_OFF . Attivare o meno il flag significa abilitare o meno il confronto tra questo valore e la data corrente per decidere se attivare o meno l'allarme.
unsigned char EnableInt (void)
Per mezzo di questa funzione possibile attivare l'interruzione esterna dell'integrato. Questa viene 81/93
www.LaurTec.com
generata quando viene attivato l'allarme per l'orario. In realt il PCF8563 gestisce anche un'altra interruzione non gestita in questa libreria. In particolare questa funzione disabilita l'altra interruzione. Per ulteriori informazioni si rimanda al relativo data sheet.
unsigned char DisableAllInt (void)
Per mezzo di questa funzione vengono disabilitati tutti gli interrupt interni al PCF8563.
unsigned char IsAlarmON (void)
Per mezzo di questa funzione possibile controllare il flag interno dell'allarme. Pu risultare utile se si gestisce in polling il controllo dell'allarme. Se l'allarme attivo ritorna 1 altrimenti ritorna 0. Se l'allarme viene trovato attivo la funzione ripulisce automaticamente il flag permettendo altri allarmi. Per poter far uso delle funzioni descritte necessario includere la libreria PCF8563.h presente nella cartella delle librerie personali Library. Vediamo ora un semplice programma lasciando al lettore la sua comprensione. Il programma imposta da prima l'orario 10:55 e data attuale e imposta i secondi a 55 in modo da raggiungere presto lo scatto del minuto. Successivamente imposta l'allarme alle 10:56. Si osservi che il giorno e la data non apparterranno all'allarme poich il loro enable impostato su OFF. Alle 10:56 viene accesa la cicalina sulla PORTE ma non verr mai spenta. Questo programma solo una bozza che pu essere facilmente modificata per ottenere una sveglia pi seria...a voi la lettura.
1 /* 2 Autore : Mauro Laurenti 3 Versione : 1.0 4 Data : 1/6/2006 5 CopyRight 2006 6 7 la descrizione di questo programma applicativo possibile trovarla nel Tutorial 8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com 9 10 */ 11 12 #include <p18f4580.h> 13 14 #include "PCF8563.h" 15 #include "LCD_44780_Freedom.h" 16 #include "Sponsor.h" 17 18 19 #pragma config OSC = HS // 20Mhz 20 #pragma config WDT = OFF // disabilito il watchdog timer 21 #pragma config LVP = OFF // disabilito programmazione LVP 22 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 23 24 25 void main (void) 26 { unsigned char *Time; 27 unsigned char *Date; 28 29 30 TRISA = 0xFF; // Tutti ingressi
82/93
www.LaurTec.com
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 OFF 66 OFF 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 PORTA = 0x00; TRISB = 0x00; PORTB = 0x00; TRISC = 0xFF; TRISD = 0x00; PORTD = 0x00; // Tutte uscite
OpenI2C(MASTER, SLEW_ON); // Initializza il modulo I2C a 400KHz SSPADD = 14; //400kHz Baud clock @20MHz OpenLCD (); // inizializzo LCD //Imposta l'ora attuale WriteSeconds (0x55); WriteMinutes (0x05); WriteHours (0x10); WriteDays (0x01); WriteWeekDays (ME); WriteMonths (0x06); WriteYears (0x06); //Imposto l'orario di allarme WriteMinutesAlarm (0x06,Enable_ON); WriteHoursAlarm (0x10,Enable_ON); WriteDaysAlarm (0x25,Enable_OFF); // non verr considerato poiche' WriteWeekDaysAlarm (LU,Enable_OFF); //non verr considerato poiche'
EnableAlarmInt (); //Abilita Enable del Real Time Clock/Calendar while (1) { HomeLCD (); Time = ReadTime(); //Leggo l'ora WriteVarLCD (Time); // scrivo sull'LCD la stringa con WriteStringLCD (" "); //inserisco uno spazio Date = ReadDate (); //leggo la data WriteVarLCD (Date); //visualizzo la stringa con la data if (IsAlarmON ()) // controllo il Flag del Real Time Clock { PORTEbits.RE1 = 0x01; //accendo la cicalina...
l'ora
83/93
www.LaurTec.com
85 86 87 } 88 89 } }
84/93
www.LaurTec.com
Come utilizzare il PWM
Nei sistemi automatici l'utilizzo della tecnica di modulazione PWM (Pulse Width Modulation) risulta particolarmente utile per il controllo di motori. In questo paragrafo si considerano gi noti i concetti introdotti nel Tutorial PWM, Pulse Width Modulation spiegando qui solo come impostare il PIC al fine di utilizzare l'hardware interno dedicato per la modulazione PWM. C18 mette a disposizione la libreria pwm.h per agevolare il programmatore nell'utilizzo dell'hardware dedicato al PWM51. Per poter utilizzare tale libreria bisogna includere il relativo file di libreria nel seguente modo #include <pwm.h>. A seconda del modello di PIC di cui si sta facendo uso possono essere presenti fino a 5 periferiche per il PWM. Per poter distinguere le varie periferiche PWM ogni funzione deve essere terminata con il numero della periferica PWM a cui si sta facendo riferimento. Il PIC18F4580 possiede una sola uscita PWM dunque le funzioni per il suo controllo termineranno tutte per 1. Le funzioni dedicate per il controllo PWM presenti all'interno della libreria pwm.h sono le seguenti: Funzioni
void ClosePWMx (void) void OpenPWMx (char) void SetDCPWMx(unsigned int) void ClosePWMx (void)
Descrizione Disabilita il canale x PWM Apre il canale x PWM Imposta un nuovo duty cycle per il canale PWM
Per mezzo di questa funzione possibile chiudere il canale PWM d'interesse cambiando la x con il numero del canale che si desidera controllare.
void OpenPWMx (char period)
Per mezzo di questa funzione possibile impostare il periodo del segnale PWM. Si ricorda che il periodo l'inverso della frequenza f =1/T . Il periodo da inserire non in realt il periodo del segnale PWM ma ad esso correlato secondo questa formula: Periodo PWM =[ period 1]4T OSCTMR2 prescaler da questa relazione si capisce che per poter utilizzare il segnale PWM bisogna anche aprire il timer TMR2. Infatti il periodo del PWM viene a dipendere dal valore del prescaler del timer TMR2. Un altro parametro che interviene nel calcolo del periodo del segnale PWM il periodo del segnale di clock generato dal nostro quarzo. Per calcolare questo basta fare l'inverso della frequenza del quarzo stesso, ovvero T OSC =1/ f QUARZO . Vediamo la formula inversa per il calcolo della variabile period una volte note le altre grandezze: period = Periodo PWM 1 4T OSCTMR2 prescaler
che pu anche essere riscritta nel seguente modo: period = Periodo PWM f QUARZO 1 4TMR2 prescaler
per poter controllare il timer TMR2 si pu far uso della libreria C18 timers.h
51
Si ricorda che se uno volesse potrebbe implementare un controllo PWM anche solo via software. Naturalmente avere dell'hardware a disposizione permette di semplificare il software e al tempo stesso permette al PIC di gestire altre cose.
85/93
www.LaurTec.com
void SetDCPWMx (unsigned int dutycycle)
Per mezzo di questa funzione possibile impostare il duty cycle del segnale PWM. Questo pu variare da un minimo di 0 a un massimo 1024 (10bit). Un duty cycle pari a 0 vincola il segnale PWM a 0 mentre un duty cycle pari a 1024 vincola il segnale PWM a 1. Vediamo un esempio di controllo PWM per mezzo del quale si controlla l'intensit luminosa di un LED posto all'uscita RC2, ovvero sull'uscita PWM.
1 /* 2 Autore : Mauro Laurenti 3 Versione : 1.0 4 Data : 25/5/2006 5 CopyRight 2006 6 7 la descrizione di questo programma applicativo possibile trovarla nel Tutorial 8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com 9 10 */ 11 12 #include <p18f4580.h> 13 #include <pwm.h> 14 #include <timers.h> 15 16 17 #include "LCD_44780_Freedom.h" 18 #include "Sponsor.h" 19 20 #pragma config OSC = HS // 20Mhz 21 #pragma config WDT = OFF // disabilito il watchdog timer 22 #pragma config LVP = OFF // disabilito programmazione LVP 23 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB 24 25 26 void main (void) 27 { unsigned int DutyCycle=0, i; 28 char Period; 29 30 TRISA = 0xFF; 31 PORTA = 0xFF; 32 33 TRISB = 0xFF; 34 PORTB = 0x00; 35 36 TRISC = 0x00; // i pin per il PWM sono output 37 PORTC = 0x00; 38 39 TRISD = 0xFF; // la PORTD settata per lavorare con l'LCD 40 PORTD = 0x00; 41 42 TRISE = 0xFF; 43 PORTD = 0x00; 44 45 OpenLCD (); // inizializzo LCD
86/93
www.LaurTec.com
46 47 48 49 TMR2 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 } WriteSponsor ();
OpenTimer2( TIMER_INT_OFF & T2_PS_1_1 & T2_POST_1_1); // apro il per il PWM Period = 249; //imposto una frequenza di 20KHz OpenPWM1( Period ); // apro il PWM while (1) { SetDCPWM1 ( DutyCycle); // aggiorno il dutycycle DutyCycle++; // incremento il dutycycle if (DutyCycle> 1024) // controllo che non sia maggiore di 2^10 { DutyCycle =0; }
Alla riga 12 e 13 si sono incluse le librerie C18 per il controllo dell'Hardware PWM e dei timer. In particolare si sono poi incluse le due librerie per il controllo LCD e dello sponsor che possono essere cancellate se si cancellano le righe 45 e 47. Alla riga 49 viene aperto il TMR2 in modo da poter far funzionare correttamente il modulatore PWM. In particolare il timer TMR2 viene aperto per non funzionare con l'interrupt (TIMER_INT_OFF) , utilizzando un prescale 1:1 (T2_PS_1_1) e un postscale 1:1 ( T2_POST_1_1 ) . Tenendo conto che si sta lavorando con un quarzo da 20MHz e delle impostazioni del TMR2 si ha che caricando Period con il valore 249, e aprendo il canale 1 PWM si ha che la frequenza del PWM 20KHz. All'interno del ciclo infinito compreso tra la riga 55 e la riga 66 no si fa altro che incrementare la variabile DutyCycle e aggiornare il canale 1 per mezzo della riga 57 con il nuovo duty cycle. Tra la riga 60 e 63 presente un piccolo controllo per mezzo del quale si evita di utilizzare un numero maggiore di 1024. Infatti l'hardware PWM considerer comunque solo i primi 10 bit della variabile DutyCycle. Alla riga 65 presente un piccolo ritardo per rendere il ciclo pi lento. In questo modo possibile vedere che il led varier la propria intensit raggiungendo il massimo per poi spegnersi in maniera ciclica. Rallentando ulteriormente la variazione del PWM possibile creare un semplice gioco di alba e tramonto. Alla riga 27 si noti che possibile dichiarare variabili dello stesso tipo sulla stessa riga, separandole con una virgola. Per ulteriori informazioni sui canali PWM disponibili sul PIC che si sta utilizzando si rimanda al relativo data sheet.
87/93
www.LaurTec.com
Bibliografia
www.LaurTec.com : sito di elettronica dove poter scaricare gli altri articoli menzionati, aggiornamenti e progetti. www.microchip.com : sito dove scaricare C18 , MPLAB descritti e il data sheet del PIC18F4580. www.philips.com : sito dove scaricare il data sheet del real time clock calendar PCF8563.h.
88/93
www.LaurTec.com
Indice
Introduzione..............................................................................................................................................3 Perch MPLAB C18?................................................................................................................................3 Installazione del software..........................................................................................................................5 Il nostro primo progetto...........................................................................................................................15 Tipi di variabili........................................................................................................................................27 Operatori matematici, logici e bitwise....................................................................................................32 Il ciclo for (...) ........................................................................................................................................34 L'istruzione condizionale if (...)..............................................................................................................37 L'istruzione condizionale while (...)........................................................................................................41 Le funzioni..............................................................................................................................................44 Visibilit delle variabili...........................................................................................................................48 Le interruzioni.........................................................................................................................................51 Come utilizzare un display alfanumerico LCD.......................................................................................58 Come utilizzare l'USART.......................................................................................................................67 Come utilizzare il bus I2C.......................................................................................................................76 Come utilizzare il PWM..........................................................................................................................85 Bibliografia..............................................................................................................................................88
89/93
www.LaurTec.com
Indice alfabetico A
accumulatore........................................................................................................................................32 Array.....................................................................................................................................................28 Array di caratteri............................................................................................................................30, 61 B BCD......................................................................................................................................................79 break...........................................................................................................................................42 e seg. BRGH...................................................................................................................................................68 Build All...............................................................................................................................................22 C char.......................................................................................................................................................27 ciclo infinito.........................................................................................................................................36 commenti..............................................................................................................................................24 controllore HD44780............................................................................................................................58 cos(x)....................................................................................................................................................32 costante.................................................................................................................................................30 D display alfanumerici.............................................................................................................................58 E EEPROM 24LC512..............................................................................................................................77 else........................................................................................................................................................37 EnablePullups()....................................................................................................................................38 EnablePullups();...................................................................................................................................39 F filtro antirimbalzo.................................................................................................................................39 floating point........................................................................................................................................27 for (...)...................................................................................................................................................34 G GIE.................................................................................................................................................51, 55 GIEH....................................................................................................................................................55 GIEL.....................................................................................................................................................55 H HS.........................................................................................................................................................24 I Il protocollo I2C...................................................................................................................................76 inizializzazione delle variabili..............................................................................................................28 int .........................................................................................................................................................27 INTCON.........................................................................................................................................51, 55 INTCON2bits.TMR0IP........................................................................................................................57 INTCONbits.GIEH........................................................................................................................57, 75 INTCONbits.GIEL...............................................................................................................................57 interruptlow..........................................................................................................................................73 interruzione a bassa priorit.................................................................................................................51 interruzione ad alta priorit..................................................................................................................51 IPEN.....................................................................................................................................................55 IPR1bits.RCIP................................................................................................................................57, 75 istruzione break....................................................................................................................................41 istruzione if (...)....................................................................................................................................37 istruzione return ( )...............................................................................................................................45 90/93
www.LaurTec.com
L
istruzione while (...)..............................................................................................................................41 l'ANSI C.................................................................................................................................................3 lettura di un pulsante............................................................................................................................38 Linker Script.........................................................................................................................................19 log(x)....................................................................................................................................................32 long ......................................................................................................................................................27 LVP......................................................................................................................................................24 M macro....................................................................................................................................................30 Maestro...................................................................................................................................................7 main......................................................................................................................................................24 math.h...................................................................................................................................................32 MAX232...............................................................................................................................................67 modulazione PWM...............................................................................................................................85 N numero binario.....................................................................................................................................26 numero decimale..................................................................................................................................26 numero esadecimale.............................................................................................................................26 O operatore ++.........................................................................................................................................32 operatore binario AND.........................................................................................................................33 operatore binario OR............................................................................................................................33 operatore binario XOR.........................................................................................................................33 operatore complemento a 1..................................................................................................................33 operatore divisione...............................................................................................................................32 operatore logico AND..........................................................................................................................33 operatore logico di uguaglianza ..........................................................................................................33 operatore logico diverso.......................................................................................................................33 operatore logico maggiore o uguale.....................................................................................................33 operatore logico minore o uguale.........................................................................................................33 operatore logico OR.............................................................................................................................33 operatore moltiplicazione.....................................................................................................................32 operatore somma..................................................................................................................................32 operatore sottrazione............................................................................................................................32 operatori bitwise...................................................................................................................................33 operatori logici.....................................................................................................................................33 operatori matematici.............................................................................................................................32 P p18f4580.h............................................................................................................................................23 PBADEN..............................................................................................................................................24 PCF8563...............................................................................................................................................78 PEIE.....................................................................................................................................................51 polling...................................................................................................................................................67 PORTA.......................................................................................................................................25 e seg. PORTB.................................................................................................................................................24 PORTBbits.RB0...................................................................................................................................39 PORTD.................................................................................................................................................25 PORTE.................................................................................................................................................26 PORTxbits............................................................................................................................................26 prescale.................................................................................................................................................87 91/93
www.LaurTec.com
prescaler...............................................................................................................................................85 Project wizard.......................................................................................................................................15 prototipo di funzione............................................................................................................................45 pull-down.............................................................................................................................................39 pull-up..................................................................................................................................................39 R RAM.....................................................................................................................................................27 RCONbits.IPEN...................................................................................................................................75 RCONbits.IPEN = 1.............................................................................................................................57 Reset.....................................................................................................................................................51 rom const char......................................................................................................................................63 RS232...................................................................................................................................................67 RS485...................................................................................................................................................67 S scope di variabili..................................................................................................................................48 sen(x)....................................................................................................................................................32 shift a destra ........................................................................................................................................33 shift a sinistra.......................................................................................................................................33 short......................................................................................................................................................27 short long..............................................................................................................................................27 signed char............................................................................................................................................27 spbrg.....................................................................................................................................................68 SSPADD...............................................................................................................................................78 static.....................................................................................................................................................49 string.h..................................................................................................................................................64 stringa.............................................................................................................................................30, 61 T tecnica del polling................................................................................................................................70 TMR2...................................................................................................................................................85 Toolsuite...............................................................................................................................................16 TRISA........................................................................................................................................25 e seg. TRISD..................................................................................................................................................25 typedef struct........................................................................................................................................29 U unsigned char........................................................................................................................................27 unsigned int..........................................................................................................................................27 unsigned long.......................................................................................................................................27 unsigned short......................................................................................................................................27 unsigned short long..............................................................................................................................27 USART.................................................................................................................................................67 V variabile static......................................................................................................................................48 void.......................................................................................................................................................25 W WDT.....................................................................................................................................................24 _ _asm...............................................................................................................................................53, 56 _endasm..........................................................................................................................................53, 56 . .hex.......................................................................................................................................................23 .map .....................................................................................................................................................23 92/93
www.LaurTec.com
#
#define..................................................................................................................................................30 #include..........................................................................................................................................23, 47 #include "eeprom.h".............................................................................................................................77 #include "LCD_44780.h".....................................................................................................................62 #include "PCF8563.h"..........................................................................................................................82 #include nome_file...........................................................................................................................47 #include <i2c.h>...................................................................................................................................76 #include <nome_file>..........................................................................................................................47 #include <portb.h>...............................................................................................................................38 #include <pwm.h>................................................................................................................................85 #include <string.h>..............................................................................................................................30 #include <timers.h>..............................................................................................................................86 #include <usart.h>................................................................................................................................72 #pragma................................................................................................................................................24 #pragma code.......................................................................................................................................53 #pragma interruptlow...........................................................................................................................53 #pragma intterupt.................................................................................................................................53
93/93