www.LaurTec.com

Transcript

www.LaurTec.com
Versione n° 1.1a
www.LaurTec.com
C18
step by step
Autore : Mauro Laurenti
email : [email protected]
Copyright © 2006 Mauro Laurenti
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.
Tutti i marchi citati in quest'opera sono dei rispettivi proprietari.
Copyright © 2006 Mauro Laurenti
2/93
www.LaurTec.com
C18 step by step
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.
Perché MPLAB C18?
La ragione principale che giustifica la scelta dell'ambiente di sviluppo MPLAB C18 è legata al fatto
che è presente una versione Student che non ha limiti nelle dimensioni del programma che è possibile
scrivere1. Il limite principale che si ha con la versione Student è che non è possibile utilizzare le
istruzione che estendono il set d'istruzioni della famiglia C18 e non sono disponibili tutte le
ottimizzazioni del programma durante la fase di compilazione.
Questi due limiti nella prima fase d'apprendimento non risultano affatto un problema, e come si
vedrà negli esempi è possibile realizzare un gran numero di applicazioni senza neanche preoccuparsi
della loro esistenza. Un apparente limite del C18 rientra nel fatto che è possibile utilizzarlo solo per la
famiglia PIC 18 e non per “i cavalli di battaglia” PIC16F84 e PIC16F877.
Anche questo limite, a mio avviso, non è un grande limite. Anche con la famiglia PIC 18 è possibile
muovere i primi timidi passi nel mondo della programmazione dei PIC. Infatti la famiglia PIC 18 ha
molte periferiche in più integrate all'interno del cip...ma se non se ne parla...è come se non fossero
presenti! Questa affermazione è inoltre rafforzata dal fatto che il C è un linguaggio ad alto livello,
dunque grazie al livello di astrazione è possibile dimenticarsi di alcune sfumature fondamentali se si
programma direttamente in linguaggio Assembly2.
Altra ragione che a mio avviso rende la famiglia C18 un buon punto di partenza è che chiunque
inizi con il PIC16F84 avvertirà dopo poco tempo l'esigenza di usare un PIC più “potente”...e passerà
al PIC16F877 che possiede più pin per interfacciarsi con più hardware esterno e qualche periferica in
più che permette di semplificare la programmazione3...nonché più memoria!
La quantità di memoria è particolarmente importante infatti in un progetto può essere un mezzo per
discriminare un PIC da altri.
Il PIC16F84 possiede per esempio un solo Kw4 di memoria mentre il PIC16F877 ne possiede 8.
Nel primo caso la programmazione ad alto livello è sconsigliata poiché con poche istruzioni, siano esse
in C, in Basic o in Pascal, si riempe subito la memoria impedendo di risolvere molti problemi. Questo
non è più vero con 8K disponibili, dove un linguaggio ad alto livello comincia a tornare utile, molti
integrati della famiglia 18F hanno 32K di memoria.
Pur essendo un accanito sostenitore del linguaggio Assembly, di cui non parlerò, non si può pensare
di scrivere un'intera applicazione (almeno da soli) che riempia i 32K disponibili.
In questi casi l'utilizzo di un linguaggio ad alto livello è fondamentale quanto il linguaggio
Assembly!
...con questo voglio dire che pur scrivendo con un linguaggio ad alto livello come il C non ci si può
scordare o pretendere di non studiare il linguaggio Assembly. Questo risulterà infatti insostituibile in
1
2
3
4
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
C18 step by step
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
C18 step by step
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.
Figura 1: Finestra principale dell'installazione dell'IDE
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
C18 step by step
Figura 2: Licenza d'uso
Bene avete accettato la licenza...possiamo continuare. Premendo nuovamente Next selezionare sulla
nuova finestra la voce Complete come riportato in Figura 3.
Figura 3: Installazione completa
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
C18 step by step
Figura 4: Selezione del percorso d'installazione
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!
Figura 5: Nuova licenza per il pacchetto software Maestro
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
C18 step by step
Figura 6: Riepilogo informazioni d'installazione
Figura 7: Fase d'installazione
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
C18 step by step
Figura 8: Finestra di conferma per l'installazione del driver USB
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.
Figura 9: Finestra di fine installazione
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
C18 step by step
Figura 10: Versione 3.02
La seconda finestra di dialogo è rappresentata dalla Licenza d'uso...leggere e decidere se proseguire!
Figura 11: Licenza d'uso
10/93
www.LaurTec.com
C18 step by step
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.
Figura 12: Descrizione “Table of Contents”
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.
Figura 13: Finestra per impostare il percorso d'installazione
11/93
www.LaurTec.com
C18 step by step
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.
Figura 14: Selezione delle applicazioni da installare
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.
Figura 15: Impostazioni dell'ambiente di sviluppo C18
12/93
www.LaurTec.com
C18 step by step
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.
Figura 16: Finestra di conferma finale per avviare l' installazione
In Figura 17 è riportata la fase d'installazione.
Figura 17: Fase d'installazione
In Figura 18 è riportata la finestra che conferma la fine dell'installazione.
13/93
www.LaurTec.com
C18 step by step
Figura 18: Finestra di fine installazione
14/93
www.LaurTec.com
C18 step by step
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.
Figura 19: Finestra principale dell'interfaccia IDE di MPLAB
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.
5
Un qualunque altro sistema di sviluppo o PIC della famiglia PIC18 è in generale utilizzabile.
15/93
www.LaurTec.com
C18 step by step
Figura 20: Finestra Project wizard
Figura 21: Finestra di dialogo per la selezione del PIC
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
C18 step by step
Figura 22: Finestra di dialogo per impostare il Toolsuite
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
C18 step by step
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.
Figura 24: Finestra di dialogo per aggiungere files al progetto
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
C18 step by step
L'interfaccia IDE apparirà ora come in Figura 26.
Figura 26: Nuovo aspetto dell'IDE dopo la creazione del Progetto
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
7
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
C18 step by step
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
C18 step by step
Figura 28: Finestra d'impostazione dell'ambiente di sviluppo C18
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 }
C18 step by step
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
C18 step by step
Figura 29: Messaggio di avviso dopo la compilazione del programma sorgente
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
C18 step by step
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
C18 step by step
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
C18 step by step
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;
//numero binario
TRISD = 0x0F;
//numero esadecimale
TRISD = 15;
//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
C18 step by step
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
Dimensione
Minimo
Massimo
char
8 bits
-128
+127
signed char
8 bits
-128
+127
unsigned char
8 bits
0
int
16 bits
-32.768
unsigned int
16 bits
0
short
16 bits
-32.768
unsigned short
16 bits
0
short long
24 bits
-8.388.608
+8.388.607
unsigned short long
24 bits
0
16.777.215
long
32 bits
-2.147.483.648
unsigned long
32 bits
0
255
+32.767
65.535
+32.767
65.535
-2.147.483.647
4.294.967.295
Tabella 1: Tipi di variabili disponibili I
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
C18 step by step
Dimensione Exp. min Exp. max.
Min. normalizzato
Max. normalizzato
32 bits
-126
+128
2–126 ≈ 1.17549435e - 38 2128 * (2-2–15) ≈ 6.80564693e + 38
double 32 bits
-126
+128
2–126 ≈ 1.17549435e - 38 2128 * (2-2–15) ≈ 6.80564693e + 38
float
Tabella 2: Tipi di variabili disponibili II
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
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
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.
28
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.
33/93
www.LaurTec.com
C18 step by step
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
}
vediamo di capirci qualcosa con il 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
9 void main (void)
10 { unsigned char i;
11
12
TRISA = 0xFF; // Tutti ingressi
13
PORTA = 0x00;
14
15
TRISB = 0xFF ; // Tutti ingressi
16
PORTB = 0x00 ;
17
18
TRISC = 0xFF; // Tutti ingressi
19
PORTC = 0x00;
20
21
TRISD = 0x00; // Tutte uscite
22
PORTD = 0x00;
23
24
for (i=0; i<10; i++)
25
{
26
PORTD = i;
27
}
28
29
30
while (1)
31
{
32
}
33
34 }
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
C18 step by step
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 }
C18 step by step
}
}
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
C18 step by step
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
C18 step by step
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
C18 step by step
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 }
C18 step by step
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
C18 step by step
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
}
un altro modo equivalente potrebbe essere:
while (3)
{
//il blocco d'istruzioni qui presenti sono ripetute all'infinito
}
un altro modo potrebbe ancora essere
while (a=5)
{
//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
C18 step by step
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
C18 step by step
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
C18 step by step
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 }
C18 step by step
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
C18 step by step
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
C18 step by step
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
Tra le opzioni di progetto si è settato questo percorso.
47/93
www.LaurTec.com
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
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:
.
.
.
//intestazione del programma
void Low_Int_Event (void);
// prototipo di funzione
#pragma code low_vector=0x18
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
C18 step by step
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;
Vediamo un esempio in cui si è collegato un pulsante tra l'ingresso RB7 e massa.
1 /*
2 Autore : Mauro Laurenti
3 Versione : 1.0
4 Data : 9/7/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
13 #include <p18f4580.h>
14 #include <portb.h>
15
53/93
www.LaurTec.com
C18 step by step
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 }
C18 step by step
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
C18 step by step
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
C18 step by step
// 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à
while (1); // Ciclo infinito in attesa d'interrupt
};
Per ulteriori programmi di esempio si rimanda ai paragrafi successivi e ai progetti presentati sui siti
riportati in Bibliografia.
57/93
www.LaurTec.com
C18 step by step
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
C18 step by step
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
Descrizione
void OpenLCD (void)
Permette d'inizializzare il dislpay LCD
void ClearLCD (void)
Pulisce le righe del Display
void CursorLCD (char,char)
1=ON cursor 0=OFF cursor; 1=ON blinking 0=OFF Blinking
void HomeLCD (void)
Riposiziona il cursore all'inizio del display
void Line2LCD (void)
Posiziona il cursore all'inizio della seconda riga
void ShiftLCD (char)
Trasla le righe di una posizione a destra o sinistra
void ShiftCursorLCD (char)
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
C18 step by step
void WriteCharLCD (char)
Scrive un carattere sul display
void WriteVarLCD(char *)
Scrive una variabile sul display
void WriteStringLCD(const
char *)
rom Scrive una stringa costante sul display
Vediamo con maggior dettaglio le singole funzioni:
void OpenLCD (void)
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);
//non visualizza il cursore e non effettua lampeggi
void HomeLCD (void)
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 posiziona il cursore all'inizio della seconda riga.
Es.
Line2LCD ( );
void ShiftLCD (char)
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
C18 step by step
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);
//sposto il cursore un carattere a destra
void WriteCharLCD (char)
Per mezzo di questa funzione è possibile scrivere un carattere ASCII sul display.
Es.
WriteCharLCD ('M');
// scrivo il carattere M
void WriteVarLCD(char *)
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 }
C18 step by step
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
C18 step by step
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 }
C18 step by step
dest++;
*dest = '\0';
//inserisco il carattere di fine stringa
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;
in realtà è possibile anche la sintassi
d = tizio.nome;
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
C18 step by step
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';
o facendo uso del puntatore d precedentemente dichiarato:
d = tizio.nome;
*d = 'M';
d++;
*d = 'A';
d++;
*d = 'U';
d++;
*d = 'R';
d++;
*d = 'O';
d++;
*d = '\0';
//d punta all'elemento tizio.nome[0]
//d punta all'elemento tizio.nome[1]
//d punta all'elemento tizio.nome[2]
//d punta all'elemento tizio.nome[3]
//d punta all'elemento tizio.nome[4]
//d punta all'elemento tizio.nome[5]
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
Figura 30: Esempio grafico di memoria RAM
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
C18 step by step
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
C18 step by step
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
Descrizione
char BusyUSART( void )
Controlla se l'USART è occupata
void CloseUSART( void )
Chiude l'USART
char DataRdyUSART( void )
Controlla se sono presenti dati ricevuti
void OpenUSART( unsigned char config,
unsigned int spbrg);
Inizializza l'USART
char ReadUSART( void )
Legge un dato dal buffer di ricezione
void WriteUSART( char data )
Trasmette un dato in uscita
char BusyUSART( void )
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 viene chiuso l'USART precedentemente aperta.
char DataRdyUSART( 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
C18 step by step
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
Interruzione RX ON
Interruzione RX OFF
Modalità USART:
USART_ASYNCH_MODE
USART_SYNCH_MODE
Modalità Asincrona
Modalità Sincrona
Larghezza dati:
USART_EIGHT_BIT
USART_NINE_BIT
8-bit
9-bit
Modalità Slave/Master:
USART_SYNC_SLAVE
USART_SYNC_MASTER
Slave modalità' sincrona (si applica solo in modalità' sincrona)
Master modalità' sincrona (si applica solo in modalità' sincrona)
Modalità di ricezione:
USART_SINGLE_RX
USART_CONT_RX
Ricezione singola
Ricezione multipla
Baud rate:
USART_BRGH_HIGH
USART_BRGH_LOW
baud rate alto
baud rate basso
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
C18 step by step
Tabella 3: Tabella per la scelta di SPBRG
69/93
www.LaurTec.com
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
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;
//abilità interruzioni con priorità
IPR1bits.RCIP = 1;
// imposta la ricezione ad alta priorità
INTCONbits.GIEH = 1; // abilità interruzioni 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
C18 step by step
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
C18 step by step
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
C18 step by step
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
4⋅F 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)
Descrizione
WriteSeconds
(unsigned Scrive i secondi dell'orario
unsigned char ReadSeconds (void)
unsigned char
char Minutes)
WriteMinutes
Legge i secondi dell'orario
(unsigned Scrive i minuti dell'orario
unsigned char ReadMinutes (void)
Legge i minuti dell'orario
unsigned char WriteHours (unsigned char Scrive l'ora dell'orario
Hours)
unsigned char ReadHours (void)
Legge l'ora dell'orario
unsigned char* ReadTimeSeconds (void)
Legge l'orario comprensivo dei secondi
unsigned char* ReadTime (void)
Legge l'orario senza secondi
78/93
www.LaurTec.com
C18 step by step
unsigned char WriteDays (unsigned char Scrive il giorno della data
Days)
unsigned char ReadDays (void)
unsigned char WriteWeekDays
char WeekDays)
Legge legge il giorno della data odierna
(unsigned Scrive il giorno della settimana
unsigned char ReadWeekDays (void)
unsigned char
char Months)
WriteMonths
Legge il giorno della settimana
(unsigned Scrive il mese della data
unsigned char ReadMonths (void)
Legge il mese della data
unsigned char WriteYears (unsigned char Scrive l'anno
Years)
unsigned char ReadYears (void)
Legge l'anno
unsigned char* ReadDate (void)
Legge la data odierna GG/MM/AA
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)
Abilita l'interruzione per l'allarme
unsigned char DisableAllInt (void)
Disabilita tutte le interruzioni
unsigned char IsAlarmON (void)
Controlla se l'allarme è stato attivato (polling)
unsigned char WriteSeconds (unsigned char Seconds)
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
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
PORTA = 0x00;
TRISB = 0x00;
PORTB = 0x00;
// Tutte uscite
TRISC = 0xFF;
// Tutti ingressi
TRISD = 0x00;
PORTD = 0x00;
// la PORTD è settata per lavorare con l'LCD
TRISE = 0x00; // la cicalina è abilitata
PORTE = 0x00;
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
l'ora
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...
83/93
www.LaurTec.com
85
86
87
}
88
89 }
C18 step by step
}
84/93
www.LaurTec.com
C18 step by step
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
Descrizione
void ClosePWMx (void)
Disabilita il canale x PWM
void OpenPWMx (char)
Apre il canale x PWM
void SetDCPWMx(unsigned int)
Imposta un nuovo duty cycle per il canale PWM
void ClosePWMx (void)
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]⋅4⋅T OSC⋅TMR2 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
4⋅T OSC⋅TMR2 prescaler
che può anche essere riscritta nel seguente modo:
period =
Periodo PWM⋅ f QUARZO
−1
4⋅TMR2 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
C18 step by step
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 }
C18 step by step
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;
}
for (i=0; i<5000; i++);
}
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
C18 step by step
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
C18 step by step
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
C18 step by step
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
C18 step by step
istruzione while (...)..............................................................................................................................41
L
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
C18 step by step
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
C18 step by step
#
#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