c18 step by step

93
Versione n° 1.1a www.LaurTec.com www.LaurTec.com C18 C18 step by step step by step Autore : Mauro Laurenti email : [email protected] Copyright © 2006 Mauro Laurenti 1/93

Upload: antonio-pisciotta

Post on 25-Jul-2015

285 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: C18 Step by Step

Versione n° 1.1a

www.LaurTec.comwww.LaurTec.com

C18C18step by stepstep by step

Autore : Mauro Laurenti

email : [email protected]

Copyright © 2006 Mauro Laurenti 1/93

Page 2: C18 Step by Step

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

Page 3: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

IntroduzioneIn 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 L'unico limite è la memoria del PIC che si sta utilizzando.2 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!3 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”.

4 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

Page 4: C18 Step by Step

www.LaurTec.comwww.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

Page 5: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Installazione del softwareAncora qualche piccolo passo prima d'iniziare...tutto il software di cui si parlerà è possibile

scaricarlo gratuitamente dal sito della Microchip www.microchip.com . Come prima cosa dobbiamo scaricare i seguenti programmi MPLAB® IDE e MPLAB® C18. Prima

di installare MPLAB® C18 bisogna installare MPLAB® IDE. La versione a cui si fa riferimento è la 7.4. All'inizio dell'installazione di MPLAB IDE si ha la Figura 1.

Premendo Next comparirà la Licenza di Figura 2...leggete la Licenza! Se non l'accettate non avete ragione di continuare a leggere questo Tutorial!

5/93

Figura 1: Finestra principale dell'installazione dell'IDE

Page 6: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Bene avete accettato la licenza...possiamo continuare. Premendo nuovamente Next selezionare sulla nuova finestra la voce Complete come riportato in Figura 3.

Premendo nuovamente il tasto Next è possibile impostare il percorso di installazione come riportato in Figura 4. Se non si hanno particolari esigenze non c'è ragione di cambiare il percorso di Default, per cui premete nuovamente Next.

6/93

Figura 2: Licenza d'uso

Figura 3: Installazione completa

Page 7: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Come riportato in Figura 5 c'è una nuova licenza che bisogna accettare o meno. Il consiglio è di accettarla anche se probabilmente non ne farete uso. L'applicazione Maestro alla quale questa Licenza fa riferimento è una particolare applicazione che raccoglie un certo numero di librerie da utilizzare nel caso si programmi in Assembly. In questo Tutorial non se ne farà uso ma la sua presenza non nuocerà!...dunque premere nuovamente Next...se si è accettata la Licenza!

A questo punto il software ha le informazioni necessarie per iniziare l'installazione, queste vengono riassunte come riportato in Figura 6. Premendo nuovamente Next si avvia l'installazione.

7/93

Figura 4: Selezione del percorso d'installazione

Figura 5: Nuova licenza per il pacchetto software Maestro

Page 8: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

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

Figura 6: Riepilogo informazioni d'installazione

Figura 7: Fase d'installazione

Page 9: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Dopo l'installazione del driver, MPLAB® IDE è installato. La conferma di fine installazione avviene per mezzo della finestra riportata in Figura 9. Per completare l'installazione è però necessario riavviare il Computer. Premendo Finish il sistema operativo viene automaticamente riavviato.

Al riavvio del Computer MPLAB® IDE è completamente installato.A questo punto è possibile iniziare l'installazione di MPLAB® C18. La versione alla quale si fa riferimento è la 3.02. In Figura 10 è riportata la prima finestra di dialogo premendo Next si avvia l'installazione.

9/93

Figura 8: Finestra di conferma per l'installazione del driver USB

Figura 9: Finestra di fine installazione

Page 10: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

La seconda finestra di dialogo è rappresentata dalla Licenza d'uso...leggere e decidere se proseguire!

10/93

Figura 10: Versione 3.02

Figura 11: Licenza d'uso

Page 11: C18 Step by Step

www.LaurTec.comwww.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.

In Figura 13 è riportata la finestra per mezzo della quale è possibile cambiare il percorso d'installazione. Se non si hanno particolari esigenze è bene lasciare il percorso di Default. Premere Next per continuare.

11/93

Figura 12: Descrizione “Table of Contents”

Figura 13: Finestra per impostare il percorso d'installazione

Page 12: C18 Step by Step

www.LaurTec.comwww.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.

In Figura 15 sono riportate alcune alcune impostazione per configurare il sistema operativo “avvertendolo” della presenza di C18. Selezionare le opzioni come riportato in Figura 15. Premere Next per continuare.

12/93

Figura 14: Selezione delle applicazioni da installare

Figura 15: Impostazioni dell'ambiente di sviluppo C18

Page 13: C18 Step by Step

www.LaurTec.comwww.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.

In Figura 17 è riportata la fase d'installazione.

In Figura 18 è riportata la finestra che conferma la fine dell'installazione.

13/93

Figura 16: Finestra di conferma finale per avviare l' installazione

Figura 17: Fase d'installazione

Page 14: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

14/93

Figura 18: Finestra di fine installazione

Page 15: C18 Step by Step

www.LaurTec.comwww.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.

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

Figura 19: Finestra principale dell'interfaccia IDE di MPLAB

Page 16: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

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

Figura 20: Finestra Project wizard

Figura 21: Finestra di dialogo per la selezione del PIC

Page 17: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

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

17/93

Figura 22: Finestra di dialogo per impostare il Toolsuite

Figura 23: Finestra di dialogo per l'impostazione del nome del progetto

Page 18: C18 Step by Step

www.LaurTec.comwww.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.

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.

18/93

Figura 24: Finestra di dialogo per aggiungere files al progetto

Figura 25: Finestra di riepilogo alla fine della creazione del Progetto

Page 19: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

L'interfaccia IDE apparirà ora come in Figura 26.

A questo punto bisognerà aggiungere i files che non si sono inseriti precedentemente. Un file che è sempre necessario aggiungere è il file per il linker con estensione .lkr. Ogni PIC possiede più file di .lkr a seconda della modalità con cui si sta programmando il PIC stesso. In questo Tutorial, come detto, la modalità estesa non verrà trattata dunque il file .lkr che bisognerà inserire sarà 18F4580.lkr. Per inserire questo file bisogna selezionare la cartella Linker Script dalla finestra all'angolo sinistro della finestra principale e premere il tasto destro del mouse. Fatto questo bisogna selezionare la voce Add File... e cercare il file nella directory C:\MCC18\lkr6.

Aggiunto il nostro file possiamo ora aggiungere il file principale dove scrivere il nostro programma. In C è tipico scrivere il programma principale all'interno di un file nominato main.c dove main significa principale7. Per inserire il nostro file bisogna creare una nuova finestra di testo dal menù File e selezionando poi New. Il file di testo cosi creato non appartiene però ancora al nostro progetto. Per poter integrare il file al progetto bisogna salvare il file da qualche parte e poi aggiungerlo alla cartella Source Files, come si è fatto per lo script precedente. Ragionevolmente il file va salvato all'interno della directory in cui stiamo lavorando, ovvero C:\Programmi\HelloWorld in modo da avere tutti i file

6 Questa directory potrebbe essere diversa se durante la fase di installazione del programma si è scelto di installare C18 in un'altra directory.

7 Altra abitudine è anche chiamare il file principale con un nome che descriva l'applicazione, quindi in questo caso potrebbe essere chiamato HelloWorld.

19/93

Figura 26: Nuovo aspetto dell'IDE dopo la creazione del Progetto

Page 20: C18 Step by Step

www.LaurTec.comwww.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.

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

Figura 27: Nuovo aspetto dell'IDE dopo l'inserimento dei vari files e la creazione del main.c

Page 21: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

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>23 #pragma config OSC = HS // 20Mhz4 #pragma config WDT = OFF // disabilito il watchdog timer5 #pragma config LVP = OFF // disabilito programmazione LVP6 #pragma config PBADEN = OFF // disabilito gli ingressi analogicisulla 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

Figura 28: Finestra d'impostazione dell'ambiente di sviluppo C18

Page 22: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

789 void main (void)10 { TRISA = 0xFF; // Tutti ingressi11 PORTA = 0x00;1213 TRISB = 0xFF; // Tutti ingressi14 PORTB = 0x00;1516 TRISC = 0xFF; // Tutti ingressi 17 PORTC = 0x00;1819 TRISD = 0xFF; // Tutti ingressi 20 PORTD = 0x00;2122 TRISE = 0b11111101; // RE1 è un'uscita23 PORTE = 0x00;2425 PORTEbits.RE1 = 1;262728 while (1)29 {30 }3132 }

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

Page 23: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

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

Figura 29: Messaggio di avviso dopo la compilazione del programma sorgente

Page 24: C18 Step by Step

www.LaurTec.comwww.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 commentoche non entrerebbe su una sola riga */

o ancora

/* questo è un commentoche non entrerebbe su una sola riga */

Vediamo ora il programma vero e proprio. Ogni programma C è una collezione di funzioni14 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 Per le varie modalità di oscillazione disponibili sul PIC che si sta utilizzando si rimanda al relativo data sheet.11 Per abilitare il WDT bisogna scrivere ON.12 Per ulteriori informazioni su tale registro si rimanda al data sheet del PIC utilizzato.13 Per ulteriori informazioni sulla programmazione LVP si rimanda al data sheet del PIC utilizzato.14 Ogni funzione contiene un certo numero d'istruzioni per mezzo delle quali la funzione svolge il compito per cui è stata

creata.

24/93

Page 25: C18 Step by Step

www.LaurTec.comwww.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 virgola16. 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 uscite18. 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 Si comprenderanno meglio le funzioni quando verranno utilizzate altre funzioni oltre al main.16 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.17 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.18 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.

19 Per una facile conversione nei vari sistemi a base differenti si può utilizzare la calcolatrice di Windows.

25/93

Page 26: C18 Step by Step

www.LaurTec.comwww.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 La PORTE come anche altre porte non ha 8 pin, dunque i bit non utilizzati di TRISE e PORTE vengono ignorati.21 L'istruzione while verrà spiegata successivamente.

26/93

Page 27: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Tipi di variabiliOgni 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 Massimochar 8 bits -128 +127signed char 8 bits -128 +127unsigned char 8 bits 0 255int 16 bits -32.768 +32.767unsigned int 16 bits 0 65.535short 16 bits -32.768 +32.767unsigned short 16 bits 0 65.535short long 24 bits -8.388.608 +8.388.607unsigned short long 24 bits 0 16.777.215long 32 bits -2.147.483.648 -2.147.483.647unsigned long 32 bits 0 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 Nel caso dei microcontrollori si ha in generale anche la memoria flash, ma questa viene generalmente utilizzata per contenere delle costanti.

23 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

Page 28: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Tipo Dimensione Exp. min Exp. max. Min. normalizzato Max. normalizzatofloat 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

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

Page 29: C18 Step by Step

www.LaurTec.comwww.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 34 #pragma config OSC = HS // 20Mhz5 #pragma config WDT = OFF // disattivo il watchdog timer6 #pragma config LVP = OFF // disattivo la programmazione LVP7 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sullaPORTB89 typedef struct10 {11 unsigned char larghezza;12 unsigned char altezza;1314 } rettangolo;15161718 void main (void)19 { rettangolo figura;2021 TRISA = 0xFF; // Tutti input22 PORTA = 0x00;2324 TRISB = 0x80 ; // RB7 input25 PORTB = 0x00 ;26

29/93

Page 30: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

27 TRISC = 0xFF; // Tutti input28 PORTC = 0x00;2930 TRISD = 0x00; // Tutte uscite31 PORTD = 0x00;3233 TRISE = 0xFF; // Tutti input34 PORTE = 0x00;3536 figura.altezza = 10; //assego l'altezza37 figura.larghezza = 3; //assegno la larghezza3839 PORTD = figura.altezza; //scrivo su PORTD l'altezza404142 while (1); //ciclo infinito4344 }

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 main24. 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 Per ulteriori informazioni a riguardo si rimanda al paragrafo sulla visibilità delle variabili (scope).25 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

Page 31: C18 Step by Step

www.LaurTec.comwww.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

Page 32: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Operatori matematici, logici e bitwiseGli 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++; // dopo la somma i vale 1i--; // 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 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.

27 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

Page 33: C18 Step by Step

www.LaurTec.comwww.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 è 00001001int ris = 0;ris = i >> 2; // ris varrà 00000010 ovvero 2

}

Per fare la divisione per 4 si è effettuato uno shift a destra di due posti ottenendo come risultato 2. Si capisce dunque che il resto della divisione viene perduto.

velocizzare i tempi di calcolo. L'accumulatore è direttamente connesso con l'ALU, ovvero Arithmetic Logic Unit. 28 Il simbolo ~ è possibile inserirlo come carattere ASCII numero 126. Tenere premuto ALT, digitare 126 e poi rilasciare

ALT.

33/93

Page 34: C18 Step by Step

www.LaurTec.comwww.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>23 #pragma config OSC = HS // 20Mhz4 #pragma config WDT = OFF // disabilito il watchdog timer5 #pragma config LVP = OFF // disabilito programmazione LVP6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB789 void main (void)10 { unsigned char i;1112 TRISA = 0xFF; // Tutti ingressi13 PORTA = 0x00;1415 TRISB = 0xFF ; // Tutti ingressi16 PORTB = 0x00 ;1718 TRISC = 0xFF; // Tutti ingressi19 PORTC = 0x00;2021 TRISD = 0x00; // Tutte uscite22 PORTD = 0x00;2324 for (i=0; i<10; i++)25 {26 PORTD = i;27 }282930 while (1)31 {32 }3334 }

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

Page 35: C18 Step by Step

www.LaurTec.comwww.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>234 #pragma config OSC = HS // 20Mhz5 #pragma config WDT = OFF // disabilito il watchdog timer6 #pragma config LVP = OFF // disabilito programmazione LVP7 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sulla PORTB8910 void main (void)11 { unsigned int i;12 char j;13 unsigned int mioArray[3];1415 TRISA = 0xFF; // Tutti ingressi16 PORTA = 0x00;1718 TRISB = 0xFF ; // Tutti ingressi19 PORTB = 0x00 ;2021 TRISC = 0xFF; // Tutti ingressi22 PORTC = 0x00;2324 TRISD = 0x00; // Tutte uscite25 PORTD = 0x00;2627 mioArray[0] = 1;28 mioArray[1] = 2;29 mioArray[2] = 3;303132 for (j=0; j<3; j++)33 {34 PORTD = mioArray[j]; //visualizzo il contenuto su PORTD3536 for (i=0; i<64000; i++) //ciclo di ritardo37 {

29 In questo caso si è parlato solo di i++ e i-- ma in realtà si possono scrivere anche altre espressioni.

35/93

Page 36: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

38 }39 }4041 while (1)42 {43 }4445 }

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

Page 37: C18 Step by Step

www.LaurTec.comwww.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

Page 38: C18 Step by Step

www.LaurTec.comwww.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>345 #pragma config OSC = HS // 20Mhz6 #pragma config WDT = OFF // disabilito il watchdog timer7 #pragma config LVP = OFF // disabilito programmazione LVP8 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sullaPORTB91011 void main (void)12 {13 TRISA = 0xFF; // Tutti ingressi14 PORTA = 0x00;1516 TRISB = 0xFF ; // Tutti ingressi17 PORTB = 0x00 ;1819 TRISC = 0xFF; // Tutti ingressi20 PORTC = 0x00;2122 TRISD = 0x00; // Tutte uscite23 PORTD = 0x00;2425 EnablePullups(); // abilita i resistori di pull-up sulla PORTB262728 for (;;) //ciclo infinito29 {30 if (PORTBbits.RB0 == 0)31 {32 PORTD = 0xF0; //ho premuto il pulsante su RB033 }34 else35 {36 PORTD = 0x0F; //il pulsante è aperto37 }38 }3940 }

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 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.

31 In particolare i 4 bits più significativi del PIC possiedono un iterrupt sul cambio di livello logico del pin, quindi viene

38/93

Page 39: C18 Step by Step

www.LaurTec.comwww.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 pull-up...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>345 #pragma config OSC = HS // 20Mhz6 #pragma config WDT = OFF // disabilito il watchdog timer7 #pragma config LVP = OFF // disabilito programmazione LVP8 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sullaPORTB91011 void main (void)12 { int i; // variabile per il filtro antirimbalzo

generato un iterrupt sia quando uno di questi ingressi passa da 0 a 1 che quando passa da 1 a 0.32 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.

33 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.

34 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

Page 40: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

1314 TRISA = 0xFF; // Tutti ingressi15 PORTA = 0x00;1617 TRISB = 0xFF ; // Tutti ingressi18 PORTB = 0x00 ;1920 TRISC = 0xFF; // Tutti ingressi21 PORTC = 0x00;2223 TRISD = 0x00; // Tutte uscite24 PORTD = 0x00;2526 EnablePullups(); // abilita i resistori di pull-up sulla PORTB272829 for (;;) //ciclo infinito30 {31 if (PORTBbits.RB0 == 0) //prima lettura32 {33 for (i=0;i<10000; i++); // pausa che filtra gli spikes3435 if (PORTBbits.RB0 == 0) //seconda lettura dopo il filtro36 {37 PORTD = 0xF0; //ho premuto il pulsante su RB038 }3940 }4142 else43 {44 PORTD = 0x0F; //il pulsante è aperto45 }46 }47 }

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

Page 41: C18 Step by Step

www.LaurTec.comwww.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

Page 42: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

3 #pragma config OSC = HS // 20Mhz4 #pragma config WDT = OFF // disabilito il watchdog timer5 #pragma config LVP = OFF // disabilito programmazione LVP6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sullaPORTB789 void main (void)10 { unsigned int i; // variabile per la pausa11 char numero; // variabile per il conteggio interno al while1213 TRISA = 0xFF; // Tutti ingressi14 PORTA = 0x00;1516 TRISB = 0xFF ; // Tutti ingressi17 PORTB = 0x00 ;1819 TRISC = 0xFF; // Tutti ingressi20 PORTC = 0x00;2122 TRISD = 0x00; // Tutte uscite23 PORTD = 0x00;2425 numero = 0; //inizializzo il numero a 02627 while (numero<16)28 {29 PORTD = numero; // visualizzo in uscita il valore di numero 30 numero++; // incremento il numero3132 for (i=0;i<64000; i++); // pausa per rallentare il conteggio33 }3435 while(1);3637 }

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

Page 43: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

1 #include <p18f4580.h>23 #pragma config OSC = HS // 20Mhz4 #pragma config WDT = OFF // disabilito il watchdog timer5 #pragma config LVP = OFF // disabilito programmazione LVP6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sullaPORTB789 void main (void)10 { unsigned int i; // variabile per la pausa11 char numero; // variabile per il conteggio interno al while1213 TRISA = 0xFF; // Tutti ingressi14 PORTA = 0x00;1516 TRISB = 0xFF ; // Tutti ingressi17 PORTB = 0x00 ;1819 TRISC = 0xFF; // Tutti ingressi20 PORTC = 0x00;2122 TRISD = 0x00; // Tutte uscite23 PORTD = 0x00;2425 numero = 0; //inizializzo il numero a 02627 while (numero<16)28 {29 PORTD = numero;30 numero++; // incremento il numero3132 if (numero==9)33 {34 break; // il conteggio si interrompe a 935 }3637 for (i=0;i<64000; i++); // pausa per rallentare il conteggio38 }3940 while(1);4142 }

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

Page 44: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Le funzioniPer 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>23 #pragma config OSC = HS // 20Mhz4 #pragma config WDT = OFF // disabilito il watchdog timer5 #pragma config LVP = OFF // disabilito programmazione LVP6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sullaPORTB78 9 //funzione per sommare due numeri interi1011 int sommaInteri (int add1, int add2)12 {13 int somma;1415 somma = add1+add2;1617 return(somma); // ritorno la somma18 }192021 void main (void)22 {2324 TRISA = 0xFF; // Tutti ingressi25 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

Page 45: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

2627 TRISB = 0xFF ; // Tutti ingressi28 PORTB = 0x00 ;2930 TRISC = 0xFF; // Tutti ingressi31 PORTC = 0x00;3233 TRISD = 0x00; // Tutte uscite34 PORTD = 0x00;3536 PORTD = sommaInteri (3,7); //effettuo la somma tra 3 e 7373839 while(1);4041 }

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 interi37 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

Page 46: C18 Step by Step

www.LaurTec.comwww.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>23 #pragma config OSC = HS // 20Mhz4 #pragma config WDT = OFF // disabilito il watchdog timer5 #pragma config LVP = OFF // disabilito programmazione LVP6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sullaPORTB78 int sommaInteri (int add1,int add2);910 void main (void)11 {1213 TRISA = 0xFF; // Tutti ingressi14 PORTA = 0x00;1516 TRISB = 0xFF ; // Tutti ingressi17 PORTB = 0x00 ;1819 TRISC = 0xFF; // Tutti ingressi20 PORTC = 0x00;2122 TRISD = 0x00; // Tutte uscite23 PORTD = 0x00;2425 PORTD = sommaInteri (3,7); //effettuo la somma tra 3 e 7262728 while(1);2930 }313233 //funzione per sommare due numeri interi3435 int sommaInteri (int add1, int add2)36 {37 int somma;3839 somma = add1+add2;4041 return(somma); // ritorno la somma42 }

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

Page 47: C18 Step by Step

www.LaurTec.comwww.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

Page 48: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Visibilità delle variabiliPer 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

Page 49: C18 Step by Step

www.LaurTec.comwww.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>23 #pragma config OSC = HS // 20Mhz4 #pragma config WDT = OFF // disabilito il watchdog timer5 #pragma config LVP = OFF // disabilito programmazione LVP6 #pragma config PBADEN = OFF // disabilito gli ingressi analogici sullaPORTB78 //**************************************************************9 //funzione che conta le sue chiamate1011 int Conteggio (void)12 {13 static int contatore=0; //dichiarazione variabile statica1415 contatore++;1617 return (contatore)18 }19//**************************************************************2021 void main (void)22 { unsigned int i;2324 TRISA = 0xFF; // Tutti ingressi25 PORTA = 0x00;2627 TRISB = 0xFF ; // Tutti ingressi28 PORTB = 0x00 ;2930 TRISC = 0xFF; // Tutti ingressi31 PORTC = 0x00;3233 TRISD = 0x00; // Tutte uscite34 PORTD = 0x00;3536 PORTD = Conteggio (); //visualizzo il valore di ritorno3738 for (i=0; i<64000; i++); //pausa3940 PORTD = Conteggio (); //visualizzo il valore di ritorno4142 for (i=0; i<64000; i++); //pausa4344 PORTD = Conteggio (); //visualizzo il valore di ritorno4546 while(1);4748 }

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

Page 50: C18 Step by Step

www.LaurTec.comwww.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

Page 51: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Le interruzioniLe 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; // Abilito l'interrupt globaleINTCONbits.PEIE = 1 ; // Abilito interrupt per periferiche

Ogni volta che un'interruzione viene generata il relativo bit di flag che la segnala è posta ad uno.

39 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.

40 In realtà anche il software interviene spesso per il salvataggio di variabili particolari che altrimenti non verrebbero salvate.

51/93

Page 52: C18 Step by Step

www.LaurTec.comwww.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

Page 53: C18 Step by Step

www.LaurTec.comwww.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 Laurenti3 Versione : 1.04 Data : 9/7/20065 CopyRight 200667 la descrizione di questo programma applicativo è possibile trovarlanel Tutorial8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com910 */111213 #include <p18f4580.h>14 #include <portb.h>15

53/93

Page 54: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

16 #pragma config OSC = HS // 20Mhz17 #pragma config WDT = OFF // disattivo il watchdog timer18 #pragma config LVP = OFF // disattivo la programmazione LVP19 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB202122 void Low_Int_Event (void); // prototipo di funzione232425 #pragma code low_vector=0x182627 void low_interrupt (void)28 {29 _asm GOTO Low_Int_Event _endasm //imposta il salto per la gestionedell'interrupt30 }3132 #pragma code3334 #pragma interruptlow Low_Int_Event3536 void Low_Int_Event (void)37 { int i; // indice per il ciclo di pausa3839 if (INTCONbits.RBIF == 1 ) // Controllo che l'interrupt sia statogenerato da PORTB 40 { for (i=0; i<30000; i++); //pausa filtraggio spikes4142 if (PORTBbits.RB7==0) // Controllo la pressione di RB743 {44 PORTD = 0xFF; //accendo tutti i LED45 for (i=0; i<30000; i++); //pausa46 PORTD = 0x00; //spengo tutti i LED47 }48 }4950 INTCONbits.RBIF = 0; //resetto il flag d'interrupt51 }525354 void main (void)55 {56 TRISA = 0xFF; // Tutti input57 PORTA = 0x00;5859 TRISB = 0x80 ; // RB7 input60 PORTB = 0x00 ;6162 TRISC = 0xFF; // Tutti input63 PORTC = 0x00;6465 TRISD = 0x00; // Tutte uscite66 PORTD = 0x00;6768 TRISE = 0xFF; // Tutti input

54/93

Page 55: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

69 PORTE = 0x00;7071 EnablePullups(); // abilito i resistori di pull-up7273 INTCONbits.RBIE = 1; // abilito le interruzioni su PORTB7475 INTCONbits.GIE = 1; // Abilito l'interrupt globale7677 while (1); //ciclo infinito in attesa d'interrupt7879 }

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 I bit della PORTB che generano un'interruzione al variare dello stato logico 0-1 o 1-0 sono RB4, RB5, RB6, RB7.42 Dopo il reset del PIC IPEN vale 0.

55/93

Page 56: C18 Step by Step

www.LaurTec.comwww.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 funzionevoid 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

Page 57: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

// Gestione interrupt ad alta priorità// in questo esempio l'USART

}

//******************************************************************//Main Program//******************************************************************

void main (void){

TRISA = 0xFF; // inizializzazione PORTAPORTA = 0x00;

TRISB = 0x00; // inizializzazione PORTBPORTB = 0x00 ;

TRISC = 0xFF; // inizializzazione PORTCPORTC = 0x00;

TRISD = 0x00; // inizializzazione PORTDPORTD = 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

Page 58: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Come utilizzare un display alfanumerico LCDI 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 = Contrastopin 4 = RSpin 5 = R/W (collegato a massa in applicazioni in cui non si legge dal controllore)pin 6 = Epin 7 = DB0pin 8 = DB1pin 9 = DB2pin 10 = DB3pin 11 = DB4pin 12 = DB5pin 13 = DB6pin 14 = DB7pin 15 = LED+pin 16 = LED-

Il 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 Ogni carattere è contenuto all'interno di una piccola matrice di punti per mezzo dei quali si ottiene la forma del carattere stesso.

44 Per una completa trattazione dei comandi disponibili per questo controllore si rimanda al relativo data sheet.45 Le linee di Enable possono anche essere due ma per il 16x2, 16x1 sono sempre 1.46 I file di libreria personale sono disponibili al sito www.LaurTec.com.

58/93

Page 59: C18 Step by Step

www.LaurTec.comwww.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 LCD_D0 PORTBbits.RB4 // you must set this pin as output #define LCD_D1 PORTBbits.RB5 // you must set this pin as output #define LCD_D2 PORTBbits.RB6 // you must set this pin as output #define LCD_D3 PORTBbits.RB7 // you must set this pin as output #define LCD_RS PORTBbits.RB2 // you must set this pin as output #define LCD_E PORTBbits.RB3 // you must set this pin as 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 Descrizionevoid OpenLCD (void) Permette d'inizializzare il dislpay LCDvoid ClearLCD (void) Pulisce le righe del Displayvoid CursorLCD (char,char) 1=ON cursor 0=OFF cursor; 1=ON blinking 0=OFF Blinkingvoid HomeLCD (void) Riposiziona il cursore all'inizio del displayvoid Line2LCD (void) Posiziona il cursore all'inizio della seconda rigavoid ShiftLCD (char) Trasla le righe di una posizione a destra o sinistravoid 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

Page 60: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

void WriteCharLCD (char) Scrive un carattere sul displayvoid WriteVarLCD(char *) Scrive una variabile sul displayvoid WriteStringLCD(const rom char *)

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

Page 61: C18 Step by Step

www.LaurTec.comwww.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>23 #include "LCD_44780.h"45 #pragma config OSC = HS // 20Mhz6 #pragma config WDT = OFF // disattivo il watchdog timer7 #pragma config LVP = OFF // disattivo la programmazione LVP8 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sullaPORTB91011 void main (void)12 {1314 TRISA = 0xFF; // Tutti input15 PORTA = 0x00 ;16

61/93

Page 62: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

17 TRISB = 0x00 ; // Tutte Uscite18 PORTB = 0x00 ;1920 TRISC = 0xFF; // Tutti input21 PORTC = 0x00 ;2223 TRISD = 0x00; // Tutte uscite24 PORTD = 0x00;2526 TRISE = 0xFF; // Tutti input27 PORTE = 0x00;2829 OpenLCD (); // Inizializzo LCD3031 WriteStringLCD ("Hello World"); //Scrivo la mia frase3233 ShiftLCD (RIGHT); //sposto la scritta a destra34 ShiftLCD (RIGHT);3536 while (1); //ciclo infinito3738 }

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>23 #include "LCD_44780.h"45 #pragma config OSC = HS // 20Mhz

62/93

Page 63: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

6 #pragma config WDT = OFF // disattivo il watchdog timer7 #pragma config LVP = OFF // disattivo la programmazione LVP8 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB91011 void copy (char * dest, rom const char * parola); //prototipo121314 typedef struct15 {16 unsigned char nome [20];17 unsigned char cognome [20];1819 } persona;20212223 void main (void)24 { persona tizio; // la variabile tizio è di tipo persona 2526 TRISA = 0xFF; // Tutti input27 PORTA = 0x00 ;2829 TRISB = 0x00 ; // Tutte Uscite30 PORTB = 0x00 ;3132 TRISC = 0xFF; // Tutti input33 PORTC = 0x00 ;3435 TRISD = 0xFF; // Tutti input36 PORTD = 0x00;3738 TRISE = 0xFF; // Tutti input39 PORTE = 0x00;4041 OpenLCD (); // Inizializzo LCD4243 copy (tizio.nome, "Mauro"); //scrivo i dati nella variabile44 copy (tizio.cognome, "Laurenti");4546 WriteVarLCD (tizio.nome); //scrivo il nome sull'LCD47 Line2LCD (); //mi sposto alla seconda linea48 WriteVarLCD (tizio.cognome); //scrivo il cognome sull'LCD4950 while (1); //ciclo infinito5152 }5354// *****************************************************************55 void copy (char *dest, rom const char *parola)56 {57 while(*parola)58 {59 *dest = (*parola); // copio il carattere dentro l'array60 parola++; // Incremento il puntatore

63/93

Page 64: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

61 dest++;62 }6364 *dest = '\0'; //inserisco il carattere di fine stringa65 }

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

Page 65: C18 Step by Step

www.LaurTec.comwww.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 [0] = 'M';tizio.nome [1] = 'A';tizio.nome [2] = 'U';tizio.nome [3] = 'R';tizio.nome [4] = '0';tizio.nome [5] = '\0';

o facendo uso del puntatore d precedentemente dichiarato:

d = tizio.nome; //d punta all'elemento tizio.nome[0]*d = 'M';d++; //d punta all'elemento tizio.nome[1]*d = 'A';d++; //d punta all'elemento tizio.nome[2]*d = 'U';d++; //d punta all'elemento tizio.nome[3]*d = 'R';d++; //d punta all'elemento tizio.nome[4]*d = 'O';d++; //d punta all'elemento tizio.nome[5]*d = '\0';

In questo esempio scrivere * d significa: scrivi nella variabile (elemento) puntata dall'indirizzo di memoria contenuto in d. Scrivere d++ o comunque d uguale a qualcosa significa cambiare il valore del puntatore ovvero il contenuto di d. Per mezzo di d++ si incrementa l'indirizzo e dunque è come se si accedesse all'elemento successivo dell'Array. Rivediamo il tutto con l'aiuto della Figura 30 in modo da comprendere l'argomento in maniera più chiara.

0 tizio.nome 19 0 tizio.cognome 19 d

Si consideri che ogni cella sia un Byte della memoria RAM dove sono contenute le nostre informazioni ovvero variabili. In particolare si consideri che le caselle dentro il rettangolo continuo siano i 20 bytes appartenenti all'Array tizio.nome mentre nel rettangolo tratteggiato siano presenti i 20 bytes dell'Array tizio.cognome mentre il rettangolo punto linea sia la variabile puntatore a char. Ogni casella avrà un proprio indirizzo che il PIC utilizzirà per sapere dove andare a leggere e dove andare a scrivere un certo dato. Quando si scrive d = tizio.nome si scrive all'interno di d l'indirizzo della prima casella dell'Array tizio.nome. L'indirizzo però è solo un numero, per poter effettivamente andare a

65/93

Figura 30: Esempio grafico di memoria RAM

Page 66: C18 Step by Step

www.LaurTec.comwww.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

Page 67: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Come utilizzare l'USARTL'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 Descrizionechar BusyUSART( void ) Controlla se l'USART è occupatavoid CloseUSART( void ) Chiude l'USARTchar DataRdyUSART( void ) Controlla se sono presenti dati ricevutivoid OpenUSART( unsigned char config,unsigned int spbrg);

Inizializza l'USART

char ReadUSART( void ) Legge un dato dal buffer di ricezionevoid 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

Page 68: C18 Step by Step

www.LaurTec.comwww.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 ONUSART_TX_INT_OFF Interruzione TX OFF

Interruzione in ricezione:USART_RX_INT_ON Interruzione RX ONUSART_RX_INT_OFF Interruzione RX OFF

Modalità USART:USART_ASYNCH_MODE Modalità AsincronaUSART_SYNCH_MODE Modalità Sincrona

Larghezza dati:USART_EIGHT_BIT 8-bit USART_NINE_BIT 9-bit

Modalità Slave/Master:USART_SYNC_SLAVE Slave modalità' sincrona (si applica solo in modalità' sincrona)USART_SYNC_MASTER Master modalità' sincrona (si applica solo in modalità' sincrona)

Modalità di ricezione:USART_SINGLE_RX Ricezione singolaUSART_CONT_RX Ricezione multipla

Baud rate:USART_BRGH_HIGH baud rate altoUSART_BRGH_LOW 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

Page 69: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

69/93

Tabella 3: Tabella per la scelta di SPBRG

Page 70: C18 Step by Step

www.LaurTec.comwww.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 Laurenti3 Versione : 1.04 Data : 25/5/20065 CopyRight 200667 la descrizione di questo programma applicativo è possibile trovarlanel Tutorial8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com910 */1112 #include <p18f4580.h>13 #include <usart.h>1415 #include "LCD_44780_Freedom.h"16 #include "Sponsor.h"1718 #pragma config OSC = HS // 20Mhz19 #pragma config WDT = OFF // disattivo il watchdog timer20 #pragma config LVP = OFF // disattivo la programmazione LVP21 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sulla PORTB2223

70/93

Page 71: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

242526 void main (void)27 { unsigned char data; // variabile che conterrà i dati ricevuti2829 TRISA = 0xFF; // Tutte ingressi3031 TRISB = 0xFF ; // Tutti ingressi32 PORTB = 0x00 ;3334 TRISC = 0x80; // Tx line è 0 and Rx line è 135 PORTC = 0x00;3637 TRISD = 0x00; // la PORTD è settata per lavorare con l'LCD38 PORTD = 0x00;3940 TRISE = 0xFF; // Tutti ingressi4142 OpenLCD (); // inizializzo LCD4344 WriteSponsor ();4546 // Configura l'USART47 // 8 bit48 // 19200 bit/s49 // 1 bit stop50 // 0 bit parità5152 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 );5960 WriteStringLCD ("Ready"); //l'USART è pronta per la ricezione6162 while(1)63 {64 while( !DataRdyUSART( ) ); //attendo di ricevere un dato dal PC65 data = ReadUSART(); // leggo il dato dal buffer di ricezione66 WriteUSART( data); //ritrasmetto il dato ricevuto67 68 if(data == 'c') // ricevendo una 'c' chiudo l'USART69 break; // esco dal loop di ricezione7071 }7273 CloseUSART(); // chiudo l'USART7475 ClearLCD (); // ripulisco LCD prima di riscrivere7677 WriteStringLCD ("Closed"); //l'USART è stata chiusa7879

71/93

Page 72: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

80 while (1); //ciclo infinito8182 }

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 bitfrequenza : 19200 bit/sstop bit : 1bit 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 Laurenti3 Versione : 1.04 Data : 25/5/20065 CopyRight 200667 la descrizione di questo programma applicativo è possibile trovarlanel Tutorial

72/93

Page 73: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com910 */111213 #include <p18f4580.h>14 #include <usart.h>1516 #include "LCD_44780_Freedom.h"17 #include "Sponsor.h"1819 #pragma config OSC = HS // 20Mhz20 #pragma config WDT = OFF // disattivo il watchdog timer21 #pragma config LVP = OFF // disattivo la programmazione LVP22 #pragma config PBADEN = OFF // disabilito gli ingressi analogigisulla PORTB232425 void Low_Int_Event (void); // prototipo di funzione262728 #pragma code low_vector=0x182930 void low_interrupt (void)31 {32 _ asm GOTO Low_Int_Event _endasm //imposta il salto per la gestione dell'interrupt33 }3435 #pragma code3637 #pragma interruptlow Low_Int_Event3839 void Low_Int_Event (void)40 { unsigned char data; // variabile che conterrà i dati ricevuti414243 if (PIR1bits.RCIF == 1 ) // Controllo che l'interrupt sia stato generato dall'USART44 {45 data = ReadUSART(); // leggo il dato dal buffer di ricezione46 WriteUSART( data); // ritrasmetto il dato ricevuto47 PORTB = data; // scrivo il dato ricevuto, sulla PORTB48 while (BusyUSART()); // attendo che il dato venga trasmesso4950 if (data == 'c')51 {52 INTCONbits.GIE = 1; // abilito l'interrupt globale53 CloseUSART(); // chiudo l'USART5455 ClearLCD (); // ripulisco LCD prima di riscrivere5657 WriteStringLCD ("Closed");58 }5960 PIR1bits.RCIF = 0;

73/93

Page 74: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

61 }6263 }64656667 void main (void)68 {69 TRISA = 0xFF; // Tutti ingressi7071 TRISB = 0x00 ; // Tutte uscite72 PORTB = 0x00 ;7374 TRISC = 0x80; // Tx line è 0 and Rx line è 175 PORTC = 0x00;7677 TRISD = 0x00; // la PORTD è settata per lavorare con l'LCD78 PORTD = 0x00;7980 TRISE = 0xFF; // Tutti ingressi8182 OpenLCD ();8384 WriteSponsor ();858687 // Configura l'USART88 // 8 bit89 // 19200 bit/s90 // 1 bit stop91 // 0 bit parità9293 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 );100101102 INTCONbits.GIE = 1; // Abilito l'interrupt globale103 INTCONbits.PEIE = 1 ; // abilito interrupt per periferiche104105 WriteStringLCD ("Ready"); //Usart pronta per la ricezione106107 while (1); //ciclo infinito in attesa d'interrupt108109 }

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

Page 75: C18 Step by Step

www.LaurTec.comwww.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

Page 76: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Come utilizzare il bus I2CIl 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 Descrizionechar WriteEEprom( unsigned char control, unsigned char addressH,unsigned char addressL, unsigned char data )

Permette di scrivere un dato all'interno della memoria EEPROM ad un determinato indirizzo.

char ReadEEprom( unsigned char control, unsigned char addressH,unsigned char addressL )

Permette di leggere un dato dalla 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

Page 77: C18 Step by Step

www.LaurTec.comwww.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>23 #include "eeprom.h"45 #pragma config OSC = HS // 20Mhz6 #pragma config WDT = OFF // disattivo il watchdog timer7 #pragma config LVP = OFF // disattivo la programmazione LVP8 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sullaPORTB91011 //**************************************************1213 void main (void)14 { int i ;1516 TRISA = 0xFF; //Tutti ingressi17 PORTA = 0x00;1819 TRISB = 0xFF ; //Tutti ingressi20 PORTB = 0x00 ;2122 TRISC = 0xFF; //Tutti ingressi23 PORTC = 0x00;2425 TRISD = 0x00; //Tutte uscite26 PORTD = 0x00;2728 TRISE = 0xFF; //Tutti ingressi29 PORTE = 0x00;303132 OpenI2C(MASTER, SLEW_ON);// Initializza il modulo I2C a 100KHz3334 SSPADD = 14; //400kHz Baud clock @20MHz353637

77/93

Page 78: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

38 WriteEEprom (0xA0,0x00,0x01,0b01010101); // scrivo il byte 55Hall'indirizzo 0001H3940 for (i=0; i<1000; i++); //pausa4142 PORTD = ReadEEprom (0xA0,0x00,0x01); // leggo l'indirizzo 0001H4344 while(1); // ciclo infinito4546 }

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=1FOSC

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 Descrizioneunsigned char WriteSeconds (unsigned char Seconds)

Scrive i secondi dell'orario

unsigned char ReadSeconds (void) Legge i secondi dell'orariounsigned char WriteMinutes (unsigned char Minutes)

Scrive i minuti dell'orario

unsigned char ReadMinutes (void) Legge i minuti dell'orariounsigned char WriteHours (unsigned char Hours)

Scrive l'ora dell'orario

unsigned char ReadHours (void) Legge l'ora dell'orariounsigned char* ReadTimeSeconds (void) Legge l'orario comprensivo dei secondiunsigned char* ReadTime (void) Legge l'orario senza secondi

78/93

Page 79: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

unsigned char WriteDays (unsigned char Days)

Scrive il giorno della data

unsigned char ReadDays (void) Legge legge il giorno della data odiernaunsigned char WriteWeekDays (unsigned char WeekDays)

Scrive il giorno della settimana

unsigned char ReadWeekDays (void) Legge il giorno della settimanaunsigned char WriteMonths (unsigned char Months)

Scrive il mese della data

unsigned char ReadMonths (void) Legge il mese della dataunsigned char WriteYears (unsigned char Years)

Scrive l'anno

unsigned char ReadYears (void) Legge l'announsigned char* ReadDate (void) Legge la data odierna GG/MM/AAunsigned char WriteMinutesAlarm (unsigned char Minutes,unsigned char AlarmEnable)

Scrive i minuti per l'allarme

unsigned char WriteHoursAlarm (unsigned char Hours,unsigned char AlarmEnable)

Scrive l'ora per l'allarme

unsigned char WriteDaysAlarm (unsigned char Days,unsigned char AlarmEnable)

Scrive il giorno per l'allarme

unsigned char WriteWeekDaysAlarm (unsigned char WeekDays,unsigned char AlarmEnable)

Scrive la settimana per l'allarme

unsigned char EnableInt (void) Abilita l'interruzione per l'allarmeunsigned char DisableAllInt (void) Disabilita tutte le interruzioniunsigned 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

Page 80: C18 Step by Step

www.LaurTec.comwww.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: domenicaLU: 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

Page 81: C18 Step by Step

www.LaurTec.comwww.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 WriteMinutesAlarm (unsigned char Minutes,unsigned char AlarmEnable)

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 WriteHoursAlarm (unsigned char Hours,unsigned char AlarmEnable)

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 WriteWeekDaysAlarm (unsigned char WeekDays,unsigned char AlarmEnable)

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

Page 82: C18 Step by Step

www.LaurTec.comwww.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 Laurenti3 Versione : 1.04 Data : 1/6/20065 CopyRight 200667 la descrizione di questo programma applicativo è possibile trovarlanel Tutorial8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com910 */1112 #include <p18f4580.h>1314 #include "PCF8563.h"15 #include "LCD_44780_Freedom.h"16 #include "Sponsor.h"171819 #pragma config OSC = HS // 20Mhz20 #pragma config WDT = OFF // disabilito il watchdog timer21 #pragma config LVP = OFF // disabilito programmazione LVP22 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sullaPORTB232425 void main (void)26 { unsigned char *Time;27 unsigned char *Date;282930 TRISA = 0xFF; // Tutti ingressi

82/93

Page 83: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

31 PORTA = 0x00;3233 TRISB = 0x00; // Tutte uscite 34 PORTB = 0x00;3536 TRISC = 0xFF; // Tutti ingressi3738 TRISD = 0x00; // la PORTD è settata per lavorare con l'LCD39 PORTD = 0x00;4041 TRISE = 0x00; // la cicalina è abilitata42 PORTE = 0x00;434445 OpenI2C(MASTER, SLEW_ON); // Initializza il modulo I2C a 400KHz4647 SSPADD = 14; //400kHz Baud clock @20MHz4849 OpenLCD (); // inizializzo LCD5051 //Imposta l'ora attuale52 WriteSeconds (0x55);53 WriteMinutes (0x05);54 WriteHours (0x10);5556 WriteDays (0x01);57 WriteWeekDays (ME);58 WriteMonths (0x06);59 WriteYears (0x06);6061 //Imposto l'orario di allarme6263 WriteMinutesAlarm (0x06,Enable_ON);64 WriteHoursAlarm (0x10,Enable_ON);65 WriteDaysAlarm (0x25,Enable_OFF); // non verrà considerato poiche' OFF66 WriteWeekDaysAlarm (LU,Enable_OFF); //non verrà considerato poiche' OFF6768 EnableAlarmInt (); //Abilita Enable del Real Time Clock/Calendar6970 while (1)71 {72 HomeLCD ();7374 Time = ReadTime(); //Leggo l'ora75 WriteVarLCD (Time); // scrivo sull'LCD la stringa con l'ora 7677 WriteStringLCD (" "); //inserisco uno spazio7879 Date = ReadDate (); //leggo la data80 WriteVarLCD (Date); //visualizzo la stringa con la data8182 if (IsAlarmON ()) // controllo il Flag del Real Time Clock83 {84 PORTEbits.RE1 = 0x01; //accendo la cicalina...

83/93

Page 84: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

85 }8687 }8889 }

84/93

Page 85: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Come utilizzare il PWMNei 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 Descrizionevoid ClosePWMx (void) Disabilita il canale x PWMvoid 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 PWM4⋅T OSC⋅TMR2 prescaler

−1

che può anche essere riscritta nel seguente modo:

period=Periodo PWM⋅ f QUARZO

4⋅TMR2 prescaler−1

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

Page 86: C18 Step by Step

www.LaurTec.comwww.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 Laurenti3 Versione : 1.04 Data : 25/5/20065 CopyRight 200667 la descrizione di questo programma applicativo è possibile trovarlanel Tutorial8 "C18 step by step" scaricabile gratuitamente dal sito www.LaurTec.com910 */1112 #include <p18f4580.h>13 #include <pwm.h>14 #include <timers.h>151617 #include "LCD_44780_Freedom.h"18 #include "Sponsor.h"1920 #pragma config OSC = HS // 20Mhz21 #pragma config WDT = OFF // disabilito il watchdog timer22 #pragma config LVP = OFF // disabilito programmazione LVP23 #pragma config PBADEN = OFF // disabilito gli ingressi analogigi sullaPORTB242526 void main (void)27 { unsigned int DutyCycle=0, i;28 char Period;2930 TRISA = 0xFF;31 PORTA = 0xFF; 3233 TRISB = 0xFF; 34 PORTB = 0x00;3536 TRISC = 0x00; // i pin per il PWM sono output37 PORTC = 0x00;3839 TRISD = 0xFF; // la PORTD è settata per lavorare con l'LCD40 PORTD = 0x00;4142 TRISE = 0xFF;43 PORTD = 0x00;4445 OpenLCD (); // inizializzo LCD

86/93

Page 87: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

4647 WriteSponsor ();4849 OpenTimer2( TIMER_INT_OFF & T2_PS_1_1 & T2_POST_1_1); // apro ilTMR2 per il PWM5051 Period = 249; //imposto una frequenza di 20KHz5253 OpenPWM1( Period ); // apro il PWM5455 while (1)56 {57 SetDCPWM1 ( DutyCycle); // aggiorno il dutycycle5859 DutyCycle++; // incremento il dutycycle60 if (DutyCycle> 1024) // controllo che non sia maggiore di 2^1061 {62 DutyCycle =0;63 }6465 for (i=0; i<5000; i++);66 }6768 }

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

Page 88: C18 Step by Step

www.LaurTec.comwww.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

Page 89: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

IndiceIntroduzione..............................................................................................................................................3Perché MPLAB C18?................................................................................................................................3Installazione del software..........................................................................................................................5Il nostro primo progetto...........................................................................................................................15Tipi di variabili........................................................................................................................................27Operatori matematici, logici e bitwise....................................................................................................32Il ciclo for (...) ........................................................................................................................................34L'istruzione condizionale if (...)..............................................................................................................37L'istruzione condizionale while (...)........................................................................................................41Le funzioni..............................................................................................................................................44Visibilità delle variabili...........................................................................................................................48Le interruzioni.........................................................................................................................................51Come utilizzare un display alfanumerico LCD.......................................................................................58Come utilizzare l'USART.......................................................................................................................67Come utilizzare il bus I2C.......................................................................................................................76Come utilizzare il PWM..........................................................................................................................85Bibliografia..............................................................................................................................................88

89/93

Page 90: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

Indice alfabeticoA accumulatore........................................................................................................................................32 Array.....................................................................................................................................................28 Array di caratteri............................................................................................................................30, 61B BCD......................................................................................................................................................79 break...........................................................................................................................................42 e seg. BRGH...................................................................................................................................................68 Build All...............................................................................................................................................22C char.......................................................................................................................................................27 ciclo infinito.........................................................................................................................................36 commenti..............................................................................................................................................24 controllore HD44780............................................................................................................................58 cos(x)....................................................................................................................................................32 costante.................................................................................................................................................30D display alfanumerici.............................................................................................................................58E EEPROM 24LC512..............................................................................................................................77 else........................................................................................................................................................37 EnablePullups()....................................................................................................................................38 EnablePullups();...................................................................................................................................39F filtro antirimbalzo.................................................................................................................................39 floating point........................................................................................................................................27 for (...)...................................................................................................................................................34G GIE.................................................................................................................................................51, 55 GIEH....................................................................................................................................................55 GIEL.....................................................................................................................................................55H HS.........................................................................................................................................................24I 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

Page 91: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

istruzione while (...)..............................................................................................................................41L l'ANSI C.................................................................................................................................................3 lettura di un pulsante............................................................................................................................38 Linker Script.........................................................................................................................................19 log(x)....................................................................................................................................................32 long ......................................................................................................................................................27 LVP......................................................................................................................................................24M macro....................................................................................................................................................30 Maestro...................................................................................................................................................7 main......................................................................................................................................................24 math.h...................................................................................................................................................32 MAX232...............................................................................................................................................67 modulazione PWM...............................................................................................................................85N numero binario.....................................................................................................................................26 numero decimale..................................................................................................................................26 numero esadecimale.............................................................................................................................26O 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.............................................................................................................................32P 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

Page 92: C18 Step by Step

www.LaurTec.comwww.LaurTec.com C18 step by step

prescaler...............................................................................................................................................85 Project wizard.......................................................................................................................................15 prototipo di funzione............................................................................................................................45 pull-down.............................................................................................................................................39 pull-up..................................................................................................................................................39R RAM.....................................................................................................................................................27 RCONbits.IPEN...................................................................................................................................75 RCONbits.IPEN = 1.............................................................................................................................57 Reset.....................................................................................................................................................51 rom const char......................................................................................................................................63 RS232...................................................................................................................................................67 RS485...................................................................................................................................................67S 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, 61T tecnica del polling................................................................................................................................70 TMR2...................................................................................................................................................85 Toolsuite...............................................................................................................................................16 TRISA........................................................................................................................................25 e seg. TRISD..................................................................................................................................................25 typedef struct........................................................................................................................................29U unsigned char........................................................................................................................................27 unsigned int..........................................................................................................................................27 unsigned long.......................................................................................................................................27 unsigned short......................................................................................................................................27 unsigned short long..............................................................................................................................27 USART.................................................................................................................................................67V variabile static......................................................................................................................................48 void.......................................................................................................................................................25W WDT.....................................................................................................................................................24_ _asm...............................................................................................................................................53, 56 _endasm..........................................................................................................................................53, 56. .hex.......................................................................................................................................................23 .map .....................................................................................................................................................23

92/93

Page 93: C18 Step by Step

www.LaurTec.comwww.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