Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
BK002-IT
Mauro Laurenti
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
Chiunque decida di far uso delle nozioni riportate nella seguente opera o decida di
realizzare i circuiti proposti, tenuto 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.
Tutti i marchi citati in quest'opera sono dei rispettivi proprietari.
Licenza Software
Tutto esempi Software presentati in questo libro sono rilasciati sotto licenza GNU, di
seguito riportata. Le librerie utilizzate potrebbero essere soggette a differente licenza
dunque per un utilizzo corretto del software e sua ridistribuzione far riferimento alle
particolari librerie incluse.
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
Additional Terms
As stated in section 7 of the license, these additional terms are included:
1) The copyright statement must be preserved.
2) Any Warranty Disclaimers must be preserved.
Nota
Parti del testo a cui si vuole dare particolare risalto sono scritti come testo
standard al fianco dell'icona Nota.
il tempo di pensare a chi dedicare le pagine che seguiranno queste parole. Credo che non bisogna
forse pensare troppo che se questo libro nato legato al fatto che io stesso sia nato e cresciuto con i giusti
stimoli ed interessi, dunque non posso non dedicare questo libro ai miei familiari, pap, mamma e i miei
tre fratelli e singola sorellina. Vorrei fare anche un passo in alto e ricordare mio nonno Adolfo che tanto
mi ha stimato per i traguardi raggiunti ma che sfortunatamente non ha avuto modo di vedere il tempo
della mia laurea e quello che sarebbe seguito.
Non vorrei dimenticare gli altri miei nonni i cui ricordi sono per offuscati dalla giovane et in cui li
ho conosciuti e visti per l'ultima volta...ma ero il loro Bobo.
Last but not least un pensiero al mio amore che mi sopporta da un po' di tempo...ma la vita
ancora lunga...!
Mauro Laurenti
Indice
Presentazione alla Terza Edizione.........................................................................................................4
Formattazione del testo...........................................................................................................................5
Com' organizzato il testo.......................................................................................................................6
Capitolo I................................................................................................................................................14
Un mondo programmabile...................................................................................................................14
Cos' un microcontrollore ..................................................................................................................14
I sistemi embedded ............................................................................................................................16
I microcontrollori della Microchip.....................................................................................................17
Cosa c' oltre al mondo Microchip.....................................................................................................19
Altri microcontrollori.........................................................................................................................21
Riassunto............................................................................................................................................23
Punti chiave ed approfondimenti........................................................................................................23
Domande ed Esercizi..........................................................................................................................24
Capitolo II..............................................................................................................................................25
Strumenti per iniziare...........................................................................................................................25
Perch si scelto il mondo Microchip...............................................................................................25
L'ambiente di sviluppo MPLAB X.....................................................................................................26
Il compilatore XC8.............................................................................................................................28
Utilizzare il C o l'Assembly?..............................................................................................................31
Il programmatore................................................................................................................................33
Scheda di sviluppo e test ...................................................................................................................35
Hardware di sviluppo base.................................................................................................................38
Riassunto............................................................................................................................................42
Punti chiave ed approfondimenti........................................................................................................42
Domande ed Esercizi..........................................................................................................................43
Capitolo III.............................................................................................................................................44
Architettura ed Hardware dei PIC18 .................................................................................................44
Architettura dei PIC18 .......................................................................................................................44
Organizzazione della Memoria...........................................................................................................49
L'oscillatore .......................................................................................................................................59
Power Management............................................................................................................................64
Circuiteria di Reset.............................................................................................................................67
Le porte d'ingresso uscita...................................................................................................................72
Riassunto............................................................................................................................................77
Punti chiave ed approfondimenti........................................................................................................78
Domande ed Esercizi..........................................................................................................................78
Capitolo IV.............................................................................................................................................80
Salutiamo il mondo per iniziare ..........................................................................................................80
Schema elettrico.................................................................................................................................80
Impostare le schede di sviluppo.........................................................................................................82
Impostazioni software........................................................................................................................82
Il nostro primo progetto......................................................................................................................83
Impostare l'ambiente di lavoro...........................................................................................................91
Scrivere e Compilare un progetto ......................................................................................................95
Programmare il microcontrollore.....................................................................................................101
Capire come salutare il mondo.........................................................................................................103
Riassunto..........................................................................................................................................111
Punti chiave ed approfondimenti......................................................................................................111
Copyright 2014 Mauro Laurenti
Domande ed Esercizi........................................................................................................................112
Capitolo V............................................................................................................................................113
Simuliamo i nostri programmi...........................................................................................................113
Schema elettrico...............................................................................................................................113
Impostare le schede di sviluppo.......................................................................................................114
Impostazioni software......................................................................................................................116
Fase di Simulazione e Debug...........................................................................................................116
Avvio di una sessione di Simulazione..............................................................................................117
Avvio di una sessione di Debug.......................................................................................................120
Il codice Assembly...........................................................................................................................121
Utilizzo dei Breakpoint.....................................................................................................................124
Controllo delle variabili e registri.....................................................................................................128
Controllo della memoria...................................................................................................................131
Analisi temporale..............................................................................................................................132
Semplici regole di programmazione.................................................................................................136
Districarsi tra il codice......................................................................................................................138
Riassunto..........................................................................................................................................141
Punti chiave ed approfondimenti......................................................................................................141
Domande ed Esercizi........................................................................................................................142
Capitolo VI...........................................................................................................................................143
Impariamo a programmare in C.......................................................................................................143
Schema elettrico...............................................................................................................................143
Impostare le schede di sviluppo.......................................................................................................144
Impostazioni software......................................................................................................................145
La sintassi in breve...........................................................................................................................146
Tipi di variabili intere.......................................................................................................................148
Tipi di variabili floating point..........................................................................................................155
Dichiarazione ed inizializzazione di variabili..................................................................................158
Gli Array...........................................................................................................................................160
Le strutture........................................................................................................................................164
Le costanti........................................................................................................................................167
I puntatori.........................................................................................................................................169
Operatori matematici........................................................................................................................175
Casting delle variabili.......................................................................................................................182
Operatori logici.................................................................................................................................184
Operatori bitwise .............................................................................................................................185
Istruzione condizionale while ( )......................................................................................................190
Il ciclo for ( ).....................................................................................................................................195
Istruzione condizionale if ( ).............................................................................................................203
Istruzione condizionale switch ( ).....................................................................................................210
Riassunto..........................................................................................................................................216
Punti chiave ed approfondimenti......................................................................................................216
Domande ed Esercizi........................................................................................................................217
Capitolo VII.........................................................................................................................................219
Le Funzioni..........................................................................................................................................219
Schema elettrico...............................................................................................................................219
Impostare le schede di sviluppo.......................................................................................................220
Impostazioni software......................................................................................................................221
Cosa sono le funzioni.......................................................................................................................221
Visibilit delle variabili....................................................................................................................226
Passaggio di variabili per valore e riferimento.................................................................................232
9
12
13
Capitolo I
Un mondo programmabile
Oggigiorno si sente parlare di lavatrici intelligenti, di frigoriferi che si riempiono da soli
e giocattoli che parlano. Tutto questo stato in parte reso possibile grazie ai bassi costi
delle logiche programmabili, tra queste vi sono i microcontrollori. In questo Capitolo
dopo un'introduzione sui microcontrollori e in particolare quelli della Microchip, sono
discusse anche le altre alternative, le quali per non sono ulteriormente trattate nel seguito
del testo.
Il mercato pieno di prodotti validi, e sebbene un testo debba certamente focalizzarsi su
una famiglia, un progettista deve opportunamente valutare quale utilizzare nel proprio
progetto al fine di soddisfare ogni specifica.
Cos' un microcontrollore
Un microcontrollore o microcalcolatore (MCU), un circuito integrato, ovvero un
semiconduttore. Al suo interno sono presenti da poche migliaia a centinaia di migliaia di
componenti elettronici elementari; generalmente le tecnologie attuali utilizzano transistor
CMOS. Le dimensioni del singolo transistor utilizzato nei processi produttivi standard
variano da 65nm a 180nm, anche se tecnologie pi piccole sono gi presenti sul mercato 1.
Fin qui nulla di nuovo se confrontati con gli altri circuiti integrati, ed in particolare con i
microprocessori. In realt ci sono molte differenze che li rendono appetibili rispetto ai
microprocessori. Se consideriamo i microprocessori attualmente sul mercato, per esempio
quelli della Intel o AMD, ben sappiamo che hanno potenze di calcolo enormi, non
sappiamo quanto, ma sappiamo che possiamo farci molto! In realt solo con il
microprocessore non si pu fare nulla! Per poter utilizzare il microprocessore necessario
circondarlo di molto altro Hardware, quale per esempio, la RAM, Hard Disk e di tutte le
altre periferiche necessarie quali per esempio una scheda video, porte di comunicazione
(USB, seriale, Ethernet, ecc...).
Il microprocessore con tutto l'armamentario attorno richiede notevole potenza, non a
caso un PC ha spesso le ventole di raffreddamento!
Bene, al microcontrollore tutto questo non serve, RAM e memoria non volatile
(tipicamente memoria Flash) sono al suo interno, come anche la maggior parte delle
periferiche di base o specifiche per determinate applicazioni 2. In ultimo, ma non meno
importante, il microcontrollore assorbe cosi poco, che alcune applicazioni, tipo orologi
digitali, possono essere alimentati per mezzo di un limone usato come batteria. Se il
microcontrollore ha tutti questi vantaggi vi chiederete per quale ragione si continuano ad
1
Normalmente tecnologie pi piccole si traducono in pi bassi costi per un microcontrollore, visto che a parit di funzioni
possibile usare una minor area di silicio. Ciononostante altre variabili associate al marketing determinano il costo finale di un
microcontrollore a prescindere dal reale costo del componente stesso.
Poich il microcontrollore possiede al suo interno un gran numero di sistemi, viene anche detto SoC, ovvero System on
Chip.
14
Per cui si capisce che quello che oggi rappresenta un'elevata integrazione, domani
rappresenter un semplice circuito integrato ottenibile con processi produttivi standard.
I sistemi embedded
Capiti i pregi e difetti del microprocessore e del microcontrollore vediamo di descrivere
brevemente le applicazioni dove i microcontrollori vengono utilizzati. Esempi tipici sono
elettrodomestici, giocattoli, sistemi di misura dedicati e applicazioni consumer in generale.
Tutte queste applicazioni rientrano in quella pi ampia dei sistemi embedded. Le
applicazioni o sistemi embedded sono un gruppo di applicazioni accomunate dal fatto di
essere un sistema di calcolo progettato per un determinato scopo (ad hoc) il cui hardware
(risorse) disponibili sono ridotte al minimo . Per tale ragione, applicazioni con i
microcontrollori rientrano nella categoria delle applicazioni embedded. Nonostante
un'applicazione embedded sia ritenuta un'applicazione con ridotte risorse hardware, tale
descrizione un po' vaga. Infatti molte schede di sviluppo utilizzate in applicazioni
3
4
5
6
7
VHDL l'acronimo di VHSIC Hardware Description Language, dove VHSIC l'acronimo di Very High Speed Integrated
Circuits.
Verilog un linguaggio descrittivo come il VHDL, ed anche noto come Verilog HDL.
Un linguaggio descrittivo per l'hardware come il VHDL e il Verilog, si presenta molto simile ad un linguaggio di
programmazione ma vi sono molti aspetti che lo rendono differenti, in particolare la concorrenzialit ovvero la
contemporanea esecuzione di parti diverse del codice da parte dell'hardware che traduce il codice stesso.
Come si vedr a breve il core ARM spesso utilizzato all'interno di microcontrollori a 32 bit.
Gordon Moore cofondatore di Intel con Robert Noyce. Nel 1965 ipotizz che le prestazioni dei microprocessori e loro
integrazione sarebbe raddoppiata ogni 12 mesi. Attualmente la legge stata ridimensionata con tempi di 2 anni. Si capisce
che sebbene sia chiamata legge, in realt non c' nulla di matematico o fisico, ma solo una previsione ben posta.
16
embedded altro non sono che mini schede di PC il cui microprocessore era il top di
qualche anno prima. Per esempio schede per applicazioni embedded fanno
frequentemente uso di microprocessori Intel della serie Pentium II e AtomTM. Il Pentium
II quando venne introdotto nel mercato era un gioiello della tecnologia, il cui costo
portava il prezzo di un PC pari al valore di uno stipendio medio italiano del tempo. Oggi
possibile acquistare schede embedded con Pentium II a meno di 100 euro.
Da quando detto si capisce dunque che la tipologia dell'hardware utilizzato in
applicazioni embedded varia con il tempo, e quello che che oggi il top della tecnologia,
domani potr essere utilizzato in applicazioni embedded. In tutto questo, per possibile
dire che per definizione se si fa uso di un microcontrollore invece di un microprocessore,
a buon diritto il sistema da considerarsi embedded. In caso contrario sar il tempo a
decidere.
In ultimo vediamo brevemente cosa significa programmare per un sistema embedded.
Qualora si faccia uso di un microcontrollore di fascia bassa, ovvero con architettura ad 8
o 16 bit, frequentemente il software, escluso le librerie utilizzabili ed offerte con il
compilatore, viene scritto dal nulla e in maniera specifica per l'applicazione. Negli ultimi
anni sono comparsi dei piccoli sistemi operativi RTOS (Real Time Operative System) dedicati
ad applicazioni generiche e utilizzabili su vari microcontrollori in cui sia richiesta la
funzione Real Time. Nel caso in cui si stia sviluppando un sistema utilizzando una
piattaforma con microcontrollore o microprocessore a 32 bit, la questione dello sviluppo
software in generale differente. Per queste strutture, qualora la CPU o MCU possieda la
MMU (Memory Managment Unit), possibile utilizzare sistemi operativi standard quali
Linux8 e Windows. L'installazione e la gestione di applicazioni naturalmente non
semplice come per un PC, visto che i sistemi operativi si trovano ad essere installati in
ambienti un po' stretti! Alcuni esempi di applicazioni a 32 bit dove vengono spesso
utilizzati sistemi operativi come Linux e Windows, sono i palmari, cellulari, router,
stampanti e molti altri. Il sistema operativo Linux stato addirittura installato in un
orologio da polso, al fine di dimostrare le poche risorse necessarie per la sua esecuzione.
L'applicazione non era molto pratica ma stato certamente un esperimento interessante.
Architettura ad 8 bit
Come detto la famiglia ad 8 bit rappresenta la pi vasta, questa si divide in:
Vi sono alcuni progetti in cui vengono supportati anche CPU e MCU a 16 bit e senza MMU, ma il loro supporto e
potenzialit ridotto. Per tale ragione, qualora si voglia far uso di Linux, consigliabile utilizzare strutture a 32 bit con
MMU.
17
Architettura media
Architettura Avanzata
Numero pin
6-40
8-64
8-64
18-100
Performance
5MIPS
5MIPS
8MIPS
10MIPS-16MIPS
Memoria
programma
Fino a 3KB
Fino a 14KB
Fino a 56KB
Fino a 128KB
Memoria Dati
Fino a 138B
Fino a 368B
Fino a 4K
Fino a 4K
no
Si (singola)
Si (singola)
Si (multipla)
PIC18
Interruzioni
PIC
Architettura a 16 bit
La famiglia a 16 bit si suddivide in microcontrollori a 16 bit e DSP (Digital Signal
Processor) noti come dsPIC. Di questa famiglia fanno parte i PIC24 e i dsPIC30,
dsPIC33. I dsPIC si differenziano dalla serie PIC24 solamente per la presenza, nella
serie dsPIC, del core DSP. L'aggiunta del core DSP fa dei dsPIC la scelta ideale in
applicazioni audio, controllo motore e grafiche (con risoluzione QVGA). Nella
famiglia 16 bit possibile trovare periferiche tipo ADC 10/12 bit, DAC 10/16 bit,
USB, DMA (Direct Memory Access9). Il numero di periferiche e risoluzione dei moduli
analogici si va sempre estendendo, visto che tra le applicazioni embedded dei dsPIC
e microcontrollori a 16 bit ci sono gli strumenti di misura residenziali, quali il
contatore della corrente, gas, acqua, per i quali sono richiesti ADC con risoluzioni
La presenza del DMA permette di gestire un grosso flusso dati da diverse periferiche interne e la memoria dati, senza
l'intervento della CPU. Per esempio se si volesse fare una FFT con risoluzione a 64 campioni, sarebbe possibile mandare i
campioni dall'ADC direttamente alla memoria dati, e far s che la CPU inizi ad accedere ai campioni, solo quando sono
presenti tutti e 64.
18
Come prima cosa, doveroso almeno citare le logiche programmabili per eccellenza, le
CPLD e FPGA10. CPLD l'acronimo inglese Complex Programmable Logic Device mentre
FPGA l'acronimo inglese di Field Programmable Gate Array. Tale dispositivi sono
composti rispettivamente da migliaia e centinaia di migliaia di porte elementari o gate. La
differenza principale tra le due famiglie di dispositivi proprio il numero di porte e le
tipologie di moduli integrati, ovvero la complessit interna, quindi il costo!
In particolare le logiche CPLD coprono applicazioni di fascia bassa 11 mentre le FPGA
10
11
I loro predecessori, ancora utilizzati ma pi per esigenze di vecchi progetti che per nuovi, sono i PLD e PAL. Il CPLD
praticamente uguale ma supporta un numero maggiore di gate e moduli interni. Tra i pi grandi produttori di FPGA e
CPLD si ricorda Xilinx e Altera.
Visti i costi ridotti delle logiche CPLD, queste risultano, da un punto di vista economico, anche una valida alternativa ai
microcontrollori, ma come sempre il tutto dipende dal progetto, quindi dalle sue specifiche.
19
quelle di fascia medio alta. La programmazione consiste nel bruciare o meno alcuni
fusibili12 per permettere o meno la connessione di determinate porte. In questo modo
possibile creare anche piccole CPU13 o comunque una qualunque funzione logica.
Frequentemente le FPGA e CPLD gi incorporano moduli elementari quali CPU o
ALU14. Le FPGA di ultima generazione hanno incorporati anche dei core ARM Cortex
M, che come vedremo a breve, rappresenta il core standard per molti microcontrollori a
32 bit. Questi dispositivi programmabili vengono utilizzati al posto dei microcontrollori
quando richiesto un elevato data throughput, ovvero una elevata quantit di dati deve
passare al loro interno. Per esempio per comunicazioni ad alta velocit (PCI express),
gestione bus o macchine a stati. In particolare le FPGA pi potenti possono essere
utilizzate in applicazioni dove le CPU pi veloci risultano troppo lente! Non un caso che
applicazioni militari tipo i missili abbondano di FPGA!
Altre applicazioni tipiche dove le FPGA trovano un largo utilizzo, sono le strumentazioni
elettroniche, come per esempio gli oscilloscopi, gli analizzatori di spettro e analizzatori di
rete. Infatti poter gestire ADC (Analog to Digital Converter15) con frequenze di
campionamento superiori al GS/s si ha necessariamente bisogno di FPGA. Le
elaborazioni delle immagini relative ad una misura, sono frequentemente delegate ad un
microcontrollore o DSP a 32bit che spesso affiancano le FPGA.
DSP
I DSP, acronimo inglese di Digital Signal Processing, o Processor, sono praticamente dei
microcontrollori ottimizzati per applicazioni di elaborazione dei segnali, ovvero in cui un
segnale analogico convertito in digitale, richiede di essere elaborato al fine di estrarre
informazioni utili. Normalmente quello che li differenzia dai microcontrollori standard
non tanto la frequenza di clock, che comunque pi o meno elevata 16, bens la presenza
di pi accumulatori (registri speciali per fare operazioni), operatori di somma e
spostamento, ottimizzati per implementare filtri digitali FIR17 e IIR18 e bus dedicati per
accedere a pi aree di memoria in contemporanea 19. I DSP sono normalmente utilizzati
per applicazioni audio e video. Molti cellulari e ripetitori per cellulari, oltre ad avere
FPGA hanno frequentemente anche dei DSP. Per tale ragione le FPGA pi potenti
stanno avendo la tendenza d'implementare core (moduli) DSP direttamente all'interno
dell'FPGA come anche microcontrollori della famiglia Cortex M.
ARM
13
14
15
16
17
18
19
I fusibili dei primi modelli di FPGA e CPLD permettevano una sola scrittura. Le tecnologie attuali permettono invece di
riscrivere i dispositivi come se fossero delle memorie Flash.
CPU l'acronimo inglese di Central Processing Unit.
ALU l'acronimo inglese per Arithmetic Logic Unit.
ADC l'acronimo di Analog to Digital Converter, ovvero convertitore Analogico Digitale.
I DSP pi potenti attualmente sul mercato hanno un dual e quad core e frequenza di clock di circa 2GHz.
FIR l'acronimo inglese per Finite Impulse Response.
IIR l'acronimo inglese per Infinite Impulse Response.
Poter accedere a pi aree di memoria in contemporanea permette in generale di poter eseguire istruzioni che utilizzano pi
operandi, in un tempo ridotto, spesso pari ad un solo ciclo di clock.
20
Altri microcontrollori
Non gentile chiamare un sotto paragrafo altri microcontrollori ma sono talmente
tanti e ognuno meriterebbe un testo a parte, per cui la lista che segue deve intendersi pi
come un breve riassunto delle offerte del mercato piuttosto che una successione di
alternative ordinate per importanza. Per tale ragione questo raggruppamento non li deve
affatto mettere in ombra visto che i microcontrollori dei produttori descritti sono molto
utilizzati e Microchip stessa in continua competizione con gli stessi.
20
ATMEL
Senza dubbio il competitore pi grande con il quale Microchip si trova a dividere
il mercato dei microcontrollori ad 8 bit. Per tale ragione nella crisi economica
globale del 2009, Microchip ha cercato di acquisire Atmel. La famiglia dei
microcontrollori ad 8 bit prodotta da Atmel nota come AVR. Atmel possiede tra
le sue linee di prodotti, microcontrollori a 32 bit, AVR32 basasti su una struttura
RISC proprietaria, ma allo stesso tempo ha prodotto anche microcontrollori a 32 bit
basati su core ARM (AT91SAM, brevemente noti anche come SAM).
Uno dei punti di forza dei microcontrollori a 32 bit basati su core ARM legato al
fatto che sono supportati dalla comunit di Linux. In particolare i microcontrollori
ARM sono forse secondi solo all'architettura 80x86. Atmel produce
microcontrollori anche con core basato sulla CPU 8051.
ST
La ST Microelectronics un altro grosso produttore di microcontrollori ad 8 bit,
famose sono la famiglia ST6 e ST7 . Oltre ai microcontrollori ad 8 bit la ST
produce microcontrollori a 16 e 32 bit. Quelli a 16 bit sono la famiglia ST10 mentre
quelli a 32 bit sono basati su core ARM. Probabilmente ST tra i produttori pi noti
E' presente anche il core M1, pensato per poter essere integrato in FPGA ma non noto come gli altri core.
21
TI
La Texas Instruments uno dei colossi del mondo dell'elettronica, praticamente
produce tutti i tipi di componenti elettronici, eccetto quelli discreti. Dal punto di
vista dei microcontrollori produce microcontrollori a 16 e 32 bit e processori
multicore a 32 bit. La famiglia a 16 bit nominata MSP430 ed nota per
applicazioni Ultra Low Power21, mentre per la famiglia a 32 bit c' il DSP C2000
basato su architettura proprietaria. Con architettura a 32 bit la TI offre anche
microcontrollori basati sul core ARM Cortex M3 e Cortex M4F22. La famiglia
Cortex M nota come TIVA (precedentemente era chiamata Stellaris23). Si noti
come TI allo stato attuale non abbia Cortex M0 o Cortex M0+. Il core Cortex M3
possibile trovarlo anche in SoC (Sytem On Chip) RF come per esempio il CC2538 per
applicazioni Zigbee.
Oltre ai microcontrollori, TI produce diverse famiglie di DSP tra cui C6000, C5000
e high hand MPU (MicroProcessor Unit) come per esempio Sitara.
Analog Devices
L'Analog Devices non un vero competitore della Microchip, visto che si
differenziata producendo un ristretto numero di microcontrollori ottimizzati per
applicazioni analogiche (DSP). In particolare possiede microcontrollori di fascia alta
basati come per TI sul core ARM. L'Analog Devices ha microcontrollori sia a 16
che 32 bit. Oltre ai microcontrollori di fascia alta, possiede qualche microcontrollore
basato sul core 8052. Particolarmente famosa la famiglia di DSP che va sotto il
nome di Shark (squalo). Simpatico il fatto che TI ha un DSP nella famiglia C2000
che va sotto il nome di Delfino!
Maxim Integrated
Maxim Integrate, (Maxim IC) utilizza un approccio simile a quello dell'Analog
Devices, ovvero produce microcontrollori a 16 bit per applicazioni specifiche. La
scelta dei microcontrollori a disposizione non paragonabile con quella della
Microchip. Il core principale dei microcontrollori della Maxim il core 8051.
Zilog
La Zilog particolarmente famosa per aver introdotto il primo microprocessore
di successo, lo Z80. Attualmente lo studio dello Z80 previsto nei piani ministeriali
degli istituti tecnici industriali. Nonostante il successo dello Z80, lo stato attuale non
come in passato, ma senza dubbio Zilog produce microcontrollori di qualit. In
particolare produce microcontrollori ad 8,16 e 32 bit. Recentemente la Zilog
divenuta parte della societ IXYS.
21
22
23
NXP
NXP nata dalla divisione componenti elettronici della Philips. Attualmente
Un corso sugli MSP430 disponibile sul sito www.LaurTec.it. Il corso introduce gli strumenti e l'architettura di tali
microcontrollori, mettendoli a confronto con le altre soluzioni presenti sul mercato.
La F dopo M4 sta ad indicare che il core possiede anche il modulo Floating Point Unit.
Il nome Stellaris discende dalla famiglia di microcontrollori della societ Luminary acquisita dalla TI nel 2009. Luminary
stata la prima societ a prendere in licenza il progetto Cortex M3 da ARM
22
Riassunto
Molte applicazioni commerciali fanno ormai uso di un microcontrollore pi o meno
potente. Oggigiorno anche un semplice giocattolo potrebbe essere considerato un sistema
embedded qualora sia capace di parlare o riconoscere il proprio padrone. In questa
moltitudine di applicazioni sono presenti molti produttori di circuiti integrati che cercano
di fornire la soluzione ideale per il progettista. Il mercato offre microcontrollori da 8, 16 e
32 bit. In particolare tra le famiglie ad 8 e 16 bit facile trovare architetture proprietarie,
mentre tra quelle a 32 bit non insolito avere a che fare con CPU ARM Cortex M. Nel
mondo ad 8 bit, Microchip tra i produttori pi famosi, ma per le famiglie a 32 bit il
mercato favorisce altri produttori come ST e TI.
Le numerose alternative offerte nel mercato permettono al progettista di valutare e
cercare la soluzione ideale per il proprio progetto, ma lo pone anche spesso nel dilemma
di quale microcontrollore utilizzare.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
24
Capitolo II
II
Sebbene Renesas non sia stata scelta per il testo, questo non vuol dire che non sia un prodotto di qualit. In effetti Renesas
ha molto successo in progetti dove sicurezza e qualit sono importanti, come per esempio in ambito Automobilistico.
25
XC8 Step by Step basato su strumenti Microchip ufficiali. Avere il servizio Microchip a
portata di mano non un beneficio da sottovalutare, specialmente se consideriamo il fatto
che una delle politiche Microchip quella di vendere i propri strumenti di sviluppo a
basso costo, sviluppando strumenti per diverse esigenze, dall'hobbysta al professionista.
La Microchip sviluppa inoltre anche il compilatore per i propri microcontrollori. Per ogni
architettura a 8, 16 e 32 bit presente un compilatore nominato rispettivamente XC8,
XC16 e XC32. Questi compilatori sono disponibili gratuitamente, anche per applicazioni
commerciali25. In particolare il seguente testo, descrivendo i PIC18 fa riferimento al
compilatore XC8. Dal momento che il compilatore XC8 supporta anche le altre sub
famiglie ad 8 bit, sebbene si faccia riferimento ai PIC18, molti esempi possono essere
utilizzati anche su altri microcontrollori con architettura ad 8 bit, quali per esempio i
PIC16.
Ricapitolando, scegliendo gli strumenti Microchip o compatibili Microchip, come la
scheda di sviluppo Freedom II e Freedom Light, si ha la possibilit di avere sempre gli
strumenti supportati direttamente dal produttore. Scegliendo invece di realizzare i vostri
strumenti, sarete lasciati allo sbaraglio delle incertezze e delle difficolt che incontrerete.
Passerete giorni a cercare di capire il problema, per poi scoprire che l'hardware da voi
usato non era supportato dal software. La spesa che sosterrete comprando un
programmatore Microchip vi far risparmiare tempo e anche denaro!
Il compilatore C18 che ha preceduto la nuova famiglia di compilatori XC era gratuito solo per applicazioni accademiche. I
compilatori della famiglia XC sono invece gratuiti indipendentemente dall'utilizzo. Ciononostante sono presenti versioni a
pagamento che forniscono un livello di ottimizzazione del codice pi avanzata.
26
Il compilatore XC8
Una volta installato l'ambiente MPLAB X, possibile installare il compilatore per
PIC18 ovvero XC8. Tale compilatore racchiude i vantaggi dei due precedenti compilatori
Microchip, ovvero il C18 e C HITECH. In particolare per la sintassi ha prevalso il C18
mentre il compilatore vero e proprio rimasto C HITECH, visto che permetteva di
raggiungere ottimizzazioni del codice migliori del C18 27. Come per l'IDE anche i
compilatori della famiglia XC possono essere installati sia in ambiente Windows, Linux e
MAC.
Il nuovo ambiente si sviluppo MPLAB X permette di installare diverse versioni per il
compilatore. Ogni progetto ha in particolare associata la versione di un compilatore ma
pu essere cambiata in un qualunque momento per mezzo delle opzioni del progetto
stesso. La versione utilizzata nel testo fa riferimento alla 1.21.
Versioni diverse del compilatore o livelli di ottimizzazione differenti potrebbero mostrare
alcuni risultati differenti per quanto riguarda gli esempi in Assembly mostrati durante il
corso.
Installare il compilatore piuttosto semplice visto che ci si trova in ambiente Windows.
Il percorso standard d'installazione del compilatore C all'interno della directory:
[...]/Microchip/xc8
27
Utilizzare la riga di comando ed integrare il compilatore con un altro IDE pu essere certamente educativo, ma dal
momento che state per imparare molte altre cose inutile mettere troppa carne sul fuoco e rischiare di creare difficolt
inutili non inerenti allo scopo principale, ovvero imparare a programmare i PIC18.
Maggiori dettagli sulle differenze tra il C18 e il nuovo compilatore XC8 possono essere trovate nell'articolo Il Compilatore
XC8, come migrare dal C18 scaricabile gratuitamente dal sito www.LaurTec.it .
28
compilatore. Nel nostro caso avremo almeno la versione v1.21 ovvero la cartella v1.21.
All'interno della cartella relativa alla versione del compilatore sono presenti altre
sottocartelle in cui sono raggruppati tutti i file necessari per poter compilare propriamente
un progetto. bene mettere subito in evidenza alcune cartelle che verranno di seguito
utilizzate.
bin
All'interno di questa cartella sono presenti i sorgenti del compilatore e del linker,
utilizzati per creare il vostro codice da caricare all'interno del PIC.
dat
La Directory dat contiene l'importante directory lkr, ovvero la directory all'interno
della quale sono presenti i linker file. Per ogni PIC presente un file che deve essere
incluso nel progetto. Se per esempio stiamo utilizzando il PIC18F4550 bisogna
includere il file 18f4550_g.lkr. Al suo interno sono presenti informazioni utilizzate
dal linker, relative all'organizzazione della
memoria interna al PIC. Tale
organizzazione varia a seconda delle opzioni utilizzate per la compilazione stessa. Il
fatto di attivare o meno le funzioni di Debug, per esempio, cambia l'organizzazione
della memoria. Consiglio di aprire il file d'interesse almeno una volta, il file scritto
in testo normale ed facilmente comprensibile. Come si vedr a breve, qualora si
faccia uso del file linker standard, l'ambiente di sviluppo MPLAB X include
direttamente il file di linker necessario, durante la fase di creazione del progetto
stesso. Infatti durante la creazione di un progetto si specifica il PIC utilizzato, per
cui viene automaticamente incluso il file di linker richiesto. File di linker diversi
possono essere inclusi in progetti particolari, come per esempio qualora si voglia
utilizzare un bootloader USB. Per ora bene non dire altro per evitare di
confondere le idee, ma l'argomento verr ripreso a tempo debito.
docs
All'interno di tale directory presente tutta la documentazione base utile per
l'utilizzo del compilatore. In particolare presente la guida d'utilizzo al compilatore
(manual.pdf) e la guida relativa alle librerie disponibili per i vari moduli interni al
microcontrollore.
All'interno della directory docs presente la sottocartella chips che contiene i file
di configurazione html per ogni chip, ovvero varianti di microcontrollori della
famiglia PIC18. Ogni PIC possiede infatti particolari configurazioni, per le quali
necessario sapere l'opportuno settaggio. Tale file risulta particolarmente comodo
all'inizio della programmazione e verr descritto in dettaglio nei prossimi Capitoli.
include
All'interno di questa directory sono presenti gli header file associati ad ogni
PIC18. Per esempio per il PIC18F4550 presente il file p18f4550.h. Anche in
questo caso, come per il file di linker il file viene incluso nel progetto
automaticamente.28 All'interno di questo file sono presenti o meglio definiti i nomi
dei pin del PIC selezionato e i registri interni. Come visibile dal Datasheet ogni PIC
ha molti registri con nomi differenti, tali nomi vengono definiti all'interno
dell'header file del PIC in modo da poter usare gli stessi nomi utilizzati nel
28
Facendo uso del compilatore C18 era necessario include tale file manualmente.
29
lib
All'interno di tale directory sono presenti i codici binari, ovvero gli eseguibili
associati ad ogni libreria, in particolare per ogni PIC viene fornito un file di libreria
che contiene compilate tutte le librerie fornite dalla microchip. Se si apre per
esempio il file
pic18-plib-c18-18c242.lpp
varie funzioni relative alle varie librerie. Nell'esempio del testo riportato sotto
possibile vedere funzioni di libreria per il modulo SPI, I2C e UART.
Pplib/SPI/spi2read.o/Pplib/SPI/spi2writ.o
CPplib/SPI/spi_clos.o_CloseSPI
Pplib/SPI/spi_dtrd.o_DataRdySPI
BPplib/SPI/spi_gets.o_getsSPI
Pplib/SPI/spi_open.o_OpenSPI
TPplib/SPI/spi_puts.o_putsSPI
pPplib/SPI/spi_read.o_ReadSPI
Pplib/SPI/spi_writ.o_WriteSPI
Pplib/SW_I2C/swacki2c.o_SWAckI2C
Pplib/SW_I2C/swckti2c.o_Clock_test'dPplib/SW_I2C/swgtci2c.o_I2
C_BUFFER_BIT_COUNTER_SWReadI2C
Pplib/SW_I2C/swgtsi2c.o_SWGetsI2C
Pplib/SW_I2C/swptci2c.o_SWWriteI2CnPplib/SW_I2C/swptsi2c.o_SWPu
tsI2CPplib/SW_I2C/swrsti2c.o_SWRestartI2C
Pplib/SW_I2C/swstpi2c.o_SWStopI2C
Pplib/SW_I2C/swstri2c.o_SWStartI2C
Pplib/SW_RTCC/close_rtcc.o_Close_RTCC
Pplib/SW_RTCC/open_rtcc.o_Open_RTCC:Pplib/SW_RTCC/update_rtcc.o
_update_RTCCPplib/SW_SPI/clrcsspi.o_ClearCSSWSPIKPplib/SW_SPI/
opensspi.o_OpenSWSPI
Pplib/SW_SPI/setcsspi.o_SetCSSWSPI
7
Pplib/SW_SPI/wrtsspi.o_WriteSWSPI
Pplib/SW_UART/openuart.o_OpenUART-
Da questa piccola nota si capisce che qualora si voglia scrivere una propria
libreria, se la si volesse utilizzare per ogni PIC18 sarebbe necessario compilarla per
ogni modello. Come verr messo in evidenza nei capitoli successivi, al fine di evitare
di dover creare file di libreria per ogni PIC baster scrivere un file .h e un file .c ed
includere entrambi nel proprio progetto. Ogni volta che si compiler il progetto i
file di libreria verranno automaticamente compilati per il modello di
microcontrollore utilizzato. Anche questo solo un anticipo di quello che verr
spiegato nel Capitolo dedicato ai file di libreria.
Utilizzare il C o l'Assembly?
Giunti alla fine dell'istallazione del compilatore C giusto spendere due parole
sull'alternativa al compilatore C ovvero la programmazione in Assembly o Assembler29.
Il linguaggio Assembly diversamente dal C in relazione 1:1 con il linguaggio macchina,
ovvero il linguaggio del microcontrollore. Le istruzioni scritte in C non sono tradotte 1:1
con il linguaggio macchina, ci permette un livello di astrazione tale da non dover
ricordare il formato di una istruzione con relativi operandi.
Per mezzo dell'Assembly possibile scrivere il codice ottimizzato al meglio, buon cervello
permettendo. Allora perch non utilizzare direttamente l'Assembly?
Una delle ragioni principali per cui bene non scrivere direttamente in Assembly legato
al fatto che la leggibilit del codice notevolmente inferiore, rendendo dunque la
29
In questo paragone tra linguaggi, non si tiene conto del fatto che sono presenti anche altri linguaggi ad alto livello come il
Basic e il Pascal. Il confronto tra C e Assembly pu essere esteso anche tra gli altri linguaggi e l'Assembly.
31
Un altro modo per migliorare la leggibilit del software quello di usare buoni commenti e nomi di variabili appropriate.
32
Il programmatore
Ogni volta che si scrive un programma, sia esso in C o in Assembly, per dar vita al
programma necessario poterlo installare o caricare all'interno del microcontrollore. Il
programma, come si vedr nei prossimi Capitoli, per poter essere installato deve essere
prima compilato. La fase di compilazione genera un file in linguaggio macchina, ovvero la
traduzione del nostro programma C nel linguaggio del microcontrollore. Sar proprio
questo file in linguaggio macchina che verr caricato all'interno del microcontrollore. Per
poter fare questo necessario un programmatore al quale poter attaccare il nostro
microcontrollore o scheda di sviluppo. In alcuni casi, se nel PIC stato precedentemente
caricato un bootloader, possibile caricare il programma per mezzo di una normale
connessione RS232 o USB, ovvero senza programmatore, ciononostante per caricare un
bootloader necessario di un programmatore.
In commercio possibile trovare un'ampia gamma di programmatori per PIC ma molti
hanno lo svantaggio di non essere direttamente integrabili con l'ambiente IDE della
Microchip e non supportano, se non con lunghi ritardi, gli ultimi modelli dei PIC. Per tale
ragione anche in questo caso consiglio un programmatore della Microchip.
Internet fornisce anche molte soluzioni fai da te, ma per esperienza nel supportare
utenti del Forum LaurTec, possono dire che sebbene gli schemi in rete non siano
complicati, non sempre funzionano. Quando si inizia a studiare qualcosa di nuovo avere
troppe variabili in cui cercare il problema pu essere piuttosto frustrante e potrebbe
indurre a lasciare lo studio. Per tale ragione vi consiglio di procedere senza indugi
nell'acquisto di un programmatore Microchip, il cui prezzo per i modelli base si aggirano
intorno a 30-50Euro.
Nel seguito del testo far uso del programmatore PIC KIT3 della Microchip. Per
completezza bene comunque riportare qualche dettaglio sugli altri programmatori.
PICKIT 2
il cavallo di battaglia tra i programmatori ma possiede tutte le funzioni richieste
da un hobbysta o studente alle prime esperienze. integrabile nell'ambiente
MPLAB X e programma i PIC10F, PIC12F5xx, PIC16F5xx, midrange PIC12F6xx,
PIC16F, PIC18F, PIC24, dsPIC30, dsPIC33, e PIC32. Il programmatore pu
inoltre essere utilizzato come data logger per mezzo del programma Programmer
Logic Analyzer scaricabile direttamente dalla home page del PICKIT 2. Il suo costo
veramente vantaggioso, viene venduto singolarmente o con piccola Evaluation
Board con un PIC della famiglia PIC16, sul mercato sono anche presenti dei cloni
visto che lo schema elettrico del PICKIT 2 fornito dalla stessa Microchip.
33
Personalmente non li ho mai provati, ma visto che sono basati sullo schema
originale del PICKIT 2, non hanno molti problemi. Visto per che il costo dei cloni
paragonabile a quello originale consigliabile comprare direttamente quello
originale. Unico neo del PIC KIT 2 che supportato in versione beta dal nuovo
ambiente MPLAB X e probabilmente rimarr tale. In particolare la Microchip non
sta rilasciando pi aggiornamenti per i nuovi microcontrollori. Ciononostante i
microcontrollori supportati sono praticamente tutti quelli pi usati nei progetti
comuni. In applicazioni hobbystiche pu ancora convenire acquistare un PICKIT 2
e risparmiare 10-15 euro piuttosto che comprare il PICKIT 3. Se si studenti di
ingegneria e si pensa di voler progredire e cavalcare l'onda delle novit bene
spendere qualche euro in pi anche se per 1-2 anni non avrete probabilmente
benefici.
PICKIT 3
Il PICKIT 3 praticamente una evoluzione del PICKIT 2 anche se in alcuni
aspetti le cose non sono realmente migliorate, ma piuttosto peggiorate o rese pi
noiose. Il PICKIT 3 infatti pi lento a programmare che non il PICKIT 2 e
richiede un aggiornamento del firmware interno per ogni sub famiglia di PIC
programmato. Il suo costo circa 10-15 euro superiore al PICKIT 2, per cui
potrebbe convenire il suo acquisto se paragonato al PICKIT 2. Personalmente sto
usando il PICKIT 3 perch 3 pi grande di 2 e non perch abbia realmente avuto
esigenza. Il testo XC8 Step by Step si pu seguire interamente con il PICKIT 2 come
anche tutti gli esempi attualmente disponibili sul sito www.LaurTec.it.
ICD 2
il primo programmatore e real-time Debugger per una fascia di utilizzatori
professionisti. Le capacit di Debug sono migliorate rispetto ai programmatori della
serie PICKIT. Come quest'ultimi supporta tutta la serie di microcontrollori
sviluppati dalla Microchip. Tale programmatore risulta obsoleto e non pi
supportato da MPLAB X. Per poterlo utilizzare necessario utilizzare MPLAB
IDE, ovvero il vecchio IDE Microchip.
ICD 3
Nuovamente un fratello maggiore! ICD 3 stato introdotto per sostituire l'ICD 2.
Tanto vero che la Microchip alla sua introduzione ha iniziato un programma di
riciclo per ritirare gli ICD2 fornendo un coupon con uno sconto del 25%
sull'acquisto dell'ICD 3 e il rottamaggio dell'ICD 2. Le caratteristiche pi
importanti dell'ICD 3 sono un ulteriore miglioramento delle capacit di Debug e un
aumento della velocit di programmazione. Diversamente dagli altri programmatori,
l'ICD 3 possiede al suo interno una FPGA al fine di supportare le alte velocit di
programmazione. Per scoraggiare ulteriormente l'acquisto dell'ICD2, l'ICD 3 costa
meno dell'ICD 2! Unico neo dei programmatori/debugger della serie ICD che il
loro costo circa 5 volte superiore al PICKIT 2 e PICKIT 3.
Caratteristica di tutti i programmatori ora descritti, che sono tra loro compatibili da
un punto di vista meccanico, ovvero il connettore dei vari programmatori la stessa.
Questo permette di sviluppare una scheda e poterla programmare con uno qualunque dei
programmatori ora descritti.
34
Alcuni PIC possono avere dei pin di programmazione diversi, come per esempio il
PIC18F14K50, ma gran parte fanno uso dei pin RB6 RB7. Durante la fase di
programmazione, sul pin MCLR viene fornita la tensione di programmazione di circa
14V31, mentre le linee RB6 e RB7 rappresentano il canale di comunicazione seriale per la
scrittura e lettura dei dati all'interno del microcontrollore. Ulteriori dettagli sulla
connessione di questi pin fornita a fine Capitolo.
Nel caso si dovesse abilitare la programmazione a bassa tensione, non viene fornita la tensione di 14V, ma viene usata Vcc.
La Microchip per i programmatori utilizza due tipi di connettori, per i quali fornisce per l'adattatore stesso.
35
Oltre alla scheda Freedom II possibile utilizzare la scheda Freedom Light, in cui
alcuni componenti sono stati rimossi per ottimizzare i costi, come riportato in Figura 2.
Ogni Capitolo in cui siano esposti degli esempi inizia con una breve spiegazione delle
impostazioni necessarie sia per la scheda Freedom II che per la scheda Freedom Light,
mostrando anche il relativo schema base utilizzato negli esempi. Qualora Freedom Light
non possieda l'hardware necessario per lo svolgimento dell'esercitazione sar necessario
collegare i componenti necessari sulla scheda di espansione PJ7011 33 riportata in Figura 3
o una Breadboard.
La scheda di espansione PJ7011 compatibile con il connettore di espansione EX1 di Freedom II e Freedom Light per cui
pu essere utilizzata indifferentemente su entrambe le schede.
36
Come detto la scheda Freedom II e Freedom Light sono compatibili con la serie
PIC16 e PIC18 a 40 pin. Nel seguente corso, si utilizzer il PIC18F4550. Altri PIC
potranno essere utilizzati, ma bisogner accertarsi che i vari codici presentati siano
opportunamente modificati.
A solo scopo riassuntivo si riportano le specifiche delle due schede usate nel corso:
37
Freedom II
Supporto USB 2.0 Low Speed e Full Speed
Supporto RS232
Supporto CAN
EEPROM I2C 24LC32
Clock Calendar I2C PCF8563
Cicalino
8 LED
4 pulsanti
Sensore temperatura LM35
Sensore Luminosit
Trimmer per ingresso analogico
LCD 16x2 con retroilluminazione
Controllo software della retroilluminazione
Trimmer per contrasto LCD
Connettore espansione 40 pin EX1
Programmazione per mezzo dell'USB Bootloader
Programmazione on board e Debug compatibile con gli strumenti Microchip
Freedom Light
Supporto USB 2.0 Low Speed e Full Speed
2 pulsanti + Reset
2 Led di utilizzo generico
LCD alfanumerico 16x2
Trimmer per contrasto LCD o ingresso analogico
Buzzer
Connettore espansione 40 pin EX1
Programmazione per mezzo dell'USB Bootloader
Programmazione on-board e Debug compatibile con gli strumenti Microchip
programma ad una frequenza scandita da un clock. Un clock non fa altro che scandire il
tempo con il quale il microcontrollore deve eseguire le operazioni. Molti microcontrollori,
tra cui il PIC18F4550 possiedono al loro interno un oscillatore che permette di generare il
clock necessario per il nostro microcontrollore. Ciononostante per ragioni pratiche che si
capiranno di seguito e compatibilit con le schede di sviluppo presentate, bene avere un
cristallo esterno collegato tra OSC1 e OSC2, il quale garantisce di ottenere una frequenza
pi accurata utile in applicazioni quali USB. Il cristallo utilizzato, al fine di non dover
cambiare le impostazioni presentate negli esempi del testo deve essere di 20MHz. Cristalli
di questa frequenza richiedono normalmente delle capacit di carico pari a 22pF, ovvero
C1 e C2. Le capacit di carico devono essere di tipo ceramico, ed in particolare devono
avere dei collegamenti corti, come anche il quarzo, in maniera da evitare problemi con
l'oscillazione del quarzo stesso. Se il quarzo non dovesse oscillare, il microcontrollore non
potrebbe eseguire correttamente il programma, ed in particolare rimarrebbe fermo 34.
Come ogni circuito integrato bene avere delle capacit di disaccoppiamento al fine di
garantire che il sistema non subisca dei Reset (riavvi di sistema) inaspettati. La capacit di
disaccoppiamento permettono di fornire rapidamente corrente al sistema ogni qual volta
sia necessario e ci sia una variazione brusca. Questo, considerando che un
microcontrollore possiede un clock per scandire le operazioni, pu avvenire alla stessa
frequenza del cristallo, per cui piuttosto frequentemente ma picchi molto rapidi.
Per permettere il corretto avvio del microcontrollore, necessario che venga eseguito
un primo Reset di sistema, questo avviene automaticamente grazie ad una capacit interna
al microcontrollore, ma al fine di garantire il corretto funzionamento della circuiteria di
Reset necessario avere anche il resistore R1 collegato alla linea MCLR. Per permettere
dei Reset manuali bene anche prevedere un pulsante di Reset BT1.
Naturalmente, come ogni sistema elettronico necessario che venga fornita energia al
sistema stesso. Per i PIC e gli esempi presentati si alimenta il tutto con 5V. bene evitare
di utilizzare alimentatori a 5V di tipo switching35 visto che possono avere una regolazione
piuttosto limitata dell'uscita e causare dei Reset del microcontrollore. meglio prevedere
un semplice alimentatore con un regolatore LM7805 il cui ingresso pu essere collegato
sia ad una batteria da 9V che ad un alimentatore da 9V-12V, questa volta anche switching,
visto che che il regolatore lineare LM7805 garantisce un'ottima regolazione della tensione
di uscita. Oltre a questo Hardware base bene prevedere almeno un LED di uscita, che
nel nostro caso collegheremo a RD0 e un pulsante in ingresso che collegheremo a RB4.
In ultimo, al fine di poter programmare il PIC necessario prevedere i collegamenti per il
programmatore, ovvero prevedere i seguenti collegamenti:
RB6: Funzione PGD
RB7: Funzione PGC
MCLR: Tensione di programmazione
Le linee RB6 e RB7, dal momento che sono utilizzate per la programmazione non
devono avere altri carichi come LED o collegamenti verso GND, Vcc con resistori di
34
35
Ci sono in realt delle scappatoie nel caso in cui l'oscillatore primario non dovesse oscillare. Le situazioni di clock fault sono
gestite in automatico a livello hardware. Questa funzione disponibile in molti microcontrollori e le impostazioni possono
dipendere dal modello usato.
Un tipico alimentatore switching che fornisce 5V in uscita e si tentati di usare, rappresenta il convertitore DC-DC dei
Personal Computer. Normalmente se si usano i 5V in uscita da questi sistemi si possono avere comportamenti anomali del
microcontrollore a causa di continui Reset di sistema. I 5V devono essere propriamente filtrati prima di poter essere forniti
ad un microcontrollore.
39
pull-down o pull-up, al fine di evitare alterazioni dei segnali di programmazione. Sui pin si
devono evitare anche carichi capacitivi. Normalmente, al fine di evitare errori di
programmazione bene che i cavi di collegamento tra RB6-RB7 con il programmatore
non eccedano 10cm. Lo schema elettrico riassuntivo di quanto appena detto a parole
riportato in Figura 4.
Si noti che sebbene lo schema sia quello base sono gi presenti un numero non esiguo
di collegamenti. Volendo pi LED e pulsanti il numero di fili necessari raggiungerebbe
facilmente un numero proibitivo in cui sarebbe richiesto un notevole ordine per garantire
il corretto funzionamento. Al fine di facilitare il montaggio su Breadboard bene utilizzare
contenitori Dual in Line (DIL). Oltre ai componenti base descritti si aggiunto il diodo di
protezione D1 che permette di proteggere il sistema in caso di inversione di polarit
dell'alimentazione. Allo stesso modo R1 stato aggiunto al fine di proteggere il
programmatore qualora si dovesse inavvertitamente premere il tasto di Reset durante la
fase di programmazione.
svantaggio stesso qualora il sistema dovesse diventare complesso. Una Tipica Breadboard
riportata in Figura 5. La Figura mostra un modello pi completo rispetto a quello che
normalmente si trova a pochi euro. In particolare contiene due Breadboard affiancate una
sopra l'altra (le linee rosse al centro appartengono alle due Breadboard) e possiede i
connettori banana per facilitare il collegamento ad un alimentazione esterna.
Altro problema tipico che si viene a verificare quello di avere le alette di fissaggio sotto i
fori che si allentano, creando falsi contatti. Questo si viene a creare soprattutto per quei
fori nei quali si sono collegati componenti i cui pin sono molto pi grandi del diametro
dei cavi usati. Tale problema si presenta con il tempo e usura della scheda. Per limitare il
verificarsi di questi problemi anche per i circuiti integrati, bene montare i componenti
pi grossi sui lati superiori o inferiori della scheda.
Riassunto
Poter programmare un microcontrollore, sia esso Microchip o di altro fornitore
richiede un numero di strumenti sia Hardware che Software. Il Capitolo ha messo in
evidenza come si debbano privilegiare strumenti ufficiali rispetto a quelli pi economici
ma non ufficiali. Dopo una breve trattazione sull'installazione dell'ambiente di sviluppo
MPLAB X e il compilatore XC8, usati nel corso, si mostrato anche l'hardware base
necessario per seguire il corso, in particolare introducendo le schede di sviluppo Freedom
II e Freedom Light, oltre a mostrare l'hardware base necessario per permettere al sistema
di funzionare anche se montato su una semplice Breadboard.
42
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
43
Capitolo III
III
Questo significa che non sono presenti conflitti legati al contemporaneo utilizzo della
memoria, per la lettura di dati o del programma.
A questa struttura si contrappone l'architettura di von Neumann, la quale possiede un
44
Tale struttura, quella utilizzata nei computer classici, infatti i microprocessori hanno
in genere un unico bus con cui possono accedere la memoria RAM, nella quale sono
presenti sia i programmi che i dati. I microprocessori di ultima generazione non hanno
nemmeno accesso diretto alla RAM, la quale viene gestita per mezzo dell'unit DMA
(Direct Memory Access). Per mezzo di cache di vario livello e tecniche di predizione dei salti,
si cerca di avere sempre in cache quello di cui si ha bisogno. La cache una delle tecniche
per mezzo delle quali si ha un accesso veloce ai dati e al programma.
Sebbene l'architettura di von Neumann non permetta accessi contemporanei alla
memoria dati e programma, possiede il vantaggio della flessibilit della memoria stessa.
Infatti i registri delle configurazioni dei moduli interni non sono altro che celle di
memoria RAM per cui facile accedere a qualunque registro per mezzo del DMA
indipendentemente dal fatto che sia un accesso alla memoria dati o programma. Questa
architettura permette inoltre di eseguire il programma indipendentemente dalla memoria,
sia essa memoria programma flash o RAM. Questo favorisce quelle applicazioni, che per
ridurre i consumi dovuti a algoritmi di calcolo complessi, sfruttano i minori consumi
raggiungibili dalla memoria RAM, dell'ordine di 60A/MHz.
L'architettura di von Neumann risulta inoltre pi semplice da implementare, favorendo
ulteriormente sistemi che devono operare con bassi consumi. Quante sono tra le ragioni
principali per cui microcontrollori come gli MSP430 della Texas Instruments fanno uso
dell'architettura di von Neumann piuttosto che Harvard.
Oltre all'architettura Harvard, si detto che la CPU dei PIC18 di tipo RISC. Il nome
RISC discende dal fatto che il numero di istruzioni eseguibili dal PIC sono limitate se
paragonate a quelle messe a disposizione da un microprocessore classico con CPU di tipo
CISC (Complex Instruction Set Computer). Infatti i PIC18 possiedono 75 istruzioni base 36
mentre microprocessori tipo il Pentium ne possiedono diverse centinaia. Come per il
discorso effettuato per i sistemi embedded, parlare di RISC e CISC semplicemente su una
base numerica non ben definita non permette di definire in maniera assoluta quando si
36
I PIC18 possiedono la modalit estesa che aggiunge 8 ulteriori istruzioni a quelle base. Tale istruzioni sono state aggiunte
per poter ottimizzare la compilazione del codice C.
46
debba parlare di RISC o CISC. In futuro ci che oggi considerato CISC verr forse
considerato RISC. I PIC della famiglia Midrange, per esempio i PIC16, possiedono per
esempio solo 35 istruzioni per cui i PIC18 potrebbero essere considerati quasi
un'architettura CISC se confrontata con i PIC16.
Dopo questa breve nota introduttiva sull'architettura dei PIC18, vediamo qualche
dettaglio in pi, in particolare considerando la struttura interna del PIC18F4550, riportata
in Figura 8.
Dallo schema a blocchi, presente in ogni Datasheet, immediato vedere le periferiche
che possiede il PIC d'interesse, in particolare l'organizzazione dei registri della memoria e
delle periferiche. Sono inoltre messi in evidenza i vari bus che interconnettono le varie
periferiche e il loro parallelismo, ovvero il numero di linee disponibili. In particolare
possibile vedere che la memoria programma e dati sono separate. Una caratteristica
importante introdotta nei PIC18 la presenza del moltiplicatore 8x8, per mezzo del quale
possibile ridurre notevolmente il numero di istruzioni (quindi il tempo di esecuzione)
necessario per l'esecuzione di moltiplicazioni. Un'altra importante caratteristica presente
nei PIC18, non visibile a livello di schema a blocchi, l'introduzione dei livelli di priorit
delle interruzioni. Diversamente dalla famiglia Midrange, i PIC 18 hanno infatti un livello
di priorit delle interruzioni alto ed uno basso. Il livello d'interruzione alto compatibile
con quello presente anche nei PIC16.
I i quadrati con la croce interna rappresentano i pin fisici del PIC, ovvero
disponibili sul contenitore del componente.
Sulla destra di Figura 8 sono presenti diversi blocchi nominati PORTx, questi
rappresentano forse i moduli principali del PIC, per mezzo dei quali possibile creare dei
percorsi di input o di output dal PIC stesso, ovvero potersi interfacciare con il mondo
esterno. Le porte PORTx permettono infatti di leggere dati dall'esterno come per esempio
dei pulsanti, sensori, ma al tempo stesso permettono di attivare e fornire delle
informazioni per mezzo di luci, LED, display o altro.
47
48
Memoria Programma
La memoria programma quella memoria dove risiede in maniera permanente il
programma che il microcontrollore deve eseguire. In particolare i PIC18 possiedono una
memoria Flash38. Tale tipo di memoria ha la caratteristica di poter essere cancellata e
riscritta pi volte (circa 100.000). Un'altra caratteristica importante della memoria Flash
quella di poter mantenere il programma memorizzato anche quando il microcontrollore
non alimentato39.
Normalmente il programma viene caricato all'interno della memoria per mezzo del
programmatore, sfruttando i pin del microcontrollore dedicati alla programmazione. Oltre
a questa modalit di programmazione, i PIC18, come anche molti altri PIC di altre
famiglie, hanno la caratteristica di auto-programmazione per mezzo del software (Selfprogramming under software control). Questo significa che possibile scrivere un programma
sul PIC e permettere al programma stesso di utilizzare la memoria flash sia in lettura che
scrittura40. Tale caratteristica permette ad un programma di aggiornarsi senza aver bisogno
37
38
39
40
Ci sono in realt casi particolari, non nei PIC18, in cui la memoria esterna al microcontrollore stesso, e viene letta per
mezzo dell'interfaccia SPI.
importante ricordare che vi sono anche PIC di altre famiglie, che al posto della memoria flash, fanno uso di una memoria
OTP, ovvero scrivibile una sola volta.
La memoria flash, come anche quella EEPROM, sono specificate per mantenere memoria dei dati per oltre 40 anni;
naturalmente tale valore solo un valore statistico.
I primi microcontrollori non avevano la possibilit di riscrivere la memoria flash interna, la quale era disponibile in sola
lettura. Programmare la memoria richiede una tensione di circa 14V mentre l'alimentazione tipicamente di 5V o 3.3V. I
49
41
42
43
44
microcontrollori con possibilit di riprogrammare la flash hanno una charge pump (pompa di carica) che permette di elevare
la tensione di alimentazione a 14V permettendo la cancellazione e riscrittura della memoria flash.
La necessit di lettura, scrittura e cancellazione in blocchi una caratteristica delle memorie Flash, non legata alla struttura
del PIC18.
Prima di poter scrivere nuovi dati su di un blocco di memoria, necessario effettuare la cancellazione dei vecchi dati.
Attualmente non vi sono PIC18 che possiedono 2MByte di memoria.
Nel caso di istruzioni a 4 byte, il registro PC viene incrementato di 4.
50
Il Reset Vector presente in tutti i microcontrollori e Microprocessori di qualunque marca. L'indirizzo a cui questo viene
posto pu differire a seconda dei modelli.
51
nostra applicazione46.
Gli indirizzi 0008h e 0018h rappresentano gli indirizzi particolari ai quali viene posto il
registro PC quando viene generata un'interruzione, ovvero quando la CPU viene
interrotta dalla normale esecuzione del programma al fine di compiere altre azioni. In
particolare un'interruzione ad alta priorit pone il PC al valore 0008h mentre
un'interruzione a bassa priorit pone il PC al valore 0018h. Come per il caso del Reset
Vector, il numero di locazioni disponibili tra i due Interrupt Vector non sufficiente per
gestire eventuali periferiche che hanno generato un'interruzione, per tale ragione le
locazioni di memoria associate agli Interrupt Vector sono in generale utilizzate per fare un
salto alla funzione che gestisce le interruzioni, ovvero l'Interrupt Service Routine (ISR).
Maggiori dettagli sull'argomento sono forniti nel Capitolo dedicato alle interruzioni.
Oltre al registro PC, in Figura 9 possibile vedere che in alto sono presenti delle celle
di memoria particolari nominate Stack. In particolare sono presenti 31 celle di memoria
della larghezza di 21 bit (giusti, giusti per memorizzare il registro PC). Tali locazioni di
memoria rappresentano un buffer di indirizzi nel quale viene memorizzato il registro PC
ogni volta che bisogna fare un salto nel nostro programma, sia per eseguire una funzione
generica sia per eseguire una routine associata ad un'interruzione. Questo buffer
necessario poich quando si interrompe il normale flusso del programma necessario
memorizzare l'indirizzo al quale bisogna tornare (ovvero quale istruzione eseguire) al
termine dell'esecuzione delle istruzioni associate alla funzione che ha interrotto il
programma. Il valore del PC che viene salvato proprio quello dell'istruzione successiva
che deve essere eseguita. L'indirizzo contenuto nel PC salvato nel buffer in pila, ovvero
l'ultimo indirizzo salvato il primo al quale ritornare, e cosi via. Dal momento che sono
presenti 31 livelli di Stack, possibile utilizzare fino a 31 livelli annidati di funzioni 47. Allo
Stack associato un registro particolare nominato Stack Pointer (STKPTR), che ha il
compito di puntare il livello di ritorno, ovvero indirizzo, che deve essere caricato nel
registro PC.
In ultimo bene ricordare che sia il Program Counter che lo Stack Pointer possono essere
gestiti manualmente via software facendo uso di registri interni dedicati. Le modalit della
loro gestione e applicazioni esulano dagli scopi di questo testo. La loro gestione di
fondamentale importanza nel caso in cui si faccia uso di sistemi operativi RTOS (Real
Time Operative System) dedicati.
Quanto detto fin ora legato strettamente alla struttura della memoria, per far
funzionare il tutto necessario un clock per sincronizzare i vari blocchi e permettere il
giusto incremento o decremento dei registri. Il clock interno al PIC, come verr spiegato
nel paragrafo dedicato, pu essere generato per mezzo dell'oscillatore interno o esterno,
in particolare vi sono diverse opzioni che possibile selezionare. Usando per esempio un
quarzo esterno da 20MHz, la CPU non lavora direttamente a tale frequenza bens ad un
quarto della frequenza principale. Dal clock principale si ottengono infatti altri quattro
segnali di clock, tra loro sfasati, ricavati dividendo per 4 il clock principale; la Figura 10
riporta il clock principale e le sue fasi derivate dalla divisione per 4. In particolare 4 cicli
del clock principale rappresentano un ciclo istruzione, ovvero la CPU impiega 4 cicli del
clock principale per poter eseguire un'istruzione.
46
47
In realt prima della funzione main vengono richiamate altre funzioni di inizializzazione, che il programmatore normalmente
non utilizza ma potrebbe sovrascrivere o arricchire.
Nel caso si faccia uso della modalit DEBUG, 5 livelli saranno utilizzati dalla funzionalit di DEBUG, dunque devono
essere preventivamente considerati per evitare problemi.
52
Quanto detto visibile nella parte bassa della Figura 10, dove possibile vedere che
durante i 4 cicli di clock che rappresentano un ciclo istruzione, viene eseguita una fase di
caricamento istruzione e una decodifica. Bisogna per porre attenzione al fatto che lo
schema a blocchi indica che la fase di caricamento dell'istruzione (detta Fetch) relativa al
valore del registro PC; ovvero c' la preparazione per caricare l'istruzione presente nella
locazione PC e PC+1 la quale caricata nell'Istruction Register (IR) durante la fase di
Execute. La fase di esecuzione (nominata Execute) invece associata al valore del registro
PC-2, ovvero all'istruzione precedente, in particolare la fase di esecuzione preceduta
dalla fase di decodifica, ovvero di comprensione dell'istruzione stessa 48. Le due fasi di
Fetch ed Execute sono eseguite per mezzo di una pipeline ovvero in parallelo49, dunque
anche se si carica un'istruzione e si esegue l'istruzione precedente, a tutti gli effetti si ha
l'esecuzione di un'istruzione completa ogni 4 cicli del clock principale ovvero ad ogni
ciclo di clock istruzione. A questa regola fanno eccezione le istruzioni di salto, le quali
richiedono due cicli istruzioni.
Memoria Dati
Ogni programma durante la sua esecuzione ha in genere bisogno di variabili per
mantenere determinati dati o risultati di operazioni. Tali informazioni sono spesso
memorizzate nella memoria dati. I PIC18 come anche gli altri microcontrollori,
implementano la memoria dati per mezzo di memoria RAM (Random Access Memory) di
tipo statica, tale tipo di memoria allo spegnimento dell'alimentazione, perde tutti i dati. La
larghezza dell'indirizzo utilizzato per puntare la memoria RAM di 12 bit. Questo
permette di indirizzare fino a 4096 locazioni di memoria. Ciononostante la memoria
48
49
Frequentemente si trova che un'istruzione viene suddivisa in tre fasi: caricamento, decodifica ed esecuzione. La fase di
decodifica ed esecuzione sono spesso tra loro combinate un'unica fase.
Una pipeline di questo tipo detta a due livelli. Oltre ad un'aggiunta di ulteriore hardware per eseguire e caricare due
istruzioni, la sua realizzazione non comporta molti problemi. CPU pi complesse, quali per esempio i microprocessori Intel,
AMD hanno molti livelli di pipeline per permettere l'esecuzione contemporanea di pi istruzioni. Con molti livelli di pipeline
l'esecuzione e la gestione del programma diviene molto pi complessa. Per esempio in caso di salti condizionali necessario
cercare di predire se il salto sar da un lato o dall'altro...se la predizione sar errata sar necessario gettare tutti i valori nella
pipeline e ricominciare i calcoli. A questo livello di complessit anche i compilatori possono ottimizzare il codice macchina
per aiutare la pipeline nell'esecuzione del codice. Si capisce dunque che a parit di frequenza, due CPU possono essere una
pi veloce dell'altra poich hanno due pipeline differenti.
53
Per tale ragione ogni volta che si deve accedere ad una variabile necessario sapere in
quale blocco situata. Molte istruzioni che accedono alla memoria possiedono solo 8 bit
per l'indirizzo, che appunto il numero di bit necessario per puntare le 256 locazioni di
memoria all'interno di un blocco. I restanti 4 bit, per determinare il blocco, vengono
prelevati dai bit BSR3:BSR0 del registro BSR.
La Figura 11 mette in evidenza che gli 8 banchi di memoria sono nominati GPR
ovvero General Purpose Register (registri per uso generico). Oltre ai registri GPR sono
presenti anche i registri SFRs ovvero Special Function Registers (registri per uso speciale).
54
I registri per usi speciali sono tutti quei registri utilizzati per impostare le periferiche
interne al PIC stesso. Infatti ogni periferica ha sempre dei registri speciali ad essa dedicati,
per mezzo dei quali possibile impostare il comportamento della periferica stessa.
Maggiori dettagli si vedranno quando si studieranno le varie periferiche interne al PIC.
Un registro speciale un po' particolare lo Status Register (Registro di Stato), tale registro
praticamente presente in ogni architettura sia essa un microcontrollore che un
microprocessore. Lo Status Register contiene lo stato aritmetico dell'ALU (Arithmetic Logic
Unit) ovvero di quella parte della CPU che viene utilizzata per svolgere tutte le operazioni
binarie. In particolare l'ALU svolge le proprie operazioni per mezzo del registro speciale
W, nominato accumulatore50. Ogni volta che necessario svolgere delle operazioni queste
devono essere svolte all'interno del registro W o tra W ed un altro registro. Ad ogni
operazione il registro di stato fornisce l'informazione con opportuni bit, se l'operazione
ha generato un risultato nullo, un overflow51 (trabocco), un numero negativo o si
verificato un riporto.
Si fa notare che la descrizione della memoria dati stata spiegata per sommi capi
poich gran parte dei dettagli associati ai blocchi possono essere trascurati quando si
programma in C. Ciononostante, qualora si dovesse avere l'esigenza di ottimizzare
l'accesso in lettura e scrittura dati all'interno di strutture dati dedicate a particolari
applicazioni, potrebbe essere necessaria una maggior comprensione dell'architettura della
memoria dati e delle sue modalit di accesso. In particolare si ricorda che la modalit
estesa dei PIC, con le sue 8 nuove istruzioni, introduce due nuove modalit di accesso alla
memoria dati, oltre alle 4 gi esistenti, permettendo di ottimizzare ulteriormente il
programma. Alcuni problemi con la memoria dati e la sua divisione in blocchi si pu
avere anche nel caso in cui si debbano creare delle strutture dati che richiedono pi di 256
byte o che comunque vengono dislocate in due o pi blocchi di memoria. Ultima nota per
quanto riguarda il PIC18F4550, che i blocchi di memoria da 4 a 7 sono dedicati al buffer
del modulo USB, qualora questo risulti attivo. Per maggiori dettagli si rimanda al
Datasheet del PIC utilizzato.
Memoria EEPROM
Qualora si abbia la necessit di memorizzare un codice segreto durante l'esecuzione del
programma, o semplicemente la temperatura massima e minima della giornata, pu
ritornare utile l'utilizzo di una memoria permanente che possa mantenere il valore
memorizzato anche nel caso in cui il microcontrollore non dovesse essere pi alimentato.
Come detto possibile fare questo per mezzo della memoria Programma ovvero Flash. In
particolare se il codice segreto deve essere fisso ed noto sin dall'inizio, sicuramente
pratico far uso della memoria programma, come si vedr nei Capitoli successivi basta
dichiarare una costante52. Qualora per i dati debbano essere cambiati durante
l'esecuzione del programma, l'utilizzo della memoria Flash risulta poco pratico, infatti,
come detto, possibile cancellare la memoria Flash solo in pagine, ovvero blocchi. Per
50
51
52
Microprocessori pi complessi possiedono normalmente pi accumulatori. Questo permette di ottimizzare i tempi di calcolo
poich non bisogna sempre caricare i dati in un solo accumulatore. La presenza di pi accumulatori inoltre fondamentale
in architetture con pipeline.
Se per esempio si sommano due numeri contenuti in un byte ed il risultato salvato in un byte, il valore deve essere al
massimo pari a 255. Se la somma fornisce un risultato maggiore di 255 si ha un overflow del registro.
Un valore costante, ovvero definito come const, viene posto dal compilatore XC8 direttamente in memoria Flash.
55
EECON1
EECON2
EEADR
EEDATA
EECON1 il registro utilizzato per il controllo della fase di scrittura e lettura. Tale
registro lo stesso che necessario utilizzare per programmare la memoria Flash, i bit
CFGS e EEPGD determinano se il suo accesso associato alla memoria Flash o
EEPROM. Di seguito si riporta il contenuto del registro EECON1. La rappresentazione
della tabella la stessa utilizzata nei Datasheet della Microchip. Il registro EECON2 viene
utilizzato per rendere sicura la fase di scrittura in memoria, ovvero evitare scritture
accidentali della memoria. Infatti prima di avviare una sequenza di scrittura, necessario
scrivere in successione nel registro EECON2 prima il valore 55h e poi AAh, in modo da
dichiarare la volont di avviare un processo di scrittura.
I registri EEADR e EEDATA sono rispettivamente utilizzati per scrivere l'indirizzo
della locazione di memoria da andare a leggere o scrivere e il valore da assegnare o
restituito dall'indirizzo selezionato55.
Vediamo ora in maggior dettaglio la sequenza di istruzioni necessarie per scrivere e
leggere la memoria EEPROM. Per ragioni di praticit e per introdurre qualche altra
informazione utile alla programmazione, si mostrano le sequenze in Assembly. In questo
caso sono molto semplici e di facile comprensione anche se non si ha una conoscenza
dell'Assembly56.
53
54
55
56
I PIC18, oltre alla protezione in lettura della memoria Programma, per permettere la protezione dell'IP (Intellectual Propriety)
associato al programma, permette di proteggere anche la EEPROM da letture esterne effettuate per mezzo del
programmatore.
Un esempio della gestione di una EEPROM per mezzo del bus I2C verr descritto nei successivi Capitoli.
Il registro EEADR un registro di un byte, da ci discende anche il limite di 256 locazioni di memoria EEPROM.
Naturalmente se si fosse utilizzato un registro pi ampio o due registri si sarebbe potuta avere pi memoria, ma i costi della
memoria sono in generale piuttosto grandi, dunque se si vuole pi memoria bisogna pagarla.
Si rimanda al Datasheet del PIC utilizzato per maggiori informazioni riguardo all'utilizzo delle istruzioni Assembly.
56
R/W-x
U-0
R/W-0
R/W-x
R/W-1
R/S-0
R/S-0
EEPGD
CFGS
FREE
WRERR
WREN
WR
RD
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
S : Settable bit
x = Bit is unknown
Sequenza di lettura
Dal codice relativo alla sequenza di lettura possibile vedere che possibile scrivere i
commenti al fianco dell'istruzione, precedendo quest'ultimi con un punto e virgola. Il
primo passo consiste nel caricare il valore dell'indirizzo di cui si vuole leggere il
contenuto. In C si sarebbe scritto solamente EEADR = indirizzo mentre in Assembly
tutti i movimenti tra registri vengono generalmente fatti per mezzo del registro
intermediario W, ovvero l'accumulatore57. Tale passaggio sarebbe anche il modo con cui il
compilatore traduce la nostra istruzione in C.
MOLW
MOVWF
BCF
BCF
BSF
MOVF
DATA_EE_ADDR
EEADR
EECON1, EEPGD
EECON1, CFGS
EECON1, RD
EEDATA, W
;
;
;
;
;
;
Una volta caricato il valore del nostro indirizzo in EEADR, necessario impostare i bit
EEPGD e CFGS. Quando il loro valore 0 vuol dire che la fase di Read associata alla
EEPROM e non alla memoria Flash. Per porre a 0 tali bit ci sono vari modi, ma
sicuramente il pi pratico quello di usare l'istruzione BCF (Bit Clear F) che pone appunto
57
L'accumulatore non solo necessario per il movimento di dati tra registri ma anche necessario per compiere operazioni tra
registri, quali per esempio somma e sottrazione. Infatti il registro W collegato direttamente con il modulo ALU (Arithmetic
Logic Unit) che permette di svolgere le operazioni tra registri.
57
DATA_EE_ADDR
EEADR
DATA_EE_DATA
EEDATA
EECON1, EEPGD
EECON1, CFGS
EECON1, WREN
;
;
;
;
;
;
;
BCF
INTCON, GIE
MOLW
MOVWF
MOLW
MOVWF
55h
EECON2
AAh
EECON2
;
;
;
;
BSF
EECON1, WR
BSF
INTCON, GIE
BCF
EECON1, WREN
; Disabilito la scrittura
Carico
Carico
Carico
Carico
55h in W
il dato in EECON2
AAh in W
il dato in EECON2
La parte iniziale della sequenza praticamente identica alla precedente, per oltre ad
impostare il registro dell'indirizzo EEADR necessario impostare anche il registro
EEDATA con il dato che si vuole scrivere. Fatto questo si seleziona la EEPROM
piuttosto che la memoria Flash, per mezzo dei bit EEPGD e CFGS. In ultimo si abilita la
possibilit di scrivere la memoria EEPROM settando il valore del bit WREN58.
Una volta abilitata la possibilit di scrivere in EEPROM consigliato disabilitare le
interruzioni (se usate), se infatti la sequenza di scrittura dovesse essere interrotta si
potrebbe avere la perdita di dati o dati corrotti. Disabilitate le interruzioni, necessario
scrivere la sequenza magica all'interno del registro EECON2, ovvero prima il byte 55h e
successivamente AAh. Come visibile tale scrittura avviene sempre per mezzo del registro
W. Dopo tale sequenza possibile avviare la scrittura ponendo ad 1 il bit WR nel registro
EECON1. Tale bit rimane ad 1 per tutto il processo di scrittura, che diversamente dalla
lettura pu impiegare pi di un ciclo di clock. In particolare se si ha interesse a sapere la
58
Oltre alla sequenza 55h e AAh da scrivere nel registro EECON2, un'altra tecnica per proteggere la EEPROM da scritture
accidentali e data dal bit WREN. In particolare all'avvio del PIC (Power Cycle) tale bit viene posto a 0. Inoltre la scrittura in
EEPROM durante la fase di Power-Up disabilitata. In questo modo si evita di compromettere i dati in EEPROM a causa di
scritture accidentali.
58
L'oscillatore
Ogni microcontrollore, per il suo funzionamento, necessita di un clock, ovvero di quel
segnale digitale per mezzo del quale vengono scandite tutte le operazioni. Come illustrato
gi in precedenza c' un legame diretto tra le istruzioni eseguite ed il clock. Il segnale di
clock viene generato per mezzo di un oscillatore. I PIC18 possiedono al loro interno tutta
la circuiteria per generare un clock interno ma anche la circuiteria per ottenere un clock
facendo uso di un quarzo esterno. La circuiteria associata all'oscillatore pi o meno
complessa a seconda del PIC utilizzato. In Figura 12 mostrato lo schema a blocchi
relativo al PIC18F4550.
Nel nostro caso, avendo preso come riferimento il PIC18F4550 bene mettere subito
in evidenza che si ha che fare con una circuiteria un po' complessa. Questo discende dal
fatto che il PIC18F4550 possiede al suo interno il modulo USB. E' possibile subito vedere
che sono presenti diverse selezioni e percorsi di cui si parler a breve.
Prima di entrare nel dettaglio dello schema a blocchi bene mettere in evidenza il fatto
che il PIC18F4550 pu avere il proprio oscillatore interno (o meglio oscillatori)
configurabile in 12 diverse modalit. Dallo schema a blocchi di Figura 12 possibile
vedere che sono presenti due oscillatori nominati primario e secondario. Il primario
normalmente utilizzato per permettere l'oscillazione di un quarzo esterno per generare il
clock utilizzato dal microcontrollore. Questo valido in generale per i PIC18, nel caso del
PIC18F4550 l'oscillatore primario viene anche utilizzato per generare il clock per il
modulo USB, quando questo viene abilitato. Oltre a questi due oscillatori, presente
l'oscillatore interno ad 8MHz. Quando abilitato pu essere utilizzato per generare il clock
del microcontrollore. E' possibile notare che la sua uscita collegata ad un postscaler
(divisore di frequenza), per mezzo del quale possibile ottenere una serie di frequenze
interne che possibile selezionare per mezzo del MUX interno. Sulla destra dello schema
a blocchi possibile osservare altri due MUX, per mezzo dei quali possibile selezionare
il clock da utilizzare per le periferiche e se utilizzare l'oscillatore principale per il modulo
USB. Un'altra caratteristica importante presente in molti PIC18 la presenza del PLL
(Phase Lock Loop) interno59. Tale dispositivo permette di ottenere clock ad elevata
frequenza, partendo da quarzi a frequenza ridotta. In particolare per l'utilizzo del modulo
USB spesso necessario l'uso del PLL al fine di ottenere i 48MHz necessari per le
specifiche USB High Speed.
59
Si noti che per il corretto funzionamento del PLL necessario dividere la frequenza del clock in maniera da avere una
frequenza in ingresso al PLL pari a 4 MHz. Per esempio facendo uso di un quarzo da 20MHz necessario impostare il
PLLDIV al valore di 5.
59
Vi sono anche altri registri associati alle impostazioni dell'oscillatore. Per maggiori dettagli si rimanda al Datasheet del PIC
utilizzato.
60
XT
La modalit XT utilizza l'oscillatore primario, al quale deve essere collegato un
quarzo.
XTPLL
E' come la modalit XT ma con il PLL abilitato.
HS
E' come la modalit XT ma viene utilizzata nel caso di quarzi ad alta frequenza.
Tale modalit pu per essere utilizzata anche senza quarzo, facendo uso di un
oscillatore esterno, come riportato in Figura 13. Quando si fa utilizzo di un
oscillatore esterno, non vi il tipico problema di ritardo legato allo start up (avvio)
dell'oscillatore. Tale ritardo pu anche essere associato ad un risveglio del
microcontrollore, oltre che ad un Power Up del PIC o del sistema. Il pin di uscita
risulta molto utile nel caso si vogliano sincronizzare altri dispositivi o effettuare delle
calibrazioni, senza influenzare l'oscillatore principale.
HSPLL
E' come la modalit HS ma viene abilitato il PLL interno.
EC
In questa modalit un clock esterno viene fornito in ingresso al PIC, ovvero non
richiesto il quarzo sull'oscillatore principale. Il secondo piedino normalmente
utilizzato per il quarzo viene utilizzato per riportare la Fosc/4 in uscita. Lo schema a
blocchi riportato in Figura 14.
ECIO
Come la modalit EC, ma il pin RA6 pu essere utilizzato come I/O standard
invece che Fosc/4. Lo schema a blocchi riportato in Figura 15.
61
ECPLL
Come la modalit EC ma con il PLL interno abilitato.
ECPIO
Come la modalit ECIO ma con il PLL interno abilitato.
INTHS
L'oscillatore interno utilizzato come clock per il microcontrollore, mentre
l'oscillatore HS utilizzato come sorgente per il clock utilizzato per il modulo USB.
INTXT
L'oscillatore interno utilizzato come clock per il microcontrollore, mentre
l'oscillatore XT utilizzato come sorgente per il clock utilizzato per il modulo USB.
INTIO
L'oscillatore interno utilizzato come clock per il microcontrollore, mentre
l'oscillatore EC utilizzato come sorgente per il clock utilizzato per il modulo USB.
Il pin RA6 utilizzato come I/O standard.
INCKO
L'oscillatore interno utilizzato come clock per il microcontrollore, mentre
l'oscillatore EC utilizzato come sorgente per il clock utilizzato per il modulo USB.
Vediamo in maggior dettaglio le modalit HS, XT, HSPLL e XTPLL. In tutte queste
modalit necessario un quarzo, la configurazione utilizzata riportata in Figura 16. E'
possibile notare che oltre al quarzo sono presenti anche due condensatori.
seconda del quarzo utilizzato. In progetti che devono essere rilasciati per applicazioni
professionali, sempre bene fare test sulla qualit dell'oscillazione, al variare della
temperatura e tensione di alimentazione, nel range dei valori per cui deve essere
specificato il sistema. La Microchip fornisce delle linee guida che permettono di
selezionare i condensatori per alcuni valori standard di cristalli, in Tabella 2 sono riportati
i valori presenti sul Datasheet. Ciononostante al fine di scegliere il valore corretto
sempre bene far riferimento al Datasheet del quarzo utilizzato e verificare la Capacit di
carico CL richiesta per garantire una corretta oscillazione.
Modalit
Frequenza
C1 e C2
XT
4 MHz
27 pF
HS
4 MHz
27 pF
HS
8 MHz
22 pF
HS
20 MHz
15 pF
Per maggiori dettagli sugli MSP430 far riferimento al corso MSP430 disponibile gratuitamente al sito www.LaurTec.it.
63
caso di malfunzionamento del clock delle periferiche (Fail Safe Clock Monitor). Qualora il
clock associato alle periferiche si dovesse interrompere sia esso il primario, secondario o
interno, il clock viene cambiato in automatico sul clock di guardia ottenuto dall'oscillatore
INTRC. Normalmente questo clock risulta a frequenza pi bassa di quella normalmente
utilizzata, dunque particolari moduli potrebbero non funzionare pi (il modulo USB tra
questi), ciononostante tale opzione risulta particolarmente importante poich permette di
abilitare funzioni di emergenza o effettuare uno spegnimento del sistema controllato. Lo
schema a blocchi di tale funzione riportato in Figura 17.
Per abilitare tale funzione necessario settare il bit FCMEN del registro CONFIG1H.
Il clock secondario ha gli ingressi T1OSO e T1OSI ai quali pu essere collegato anche un
quarzo esterno. Questo secondo oscillatore pensato per applicazioni a bassa frequenza
ed in particolare pu essere utilizzato per far oscillare quarzi a 32KHz o meglio 32768Hz.
Il valore ti tale cristallo pensato ad hoc per realizzare in maniera pratica riferimenti di 1
secondo necessari per esempio per realizzare degli orologi, ovvero calendari. Questa
funzione, come si vedr nei capitoli successivi pu essere implementata nel proprio
sistema anche facendo uso di circuiti integrati esterni come il PCF8563, ovvero per mezzo
di RTCC Real Time Clock Calendar.
Power Management
In un mondo che richiede sempre pi energia, avere la possibilit di ridurre i consumi
sicuramente un'opzione ben gradita. Le nuove tecnologie grazie alla miniaturizzazione dei
processi di realizzazione dei circuiti integrati, nonch della diminuzione delle tensioni
operative, ha permesso di ridurre notevolmente i consumi. Oltre alle innovazioni
tecnologiche, accorgimenti pi semplici possono essere altrettanto validi quale opzione
per ridurre i consumi. In particolare una tecnica utilizzata quella di utilizzare una
frequenza di clock non pi alta del necessario, infatti all'aumentare della frequenza di
clock aumenta anche la potenza richiesta dal sistema. I PIC18 mettono a disposizione la
circuiteria necessaria per cambiare la frequenza di clock anche durante l'esecuzione del
programma. Oltre alla possibilit di variare la frequenza di clock vi sono altre opzioni e
tecniche utilizzate per la riduzione dei consumi. Il PIC18F4550 possiede sette modalit
operative differenti, per mezzo delle quali possibile gestire in maniera ottimale i
64
consumi, in accordo con le specifiche del progetto 62. Le varie modalit si raggruppano in
tre diversi stati in cui il microcontrollore pu trovarsi:
Modalit Run
Modalit Idle
Modalit Sleep
Nella modalit Run, sia la CPU che le periferiche sono attive, il variare della frequenza
di clock permette di controllare i consumi. Nella modalit Idle, la CPU disattivata mentre
le periferiche possono rimanere abilitate a seconda delle esigenze. In questa modalit
possibile ottenere consumi ridotti fino a 5.8A (far riferimento al Datasheet per i valori
esatti associati al modello utilizzato). Nella modalit Sleep, sia la CPU che le periferiche
vengono disabilitate riducendo ulteriormente i consumi a 0.1A. Dal momento che sia
nella modalit Idle che Sleep la CPU viene disattivata, si capisce che non viene eseguita
nessuna istruzione. Da ci discende il fatto che l'unico modo per riattivare la CPU per
mezzo di un Reset o di un Interrupt dalle periferiche (preventivamente abilitato), in
particolare il Reset pu anche essere generato dal Watchdog Timer (descritto a breve).
Alcuni modelli di microcontrollori Microchip hanno anche la modalit Deep Sleep Mode,
ovvero sonno profondo, in cui i consumi sono ridotti ulteriormente a poche decine di
nA. Il risveglio da questo stato possibile per mezzo di Interrupt e Reset, oltre
naturalmente a un Power Cycle (Accendi e Spegni). In Deep Sleep Mode viene tutto congelato
e i dati in memoria vengono persi. Anche il clock a bassa frequenza e l'RTC viene
disattivato per il Watchdog Timer viene disattivato. La circuiteria BOR (Brown Out Reset)
viene anche disattivata a meno di non volerla attiva a scapito di maggiori consumi. In
applicazioni industriali e professionali la circuiteria BOR viene generalmente sempre
lasciata attiva. Dal momento che i dati in memoria vengono persi, vuol dire che al riavvio
del sistema necessaria una nuova inizializzazione delle periferiche. I microcontrollori
con opzione Deep Sleep Mode hanno spesso l'opzione di un Back up System per mezzo di
un'alimentazione ausiliaria, per esempio una batteria a bottone, che permette di mantenere
alimentate alcune celle RAM speciali in cui salvare dati importanti prima di entrare in Deep
Sleep Mode.
Le varie modalit di risparmio energetico sono mostrate in Tabella 3, dove possibile
vedere i dettagli dei bit di configurazione necessari per avviarle. In particolare si fa uso del
bit IDLEN presente nel registro OSCCON e dei bit SCS1:SCS0 presenti ancora nel
registro OSCCON.
Per poter entrare nella modalit Idle o Sleep, necessario eseguire l'istruzione SLEEP,
poi, a seconda del valore del bit IDLEN, il microcontrollore entra nello stato di Sleep o
Idle; i bit SCS1:SCS0 determinano poi la sorgente di clock. Si fa subito notare che in caso
si voglia utilizzare la modalit Idle o Sleep, dal momento che dopo l'esecuzione
dell'istruzione SLEEP non vengono pi eseguite ulteriori istruzioni, i bit di competenza
della modalit d'interesse devono essere preventivamente impostati come richiesto.
62
Nella famiglia PIC18 I dispositivi ottimizzati per avere consumi ridotti appartengono alla famiglia XLP (Extra Low Power). Il
nome discende probabilmente da ragioni di marketing per contrapporsi agli MSP430 i cui bassi consumi sono invece
pubblicizzati con la dicitura ULP (Ultra Low Power).
65
Mode
IDLEN
SCS1:SCS0
Stato CPU
Stato Periferiche
PRI_RUN
N/A
00
ON
ON
SEC_RUN
N/A
01
ON
ON
RC_RUN
N/A
1x
ON
ON
PRI_IDL
00
OFF
ON
SEC_IDL
01
OFF
ON
RC_IDL
1x
OFF
ON
Sleep
N/A
OFF
OFF
Modalit PRI_RUN
Questa modalit rappresenta quella principale che viene impostata dopo il Reset.
Il clock disponibile quello selezionato per mezzo dei bit FOSC3:FOSC0 del
registro di configurazione CONFIG1H.
Modalit SEC_RUN
Questa modalit rappresenta quella compatibile con gli altri microcontrollori
PIC18 che possiedono la modalit di risparmio energetico. Questa viene impostata
cambiando il valore dei bit SCS1:SCS0 al valore 00. Il clock disponibile il Timer1.
Modalit RC_RUN
Questa modalit rappresenta quella a maggior risparmio energetico, dal momento
che fa uso della frequenza di clock pi bassa, generata per mezzo dell'oscillatore RC
interno. Sia la CPU che le periferiche prelevano il clock dal INTOSC multiplexer,
inoltre il clock primario disattivato, riducendo ulteriormente i consumi 63. Tale
modalit viene selezionata impostando i bit SCS1:SCS0 a 10 64.
Modalit PRI_IDL
Questa modalit la prima dello stato Idle ed quella che permette un minor
risparmio energetico tra le modalit Idle. Questo discende dal fatto che, pur
disattivando la CPU, le periferiche continuano ad essere alimentate ed hanno a
disposizione il clock principale. Questo se da un lato mantiene i consumi associati al
clock primario, permette di passare dallo stato Idle allo stato Run in maniera veloce,
visto che l'oscillatore primario non deve essere riattivato. Per entrare in questa
modalit bisogna settare il bit IDLEN, impostare i bit SCS1:SCS0 al valore 00 ed
eseguire l'istruzione di SLEEP. Al risveglio del microcontrollore la CPU utilizza
come clock l'oscillatore primario.
63
64
Modalit SEC_IDL
Dal momento che il clock primario viene disattivato, quando si ha la necessit di passare nuovamente al clock primario, si ha
il ritardo dovuto all'attivazione e la stabilizzazione dello stesso. Tale ritardo lo stesso che si ha con un power cycle.
In realt per impostare tale modalit sia il valore 10 che 11 verrebbero accettati, per, al fine di mantenere eventuali
compatibilit con versioni successive di PIC si raccomanda di utilizzare il valore 10 piuttosto che 11.
66
Modalit RC_IDL
Questa modalit, quella a maggior risparmio energetico tra le modalit Idle,
poich possibile impostare il clock a pi bassa frequenza. La CPU, come per ogni
modalit Idle, viene disattivata. Il clock fornito alle periferiche quello generato
dall'INTOSC. Per entrare in questa modalit bisogna settare il bit IDLEN,
impostare i bit SCS1:SCS0 al valore 01 ed eseguire l'istruzione di SLEEP. Al
risveglio del microcontrollore la CPU utilizza come clock l'oscillatore INTOSC.
Modalit Sleep
Questa modalit rappresenta quella a maggior risparmio energetico poich tutte le
periferiche e la CPU vengono disattivate.
Se il Watchdog Timer attivo, il suo clock viene mantenuto attivo garantendo in
questo modo l'evento di time out del Watchdog. Tale evento, assieme all'evento di
Reset e Interrupt da periferiche, permette il risveglio del microcontrollore. Il clock
che vine utilizzato al risveglio dipende dal valore dei bit SCS1:SCS0 prima
dell'attivazione della modalit Sleep.
Circuiteria di Reset
Come si visto nella parte relativa all'organizzazione della memoria, il programma
eseguito a partire dall'indirizzo del Reset Vector. L'evento di Reset pu essere considerato
un tipo d'interruzione ad alta priorit per la CPU, in particolare la CPU non pu ignorare
una richiesta di Reset. Ogni microcontrollore, come anche microprocessore, possiede una
circuiteria interna di Reset. Nonostante l'operazione di Reset sia apparentemente
semplice, ovvero portare il Program Counter nella posizione del Reset Vector, la circuiteria
associata al Reset relativamente pi complicata di quanto si possa pensare. La circuiteria
di Reset del PIC18F4550 riportata in Figura 18.
La ragione della sua complessit legata al fatto che i PIC18 possiedono differenti
sorgenti che possono generare lo stato di Reset della CPU. Lo stato di Reset oltre che a
cambiare il valore del Program Counter determina anche il cambio del valore di alcuni
registri interni. In particolare il Datasheet riporta sempre le tabelle con i vari registri e il
valore assunto dai vari bit in caso di un Reset della CPU. Alcune volte i bit vengono posti
a 0 o 1, mentre altre volte assumono un valore casuale. Ritornando alla Figura 18
possibile osservare che il segnale di Reset rappresenta il risultato di operazioni logiche pi
o meno complesse, ma fondamentalmente presente una OR con ingresso i segnali
derivanti dalle varie possibilit di Reset e una AND che disabilita le funzioni di Reset nel
caso in cui non sia trascorso un tempo minimo dall'attivazione del microcontrollore.
67
Per sapere quale tipo di evento ha generato il Reset, possibile leggere il valore del
registro RCON. Vediamo ora in che modo la CPU pu essere resettata.
Power On Reset
Il Power On Reset (POR) rappresenta uno dei primi eventi che si viene a verificare una
volta che si alimenta il sistema. Una volta passato un tempo minimo dal momento in cui
viene rilevata la tensione minima operativa, viene attivato un Reset automatico, in maniera
da portare i registri interni al PIC in uno stato noto. Per sapere il valore iniziale dei registri
bene sempre far affidamento al Datasheet del PIC utilizzato.
68
La ragione per cui necessario attendere un tempo minimo legata al fatto che prima
di iniziare una qualunque operazione, necessario che l'oscillatore si sia stabilizzato. In
questo modo possibile garantire che le varie istruzioni siano eseguite correttamente e
soprattutto. Per permettere il corretto funzionamento della circuiteria interna POR
necessario collegare un resistore compreso tra 1Kohm e 10Kohm tra il pin MCLR e Vcc
(per un esempio pratico si faccia riferimento allo schema base di Figura 4).
69
70
Stack Reset
Il Reset associato allo Stack pu risultare particolarmente utile per ripristinare il
corretto funzionamento del sistema. Si ricorda infatti che lo Stack rappresentato da
quell'insieme di registri il cui compito di memorizzare l'indirizzo di ritorno da una
chiamata ad una funzione, sia essa associata ad un Interrupt o semplicemente una
funzione standard. Si capisce che se lo Stack dovesse avere un evento di overflow, ovvero di
traboccamento, vorrebbe dire che delle informazioni di ritorno sono perse, portando
prima o poi il sistema in uno stato non voluto. Lo stesso potrebbe accadere in caso di
underflow ovvero si prelevino informazioni dallo Stack anche se non presenti. Il Reset
associato allo Stack viene abilitato per mezzo del bit STVREN del registro CONFIG4L.
Se abilitato (posto ad 1), un eventuale overflow o underflow dello Stack, genera un Reset del
sistema.
Nel caso in cui il Reset dello Stack non dovesse essere abilitato, i bit STKFUL e
STKUNF vengono settati di conseguenza. Tali bit sono presenti nel registro STKPTR e
vengono resettati o via software o successivamente ad un POR Reset. In sistemi in cui sia
richiesta una certa robustezza, il Reset da parte dello Stack permette di ripristinare il
normale funzionamento del sistema, ciononostante, eventuali Reset dovuti a problemi
dello Stack, possono essere indice di problemi software. In applicazioni in cui il software
si trova in esecuzione 24 ore su 24, 365 giorni all'anno, pur non essendo presenti
problemi software si possono comunque verificare problemi nell'esecuzione del
programma a causa di disturbi esterni. In particolare radiazioni elettromagnetiche
potrebbero cambiare i contenuti della memoria RAM alternando delle impostazioni o la
normale esecuzione del programma. Con il tempo anche la memoria Flash pu essere
soggetta ad alterazioni dei contenuti, specialmente in condizioni di alta temperatura.
Infatti non insolito che bit di valore 1 possano invertirsi in 065.
Errori software che possono portare a un Reset sono pi frequenti se si scrive il
programma in Assembly. Dal momento per che tale tipologia di Reset deve essere
abilitata, qualora si tenga lo Stack Reset disabilitato e non si ha nessun Reset che inizializza
nuovamente il sistema, quello che si osserva generalmente un comportamento strano
dello stesso.
Poich lo Stack ha un numero limitato di celle, bisogna fare attenzione a non riempirlo
eseguendo un numero elevato di chiamate a funzioni annidate. Bisogna inoltre sempre
tenere a mente che oltre alle funzioni annidate, qualora si stiano usando gli Interrupt,
bisogna preventivare sempre altri due livelli per le interruzioni ad alta e bassa priorit.
Alcuni microcontrollori come gli MSP430 non hanno un numero prefissato di celle RAM
per lo Stack. Infatti lo Stack Pointer indirizza memoria RAM standard, la quale viene
assegnata nel file di linker.
Istruzione RESET
Tutti i vari eventi finora descritti sono pi o meno legati ad eventi hardware.
L'ultimo caso qui descritto in realt di tipo software. Tra le varie istruzioni che il PIC18
pu eseguire vi quella di RESET ovvero l'istruzione che permette di resettare la CPU per
mezzo del codice stesso. Questa istruzione, una volta eseguita ha gli stessi effetti di un
65
Il caso inverso, ovvero l'inversione da 0 a 1 non in generale possibile o meglio improbabile. Infatti ogni bit della memoria
Flash un condensatore, ed pi probabile che sia soggetto a scarica che non a carica. In particolare ad alte temperature la
corrente di dispersione (Leakage Current) tende ad aumentare ed agevolare la scarica delle celle di memoria Flash.
71
Reset Hardware. Tale istruzione risulta molto utile per permettere il Reset del sistema per
mezzo di un altro sistema master, si pensi ad esempio ad una rete composta da vari nodi.
Un altro utilizzo particolarmente utile potrebbe essere associato ad un sistema
diagnostico, si pensi ad esempio al caso in cui il software si sia reso conto che qualcosa
accaduto e si siano persi dei dati, l'istruzione Reset potrebbe tornare utile al fine di forzare
una inizializzazione di sistema.
La possibilit che un sistema si possa resettare, sebbene possa sembrare un mezzo
preventivo per garantire una buona affidabilit del sistema, non deve causare Reset di
sistema ogni 5 minuti. I casi estremi si potrebbero verificare una volta l'anno in sistemi
che sono sempre attivi. Naturalmente tutto dipende dal software e la qualit con cui
scritto, quindi una buona responsabilit ricade sul programmatore. Sebbene il software
possa essere soggetto a bug, non bisogna scordarsi che anche un microcontrollore nella
sua architettura soggetto a bug per cui pu essere fonte di anomalie anche qualora il
software sia perfetto. Per tale ragione, al fine di scrivere un software robusto sempre
bene verificare se i bug del microcontrollore utilizzato non possano influenzare il corretto
funzionamento del sistema.
Queste informazioni possibile trovarle nel documento Errata, scaricabile dalla stessa
pagina in cui possibile prelevare il Datasheet. Normalmente, quando possibile,
nell'Errata viene anche fornito il cosiddetto Workaround, ovvero una soluzione e/o
comportamento per evitare il problema derivante dal bug.
Non inusuale che uno stesso microcontrollore sia presente sul mercato con diverse
versioni, le quali sono soggette a bug differenti. Alcune volte pu capitare che lo scrivere
il software al fine di implementare il Workaround per un bug possa causare problemi
qualora lo stesso software sia utilizzato su una versione del microcontrollore senza il bug.
TRIS
Il registro TRIS ha il compito di impostare la direzione (ingresso od uscita) del
pin. Tale registro composto da un byte ovvero da 8 bit; ad ogni bit corrisponde
il pin della porta. Per esempio il bit 0 di TRISA imposta il pin RA0. Se il bit vale
0 il pin impostato come output, mentre se vale 1, il pin viene impostato come
ingresso. Una semplice regola per ricordare tale impostazione quella di pensare
1 come I (Input) e 0 come O (Output).
PORT
Il registro PORT viene utilizzato per scrivere un valore in uscita e/o leggere il
valore del pin.
LAT
Il registro LAT viene utilizzato per scrivere un valore in uscita e/o leggere il
valore del Latch66 (non il pin).
Lo schema a blocchi di ogni pin, riportato in Figura 20. In tale schema a blocchi non
visualizzato l'hardware specifico per ogni pin, necessario per gestire le opzioni di
ulteriori periferiche. In particolare visibile la presenza di due Latch, uno associato al
registro TRIS, mentre il secondo associato al registro PORT. Quando l'uscita Q del
66
Il Latch rappresenta la cella elementare di memoria che permette di memorizzare un bit. Un registro composto da un
insieme di Latch. Dunque una porta ha fino a 8 Latch.
73
Latch TRIS vale 1, il buffer che porta l'uscita Q del Latch PORT all'uscita del pin, viene
disabilitato, ovvero posto ad alta impedenza. Questo permette di poter leggere il pin senza
essere influenzati dal valore del Latch PORT. Il valore del registro TRIS normalmente
viene impostato all'inizio del programma e non viene pi cambiato, ma nulla vieta di
sviluppare applicazioni in cui sia necessario cambiare il valore del registro TRIS durante
l'esecuzione del programma. Qualora l'uscita Q del Latch TRIS valga 0, viene abilitato il
buffer di uscita, che permette di portare il valore dell'uscita Q del registro PORT in uscita
al pin.
Vediamo meglio cosa sono i registri PORT e LAT e quali sono le loro differenze. In
particolare, per chi ha gi studiato i PIC16, quale per esempio il famoso PIC16F84 e
PIC16F877, il registro LAT una cosa nuova, infatti i PIC citati possiedono solo il
registro TRIS e PORT. Effettivamente possibile scrivere 1 o 0 sul pin di uscita facendo
solo uso del registro PORT; inoltre anche possibile leggere il valore del pin di uscita e
sapere se vale 0 o 1. Da quanto detto, non si capisce bene la ragione per cui nei PIC18 sia
stato introdotto il registro LAT, per mezzo del quale possibile a sua volta scrivere il pin
di uscita o leggere il valore del Latch di uscita (non il valore del pin). Il registro LAT
funzionalmente identico al registro PORT in scrittura, infatti in Figura 20 possibile
vedere che il data Latch scrive il valore presente sul data bus in uscita, indistintamente se
si effettua una scrittura sul registro PORT o LAT. La differenza dei due registri nella
fase di lettura, infatti quando si legge il registro PORT, il valore che viene posto sul data
bus proprio quello del pin, mentre quando si effettua la lettura del registro LAT, il dato
posto sul data bus il valore d'uscita del Latch.
Bene, allora qual' la differenza?! Quando pongo il pin ad 1, l'uscita del Latch vale 1 e il pin in
uscita vale 1, dunque leggere il registro PORT o LAT uguale!
La differenza proprio qui, il valore del registro PORT e LAT non sono sempre
uguali! Il fatto va ricercato esternamente al PIC. Infatti la periferica o carico al quale il pin
collegato, potrebbe trovarsi ad una certa distanza dal PIC stesso, tale distanza potrebbe
essere di soli 10cm, ma le piste potrebbero avere elementi parassiti (R, C ed L) tali per cui
sia necessario del tempo prima che il valore cambi di stato da 0 a 1 o viceversa. Dunque il
valore del pin potrebbe valere 0, quando invece il valore in uscita al Latch 1. Dunque
leggere il pin tramite il registro PORT o leggere il Latch per mezzo del registro LAT pu
avere le sue differenze67. Questo causa dei problemi quando vengono eseguite istruzioni
per cui, prima di cambiare il valore del bit richiesto sapere il valore del pin stesso.
Dal momento che in scrittura i due registri LAT e PORT sono uguali,
mentre in lettura LAT pu evitare dei problemi, quando si utilizza un pin come uscita
consigliabile utilizzare sempre LAT sia in lettura che in scrittura. Qualora il pin sia
impostato come ingresso il pin deve essere letto per mezzo del registro PORT.
Vediamo ora in breve le peculiarit di ogni porta, ed in particolare le sue funzioni.
Quanto di seguito descritto fa rifermento al PIC18F4550, il numero di porte e funzioni
pu variare in base al PIC utilizzato. Per maggior dettagli fare sempre riferimento al
Datasheet del PIC utilizzato.
67
Il problema ora spiegato viene anche a dipendere dalla velocit (frequenza) a cui vengono letti e scritti i registri.
74
PORTA
La PORTA possiede al massimo 7 pin, ovvero fino ad RA6, in particolare RA6
disponibile solo se non si fa uso del quarzo esterno. Per poter utilizzare il pin RA6
necessario scrivere la direttiva:
#pragma config OSC = IRCIO67
Dal momento che si fa uso di una direttiva, tale impostazione non pu essere pi
cambiata, se non riprogrammando il PIC 68. I pin della PORTA sono inoltre utilizzati
come ingressi analogici. All'avvio del PIC tale funzione prevale sul valore impostato
sul registro TRISA. Per poter utilizzare i pin della PORTA tutti come I/O
necessario scrivere 0x0F (ovvero 15) nel registro ADCON169. Maggiori dettagli su
tale registro sono riportati nel Capitolo dedicato agli ADC. Ogni volta che si avvia il
PIC bene che tra le prime istruzioni sia presente l'inizializzazione delle porte, in
modo tale da evitare il danneggiamento dei sistemi collegati al PIC, nonch il PIC
stesso. L'inizializzazione della PORTA, facendo uso di LATA invece di PORTA,
la seguente:
LATA = 0;
ADCON1= 16;
TRISA =
0b00001111;
PORTB
La PORTB, diversamente dalla PORTA possiede 8 pin, dunque possiede i pin
RB0..RB7 e LATB0...LATB7 rispettivamente. Come le altre porte, ogni pin
bidirezionale ma pu essere anche utilizzato per altre funzioni. In particolare,
all'avvio del PIC, i pin RB0-RB4 sono impostati come ingressi analogici. Per poterli
utilizzare come ingressi digitali necessario utilizzare la direttiva:
#pragma config PBADEN = OFF
Dal momento che si fa uso di una direttiva, tale impostazione non pu essere pi
cambiata, se non riprogrammando il PIC. Qualora si debbano utilizzare i pin come
ingressi analogici necessario porre ad ON l'opzione PBADEN. Una caratteristica
importante della PORTB che spesso viene trascurata, la presenza di resistori di
pull-up che possono essere abilitati per mezzo del bit RBPU del registro
INTCON2. Tale funzione particolarmente utile nel caso si utilizzino dei pulsanti
esterni, infatti permette di evitare l'utilizzo dei resistori di pull-up altrimenti necessari
68
69
In realt essendo la memoria Flash di tipo Self Programming, essendo presente una Charge Pump integrata, vi la possibilit di
riprogrammare le Configuration Words. Il bootloader sfrutta per esempio questa opzione per riprogrammare i bit di
configurazione.
Si faccia riferimento al Capitolo sul modulo ADC, per maggior dettagli su come impostare tale registro.
75
per fissare il livello logico del pulsante aperto 70. Un'altra caratteristica della PORTB
quella delle interruzioni al variare dello stato logico, cio viene generata
un'interruzione se uno qualunque dei pin RB4-RB7 cambia di valore, ovvero da 0 ad
1 o viceversa. Si vedranno esempi su quanto descritto, nei prossimi Capitoli. La
PORTB pu essere inizializzata per mezzo delle seguenti istruzioni:
LATB = 0;
ADCON1= xx;
TRISB =
0b00001111;
PORTC
La PORTC possiede 7 pin ovvero RC0-RC6 e LATC0...LATC6 rispettivamente.
Tra le periferiche principali che utilizzano tale porta si ricorda la EUSART e il
modulo USB, il quale fa per utilizzo anche della PORTA. Di default, diversamente
dalle PORTA e PORTB i pin sono digitali, dunque la sua inizializzazione pi
semplice:
LATC = 0;
TRISC =
0b00001111;
PORTD
La PORTD possiede 8 pin ovvero RD0-RD7 e LATD0...LATD7
rispettivamente. Tra le periferiche principali presenti su tale porta si ricorda la
presenza dell'Enhanced PWM, ottimizzato per il controllo dei motori, grazie alla
presenza di uscite complementari. La sua inizializzazione tipo quella della PORTC:
LATD = 0;
TRISD =
0b00001111;
70
Se si leggesse il valore di un pulsante aperto, equivarrebbe a leggere un valore fluttuante che potrebbe essere sia 0 che 1 e
variare col tempo. Il resistore di pull-up fissa ad 1 il valore del pulsante aperto. Come alternativa anche possibile utilizzare
resistori di pull-down che fissano il valore del pulsante aperto a 0, ma visto che presente il resistore di pull-up interno al PIC,
bene far uso di quello a meno di non avere altre otivazioni.
76
PORTE
La PORTE
possiede 4 pin ovvero RE0-RE3 e
LATE0...LATE3
rispettivamente. Una loro peculiarit sta nel fatto che gli ingressi hanno un buffer a
Trigger di Schmitt, permettendo agli ingressi di avere una maggiore immunit a
disturbi esterni. Come per la PORTA la PORTE possiede i pin multiplexati con il
convertitore analogico digitale. In particolare, dal momento che all'avvio del PIC
PORTE impostata con ingressi analogici, necessario impostare propriamente il
registro ADCON1:
LATE = 0;
ADCON1= xx;
TRISE =
0b00001111;
Riassunto
In questo Capitolo stata trattata con qualche dettaglio l'architettura dei PIC18, in
particolare focalizzando l'attenzione sul PIC18F4550. Partendo dall'architettura base
Harvard e di von Neumann, si sono descritte le varie parti principali del microcontrollore
descrivendo vantaggi e svantaggi derivanti dall'utilizzo di un'architettura rispetto ad
un'altra. Al fine di agevolare la comprensione e confronto, si sono introdotte anche le
caratteristiche base degli MSP430. Ogni parte trattata ha mostrato in particolare come
determinate caratteristiche architetturali discendano dalla volont di poter soddisfare
esigenze pratiche di prodotti reali. Per tale ragione aspetti apparentemente semplici come
il clock sono spesso piuttosto complicati visto che la flessibilit dei moduli fornisce molte
opzioni al fine di adeguarsi a molteplici applicazioni.
77
Domande ed Esercizi
1. Qual' la differenza tra l'architettura di Von Neumann e Harvard?
2. Qual' la differenza tra il registro LAT e PORT e quando bene usare l'uno o l'altro?
3. Descrivere le funzioni del registro TRIS associato ad ogni porta.
4. Per quale ragione diminuire la frequenza di clock permette di ridurre i consumi della CPU?
5. Qual' la funzione del PLL?
6. Qual' la frequenza di clock richiesta per supportare una comunicazione USB Full Speed?
7. Qual' l'utilizzo tipico della memoria Flash?
8. Qual' l'utilizzo tipico della memoria RAM?
9. Che cos' la FRAM?
10. Qual' il valore di default della memoria Flash una volta cancellata?
11. Per quale ragione la memoria Flash potrebbe perdere memoria del valore memorizzato al
78
79
Capitolo IV
IV
Schema elettrico
Per poter seguire l'esempio presentato in questo capitolo necessario realizzare su
Breadboard lo schema elettrico di Figura 21 o far uso delle schede di sviluppo impostate come
descritto nel paragrafo successivo.
80
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 22: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 23: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione Software particolare oltre alla
semplice creazione di un progetto.
Gli esempi mostrati sono spiegati nel dettaglio, mostrando il codice Assembly,
per cui, a seconda delle impostazioni delle ottimizzazioni e della versione del
compilatore utilizzato, possono essere presenti delle differenze tra il codice
mostrato e quello che otterrete in fase di compilazione.
82
Si aprir una finestra che guider la creazione di un nuovo progetto, come riportato in
Figura 25. A questo punto, nel riquadro Categories, bisogna selezionare la voce Microchip
Embedded mentre nel riquadro Projects, bisogna selezionare Standalone Project. Cliccando sul
pulsante Next si avr la nuova finestra di Figura 26, dove bisogner selezionare il PIC
della famiglia PIC18 che si vuole utilizzare. Negli esempi che seguiranno si far uso del
PIC18F4550 che andr dunque montato sul sistema embedded Freedom II 71 o scheda di
sviluppo utilizzata. In generale anche altri PIC potranno essere utilizzati senza alcun
problema, ma si dovr avere l'accortezza di cambiare l'header file, di cui si parler a breve,
associato al PIC utilizzato. Dopo aver selezionato il PIC si pu premere nuovamente il
pulsante Next. Nella schermata successiva riportata in Figura 27 fornita la possibilit di
selezionare lo strumento di programmazione e Debug, che nel seguente corso il
PICKIT 3. Si noti che al fianco di ogni strumento sono presenti dei cerchi colorati. Verde
significa che lo strumento supportato, giallo che lo strumento parzialmente supportato
e rosso ad indicare che lo strumento non supportato.
In particolare si noti che il programmatore PICKIT 2 parzialmente supportato e
probabilmente rimarr tale, mentre il programmatore Debugger ICD 2 non supportato.
Tra gli strumenti selezionabili vi anche il simulatore, da selezionare qualora non si abbia
nessun Hardware a disposizione e si voglia testare il software per mezzo del PC. La
selezione fatta in questo passo, come anche le precedenti, possono essere cambiate in un
secondo tempo. Cosa interessante da notare che lo strumento selezionato e relativo
numero seriale, vengono memorizzati nel progetto. Qualora cambiassimo il
programmatore in un secondo momento, per esempio si dovesse usare un PICKIT 2
invece del PICKIT 3, questo verrebbe segnalato e prima di poter programmare
nuovamente il microcontrollore richiesta la conferma per poter utilizzare il nuovo
programmatore.
71
Un qualunque altro sistema di sviluppo o PIC della famiglia PIC18 in generale utilizzabile.
83
84
Il precedente ambiente di sviluppo MPLAB IDE non permetteva di selezionare la versione del compilatore, e ad ogni
installazione di un nuovo compilatore si era costretti a ricompilare il progetto con la nuova versione. Sebbene molti siano
abituati al concetto dell'avere sempre l'ultima versione del software, questo , in applicazioni professionali sensibili, come per
esempio in ambito automobilistico e industriale, non accettato. Infatti aggiornare un compilatore richiede il dover testare
l'intera applicazione dall'inizio.
85
Premendo il tasto Next possibile procedere con l'ultima impostazione del progetto,
ovvero il nome e percorso in cui salvarlo, come mostrato in Figura 29.
In particolare selezionare il percorso da impostare nella Project Location e fornire un nome
nel Project Name73. Il campo Project Folder verr automaticamente popolato con la cartella
del progetto, che prender il nome fornito e un .X alla fine.
Il check box Set as main project permette di selezionare il progetto appena creato come
principale. Infatti l'ambiente di sviluppo permette di avere pi progetti aperti ma solo uno
attivo.
Dal momento che il nuovo ambiente di sviluppo supporta anche l'ambiente di sviluppo Linux e MAC bene creare nomi
non separati da spazi ma da underscore. Sebbene sia possibile usare gli spazi non raccomandato farlo.
86
87
Mostrando i Files, si pu subito vedere che pur non avendo scritto una riga di codice, il
nostro progetto ha gi oltre 10 Files. Questi Files sono creati in automatico dall'ambiente
di sviluppo basato su Netbeans ed in particolare mantengono le informazioni del nostro
progetto e dell'editor. Dal momento che non ci troveremo a dover modificare questi Files,
bene mantenere le informazioni del Tab Projects, come mostrato in Figura 31.
Altre informazioni utili sul progetto sono mostrate in basso a sinistra, nella Dashboard,
come mostrato in Figura 33.
Il menu mostra molte altre voci che piano piano bene conoscere al fine di prendere
maggiore dimestichezza con l'ambiente di sviluppo. Questo permetter, durante lo
sviluppo di progetti pi complessi di risparmiare molto tempo.
Dopo molte parole abbiamo in realt ancora un progetto vuoto, per cui a questo punto
bene iniziare a scrivere il nostro programma. Per poter scrivere il programma
necessario creare un file di testo con estensione .c (il nostro programma infatti scritto in
C). Il file creato deve poi essere incluso tra i file di progetto nella directory Project Files.
Per fare questo si pu creare per esempio nella directory del nostro progetto un file
nominato main.c , selezionare la voce Source Files dalla finestra Files di Figura 31 e con il
89
tasto destro del mouse far comparire il menu laterale dal quale selezionare la voce
Add Existing Items...
Selezionata la voce viene mostrata una finestra di dialogo Open File standard, dalla quale
cercare il file appena creato all'interno della nostra directory. Questa tecnica pu essere
pratica qualora il file sorgente esista gi e debba essere incluso nel progetto. Qualora il file
non esista ancora il modo pi pratico riportato in Figura 35, ovvero selezionando la
voce Source Files e premendo il tasto destro del mouse selezionare la voce:
New C Source File...
main
90
Il nome main sta solo ad indicare che il file che stiamo creando quello principale ma
potrebbe avere anche un altro nome. Quando creeremo progetti pi complessi e
divideremo il nostro programma in pi file sorgenti, dovremo creare altri file con
estensione .c, dando ad ognuno un nome significativo che riflette i contenuti del file
stesso. Premendo il tasto Finish, l'ambiente di sviluppo mostrer i cambiamenti come
mostrato in Figura 37. Si noti in particolare che la cartella Source Files, contiene ora il file
main.c mentre la finestra principale sul lato destro mostra in file vuoto main.c .
92
A questo punto bene impostare anche qualche opzione del Linker, come riportato in
Figura 40. In particolare necessario impostare la voce XC Linker e l'Option Categories
Memory Model. Tra le opzioni che vengono visualizzate necessario cambiare l'opzione
Size of Double e Size of Float, da 24 bit a 32 bit. Questo cambiamento serve solo ad evitare
che compaiano le seguenti Warning:
:: warning: 24-bit floating point types are not supported; float have been
changed to 32-bits
:: warning: 24-bit floating point types are not supported; double have
been changed to 32-bits
93
Fatto questo facciamo un passo indietro, anche se in realt quanto segue potr essere
fatto in contemporanea agli altri cambiamenti. Per poter compilare i codice di esempio del
testo si fa uso della libreria LaurTec per PIC18, scaricabile gratuitamente dal sito
LaurTec74. Una volta scaricata e salvata la libreria, necessario impostare l'ambiente di
lavoro al fine di utilizzarle. In Figura 41 mostrato un dettaglio dei percorsi da aggiungere
nel campo Include Directories. In particolare necessario includere i seguenti tre percorsi
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.2.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.2.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.2.0\src
Le tre directory, conf, inc e src rappresentano rispettivamente le directory dove sono
presenti i file di configurazione, gli header file di libreria e i codici sorgenti delle librerie 75.
Nei primi esempi del testo basta includere il solo percorso conf, visto che non faremo uso
di librerie per alcuni Capitoli.
Impostare il percorso delle librerie un passo sempre necessario che bisogna
compiere in qualunque ambiente di sviluppo e microcontrollore utilizzato.
Errori nell'impostare i percorsi di libreria rappresentano normalmente la
maggioranza dei problemi che si incontrano quando si inizia programmare.
74
75
La libreria viene fornita in file zip, per cui necessario scompattare il file prima di poterlo utilizzare.
Per ora non vi preoccupate per come sono strutturate le directory e sul significato di header file, maggiori dettagli saranno
dati a tempo debito.
94
A questo punto abbiamo realmente completato ogni impostazione per poter compilare
con successo il nostro programma. Tali operazioni andranno ripetute alla creazione di
ogni progetto. Per maggiori dettagli sulle altre impostazioni del compilatore e del Linker,
rimando alla user guide del compilatore XC8 che possibile trovare all'interno della
directory di installazione del compilatore stesso. Un esempio di percorso dove trovarla il
seguente:
C:\Program Files (x86)\Microchip\xc8\v1.21\docs
95
In ogni modo questa non la sede in cui vi insegner l'approccio alla programmazione,
ma non posso non consigliarvi la lettura di testi per l'ingegneria del software.
giunto il momento di programmare...
Copiate ed incollate brutalmente il seguente codice, facendo attenzione che non
compaiano simboli strani dovuti per esempio al passaggio da una pagina all'altra. Quando
sarete pi esperti scriverete il vostro programma, ma in questa fase ho solo interesse nel
mostrare i passi da compiere per scrivere e compilare il file sorgente, ovvero il file C. Il
programma non molto complesso, ma dal momento che ogni sua linea una cosa
nuova, sar analizzato in dettaglio, ma tempo al tempo. Una volta scritto o copiato il
programma nel file main.c, salvate il file e avviate la compilazione per mezzo della voce
del menu
Run Build and Clean Main Project
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void){
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutti ingressi e RD0 come uscita
LATD = 0x00;
TRISD = 0b11111110;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
LATDbits.LATD0 = 1;
// Ciclo infinito
while (1) {
}
96
97
used
used
used
used
used
2Eh
0h
7h
0h
8h
(
(
(
(
(
46)
0)
7)
0)
8)
of
of
of
of
of
8000h
800h
7h
100h
8h
bytes
bytes
words
bytes
bytes
( 0.1%)
( 0.0%)
(100.0%)
( 0.0%)
(100.0%)
Per eliminare gli errori bene procedere risolvendo il primo errore, infatti pu capitare
che altri errori siano solo dovuti ad effetti collaterali del primo. Per tale ragione, quando si
pensa di aver risolto il primo errore, bene ricompilare il codice. Questo dove essere
ripetuto fino alla eliminazione di ogni errore.
Qualora dovessero essere generate delle Warning il nostro file .hex
generato senza problemi.
viene
A questo punto bene mettere in evidenza che ogni programma, per buona pratica di
programmazione e robustezza delle applicazioni, deve essere compilato senza nessun
messaggio di Warning. Qualora dovessero essere visualizzati dei messaggi di Warning
bene capire fino in fondo il significato del messaggio e fare in modo che non venga
visualizzato77. Le Warning possono nascondere infatti dei bug subdoli...la cui caccia pu
essere poco divertente. Dopo aver compilato il nostro primo programma, diamo
un'occhiata all'interno della directory 01_Hello_World per vedere i vari file che sono stati
generati per il nostro progetto.
Un modo per far scomparire tutti i messaggi di Warning quello di disabilitarle per mezzo delle opzioni di progetto associate
al compilatore XC8, sezione Preprocessing and Messages. Normalmente vengono visualizzati sia gli errori che le Warning ma pu
essere scelto di visualizzare solo gli errori. Questa pratica per altamente sconsigliata, le Warning devono essere eliminate
capendo la causa che le ha generate.
99
Dalla Figura 43, selezionando il Tab Files, possibile vedere l'architettura delle cartelle
all'interno della cartella principale. In particolare tutti i file generati dalla compilazione
sono salvati all'interno della cartella
dist default production
Tra i vari file generati vi il file .map ovvero il mapping file. Questo file possiede al suo
interno le informazioni relative alle varie variabili e registri utilizzati nel programma. Viene
generato come file di output dal Linker. Al suo interno possibile anche trovare le
informazioni associate alla quantit di memoria utilizzata (visualizzate anche nella finestra
di Output).
Un modo pi pratico per vedere la memoria utilizzata per mezzo della finestra
Dashboard visualizzata in Figura 44. Nel caso in cui la Dashboard non sia visibile possibile
mostrarla dal menu:
78
Il Linker file contiene le informazioni relative all'organizzazione della memoria e il suo possibile utilizzo. In particolare
impone dei vincoli in maniera da permettere al Linker di comprendere dove poter posizionare il programma. Maggiori
dettagli li si comprenderanno nel corso del testo.
100
Window Dashboard
In particolare mostrato l'utilizzo della memoria RAM e Flash. Nel caso specifico viene
mostrato un suo percentuale pari a 0%. Nel caso della memoria RAM, non avendo
dichiarato alcuna variabile non ci sorprende ma per la memoria Flash, osservando con
maggior dettaglio si sono usati 68 byte ovvero 0x44 byte. 68 Byte su 32700 disponibili
sono visualizzati come 0%. Il numero di byte pari a 68 si potrebbe anche ricavare dal hex
file sommando tutti i campi numero di byte il cui Tipo dati pari a 00.
In ultimo bene evidenziare che il numero e tipo di file generati viene a dipendere
dalle impostazioni del compilatore.
Programmare il microcontrollore
Una volta scritto il nostro programma e compilato con successo, possibile caricare il
nostro file .hex all'interno del nostro PIC. Questa fase pu essere differente a seconda del
programmatore utilizzato. Nel caso di programmatori ufficiali Microchip, supportati
dall'ambiente MPLAB X stato gi selezionato durante la fase di creazione del progetto.
Qualora il programmatore non fosse stato selezionato, andr impostato tra le propriet
del progetto, come mostrato in precedenza 79. I passi che seguono fanno riferimento al
programmatore PICKIT 3 ma sono in realt simili anche per gli altri programmatori
integrati con MPLAB X .
In Figura 45 riportato il dettaglio della Tool Bar, qualora il programmatore strato
selezionato (selezionando il simulatore, come si vedr, la Tool Bar differente).
79
Qualora si facesse uso di un programmatore con integrato con l'ambiente di sviluppo MPLAB X, necessario aprire la GUI
disponibile per il programmatore e selezionare il relativo PIC da programmare. Successivamente sar necessario importare il
file .hex da caricare.
101
Nel caso in cui il programmatore non sia collegato o diverso da quello impostato
durante la creazione del progetto, viene visualizzata la seguente finestra di messaggio:
Figura 46: Finestra di messaggio nel caso in cui il programmatore non sia collegato o sia stato cambiato.
Le altre icone presenti nella Tool Bar permettono rispettivamente di leggere il contenuto
del microcontrollore:
Di mantenere in stato di Reset la scheda dopo la programmazione:
Nel caso specifico delle schede di sviluppo usate nel testo, essendo l'alimentazione pari
a 5V, non ci sono problemi, per cui possibile selezionare il check box Do not show this
message again.
Si noti che al termine della riga non presente il punto e virgola, che invece verr utilizzato al termine di ogni istruzione C.
103
indirettamente il file pic18f4550.h, che riflette quello selezionato nel progetto, per cui
cambiando microcontrollore viene incluso il relativo file. Questo file, come detto,
contiene le informazioni del PIC che si sta utilizzando81.
Il file pic18f4550.h incluso e il file di Linker sono tra loro sempre coerenti, visto che
l'informazione prelevata dal progetto stesso, ovvero dal microcontrollore che abbiamo
selezionato. Sebbene sia possibile includere manualmente il file pic18f4550.h, facendo
uso della modalit CCI (Common C Interface) necessario includere il file xc.h. In
particolare questo file deve essere incluso in tutti i file di progetto che vengono inclusi
nell'applicazione. Come si vedr negli esempi successivi la direttiva #include viene anche
utilizzata per includere file di libreria.
A seguire viene incluso un secondo file:
#include <PIC18F4550_config.h>
Questo file viene prelevato dalla libreria LaurTec e contiene tutte le configurazioni
necessarie per il PIC18F4550, per tale ragione durante la creazione del progetto stato
necessario impostare il percorso d'inclusione della libreria LaurTec, proprio per
permettere che questo file sia trovato. Dal nome si capisce che in base al microcontrollore
utilizzato bisogna cambiare file82.
Posizionando il mouse sopra il file e premendo CTRL+B possibile aprire il file stesso
ed esplorare il suo contenuto. In particolare possibile trovare la nuova direttiva #pragma
utilizzata per impostare le varie configurazioni 83. Tra le principali che bisogna impostare vi
sono le seguenti.
#pragma
#pragma
#pragma
#pragma
config
config
config
config
FOSC = HS
WDT = OFF
LVP = OFF
PBADEN = OFF
Il compilatore XC8 diversamente dal precedente compilatore C18, richiede che tutte le
configurazioni siano impostate, altrimenti vengono generate delle Warning. Proprio per
tale ragione, visto il numero elevato di configurazioni necessarie ho creato un file che le
racchiude tutte.
Come visto durante la spiegazione dell'architettura PIC18, ogni PIC ha diversi registri
di configurazione (registri CONFIG) il cui scopo settare l'hardware di base del PIC
stesso in modo che possa funzionare correttamente ogni volta che viene alimentato il
PIC. Per poter cambiare il valore dei registri di configurazione necessario utilizzare
l'opzione config, seguita dal nome dell'opzione da impostare. Per ogni particolare
configurazione possibile assegnare vari valori a seconda dell'hardware disponibile. Il
nome delle opzioni e le loro possibili impostazioni sono mostrate per ogni PIC all'interno
del file 18f4550.html che possibile trovare nella directory chips presente nella directory
d'installazione del compilatore:
81
82
83
Tra le informazioni presenti nel file vi il nome dei registri che in generale hanno lo stesso nome utilizzato anche sul
Datasheet 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.
I file di configurazione disponibili nella libreria LaurTec possibile trovarli all'interno della directory conf.
La direttiva #pragma pu essere utilizzata anche in altri contesti oltre alla configurazione dei parametri interni del
microcontrollore.
104
Il nome delle opzioni non sempre uguale per tutti i PIC, dunque bene sempre fare
riferimento al file indicato per maggior informazioni. L'utilizzo di parametri errati genera
un errore del tipo opzione non valida84.
Qualora il file di configurazione non fosse disponibile nelle librerie LaurTec, possibile
crearne uno facendo uso dello strumento integrato all'interno di MPLAB X come
riportato in Figura 48. Questo strumento possibile trovarlo nel menu:
Window PIC Memory Views Configuration Bits
Le configurazioni impostate si riflettono all'interno del file .hex che viene generato alla
fine della compilazione del progetto, per cui vengono programmate all'interno della
microcontrollore durante la stessa fase di programmazione.
Molti programmi venduti con i programmatori permettono anche di impostare i bit di
configurazione prima di programmare il PIC, sovrascrivendo eventuali valori letti dal file
.hex.
Tra le impostazioni necessarie per le schede di sviluppo proposte, FOSC impostato
ad HS, ovvero ad alta velocit questo poich le schede di sviluppo fanno uso di un
quarzo da 20MHz. Qualora si voglia far uso del quarzo interno si deve selezionare
l'opzione appropriata. Volendo usare il modulo USB a sua volta necessario l'utilizzo di
un'altra opzione. Qualora si abbia l'esigenza di cambiare delle configurazioni si consiglia di
lasciare il file originale PIC18F4550_config.h invariato e creare un nuovo file cambiando
84
Le varie configurazioni possibile impostarle, solo per mezzo del codice e non pi per mezzo della GUI, come era possibile
per mezzo di MLAB IDE.
105
MHz
MHz
MHz
MHz
PLL
PLL
PLL
PLL
Src:
Src:
Src:
Src:
/3]
/6]
/4]
/2]
I commenti in C devono essere preceduti dal doppio slash //. I commenti scritti in
questo modo devono per stare su di una sola riga. Quando si ha l'esigenza di scrivere
pi righe di commento bisogna scrivere // all'inizio di ogni riga o scrivere /* alla prima
riga e scrivere */ alla fine dell'ultima riga. Un possibile esempio di commento a riga
multipla il seguente:
// questo un commento
// che non entrerebbe su una sola riga
o anche
/* questo un commento
che non entrerebbe su una sola riga */
o ancora
/* questo un commento
che non entrerebbe su una sola riga
*/
o ancora
/*
* questo un commento
* che non entrerebbe su una sola riga
*/
85
La modalit LVP pu risultare pratica qualora non sia disponibile la tensione normalmente utilizzata per programmare,
normalmente pi alta di quella di alimentazione. Nel nostro caso non un problema poich la tensione di programmazione
ottenuta direttamente dal programmatore.
106
Normalmente il commento, oltre a descrivere una riga di codice, viene anche utilizzato
per disattivare eventuali righe di codice che stanno potenzialmente creando problemi. Per
agevolare la fase di Debug in cui si voglia commentare una parte di codice da disattivare
per poi riattivarla in un secondo momento, l'editor di MPLAB X fornisce i seguenti
pulsanti:
87
88
Ogni funzione contiene un certo numero di istruzioni per mezzo delle quali la funzione svolge il compito per cui stata
creata.
Tutte le funzioni all'interno del nostro progetto devono essere individuate da un nome univoco.
Quanto appena detto non deve essere preso per oro colato, ma un modo valido per semplificare la trattazione
dell'argomento. Infatti dietro le quinte, prima della funzione main sono presenti altre funzioni nascoste, dalle quali viene
richiamata la funzione main.
107
Per molto tempo ho usato questo secondo modo, ma dal momento che tutti i
programmatori pi famosi fanno pi uso della prima modalit, ho deciso di utilizzare la
prima forma89. L'aderire ad una versione o l'altra, non di vitale importanza ma
importante che una volta scelta la preferita si mantenga lo stile scelto. Questo rende il
codice pi leggibile, grazie alla sua uniformit. un qualcosa che apprezzerete quando
dovrete scrivere del codice che va oltre qualche linea per salutare il mondo, e lo
apprezzerete ancor pi quando sarete costretti a rimettere mani sul codice a distanza di
tempo.
Vediamo ora il programma che abbiamo scritto all'interno delle nostre parentesi
graffe. Queste sono le prime righe di codice:
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
90
Questa scelta non mi fa rientrare tra i programmatori pi famosi ma dovrebbe aiutare la gente che ha letto i libri di
programmatori famosi, visto che in questo testo user lo stesso standard.
Si fa notare che il nome LATA non era stato definito da nessuna parte. Questo discende dal fatto che il nome in realt
definito all'interno del file p18f4580.h incluso indirettamente includendo il file xc.h.
108
Vi sarete forse chiesti per quale ragione la PORTA stata impostata con tutti i pin
come ingresso, anche se in realt la PORTA non affatto utilizzata nel nostro
programma. La ragione solo a scopo precauzionale, infatti, compatibilmente con
l'hardware della scheda, bene porre un pin non utilizzato come ingresso in modo da
limitare problemi di cortocircuito accidentale91.
Tutte le porte seguono le impostazioni appena descritte per la PORTA, tranne la
PORTD, dal momento che presente il LED con il quale si vuole salutare il mondo. I pin
dei PIC possono pilotare senza problemi un LED standard fino a correnti massime di
20mA92. Per ragioni pratiche, quando devo impostare un bit noto a 0 o 1 preferisco la
rappresentazione binaria, visto che non necessario sapere il valore rappresentato dal
codice binario. In particolare per impostare RD0 come uscita si ha il seguente codice:
// Imposto PORTD tutti ingressi e RD0 come uscita
LATD = 0x00;
TRISD = 0b11111110;
In questo caso per bisogna fare attenzione che il numero totale di 0 e 1 sia 8, infatti
facile metterne uno 0 o 1 di troppo e in meno. Qualora si dovesse verificare questa
situazione, si hanno normalmente comportamenti strani, visto che il valore scritto nel
registro TRISx non quello che in realt si vuole.
Una volta inizializzati tutti i registri d'interesse viene acceso il LED ponendo ad 1 il bit del
91
92
Sebbene porre un pin come ingresso possa aiutare durante la fase di sviluppo per evitare dei cortocircuiti accidentali, qualora
il sistema sia progettato per applicazioni a basso consumo necessario che gli ingressi non siano floating ovvero lasciati
liberi. Questo pu infatti causare delle transazioni sporadiche del livello logico in ingresso, dovute al rumore, causando dei
consumi maggiori. Per tale ragione in questi casi si preferisce impostare un pin come uscita o come ingresso ma con
resistore di pull-up o pull-down, in maniera da tenere vincolato il livello logico in ingresso.
Si ricorda che, anche se possibile pilotare un LED direttamente con il pin di uscita del PIC, ogni porta di uscita ha un
limite massimo di corrente. Per maggiori informazioni sui limiti massimi di ogni porta si rimanda alle caratteristiche
elettriche descritte nel Datasheet del PIC utilizzato. In generale tale valore non supporta il caso limite in cui ogni pin si trova
a fornire il massimo della corrente. Oltre a questo limite bisogna anche osservare il limite massimo della corrente che
possono fornire rispettivamente le linee Vpp e Vdd (Vcc GND).
109
Un altro modo per accendere il LED, visto che il resto dei pin della PORTD impostato
come input, potrebbe essere:
LATD = 0x01;
Ogni porta oltre ad avere il registro LATx possiede una variabile particolare (una
struttura) nominata LATxbits che permette di accedere ogni singolo pin della porta stessa
con il suo nome. La struttura associata al registro LATx presente anche per gli altri
registri interni al PIC e torna utile quando si ha la necessit di leggere o scrivere singoli
bit93. Una volta acceso il LED, presente il seguente codice:
while (1){
}
Nel seguito del testo i commenti sono invece scritti prima del codice, ovvero
// Imposto PORTD tutti ingressi e RD0 come uscita
LATD = 0x00;
93
Come detto il LED si sarebbe potuto accendere anche con il registro PORTD.
110
TRISD = 0b11111110;
Riassunto
In questo capitolo si mostrato come creare il primo progetto utilizzando l'ambiente di
sviluppo MPLAB X. In particolare sono stati messi in evidenza le varie impostazioni
richieste per creare un progetto e come configurare il compilatore al fine di poter
utilizzare la configurazione CCI. Il primo programma di esempio stato mostrato con
molti dettagli anche se alcuni aspetti della programmazione sono stati rimandati nei
capitoli che seguiranno. Sebbene posto in secondo piano stato mostrato anche lo stile
di programmazione adottato nel testo, che si consiglia di utilizzare al fine di avere uno
stile di programmazione definito.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
112
Capitolo V
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, esclusa la parte della della
simulazione, in cui non necessario alcun hardware, richiesto realizzare su Breadboard
lo schema elettrico di Figura 49 o far uso delle schede di sviluppo impostate come
descritto nel paragrafo successivo.
113
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo, la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 50: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 51: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
La scheda Freedom Light possiede i LED solo sui pin RD0 e RD1. Per poter
visualizzare gli altri LED necessario montarne altri sul connettore di
espansione, facendo per esempio uso della scheda di espansione Breadboard
PJ7011 o limitare il Debug su due soli LED.
114
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione software particolare oltre alla
semplice creazione di un progetto.
Gli esempi mostrati sono spiegati nel dettaglio, mostrando il codice Assembly,
per cui, a seconda delle impostazioni delle ottimizzazioni e della versione del
compilatore utilizzato, possono essere presenti delle differenze tra il codice
mostrato e quello che otterrete in fase di compilazione.
I vari punti, sebbene sequenziali possono essere ripetuti in maniera ciclica dopo una fase
di Test e Debug, ovvero di controllo. In particolare se qualcosa non dovesse funzionare ci
si ritrova a dover riscrivere il codice o rianalizzare anche il problema, per poi ripetere i
passi che seguono.
Indipendentemente da quanto tempo si sia passato ad analizzare il problema, si
possono verificare difficolt pi o meno banali che necessario risolvere. Avere una
scheda di sviluppo sulla quale caricare il programma certamente un aiuto (sempre che sia
possibile osservare fisicamente il problema). Nel caso in cui il problema sia difficilmente
osservabile, come per esempio una comunicazione seriale che non funziona, risolvere la
situazione pu risultare tutt'altro che banale. Gli approcci che vengono seguiti al fine di
risolvere questa tipologia di problemi possono essere diversi e l'esperienza del
programmatore gioca sicuramente un ruolo importante. Questa fase dello sviluppo, come
detto, prende il nome di Debug o Debugging, e mette a dura prova il programmatore. La
soluzione del problema richiede spesso aspetti multidisciplinari che vanno ben oltre la
semplice programmazione. Spesso l'esperienza pratica e l'utilizzo di strumenti di
laboratorio come il multimetro digitale e l'oscilloscopio, ritornano molto utili.
Tra gli strumenti per il Debug che la Microchip mette a disposizione, vi il Simulatore
MPLAB SIM che permette di emulare il microcontrollore a livello software, ovvero senza
aver bisogno di schede di sviluppo dove caricare il programma. Una seconda modalit per
investigare o comprendere un problema per mezzo della funzione di Debug offerta dai
Debugger, che permettono di accedere ai registri interni del PIC durante l'esecuzione del
programma. I programmatori Microchip della serie PICKIT 2, PICKIT 3, ICD 3 possono
tutti funzionare come Debugger oltre che come programmatori. Le performance dei vari
Debugger sono in generale proporzionali al costo dello stesso. I limiti sono associati al
116
A questo punto potete creare un nuovo progetto e creare un file main all'interno del
quale copiare il codice sorgente che segue. L'esempio leggermente pi complesso del
precedente ed in particolare si sono introdotti alcuni concetti nuovi che per il momento
non necessario spiegare nel dettaglio e spero che il semplice commento possa chiarire le
prime domande. Il programma introduce all'inizio della funzione main delle variabili
117
mentre prima del ciclo infinito while introduce un conteggio for che permette di contare,
al fine di perdere tempo, ovvero abbiamo creato una pausa.
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void){
// Variabili per memorizzare dei dati/informazioni
unsigned char mia_variabile = 0x55;
unsigned char mia_stringa [] = "LaurTec";
unsigned int contatore = 0;
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD come porta di uscita
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
LATD =
mia_variabile;
Alla pressione del tasto viene compilato il programma, il quale, qualora non siano presenti
errori di compilazione, viene avviato in automatico. Se tutto andato a buon fine
compare la barra degli strumenti aggiuntiva mostrata in Figura 53 (i tasti abilitati possono
118
differire).
Run to Cursor: Il tasto permette di eseguire il programma fino alla posizione del
cursore del mouse, preventivamente posizionato in un'istruzione d'interesse nel
programma.
Focus Cursor at PC: Posiziona il cursore del mouse alla posizione attuale del PC.
Dal momento che l'esecuzione del programma si avvia in automatico con la fase di
Debug, ed piuttosto veloce, non ci si rende conto di nulla. Per tali ragioni, la
simulazione di un programma viene normalmente eseguita passo passo, ovvero
un'istruzione alla volta. Per fare questo necessario prima arrestare la simulazione in
corso premendo il tasto Pause:
94
Una funzione rappresenta un insieme di istruzioni le quali possono essere richiamate per mezzo di un nome. Maggiori
dettagli sono dati in seguito chiarendo e definendo meglio il tutto, visto che una funzione certamente di pi che un
semplice insieme di istruzioni.
119
120
In particolare si noti che nella finestra Dashboard sono presenti le seguenti informazioni
relative alla fase di Debug:
Queste mostrano il numero di Breakpoint (BP) utilizzati (il numero di quelli disponibili
viene a dipendere dal Debugger utilizzato). In particolare interessante notare come alcune
risorse del sistema siano dedicate al Debug, ovvero 32 Byte di RAM e 577 Byte di
memoria Flash. Questo significa che in applicazioni particolarmente grandi, qualora
stessimo utilizzando tutta o quasi la memoria RAM o Flash, potremmo trovarci nella
situazione di non poter effettuare il Debug a causa dell'impossibilit di poter assegnare
risorse al Debugger. Negli esempi che seguiranno questo non un problema visto le ridotte
dimensioni degli stessi. Si ricorda nuovamente che quanto verr detto a breve per la fase
di Debug vale anche per la fase di simulazione, tranne alcune eccezioni che sono messe in
evidenza.
Il codice Assembly
Durante la fase di Debug, soprattutto quando la nostra esperienza aumenter, torna
particolarmente utile poter visualizzare il codice Assembly, ovvero il codice generato dal
compilatore quale traduzione del nostro programma scritto in C. Ogni passo eseguito nel
codice sorgente si traduce in uno o pi istruzioni Assembly. Poter esplorare il codice
Assembly particolarmente utile anche in fase di ottimizzazione, al fine di valutare se
cambiando il nostro codice C si riscontrano differenze nel numero di istruzioni Assembly
utilizzate. Negli esempi mostrati nei prossimi Capitoli il codice Assembly generato dal
compilatore spesso utilizzato nella spiegazione delle varie istruzioni al fine di mostrare il
comportamento del compilatore. Per visualizzare il codice Assembly basta andare al menu:
Window Output Disassembly Listing File
Nel caso specifico del nostro esempio, il codice Assembly che si ottiene :
1:
2:
3:
4:
5:
6:
7:
8:
9:
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void){
// Variabili per memorizzare dei dati/informazioni
unsigned char mia_variabile = 0x55;
121
7CBC
7CBE
7CC0
7CC2
10:
7CC4
7CC6
7CC8
7CCA
7CCC
7CCE
7CD0
7CD2
7CD4
11:
12:
13:
7CD6
7CD8
14:
7CDA
15:
16:
17:
7CDC
7CDE
18:
7CE0
19:
20:
21:
7CE2
7CE4
22:
7CE6
23:
24:
25:
7CE8
7CEA
26:
7CEC
7CEE
27:
28:
29:
7CF0
7CF2
30:
7CF4
31:
32:
7CF6
7CF8
33:
34:
35:
36:
7CFA
37:
7CFC
38:
6E01
0E55
6E0A
5001
EE20
F00B
EE10
F002
0E08
CFDE
FFE6
2EE8
D7FC
0E00
6E89
6892
0E00
6E8A
6893
0E00
6E8B
6894
0E00
6E8C
0E00
6E95
6896
C00A
FF8C
LATD = mia_variabile;
MOVFF mia_variabile, LATD
NOP
0E00
6E8D
908C
808C
// Ciclo infinito
while (1) {
LATDbits.LATD0 = 0;
BCF LATD, 0, ACCESS
LATDbits.LATD0 = 1;
BSF LATD, 0, ACCESS
}
122
39:
40:
possibile notare che il codice C riportato tra il codice Assembly, seguito dalla
traduzione dello stesso. Sulla sinistra presente il valore dell'indirizzo di memoria in cui
salvata una determinata istruzione, ovvero rappresenta i vari valori che assume il Program
Counter durante l'esecuzione del programma.
La finestra appena visualizzata sebbene visualizzi il codice, non rappresenta altro che la
visualizzazione del file di output generato durante la fase di compilazione, che possibile
trovare tra i vari file nella cartella del progetto.
Durante la fase di Debug, volendo eseguire il codice e visualizzare la particolare istruzione
in esecuzione, bisogna aprire il codice Assembly dal menu:
Window Debugging Disassembly
La finestra con il codice Assembly pu essere spostata (trascinata) come in Figura 56, al
fine di visualizzarla in contemporanea al codice sorgente C.
Nel caso particolare mostrata una fase di Debug gi avviata in cui alcune istruzione
sono state gi eseguite. In particolare la riga selezionata rappresenta l'istruzione che verr
eseguita al prossimo passo e viene visualizzata sia nel codice C che nel codice Assembly. Si
noti che nel codice Assembly l'istruzione TRISD=0x00 tradotta in due passi, ovvero due
istruzioni base. Prima si carica il valore 0x00 nell'accumulatore (Registro W) e poi si carica
l'accumulatore nel registro TRISD. Queste due istruzioni sono rispettivamente
all'indirizzo 0x7CEC e 0x7CEE. In alto a destra dell'immagine possibile notare che il
Program Counter posizionato proprio sull'indirizzo 0x7CEC. Premendo nuovamente il
tasto Step Over per eseguire una nuova istruzione, si ha che il PC si posiziona su 0x7CF0,
ovvero all'indirizzo associato alla nuova istruzione LATE=0x00. Questo mostra che
l'eseguire una istruzione alla volta in C spesso tradotta in pi istruzioni Assembly, ma il
tutto trasparente.
123
o si posiziona un Breakpoint (punto di arresto) nel punto del programma in cui si vuole che
l'esecuzione si arresti in attesa di un nuovo comando.
Per posizionare un Breakpoint basta cliccare con il tasto sinistro del mouse sul numero
della riga d'interesse, come mostrato in Figura 57. La riga con il Breakpoint viene
124
evidenziata in rosso e un quadrato rosso al posto del numero della riga stessa.
Nel caso specifico il Breakpoint usato solo per eseguire poche istruzioni e non
rappresenta il caso tipico di utilizzo. Come detto la funzione Run to Cursor avrebbe
funzionato altrettanto bene. Un utilizzo importante del Breakpoint nell'arrestare il
programma quando si verifica una determinata condizione o un determinato codice viene
eseguito. Supponiamo per esempio che il programma svolge delle determinate funzioni,
quali per esempio il controllo della luce ambientale. Qualora dovesse essere premuto un
pulsante si potrebbe per esempio voler accendere una luce. Supponiamo che durante i
nostri test ci accorgiamo che alla pressione della luce, invece di accendere la luce ne
accendiamo un'altra. In questo caso potrebbe essere interessante mettere un Breakpoint
all'inizio del codice del programma che viene eseguito alla pressione del tasto e da quel
punto in poi eseguire passo passo per vedere il contenuto dei registri e variabili di sistema.
A seconda dell'ambiente di sviluppo utilizzato, ad un Breakpoint possono essere
associate anche altre funzioni, che non necessariamente implicano l'arresto dell'esecuzione
del programma. Alcune funzioni interessanti potrebbero essere il dump (salvataggio) dei
125
registri interni o intervallo di memoria, ogni qual volta viene incontrato il Breakpoint,
salvando il tutto in un file di testo (nel caso di MPLAB X questa funzione non
presente). Per vedere le propriet del Breakpoint basta posizionarsi con il mouse sul
quadrato rosso del Breakpoint e premere il tasto destro del mouse. Selezionare poi la voce:
Breakpoint Properties
Ogni volta che si inseriscono i Breakpoint possibile vedere che il numero mostrato
nella Dashboard aumenta. Inserendo l'ultimo dei 3 disponibili, viene mostrato un avviso
come in Figura 61, in cui si avvisa che l'ultimo disponibile sta per essere usato. Nel caso
di una simulazione non sono presenti questi limiti.
126
Si fa notare che l'esempio un po' limite visto l'uso ravvicinato degli stessi Breakpoint.
127
96
Nel caso dei microcontrollori MSP430, vista l'architettura di von Neumann e la presenza di un solo bus degli indirizzi, i
Breakpoint sono piuttosto flessibili ed offrono indifferentemente un controllo sull'accesso di un indirizzo in memoria Flash,
RAM (variabili) o di un registro delle impostazioni.
128
Per mezzo del tasto destro del mouse e il menu Add Watch, possibile inserire una
qualunque variabile dichiarata nel programma o altro registro interno, come riportato in
Figura 65, basta infatti scrivere il nome del registro o variabile all'interno della casella di
testo.
Figura 65: Finestra per aggiungere una variabile nella finestra Watch.
Il contenuto della variabile o registro viene visualizzato all'interno della colonna Value.
In particolare il suo valore espresso in esadecimale. Per visualizzare altri formati
possibile cliccare il tasto destro con il mouse sopra la il nome Value, ottenendo la finestra
di Figura 66. Per mezzo di questa finestra possibile selezionare le colonne da aggiungere.
129
La colonna Value, diversamente dalle colonne che hanno il nome del formato nella
colonna stessa (Char, Hexadecimal, Decimal, Binary) pu mostrare un qualunque formato.
Per selezionare quello di maggior interesse basta cliccare con il tasto destro sulla tabella e
selezionare Display Value Columns As, e selezionare il valore d'interesse.
Oltre alla finestra Watch possibile aprire anche la finestra Variables, sempre dal menu
Debugging, ma il risultato praticamente lo stesso della finestra Watches. Un modo molto
semplice per aggiungere delle variabili, piuttosto che scrivere il nome nella tabella Watches,
quello di porre il cursore del mouse sulla variabile scritta nel codice sorgente e con il
tasto destro del mouse selezionare la voce
New Watch...
E' importante notare che all'interno della tabella Watches possibile aggiungere molte
variabili e monitorarle in contemporanea. Il valore delle variabili viene aggiornato ad ogni
130
Program Memory
File Registers
SFRs
Configuration Bits
EE Data Memory
User ID Memory
131
Si noti che l'indirizzo della memoria Flash viene selezionato sia nella finestra Assembly che
nella finestra della memoria Flash.
Analisi temporale
In alcune applicazioni torna utile poter eseguire un determinato insieme di istruzioni in
un tempo noto o inferiore a quanto richiesto da una determinata specifica di progetto.
Queste applicazioni ed esigenze normalmente non rientrano nelle applicazioni degli
esempi base mostrati in questo testo, ma comunque bene tenerlo a mente per sviluppi
futuri.
Dal momento che il tempo di esecuzione viene a dipendere dal clock utilizzato, si
capisce che necessario impostare tale parametro all'interno dell'ambiente di sviluppo.
Questo possibile farlo selezionando il Simulatore tra gli strumenti di sviluppo del
progetto, ovvero nelle propriet del progetto. Selezionato il Simulatore possibile
selezionare la voce Simulator come riportato in Figura 70.
Da questa finestra possibile impostare la frequenza del Clock del ciclo istruzioni,
ovvero pari a quella del clock diviso 4, che nel caso di Freedom II pari a 5MHz, ovvero
con un clock pari a 20MHz. importante notare come in questo caso si sia considerato
20MHz come il cristallo esterno, ma bene notare che la frequenza di clock potrebbe non
coincidere con quella del cristallo. In particolare il clock utilizzato per l'esecuzione delle
istruzione potrebbe provenire dall'uscita del PLL che in generale ha una frequenza pi alta
del cristallo. Nel valutare la frequenza di Clock bisogna anche tenere a mente eventuali
132
divisioni effettuate da un pre-scaler o post-scaler. Per tale ragione bisogna fare attenzione
alle impostazioni del modulo associato al Clock, il quale differisce in base al modello del
PIC utilizzato. Detto questo, impostati i nostri 5MHz, possibile premere ok.
A questo punto si pu avviare il nostro cronometro dal menu:
Debugging Stopwatch
Si possono poi posizionare due Breakpoint nei punti tra cui si vuole misurare il tempo di
esecuzione del programma ed avviare la simulazione. Il Programma si arresta al
raggiungimento del primo Breakpoint come mostrato in Figura 71.
Dalla finestra possibile vedere che sono tate eseguite 126 istruzioni e il tempo di
esecuzione con un clock di 5MHz (riportato in basso a destra), pari a 25.2us. Premendo
il tasto:
133
possibile far arrestare il Program Counter sul secondo Breakpoint. A questo punto il
nostro strumento Stopwatch visualizza il nuovo tempo di esecuzione, come riportato in
Figura 73.
In particolare il tempo di esecuzione mostra che sono stati impiegati 2.2us per eseguire
11 istruzioni. Controllando il codice Assembly si pu vedere che effettivamente tra il
primo Breakpoint ed il secondo sono appunto eseguite 11 istruzioni.
!// Imposto PORTA tutti ingressi
!LATA = 0x00;
0x7FD6: MOVLW 0x0
0x7FD8: MOVWF LATA, ACCESS
!
TRISA = 0xFF;
0x7FDA: SETF TRISA, ACCESS
!// Imposto PORTB tutti ingressi
!
LATB = 0x00;
0x7FDC: MOVLW 0x0
0x7FDE: MOVWF LATB, ACCESS
!
TRISB = 0xFF;
0x7FE0: SETF TRISB, ACCESS
!// Imposto PORTC tutti ingressi
!
LATC = 0x00;
0x7FE2: MOVLW 0x0
0x7FE4: MOVWF LATC, ACCESS
!
TRISC = 0xFF;
0x7FE6: SETF TRISC, ACCESS
!// Imposto PORTD come porta di uscita
!
LATD = 0x00;
0x7FE8: MOVLW 0x0
0x7FEA: MOVWF LATD, ACCESS
!
TRISD = 0x00;
0x7FEC: MOVLW 0x0
0x7FEE: MOVWF TRISD, ACCESS
134
e
0x7FEA: MOVWF LATD, ACCESS
L'esempio appena mostrato non mette in evidenza l'utilit dello strumento, infatti per
sole 11 istruzioni si potrebbe fare un conteggio manuale. Nel caso di codice di
programma pi lunghi questo potrebbe non essere pi pratico per cui lo strumento
StopWatch di particolare importanza. Un utile esercizio potrebbe essere svolto
posizionando un altro Breakpoint dopo il ciclo for e vedere come il numero delle
istruzioni eseguite e il tempo di esecuzione aumentano anche se apparentemente il
numero di righe di codice eseguite non sono molte.
A solo scopo informativo si fa presente che il tempo di esecuzione del programma
d'interesse viene a dipendere anche dalla versione del compilatore e dal livello di
ottimizzazione utilizzato. Infatti in ambedue i casi si potrebbero avere dei codici
Assembly differenti da quello mostrato, per cui il numero di istruzioni da eseguire sarebbe
diverso. Questa una ragione per cui in applicazioni sensibili al tempo di esecuzione del
programma, come per esempio un sistema operativo, si garantisce il funzionamento dello
stesso per determinate versioni del compilatore e livelli di ottimizzazione.
Frequentemente per rendere il codice sorgente indipendente dal compilatore e livello di
ottimizzazione, si scrivono delle parti di codice direttamente in Assembly, in maniera da
impedire ottimizzazioni da parte del compilatore. A seconda dell'ambiente di sviluppo e
compilatore, potrebbero essere presenti anche altre possibilit per impedire al compilatore
di effettuare ottimizzazioni in particolari parti del codice.
Un piccolo esempio di ottimizzazione si pu notare anche nel codice di esempio appena
mostrato. Infatti per impostare a 0xFF il registro TRISB (ovvero tutti ingressi), viene
eseguita una sola istruzione, sfruttando l'istruzione SETF, che permette appunto di settare
un determinato registro a 0xFF, in questo caso TRISB.
!
TRISB = 0xFF;
0x7FE0: SETF TRISB, ACCESS
Per impostare il registro LATB a 0x00 viene invece eseguito il seguente codice:
!
LATB = 0x00;
0x7FDC: MOVLW 0x0
0x7FDE: MOVWF LATB, ACCESS
135
I vari passi presuppongono che l'hardware sia stato gi sviluppato altrimenti come
primo passo sar presente lo sviluppo dell'hardware. Qualora l'hardware non sia ancora
testato sempre bene procedere per passi e realizzare dei piccoli programmi di test che
permettano di testare i singoli componenti e funzioni che l'hardware supporta. E' inutile
iniziare con il programma completo che include tutte le funzioni, altrimenti le variabili in
gioco che possono nascondere un problema sono troppe. Quando si ha un hardware che
funziona ci si pu concentrare sul solo software, anche se in un sistema embedded, sia
l'hardware che il software possono nascondere problemi allo stesso tempo. Per tale
ragione, sebbene si abbia un hardware testato non sottovalutare mai che il problema possa
essere anche hardware.
Quando si inizia lo sviluppo del software non bisogna mai partire in quinta o si rischia
di perdere qualche pezzo che poi difficile ritrovare. Bisogna come prima cosa iniziare a
programmare con carta e penna e non con monitor e tastiera. Una volta scritto il nostro
programma in maniera astratta e scorporate le varie funzioni, possibile entrare nel
dettaglio di ogni funzione e scrivere i dettagli di quello che deve fare ognuna di esse.
Una volta individuati i vari blocchi e scritto l'algoritmo nero su bianco, possibile iniziare
a programmare.
Quando si scrive il programma bene testarlo per pezzi, in maniera progressiva. Una
semplice compilazione aiuta a ripulire il programma da semplici errori di battitura.
Il programma deve essere compilato sempre senza errori e senza Warning. Ogni
Warning, se presente, deve essere compresa al fine di essere certi che non possa causare
problemi al programma. Un modo per rinforzare la confidenza che il programma
136
testare! Sebbene possa essere esagerato, in realt avere un piano sistematico su cosa
testare pu aiutare molto. In molti esempi semplici non serve una pianificazione ma
quando il sistema diviene complesso bene creare una tabella in cui scrivere la serie di
test da eseguire e mettere lo spazio con le croci ad indicare che il test stato passato con
successo. Quanto detto, sebbene accennato in fretta lo si comprender meglio durante la
lettura del testo, e quando farete vostre queste regole, vedrete che i programmi saranno
pi robusti e il tempo che passerete ad effettuare il Debug di sistema, alla ricerca di
problemi, si ridurr.
Graph
Refactoring
Local History
Per mezzo di queste funzioni possibile andare direttamente nel punto esatto in cui
stata dichiarata o implementata la variabile che stiamo usando (si capir in seguito la
differenza tra dichiarazione e definizione).
Un'altra funzione molto utile sempre dal menu che compare premendo il tasto destro :
Find Usage
Questa funzione permette di trovare tutti i riferimenti nel progetto in cui viene utilizzata
la variabile o funzione selezionata.
Nel caso di variabili globali questa funzione torna molto utile. Nel caso di funzioni, Find
usage permette di visualizzare tutte le chiamate alla funzione. Nel caso delle funzioni torna
utile anche la voce del menu:
Show Call Graph
Cliccando su ogni singola funzione viene visualizzato nel codice il punto in cui viene
implementata.
Spesso, al fine di rendere il codice pi leggibile, anche qualora tutto stia funzionando
bene, ci si trova nella situazione di voler cambiare il nome di una variabile. Questa
situazione viene detta in gergo software, Refactoring. Sebbene il cambio del nome sia un
aspetto relativamente facile, ci sono libri interi che spiegano molte tecniche al fine di
evitare errori. Con poche righe di codice, il tutto pu sembrare banale, quando per ci si
trova a dover gestire progetti di migliaia di righe di codice o centinaia di migliaia di righe,
la questione si fa seria ed bene affidarsi a strumenti collaudati per lo scopo.
Un modo per cambiare il nome ad una variabile potrebbe essere quella di cambiare il
nome della stessa nel punto della definizione e ricompilare il programma. Il compilatore
trover molti errori, precisamente nei punti in cui la variabile con il vecchio nome viene
utilizzata. In questo modo, errore per errore possibile cambiare il nome della variabile in
ogni punto. Allo stesso modo si pu fare con una funzione.
Per semplificare il tutto si pu fare uso di uno strumento molto pi comodo, ovvero degli
strumenti di Refactoring disponibili in MPLAB X. Personalmente tendo ad usare spesso
solo quello per rinominare funzioni e variabili, ma ce ne sono disponibili di vari a seconda
delle esigenze. In particolare mettendo il mouse sulla variabile o funzione da rinominare,
premere il tasto destro e selezionare la voce del menu:
Refactor Rename...
Fatto questo compare la finestra come in Figura 76, in cui possibile cambiare il nome
della variabile, che nel nostro caso ho cambiato da mia_variabile a mia_variabile_2.
139
Premendo il tasto Refactor, tutti i punti in cui si fa uso della variabile vengono cambiati
con il nuovo nome. Interessante la vista Preview che permette di visualizzare il codice
prima e dopo il cambio, ovvero individuare tutti i punti in cui verr effettuato il cambio
del nome della variabile o della funzione. In Figura 77 mostrato l'esempio della variabile
mia_variabile.
Quando ci si trova a dover apportare molte modifiche bene salvare delle copie del
progetto in maniera da poter fare dei salti a gambero e ripristinare delle copie intermedie.
In ambito professionale ci sono server dedicati allo scopo, ma alcuni strumenti dell'IDE
possono tornare utili quando non avessimo fatto una copia di recente. Dal menu:
Team Local History Show Local History
140
Figura 78: Strumento Local History per il confronto del vecchio codice e ripristino.
Riassunto
Nel Capitolo si sono introdotti vari strumenti di Debug offerti dall'ambiente di
sviluppo MPLAB X, mostrando in particolare l'utilit del visualizzare il codice Assembly
al fine di comprendere meglio il comportamento del programma. Oltre agli strumenti di
Debug si sono introdotte semplici regole di buon comportamento al fine di limitare il
tempo che dovremo passare ad effettuare il Debug del nostro programma. Infatti, pur
dovendo sempre testare il programma, alcuni problemi possono essere ridotti facendo
attenzione al modo con cui si scrive il programma stesso. Una certa importanza stata
data anche allo stile di programmazione, mostrando come un codice ordinato possa essere
riletto e corretto con maggior facilit. Su questo aspetto si torner spesso anche nei
capitoli successivi. In ultimo si sono introdotti alcuni strumenti che permettono di
agevolare la navigazione e il cambio del codice, ovvero gli strumenti di Refactoring.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
142
Capitolo VI
VI
Impariamo a programmare in C
Forti dell'esperienza e delle conoscenze dei Capitoli precedenti, viene ora introdotta
con maggior dettaglio la sintassi e le parole chiave del linguaggio C. Il tutto viene spiegato
partendo da zero, non facendo affidamento a nessun background che non sia basato sui
Capitoli precedenti. In ogni modo qualora abbiate familiarit con il C o altri linguaggi di
programmazione, il tutto sar di pi facile comprensione.
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesta la realizzazione su
Breadboard dello schema elettrico di Figura 79 o far uso delle schede di sviluppo
impostate come descritto nel paragrafo successivo.
143
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 80: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 81: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
La scheda Freedom Light possiede i LED solo sui pin RD0 e RD1. Per poter
visualizzare gli altri LED necessario montarne altri sul connettore di
espansione, facendo per esempio uso della scheda di espansione Breadboard
PJ7011 o limitare il Debug su due soli LED.
144
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione software particolare oltre alla
semplice creazione di un progetto.
Dal momento che molti esempi mostrati per le varie istruzioni sono solo parti di codice
incomplete pu tornare utile scrivere lo scheletro di un codice con delle inizializzazioni
base, dopo le quali possibile scrivere il frammento di codice di esempio:
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void){
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// *******************************************************
// Il codice di esempio deve seguire il seguente commento
// *******************************************************
// Ciclo infinito
while (1) {
}
}
Gli esempi mostrati sono spiegati nel dettaglio, mostrando il codice Assembly,
per cui, a seconda delle impostazioni delle ottimizzazioni e della versione del
compilatore utilizzato, possono essere presenti delle differenze tra il codice
mostrato e quello che otterrete in fase di compilazione.
145
La sintassi in breve
Nel nostro primo progetto abbiamo gi visto alcuni aspetti legati alla sintassi del C,
ovvero a quell'insieme di regole che permettono di strutturare il programma in maniera
tale che il compilatore possa propriamente interpretarle. Il C possiede in particolare delle
parole chiave ovvero Keyword che in grado d'interpretare. Ogni programma rappresenta
una combinazione pi o meno complessa di tali parole chiave pi delle istruzioni. Il
compilatore XC8 conforme allo standard ANSI C dunque possiede le seguenti parole
chiave:
Parole Chiave ANSI C
auto
int
break
long
case
register
char
return
const
short
continue
signed
default
sizeof
do
static
double
struct
else
switch
enum
typedef
extern
union
float
unsigned
for
void
goto
volatile
if
while
Ogni parola chiave, qualora si stia facendo uso dell'IDE MPLAB X viene
automaticamente riconosciuta e messa in grassetto 97. Nel seguente Capitolo sono
illustrate le parole chiave pi utilizzate, mostrando per ognuna degli esempi. Essendo il
compilatore XC8 conforme allo standard ANSI C un qualunque testo di C sufficiente
per comprendere le parole chiave non trattate o approfondire quanto descritto di
seguito.
Come detto il programma oltre che essere composto da parole chiave composto da
istruzioni, ovvero operazioni da compiere. Ogni istruzione deve essere terminata per
mezzo di un punto e virgola.
a = b + c;
Su ogni linea possibile scrivere anche pi istruzioni, purch siano tutte sperate da un
punto e virgola:
97
Per mezzo delle propriet dell'Editor possibile cambiare il colore ed altre propriet associate alle varie tipologie di testo e
caratteri che l'IDE riconosce automaticamente.
146
a = b + c; d = e a; f = d;
Si capisce che in questo modo...non si capisce nulla, dunque bene scrivere il codice in
maniera da avere un'istruzione per linea:
a = b + c;
d = e a;
f = d;
In questo modo si aumenta molto la leggibilit del codice stesso. Negli esempi che
seguiranno verr messo pi volte in evidenza che la leggibilit del codice molto
importante.
Alla regola del punto e virgola fanno eccezione le direttive, dal momento che non sono
delle istruzioni che il compilatore traduce in codice macchina. Un esempio potrebbe
essere la riga di codice che abbiamo gi incontrato:
#include <xc.h>
Alcune parole chiave fanno utilizzo di blocchi d'istruzione ovvero insieme di istruzioni.
Un blocco identificato da parentesi graffe:
if (a < b) {
d = e a;
f = d;
}
Come si vedr in seguito l'insieme di istruzioni nelle parentesi graffe viene eseguito
solo se a minore di b.
Le varie linee di codice ammettono commenti che possono essere introdotti per mezzo
di varie sintassi, introdotte nel primo esempio di programmazione. Come per le parole
chiave, l'IDE MPLAB X riconosce in automatico i commenti e li colora normalmente di
verde.
Una caratteristica particolare del C, che lo differenzia da altri linguaggi di
programmazione, il fatto che un linguaggio case sensitive, ovvero sensibile alle maiuscole
e minuscole. Questo significa che le parole chiave appena introdotte, se scritte in maniera
differente non saranno riconosciute come tali. Quanto detto va inoltre generalizzato
anche al nome di variabili e funzioni. Un esempio potrebbe essere:
int temperatura = 0;
147
Dimensione
Tipo aritmetica
bit
1 bit
signed char
8 bit
unsigned char
8 bit
signed short
16 bit
unsigned short
16 bit
signed int
16 bit
unsigned int
16 bit
24 bit
24 bit
signed long
32 bit
unsigned long
32 bit
32 bit
32 bit
Minimo
Massimo
0
-128
+127
255
-32.768
+32.767
65535
-32.768
+32.767
65535
-8388608
8388607
16777215
-2147483648
2147483647
4294967295
-2147483648
2147483647
4294967295
indian ovvero con il bit meno significativo posto alla parte bassa del byte a all'indirizzo
meno significativo.
Sebbene la dimensione minima di una variabile sia una locazione di memoria
RAM, ovvero 8 bit (1 byte), la variabile bit viene dichiarata essere di un solo bit.
La ragione discende dal fatto che il compilatore, pu accorpare pi variabili bit
all'interno di una sola locazione di memoria, al fine di ottimizzare spazio e tempo
di esecuzione del codice.
Le variabili di tipo bit permettono di ottimizzare il codice sia per quanto riguarda lo
spazio che il tempo di esecuzione poich sono lette e scritte facendo uso delle istruzioni
Assembly BSF e BCF. Il loro uso ritorna utile per memorizzare dei flag, ovvero promemoria
che segnalano o meno un determinato evento, contenuto o qual si voglia significato si
voglia associare a 0 e 1. La variabile bit paragonabile alla variabile Boolean (ovvero
booleana) supportata da alcuni compilatori e linguaggi di programmazione (non il
compilatore XC8). In particolare una variabile Boolean pu assumere il solo valore 0 e 1 o
TRUE e FALSE. Per un corretto utilizzo delle variabile bit, bisogna dichiararle globali o
statiche (maggiori dettagli sullo scope delle variabili sar dato in seguito).
Si fa notare che i tipi di variabile:
signed short int
unsigned short int
short long int
long int
long long int
possono essere dichiarate, secondo lo standard C, anche senza scrivere la parola int, per
cui a signed short equivale a scrivere signed short int.
Negli esempi che seguiranno dichiaro sempre in maniera esplicita il tipo in maniera da
evitare equivoci ed essere espliciti in ci che potrebbe essere anche sottinteso.
Si noti che ogni tipo riportato in Tabella 5 esplicitamente dichiarato di tipo
signed o unsigned, ovvero con o senza segno. Nel caso in cui si dovesse
omettere di scrivere tali parole chiave, il compilatore XC8 considera la variabile
come signed ad eccezione del char, che di default sono considerate unsigned.
Da questa inconsistenza si capisce che sempre bene scrivere il tipo della
variabile.
L'interfaccia CCI (Common C Interface) richiede, che ogni variabile sia esplicitamente
dichiarata signed o unsigned visto che i vari compilatori della famiglia XC8 si
comportano, di default, in maniera diversa, per cui esplicitare il segno risolve ogni
problema.
Da quanto appena visto si capisce che a seconda del tipo di dato che bisogna
memorizzare e dell'operazione da compiere si viene ad essere vincolati dal valore
numerico minimo o massimo del risultato, per cui bisogna scegliere un tipo di variabile
idonea. Una scelta attenta permette di risparmiare locazioni di memoria RAM e anche il
149
tempo necessario per lo svolgimento delle operazioni. Infatti fare una somma tra due
interi contenuti in una variabile unsigned char pi veloce che fare la somma tra due
interi di tipo unsigned int. Quanto detto pu essere visto con un semplice esempio in
cui si dichiarano delle variabili unsigned char e si fa la somma tra loro. Lo stesso
esempio poi ripetuto per mezzo di variabili di tipo unsigned int.
#include <xc.h>
#include "PIC18F4550_config.h"
// Variabili globali
bit flagA;
bit flagB;
bit flagC;
int main (void){
// Dichiarazione delle variabili
unsigned char charA;
unsigned char charB;
unsigned char charC;
unsigned int intA;
unsigned int intB;
unsigned int intC;
// Esempio inizializzazione e somma tra char
charA = 5;
charB = 8;
charC = charA + charB;
// Esempio inizializzazione e somma tra int
intA = 5;
intB = 8;
intC = intA + intB;
// Esempio inizializzazione di variabili bit
flagA = 0;
flagB = 1;
flagC = 1;
flagC = 0;
// Ciclo infinito
while (1) {
}
}
Nell'esempio possibile osservare che per dichiarare una variabile di un certo tipo
necessario precedere il nome della variabile con il tipo voluto.
// Dichiarazione delle variabili
unsigned char charA;
150
In tale finestra mostrato il codice generato dalla compilazione. Dal codice sotto
riportato possibile subito vedere che la parte associata all'assegnazione dei valori nelle
variabili unsigned char (ovvero la loro inizializzazione) risulta molto semplice, e come ci
si poteva aspettare coinvolge semplici movimenti con il registro W (accumulatore). Uno
per caricare la costante e l'altro per caricare tale valore nel registro voluto. La somma tra i
due registri risulta un po' pi laboriosa dell'inizializzazione, ma pur sempre piuttosto
semplice.
17:
18:
7FAC
98
99
6E01
Anche se in generale non si consiglia di ottimizzare il codice sin dall'inizio, in applicazioni embedded sempre bene tenere
a mente che le risorse sono limitate e la scelta opportuna delle variabili bene farla sin dall'inizio.
Nei casi in cui si faccia uso di soluzioni brillanti sempre bene documentare il tutto con buoni commenti. L'essere brillanti
ristretto a frangenti temporali che difficile ripetere...gran parte delle volte abbiamo bisogno di una bella spiegazione per
raggiungere quello che un momento di lucidit ha richiesto pochi secondi!
151
7FAE
7FB0
7FB2
19:
7FB4
7FB6
7FB8
7FBA
20:
21:
7FBC
7FBE
7FC0
0E05
6E09
5001
6E01
0E08
6E0A
5001
MOVLW 0x5
MOVWF charA, ACCESS
MOVF 0x1, W, ACCESS
charB = 8;
MOVWF 0x1, ACCESS
MOVLW 0x8
MOVWF charB, ACCESS
MOVF 0x1, W, ACCESS
5009
240A
6E04
52DE
0E08
6EDD
0E01
50DB
24DF
6EE7
0E02
CFE7
FFDB
0E05
6EDF
0E00
6E06
0E05
6E05
0E00
6E08
152
7FCE
7FD0
26:
27:
7FD2
7FD4
7FD6
7FD8
7FDA
7FDC
0E08
6E07
MOVLW 0x8
MOVWF intB, ACCESS
5007
2405
6E02
5008
2006
6E03
0E08
6EF3
0E05
CFF3
FFDB
0E06
6ADB
0E05
CFDB
F002
0E06
CFDB
F003
0E03
50DB
2402
6E00
0E04
50DB
2003
6E01
0E07
C000
FFDB
0E08
C001
FFDB
0E05
6EF3
0E03
CFF3
FFDB
0E04
6ADB
153
diversamente dalla altre variabili, la si posta prima della funzione main. Come si vedr in
seguito questo il modo per poter dichiarare delle variabili in maniera globale, cosa
richiesta per le variabili bit. Il codice Assembly derivante dalla compilazione del codice :
35:
36:
7FEE
7FF0
37:
7FF2
7FF4
38:
7FF6
7FF8
39:
7FFA
7FFC
0100
900C
0100
800B
0100
820B
0100
920B
bit
In particolare in entrambi i casi l'istruzione BCF punta il registro flagB, ovvero lo stesso
indirizzo di memoria ma in un caso scrive nel bit 0 mentre nell'altro nel bit 1. Questo
154
mostra come pi variabili bit possano essere accorpate dal compilatore in un unico byte.
Nel caso specifico, effettuando un Debug del codice e visualizzando le variabili bit, come
riportato in Figura 82, possibile vedere come flagC e flagB abbiano lo stesso indirizzo
mentre flagA ne abbia uno diverso.
Sebbene possiate essere tentati di farne uso, dal momento che la variabile bit non
standard pensateci due volte, personalmente nel testo non ne far uso.
Dimensione
Exp. min
Exp. max.
Min. normalizzato
Max. normalizzato
float
32 bit
-126
+128
2126 1.17549435e - 38
double
32 bit
-126
+128
2126 1.17549435e - 38
long double
32 bit
-126
+128
2126 1.17549435e - 38
In Tabella 6 mostrato il fatto che le varie variabili sono in formato a 32 bit. In realt
tra le configurazioni di progetto il formato pu essere cambiato tra 24 bit o 32 bit. Nel
caso in cui si faccia uso dell'interfaccia CCI necessario cambiare il valore di Default da
24 bit a 32 bit, come riportato in Figura 83.
100
Il fatto che un numero floating point non sia sempre un valore esatto del risultato spesso sottovalutato. In algoritmi in cui
viene calcolato in maniera iterativa un risultato si pu avere la divergenza del risultato ovvero dell'algoritmo, proprio a causa
di questi errori. Questo problema non riguarder comunque nessuna delle applicazioni discusse in questo testo.
155
Sebbene la Tabella 6 riporti il fatto che entrambi i formati float e double sono a 32
bit, in realt il formato 24 bit e 32 bit pu essere cambiato singolarmente, per, come
detto la sintassi CCI richiede che entrambi siano in formato 32 bit. Non impostando il
formato a 32 bit si ottiene le seguente Warning:
:: warning: (1426) 24-bit floating point types are not supported; float
have been changed to 32-bits
:: warning: (1426) 24-bit floating point types are not supported; double
have been changed to 32-bits
interessante notare che i formati floating point, dal momento che, diversamente dai
numeri interi tendono ad approssimare il risultato con il valore pi prossimo
rappresentabile nel formato floating point, potrebbero generare dei risultati apparentemente
errati, derivanti da approssimazioni.
La user Guide del compilatore XC8 riporta un semplice esempio molto rappresentativo in
cui viene mostrato che nel caso di un confronto tra due numeri floating si possono avere
confronti positivi anche qualora i numeri siano apparentemente diversi.
float myFloat;
unsigned char test;
myFloat = 95002.0;
test = 0;
156
Le variabili tipo floating point devono essere utilizzate in maniera meticolosa visto che la
loro complessit porta a codici Assembly pi complessi. Per esempio una semplice somma
tra due variabili float risulta:
12:
7CE6
7CE8
7CEA
7CEC
7CEE
7CF0
7CF2
7CF4
13:
7CF6
7CF8
7CFA
7CFC
7CFE
7D00
7D02
7D04
14:
15:
7D06
7D08
7D0A
7D0C
7D0E
7D10
7D12
7D14
7D16
7D18
7D1A
7D1C
7D1E
7D20
7D22
7D24
7D26
7D28
7D2A
7D2C
7D2E
7D30
7D32
7D34
7D36
7D38
0E00
6E1F
0E00
6E20
0E48
6E21
0E42
6E22
float_A = 100.0;
MOVLW 0x0
MOVWF float_A, ACCESS
MOVLW 0x0
MOVWF 0x1C, ACCESS
MOVLW 0xC8
MOVWF 0x1D, ACCESS
MOVLW 0x42
MOVWF 0x1E, ACCESS
float_B = 50.0;
MOVLW 0x0
MOVWF float_B, ACCESS
MOVLW 0x0
MOVWF 0x20, ACCESS
MOVLW 0x48
MOVWF 0x21, ACCESS
MOVLW 0x42
MOVWF 0x22, ACCESS
C01B
F00B
C01C
F00C
C01D
F00D
C01E
F00E
C01F
F00F
C020
F010
C021
F011
C022
F012
EC1C
F03F
C00E
F01A
C00D
F019
C00C
F018
C00B
F017
0E00
6E1B
0E00
6E1C
0EC8
6E1D
0E42
6E1E
Il codice visibilmente pi lungo della semplice somma tra interi, ma con occhi attenti si
157
Cercando nel codice Assembly l'indirizzo 0x7E38 dove inizia la funzione, si pu vedere
come la somma tra le due variabili float richieda molte pi istruzioni. Il semplice esempio
richiede per esempio circa 800 byte di memoria flash mentre l'intero esempio delle
variabili unsigned char e unsigned int, richiede solamente 100 byte.
Le due modalit d'inizializzazione delle variabili sono del tutto equivalenti ai fini pratici.
Inizializzare una variabile una buona abitudine al fine di evitare comportamenti anomali
del software. Durante la fase di compilazione, qualora dovessimo usare una variabile
senza averla prima inizializzata riceveremo una Warning che ci avvisa appunto che la
variabile x usata senza essere stata inizializzata.
warning: (1257) local variable "x" is used but never given a value
capisce che in realt la funzione main non proprio la prima funzione ad essere
richiamata. Sebbene si possa intervenire nello startup code inserendo del proprio codice, i
casi che effettivamente lo potrebbero richiedere sono limitati e hanno in generale a che
fare con un'esigenza di una rapida esecuzione del codice prima che venga richiamata la
funzione main.
L'inizializzazione delle variabili permette come detto di resettare in automatico ogni
variabile per cui ad ogni Reset tutte le nostre variabili perdono il loro contenuto e sono
inizializzate a 0x00. Questo comportamento di Default potrebbe non essere voluto,
ovvero si potrebbe avere l'esigenza di mantenere il contenuto della variabile dopo un
Reset di sistema.
Per ottenere questo comportamento bisogna dichiarare la variabile persistent, in
particolare usando lo standard CCI bisogna dichiarare la variabile __persistent come nel
seguente esempio:
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void){
__persistent unsigned char y;
y = 0xAA;
// Inizializzo la PORTD come uscita
TRISD = 0x00;
LATD = y;
// Ciclo infinito
while (1) {
}
bene ricordare che la dichiarazione delle variabili deve essere fatta all'inizio,
ogni funzione sia essa la funzione main o un'altra, deve dichiarare le sue variabili
prima d'iniziare a svolgere una qualunque operazione. Chi abituato a
programmare in C++, a dichiarare le variabili in prossimit del proprio utilizzo,
deve momentaneamente perdere tale buona abitudine, da non perdere per
quando si scrive in C++.
159
Gli Array
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 101. 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 XC8 per dichiarare un Array si procede come per il C standard, ovvero:
int main (void){
// Array di caratteri con 10 elementi
unsigned char mioArray[10];
}
Si fa presente che l'Array, una volta dichiarato, possiede al suo interno valori casuali;
compito del programmatore aver cura d'inizializzarlo. L'inizializzazione di un Array pu
avvenire in vari modi. Un modo quello di inizializzare un valore alla volta richiamando
un indice alla volta. Un secondo metodo pi pratico quello di inizializzare l'Array
101
102
Questi tipi di variabili vengono spesso utilizzati per la realizzazione di buffer di lettura e scrittura.
Errori tipo questi sono difficili da individuare e possono creare comportamenti inaspettati solo quando l'indice va oltre il
range consentito. Questo potrebbe avvenire durante la fase di sviluppo, mostrando dunque la presenza del problema, ma
potrebbe, nel caso peggiore, mostrarsi solo quando abbiamo messo il nostro sistema nelle mani del cliente. Nella
programmazione su PC tale tipo di problema noto come buffer overflow e viene sfruttato dai virus per accedere locazioni di
memoria altrimenti inaccessibili. Questo problema potrebbe anche permettere ad un semplice file di testo di inglobare un
virus...anche se apparentemente un file di testo non un eseguibile!
160
In questo esempio si creata una stringa, ovvero un insieme di caratteri e una semplice
tabella numerica. In particolare bene notare che la lunghezza dell'Array non stata
definita, infatti si sono lasciate le parentesi quadre senza dimensioni. Il compilatore, a
seconda del valore d'inizializzazione assegna le dimensioni opportune, in questo caso 5 in
entrambi gli esempi.
Si noti inoltre che per inizializzare la stringa si fatto uso del doppio pedice mentre
per la tabella numerica si fatto uso delle parentesi graffe { }.
Qualcuno se avr contato il numero di lettere contenute nella parola ciao, avr
commentato, ciao contiene solo 4 caratteri e non 5. Questo vero ma le stringhe sono
una cosa a parte. Il C sebbene non supporti nativamente il formato dati di tipo stringa,
come altri linguaggi, supporta le stesse come Array di caratteri con la caratteristica che
siano terminati con il carattere speciale NULL ovvero \0.
Sebbene il carattere \0 sia composto di due simboli, viene in realt tradotto in un
solo valore numerico. In particolare tale simbolo viene nominato Escape
Character. Il C contiene altri Escape Character, tra i quali si ricorda \n (nuova
Riga), \t (tab), \r (Ritorno a capo). Questi Escape Character sono molto usati per
formattare i dati in scrittura sul monitor di un PC. A meno di non usare un
display grafico, sono meno usati in applicazioni embedded.
Il fatto di avere usato il doppio pedice nell'inizializzazione della stringa, serve proprio per
avvisare il compilatore che l'Array deve essere considerato come una stringa. Per tale
ragione la dimensione dell'Array 5 e non 4, proprio per la presenza del carattere \0.
Da quanto detto si pu intuire che il carattere \0 rappresenta il carattere di fine stringa.
Qualora si voglia dichiarare un Array di caratteri senza avere una stringa, si potrebbe
scrivere:
unsigned char mioArray [] = {'c','i','a','o'};
ovvero scrivere un Array come nel caso dell'Array numerico. I caratteri dell'alfabeto, al
fine di essere scritti nell'Array devono essere contenuti tra due pedici. In questo caso
l'Array risultante ha una dimensione pari a 4.
Il compilatore quando deve scrivere una lettera all'interno di un Array, scrive il valore
numerico ASCII del carattere. Il codice ASCII (American Standard Code for Information
Interchange) rappresenta uno standard Americano per mezzo del quale, usando un valore
numerico a 7 bit, viene associato ad ogni numero un carattere. Tale standard ideato nel
1961 da Bob Bemer (IBM) stato successivamente standardizzato dall'ente ISO
(International Organization for Standardization) sotto lo standard ISO646, facendo del codice
ASCII uno standard internazionale ufficiale. In Tabella 5 riportato il dettaglio delle
corrispondenze tra i caratteri dell'alfabeto e il relativo valore numerico. Per esempio alla
lettera 'a' corrisponde il valore numerico 97 ovvero 0x61 in esadecimale.
161
Dec
Hex
Char
Dec
Hex
Char
Dec
Hex
Char
Dec
Hex
Char
0x00
Null
32
0x20
Space
64
0x40
96
0x60
0x01
Start of Heading
33
0x21
65
0x41
97
0x61
0x02
Start of Text
34
0x22
66
0x42
98
0x62
0x03
End of Text
35
0x23
67
0x43
99
0x63
0x04
End of transmit
36
0x24
68
0x44
100
0x64
0x05
Inquiry
37
0x25
69
0x45
101
0x65
0x06
Acknowledge
38
0x26
&
70
0x46
102
0x66
0x07
Audible Bell
39
0x27
'
71
0x47
103
0x67
0x08
Backspace
40
0x28
72
0x48
104
0x68
0x09
Horizontal Tab
41
0x29
73
0x49
105
0x69
10
0x0A
Line feed
42
0x2A
74
0x4A
106
0x6A
11
0x0B
Vertical Tab
43
0x2B
75
0x4B
107
0x6B
12
0x0C
Form Feed
44
0x2C
76
0x4C
108
0x6C
13
0x0D
Carriage Return
45
0x2D
77
0x4D
109
0x6D
14
0x0E
Shift Out
46
0x2E
78
0x4E
110
0x6E
15
0x0F
Shift In
47
0x2F
79
0x4F
111
0x6F
16
0x10
48
0x30
80
0x50
112
0x70
17
0x11
Device control 1
49
0x31
81
0x51
113
0x71
18
0x12
Device control 2
50
0x32
82
0x52
114
0x72
19
0x13
Device control 3
51
0x33
83
0x53
115
0x73
20
0x14
Device control 4
52
0x34
84
0x54
116
0x74
21
0x15
Neg. Acknowledge
53
0x35
85
0x55
117
0x75
22
0x16
Synchronous idle
54
0x36
86
0x56
118
0x76
23
0x17
55
0x37
87
0x57
119
0x77
24
0x18
Cancel
56
0x38
88
0x58
120
0x78
25
0x19
End of medium
57
0x39
89
0x59
121
0x79
26
0x1A
Substitution
58
0x3A
90
0x5A
122
0x7A
27
0x1B
Escape
59
0x3B
91
0x5B
123
0x7B
28
0x1C
File Separator
60
0x3C
<
92
0x5C
124
0x7C
29
0x1D
Group Separator
61
0x3D
93
0x5D
125
0x7D
30
0x1E
Record Separator
62
0x3E
>
94
0x5E
126
0x7E
31
0x1F
Unit Separator
63
0x3F
95
0x5F
127
0x7F
Dal momento che il compilare converte i vari caratteri in valori numerici, si capisce che
i due Array:
unsigned char mioArray [] = {'c','i','a','o'};
e
unsigned char mioArray [] = {99,105,97,111};
sono equivalenti.
162
due a due poich un intero richiede due byte. Gli altri Array hanno indirizzi
consecutivi.
possibile notare che l'indirizzo di ogni Array coincide con l'indirizzo del primo
elemento dell'Array stesso.
Gli elementi degli Array di char sono visualizzati con indice 0, 1, 2 non
consecutivo, anche se l'indirizzo di memoria lo . Questo solo un problema di
visualizzazione e potrebbe sparire in versioni successivi di MPLAB X. Il
contenuto di ogni indirizzo infatti corretto.
Le strutture
Il tipo di variabili introdotte permettono di soddisfare le esigenze di molti progetti, ma
alcune volte potrebbe essere utile avere quel qualcosa in pi che permetta di organizzare i
dati della nostra applicazione. Quando si avverte questa esigenza si ha anche la possibilit
di definirne nuovi tipi, per mezzo dei quali possibile gestire in maniera pi snella
strutture dati pi complesse. Il tipo di variabile definibile dall'utente va sotto il nome di
struttura ovvero struct.
Per esempio, se dobbiamo risolvere un problema che gestisce rettangoli, sappiamo che
un rettangolo ha sempre un'altezza e una larghezza. Dal momento che questi parametri
caratterizzano ogni rettangolo, possibile dichiarare un nostro tipo di variabile chiamata
164
rettangolo e che sia caratterizzata dai parametri precedentemente scritti, ovvero 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. Le variabili interne 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 di un tipo primitivo. Vediamo il
seguente esempio:
#include <xc.h>
#include "PIC18F4550_config.h"
// Dichiarazione della struttura rettangolo
typedef struct {
unsigned char larghezza;
unsigned char altezza;
} rettangolo;
int main (void){
// Dichiarazione della variabile di tipo rettangolo
rettangolo figura;
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Inizializzazione del record
figura.altezza = 10;
165
figura.larghezza = 3;
// Scrivo su PORTD l'altezza
LATD = figura.altezza;
// Ciclo infinito
while (1) {
}
}
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 main103.
Per quanto riguarda la spiegazione, essendo il programma del tutto simile al progetto
Hello_World, non mi soffermo sui dettagli. bene notare che la sintassi del C non
permette di inizializzare le variabili interne alla struttura nella fase della loro dichiarazione.
L'inizializzazione delle variabili definite all'interno della struttura deve essere fatta un
campo alla volta. Dalle seguenti righe di codice possibile notare che la dichiarazione di
una variabile facendo uso di una struttura, del tutto analoga alla dichiarazione di una
variabile di tipo standard. Si possono anche creare degli Array di tipo rettangolo.
// Dichiarazione della variabile di tipo rettangolo
rettangolo figura;
Dal seguente codice possibile vedere che per accedere ai campi della nostra variabile
figura bisogna utilizzare il punto. Da quanto si detto si capisce quanto precedentemente
spiegato sulla porta LATxbits, ovvero che dietro tale variabile presente una struttura.
// Inizializzazione del record
figura.altezza = 10;
figura.larghezza = 3;
Dal codice scritto possibile notare che facendo uso di commenti ben posti e di un
nome di variabili chiare, sia possibile leggere il codice come se stessimo leggendo un libro.
Un buon codice deve infatti essere auto-spiegante senza necessitare di un commento
per ogni linea di codice104.
Effettuando il Debug del programma interessante vedere come viene mostrato il
contenuto della variabile figura. possibile notare che l'indirizzo della struttura figura
0x02 e coincide con l'indirizzo della variabile larghezza, ovvero il primo elemento della
struttura, proprio come per un Array.
103
104
Per ulteriori informazioni a riguardo si rimanda al paragrafo sulla visibilit delle variabili (scope).
In questo testo si fa uso di pi commenti di quanto non siano in realt necessari.
166
Le costanti
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 durante
l'esecuzione del programma, ovvero possibile cambiare, o meglio impostare, un valore
solo prima della compilazione del programma. 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 #define105 per mezzo della quale si definisce una
corrispondenza tra un nome e un valore numerico. In questo modo ogni volta che
bisogna scrivere lo stesso valore, basta scrivere il nome che gli si assegnato. Questo un
tipico esempio:
#define MAX_VALUE 56
Si capisce che l'utilizzo delle costanti risulta particolarmente utile qualora si vogliano
definire dei limiti. L'utilit nel definire la costante MAX_VALUE che se si dovesse
variare questo valore non necessario cambiarlo in ogni punto del programma ma basta
cambiare la riga precedente con il nuovo valore.
L'utilizzo di numeri nel codice noto come utilizzo di numeri magici (magic number) il
suo utilizzo altamente sconsigliato, salvo in codici brevi di esempio. Infatti quando ci si
trova a cambiare dei magic number, facile cambiare un numero per un altro, creando
problemi al codice. Un esempio di utilizzo della direttiva #define pu essere il seguente:
#define BUFFER_SIZE 10
int main (void) {
// Dichiarazione Array di caratteri con BUFFER_SIZE elementi
unsigned char mioArray[BUFFER_SIZE];
// ...resto del codice
}
105
La direttiva #define pu essere utilizzata anche per la definizione di macro ovvero un blocco di codice che possibile
riscrivere facendo riferimento al nome con cui stato definito. Una macro non una funzione poich al posto del nome da
dove viene richiamata viene sostituito l'intero codice e non un salto. Il suo utilizzo viene sconsigliato in C++ poich pu
portare a problemi difficili da trovare. Personalmente consiglio di realizzare una funzione ogni qual volta si stia pensando di
realizzare una macro. Eccezioni sono sempre possibili, ma state in guardia.
167
Si vede subito la sua utilit qualora il buffer dovesse avere dimensioni diverse.
La direttiva #define oltre che a definire costanti risulta molto utile per assegnare un
nome ad un determinato pin del PIC. In questo modo se il pin viene spesso utilizzato nel
programma si pu far riferimento al nome piuttosto che al nome del pin stesso. Questo
metodo di rinominare il pin risulta particolarmente utile qualora, in fase di sviluppo, si
voglia cambiare il pin dove per esempio attaccato un LED rosso di allarme. Infatti
baster in un solo punto del programma cambiare l'assegnazione di un nome ed il resto
del programma far uso del nuovo pin. Un esempio di assegnazione di un nome ad un pin
il seguente:
#define LED_ROSSO LATDbits.LATD1
Nel caso in cui si voglia dichiarare una famiglia di costanti, la direttiva #define pu
essere utilizzata per dichiarare tutte le costanti. Per esempio se si volesse scrivere un
valore di inizializzazione particolare per i valori contenuti in una struttura del tipo
rettangolo, potremmo scrivere:
#define BASE_STANDARD 10
#define ALTEZZA_STANDARD 20
ed inizializzare il nostro record o Array con tali valori. Un altro modo utilizzato in C per
raggruppare delle costanti per mezzo della struttura enum, ovvero enumerando le
costanti da utilizzare. La sintassi per definire una struttura enum molto simile a quella
utilizzata per definire un nuovo tipo di variabile.
enum RETTANGOLO_STANDARD {
BASE_STANDARD
ALTEZZA_STANDARD
= 10,
= 20
};
Si pu vedere che dopo enum bisogna mettere il nome della raccolta di costanti. Le
costanti diversamente dalla direttiva #define vengono assegnate ad un valore per mezzo
dell'operatore =, inoltre al temine della riga viene posta una virgola piuttosto che un
punto e virgola, mentre l'ultimo elemento, diversamente dagli altri non ha la virgola. La
costante definita all'interno di una struttura enum pu essere utilizzata allo stesso modo di
una costante definita per mezzo della direttiva #define. Negli esempi che seguiranno nel
testo si fa spesso uso della definizione di costanti prediligendo la direttiva #define.
Le costanti, diversamente dalle variabili, non risiedono in RAM ma in memoria Flash,
questo discende dal fatto che non si ha l'esigenza del doverle cambiare.
Detto questo vediamo un altro modo per dichiarare delle costanti, ed in particolare avere
anche la possibilit di definire il tipo della stessa.
168
"LaurTec";
La stringa definita, diversamente dalla stringa definita nel paragrafo degli Array, non
pu essere pi cambiata. Anche in questo caso stringa, apparentemente una variabile,
viene in realt salvata in memoria Flash. Il vantaggio nel definire una costante in questo
modo risiede nel fatto che il compilatore pu effettuare un numero di controlli maggiori
conoscendo il tipo della costante.
I puntatori
Frequentemente si sente dire che il linguaggio C un linguaggio pi potente o
migliore di altri. Sebbene non si capisca spesso il significato di quello che si sta
affermando. Il linguaggio C fornisce molte possibilit al programmatore di intervenire
sulle strutture dati, fornendo spesso la stessa flessibilit che rivendica il programmatore
che fa uso del solo Assembly. Tra gli strumenti che permettono un ottimo controllo sulle
strutture dati vi il puntatore.
Il puntatore un tipo di variabile particolare, visto che il suo contenuto un
indirizzo. Come dice il nome stesso, un puntatore serve per puntare, ovvero
indirizzare il contenuto di una locazione di memoria.
ovvero precedere il nome del puntatore con l'asterisco permette di scrivere all'interno
dell'indirizzo contenuto nel puntatore. Vediamo un esempio completo ed effettuiamo il
Debug dello stesso.
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void){
unsigned char mia_variabile;
unsigned char *puntatore;
puntatore = &mia_variabile;
*puntatore = 0x05;
// Imposto la PORTD come output
TRISD = 0x00;
LATD = mia_variabile;
// Ciclo infinito
while (1) {
}
}
170
Dalla Figura 86 possibile vedere come il contenuto finale della variabile mia_variabile
sia 0x05. Tale valore lo si scritto all'interno della variabile accedendo direttamente
all'indirizzo della stessa e non per mezzo del suo nome. L'indirizzo della variabile
mia_variabile 0x03 mentre l'indirizzo del puntatore, che rappresenta pur sempre una
variabile nella memoria RAM pari a 0x01. Il contenuto del puntatore pari a 0x03, che
rappresenta proprio l'indirizzo della variabile mia_variabile. Il contenuto di *puntatore
invece 0x05 ovvero il contenuto dell'indirizzo 0x03 cio di mia_variabile.
Qualora si provasse a dichiarare un puntatore di un tipo ma assegnare l'indirizzo di una
variabile di altro tipo, come per esempio in questo codice:
unsigned int mia_variabile;
unsigned char *puntatore;
puntatore = &mia_variabile;
puntatore = &mio_Array[0];
ovvero assegnare l'indirizzo del primo elemento dell'Array. Un altro modo potrebbe
essere:
puntatore = mio_Array;
Nel caso di una struttura, come per esempio quella precedentemente usata:
typedef struct {
unsigned char larghezza;
unsigned char altezza;
} rettangolo;
Per accedere ad un elemento della struttura facendo uso del puntatore si potrebbe pensare
di usare il seguente metodo:
puntatore.larghezza = 0x05;
In realt sebbene sia logico usare questo metodo, in C, qualora si debba selezionare un
elemento di una struttura per mezzo di un puntatore, si fa uso della seguente sintassi:
puntatore->larghezza = 0x05;
172
=
=
=
=
=
0x01;
0x02;
0x03;
0x04;
0x05;
173
L'elemento 0 vale effettivamente 0x01 per cui giusto che si accenda un solo LED, ma
nel secondo caso :
LATD = puntatore_array_int[1];
ovvero ad 8 bit (1 byte) nel primo caso e a 16 bit (2 byte) nel secondo.
174
Quando il puntatore ad interi viene impostato per puntare l'elemento [1] viene sommato 2
all'indirizzo, per cui il valore puntato (non mostrato) sarebbe:
00000100 00000011
Quando si scrive il valore a 16 bit nella porta PORTD, di soli 8 bit, viene visualizzato il
solo valore
00000011
Operatori matematici
Ogni volta che si crea una variabile si ha pi o meno l'esigenza di manipolare il suo
contenuto per mezzo di operazioni algebriche. Il compilatore XC8 supporta in maniera
nativa i seguenti operatori matematici:
+
/
*
%
: operatore somma
: operatore sottrazione
: operatore divisione
: operatore moltiplicazione
: resto divisione tra interi o modulo
Per operatori nativi si intende che possibile utilizzarli senza dover includere delle
librerie esterne. A questi operatori si aggiungono altre funzioni matematiche particolari,
quali i sen(x), cos(x), log(x), ecc., e relative funzioni inverse. Per poter utilizzare questi
ulteriori operatori bisogna includere, per mezzo della direttiva #include, la libreria
math.h106.
Nonostante per gli operatori base non sia necessario includere alcuna libreria, non vuol
dire che la relativa operazione sia direttamente supportata dal microcontrollore per mezzo
di una sola istruzione Assembly. Si gi visto infatti, che nel fare una divisione tra numeri
reali viene richiamata una funzione per la gestione del segno, mantissa ed esponente.
Ciononostante questo avviene in maniera trasparente senza dover includere delle librerie
esterne.
106
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 il C18.
175
Ovvero non si fa altro che far precedere il segno di uguale con il relativo operatore
d'interesse, lasciando sottintesa la variabile a.
Il vantaggio della seconda scrittura quello di permettere al compilatore di effettuare
alcune ottimizzazioni sul codice. Dal codice Assembly si pu vedere che il primo caso
tradotto nel seguente codice:
12:
7CEA
7CEC
7CEE
7CF0
7CF2
7CF4
0E03
2403
6E03
0E00
2004
6E04
a = a + 3;
MOVLW 0x3
ADDWF a, W, ACCESS
MOVWF a, ACCESS
MOVLW 0x0
ADDWFC 0x4, W, ACCESS
MOVWF 0x4, ACCESS
0E03
2601
0E00
2202
a+= 3;
MOVLW 0x3
ADDWF a, F, ACCESS
MOVLW 0x0
ADDWFC 0x2, F, ACCESS
Oltre a quanto mostrato sopra, 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:
int main (void) {
176
Un altro modo per effettuare questo tipo di somma in maniera snella per mezzo
dell'operatore incremento ++, come riportato nel seguente codice:
int main (void) {
unsigned char i=0;
// Dopo la somma i vale 1
i++;
}
177
// Incremento di un char
y++;
// Ciclo infinito
while (1) {
}
}
Dal codice Assembly possibile vedere come vengono tradotte le istruzioni di somma
ed incremento per mezzo dell'operatore ++:
12:
13:
7CE6
7CE8
7CEA
7CEC
7CEE
7CF0
14:
15:
16:
7CF2
7CF4
17:
18:
19:
7CF6
7CF8
20:
21:
22:
7CFA
0E01
2402
6E02
0E00
2003
6E03
// Sommo 1 ad un intero
i = i + 1;
MOVLW 0x1
ADDWF i, W, ACCESS
MOVWF i, ACCESS
MOVLW 0x0
ADDWFC 0x3, W, ACCESS
MOVWF 0x3, ACCESS
4A02
2A03
// Incremento di un intero
i++;
INFSNZ i, F, ACCESS
INCF 0x3, F, ACCESS
2804
6E04
// Sommo 1 ad un char
y = y + 1;
INCF y, W, ACCESS
MOVWF y, ACCESS
2A04
// Incremento di un char
y++;
INCF y, F, ACCESS
Come visibile, escluse le inizializzazioni delle variabili charA e charB, quello che si ha
che charB assegnato il risultato della somma di 1 pi l'incremento di charA, una volta
ottenuto con ++ posto prima della variabile, e una volta posto dopo. Ovviamente il
risultato deve essere uguale a 2 in tutti e due i casi!
...ed qui che avvengono i problemi!
Nel primo caso, charB vale 1 mentre nel secondo caso charB vale 2!
La ragione legata al fatto che il ++ prima o dopo effettivamente permette
d'incrementare la variabile subito (ovvero prima della somma) o dopo la somma. Questo
se non tenuto in considerazione pu portare a gravi errori. Un modo semplice per evitare
tali errori far uso di una sola modalit d'incremento ++i o i++, una vale l'altra, e
scrivere l'istruzione in una riga di codice a parte, ovvero non all'interno di espressioni di
alcun tipo. In questo modo si garantisce che quello che ci si aspetta sia quello che si
ottiene. Dal codice Assembly possibile vedere quanto ora spiegato, ovvero come
l'istruzione INCF sia eseguita dopo la somma (primo caso) o prima della somma (secondo
caso).
29:
30:
7CBC
7CBE
7CC0
7CC2
31:
7CC4
7CC6
7CC8
7CCA
32:
33:
34:
7CCC
0E00
6E02
0E00
6E01
// Inizializzo le variabili
charA = 0;
MOVLW 0x0
MOVWF 0x4, ACCESS
MOVLW 0x0
MOVWF charA, ACCESS
charB = 0;
MOVLW 0x0
MOVWF 0x2, ACCESS
MOVLW 0x0
MOVWF charB, ACCESS
0E01
0E00
6E04
0E00
6E03
179
7CCE
7CD0
7CD2
7CD4
7CD6
7CD8
7CDA
35:
36:
37:
7CDC
7CDE
7CE0
7CE2
38:
7CE4
7CE6
7CE8
7CEA
39:
40:
41:
7CEC
7CEE
7CF0
7CF2
7CF4
7CF6
7CF8
7CFA
2403
6E01
0E00
2004
6E02
4A03
2A04
0E00
6E02
0E00
6E01
// Inizializzo le variabili
charA = 0;
MOVLW 0x0
MOVWF 0x4, ACCESS
MOVLW 0x0
MOVWF charA, ACCESS
charB = 0;
MOVLW 0x0
MOVWF 0x2, ACCESS
MOVLW 0x0
MOVWF charB, ACCESS
4A03
2A04
0E01
2403
6E01
0E00
2004
6E02
0E00
6E04
0E00
6E03
Nel primo caso, dal momento che intB vale dieci e viene diviso per un suo
sottomultiplo, si ha che il resto 0, dunque intA vale 0. Nel secondo caso dal momento
che intB vale 10 e viene diviso per 3, si ha resto 1, dunque intA vale 1.
Ultima nota va data riguardo alla priorit degli operatori. Ogni operatore ha una
propria priorit all'interno di un'espressione matematica ma dal momento che spesso
difficile tenere a mente le priorit sempre bene far uso delle parentesi tonde in modo da
raggruppare ed ordinare espressioni complesse.
a = b * c + 5;
meglio scriverla:
a = (b * c) + 5;
piuttosto che far affidamento al fatto che la moltiplicazione viene svolta comunque per
prima. Nel caso in cui si voglia fare prima la somma invece obbligatorio scrivere:
a = b * (c + 5);
Nel caso di espressioni complesse bene creare della variabili intermedie, il cui nome
deve essere espressivo della loro funzione. Per esempio per il calcolo della velocit della
bicicletta l'espressione potrebbe essere:
speed = (numero_giri_ruota * DIAMETRO_RUOTA)/(num_base_tempo *
BASE_TEMPO);
Si noti che in questo caso il tutto risulta molto pi leggibile, per cui risulta
particolarmente pratico nel caso in cui si debbano risolvere dei problemi complessi.
181
Questa pratica per nominare le variabili anche seguita per nominare le funzioni. In
ultimo si noti l'importanza nel dare alle variabili un nome significativo. Questo permette
di ridurre i commenti mantenendo il codice pi pulito ma sempre molto leggibile. In un
certo qual modo un nome della variabile potrebbe racchiudere il commento o spiegazione
della variabile stessa.
Per evitare che la divisione dia risultato 2 anche quando la variabile per il risultato di
tipo float, bisogna promuovere, ovvero fare il Casting di uno dei due numeri del valore
destro (o del dividendo o del divisore), ovvero:
floatA = (float) 9 / 4;
Dall'esempio si vede subito che fare il Casting significa mettere tra parentesi il tipo in
cui vogliamo convertire la nostra variabile (o costante in questo caso). A questo punto il
compilatore quando trova l'operazione vede che la divisione tra un float ed un int,
dunque nel fare la divisione, al fine di gestire i calcoli, utilizzer la struttura dati pi
complessa ovvero float.
Lo stesso risultato si sarebbe ottenuto qualora nel fare la divisione, o il dividendo o il
divisore gi di tipo float:
float floatA = 9;
floatB = floatA / 4;
In questo caso floatB vale 2.25 come nel caso in cui era stata fatta la promozione
volontaria a float.
Nel caso specifico, avendo fatto uso di solo costanti e non di variabili intero, piuttosto
che far uso del Casting, si potrebbe scrivere:
floatA = 9.0 / 4;
Ovvero una delle costanti deve essere dichiarata con il punto decimale. In questo modo la
costante viene ritenuta float ed il risultato 2.25 anche se non abbiamo fatto alcun
Casting. Nel caso di variabili il Casting l'unica soluzione per convertire un intero in float.
Alcune volte quando tutte le variabili sono di tipo float e si sta facendo una divisione
di cui non si ha interesse nella parte decimale, ovvero si vuole solo la parte intera della
divisione, pu ritornare utile fare un Casting ad int:
float floatA = 9;
floatB = (int) floatA / 4;
In questo caso floatB, grazie al Casting, ci permette di avere come risultato 2. Dunque
non tutti i mali vengono per nuocere, ma bisogna certamente stare attenti. Da quanto
spiegato sembra che il pericolo stia solamente nell'utilizzo dei float e con la divisione,
mentre in realt ogni volta che facciamo operazioni con strutture dati differenti, ovvero
tipi diversi, c' il rischio che a causa di una qualunque manipolazione delle variabili ci
siano delle perdite d'informazione. Un esempio lo abbiamo visto anche nel caso dei
puntatori, in cui il puntatore e la variabile puntata devono essere dello stesso tipo.
Vediamo ora un esempio tra char e int.
intA = 10;
183
charA = intA;
intA = 300;
charA = intA;
Nel primo caso carichiamo 10 in una variabile intera e poi carichiamo tale variabile int
in una char. Quello che ci si aspetta che charA valga 10. Effettivamente questo il
valore di charA. A questo punto sembra che tale operazione possa essere fatta in maniera
innocua, per ripetendo l'operazione con il valore di 300 vediamo che charA vale questa
volta 44!
Effettivamente 300 in binario si scrive 00000001 00101100, ovvero richiede due byte,
cosa che un int effettivamente ha. Nel momento in cui si carica il valore di un intero in
una variabile char, solo il primo byte viene caricato dunque solo i primi 8 bit, ovvero
00101100, cio il nostro 44. In questo caso abbiamo dunque perso delle informazioni ma
il Casting non ci avrebbe aiutato. Quello che avremo dovuto fare era quello di mettere il
risultato in un intero invece che in un char.
Questo secondo esempio serve anche per mettere in guardia nel caso in cui si facciano
operazioni complesse in cui valori intermedi possono causare l'overflow di qualche
variabile. Errore tipico potrebbe essere quello della moltiplicazione tra variabili, in cui il
valore intermedio pu facilmente eccedere il valore massimo della variabile.
Operatori logici
Frequentemente all'interno di un programma ci si trova a dover prendere delle
decisioni in base al particolare valore di una variabile. Al fine di poter effettuare dei test
sulle variabili, oltre a particolari parole chiave che vedremo a breve, sono necessarie anche
le operazioni logiche o operatori logici.
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
operatore logico maggiore
operatore logico minore
Tali operatori saranno discussi in maggior dettaglio quando si parler delle istruzioni
condizionali, ovvero quelle istruzioni che permettono al programma di gestire le domande
poste per mezzo degli operatori logici. Come verr messo in evidenza parlando
dell'istruzione if (...) la domanda A uguale a B si pone scrivendo if (A==B) e non
if (A=B).
184
Operatori bitwise
Gli operatori bitwise permettono di manipolare un registro (variabile) variandone i
singoli bit. Tali operatori sono quelli che permettono di svolgere operazioni secondo
l'algebra booleana. Gli operatori bitwise sono:
operatore binario AND
operatore binario OR
operatore binario XOR
operatore complemento a 1 (i bit vengono invertiti)107
shift a sinistra
shift a destra
Come visibile and ed or binario sono molto simili a quelli logici, se non per il fatto che il
simbolo deve essere ripetuto solo una volta.
&
|
^
~
<<
>>
:
:
:
:
:
:
Vediamo a scopo riassuntivo le tabelle della verit dei vari operatori, applicati nel caso di
due ingressi. In Tabella 8 riportata la tabella della verit dell'operatore AND. La tabella
della verit dell'operatore AND (&) si pu leggere come:
L'uscita vale 1 solo nel caso in cui sia A che B valgono 1.
Input A
Input B
A&B
si ha che il risultato, 01000001, ovvero solo i bit il cui valore 1 in entrambe le variabili
rimangono ad 1. Da questo si capisce che una possibile applicazione dell'operatore &
proprio quello di maschera, ovvero permette di azzerare i bit di cui non si ha interesse.
Quando si scrive un numero in formato binario 0b00000011 bisogna sempre
accertarsi si inserire il numero corretto di cifre dopo 0b. Qualora si dovessero
scrivere un numero maggiore di 0 o 1, il programma si potrebbe comportare in
maniera anomala a causa di troncamenti del numero.
107
Il simbolo ~ possibile inserirlo come carattere ASCII numero 126. Tenere premuto ALT, digitare 126 e poi rilasciare ALT.
185
In Tabella 9 riportata la tabella della verit dell'operatore OR. La tabella della verit
dell'operatore logico OR (|) si pu leggere come:
L'uscita vale 1 nel caso in cui o A o B valgono 1.
Input A
Input B
A|B
Input B
A ^B
Si ha che il risultato vale 01110100, ovvero i bit che hanno entrambi il valore 1 sono posti
a 0. Un tipico uso dell'operatore XOR il lampeggio di un LED.
Scrivendo per esempio:
LED ^= 0x01;
186
dove LED potrebbe essere definita come una costate per un pin qualunque del
microcontrollore, come per esempio:
#define LED PORTDbits.RD0
il valore del pin viene invertito, infatti supponendo che inizialmente LED sia posto a 0,
facendo 0 ^ 1 si ha 1 (LED acceso). La seconda volta che si esegue l'istruzione si ha 1 ^ 1,
il risultato vale 0, per cui il LED si spegne. Continuando il discorso e mettendo un ritardo
tra un'esecuzione e l'altra dell'istruzione, si ha il lampeggio del LED.
In Tabella 11 riportata la tabella della verit dell'operatore NOT. La tabella
dell'operatore NOT (~) si legge:
L'uscita vale 1 se l'ingresso vale 0 mentre vale 0 se l'ingresso vale 1.
Input A
~A
In altre parole l'operatore NOT inverte il valore dei bit nel registro.
Scrivendo il seguente codice:
input_A = 0b01110001;
risultato = ~input_A;
tipo unsigned
char:
charA = 16;
// Divisione classica
charB = charA /4;
charA = 16;
// Divisione per mezzo dello shift
charB = charA >> 2;
Dal momento che la divisione per 4 ovvero una potenza di due possibile utilizzare
lo shift a destra, di due posizioni, quale passo equivalente alla divisione per due.
Controllando il codice Assembly possibile vedere che effettivamente lo shift ha permesso
di rendere l'implementazione della divisione pi snella, permettendo di raggiungere
dunque maggiori velocit di esecuzione e risparmiare memoria.
12:
7F40
7F42
7F44
7F46
13:
14:
15:
7F48
7F4A
7F4C
7F4E
7F50
7F52
7F54
7F56
7F58
7F5A
7F5C
16:
17:
7F5E
7F60
7F62
7F64
18:
19:
20:
7F66
7F68
7F6A
7F6C
6E0A
0E10
6E0C
500A
dividendo_A = 16;
MOVWF 0xA, ACCESS
MOVLW 0x10
MOVWF dividendo_A, ACCESS
MOVF 0xA, W, ACCESS
C00C
F001
6A02
0E00
6E04
0E04
6E03
ECB8
F03F
5001
6E0B
// Divisione classica
risultato = dividendo_A /4;
MOVFF dividendo_A, dividend
NOP
CLRF 0x2, ACCESS
MOVLW 0x0
MOVWF 0x4, ACCESS
MOVLW 0x4
MOVWF divisor, ACCESS
CALL 0x7F70, 0
NOP
MOVF dividend, W, ACCESS
MOVWF risultato, ACCESS
6E0A
0E10
6E0C
500A
dividendo_A = 16;
MOVWF 0xA, ACCESS
MOVLW 0x10
MOVWF dividendo_A, ACCESS
MOVF 0xA, W, ACCESS
400C
42E8
0B3F
6E0B
Dal codice Assembly possibile vedere che per fare la divisione per 4 si effettuato uno
shift a destra di due posti (ovvero eseguita RRNCF per due volte) ottenendo come risultato
4. La divisione per 4 facendo uso dell'operatore matematico / richiede molte pi
istruzioni ovvero tempo di esecuzione.
Se ripetete l'esempio dividendo per 16 invece di 4 ci si accorge di una cosa strana,
188
6E0A
0E10
6E0C
500A
dividendo_A = 16;
MOVWF 0xA, ACCESS
MOVLW 0x10
MOVWF dividendo_A, ACCESS
MOVF 0xA, W, ACCESS
380C
0B0F
6E0B
RRNCF
bens con
Questo esempio dovrebbe mettervi in guardia ancora una volta che non bisogna mai
ottimizzare il codice alla ceca, soprattutto quando sono presenti delle costanti. In
particolare l'esempio mette in evidenza che spesso il compilatore piuttosto sveglio, visto
che riassume l'esperienza e trucchi di molti programmatori esperti.
Ripetendo ancora una volta il nostro esempio facendo uso di variabili di tipo float si ha:
floatA = 16;
// Divisione classica
floatB = floatA /4;
floatA = 16;
// Divisione per mezzo dello shift
floatB = (unsigned int) floatA >> 2;
In questo caso necessario effettuare un Casting della variabile float ad intero, poich
l'operatore shift pu essere utilizzato solo per variabili di tipo intero o char. Controllando
il codice Assembly (non riportato per brevit) si pu vedere che in ambedue le
implementazioni sono necessarie molte decine di istruzioni. Dunque se si pensava di
ottimizzare il codice per mezzo dell'utilizzo dello shift si sarebbe fatto un buco
nell'acqua108. In casi come questo dove non si ha il beneficio voluto bene far uso della
forma del codice di pi facile lettura, ovvero:
floatB = floatA /4;
Anche questo esempio dovrebbe essere una lampadina da utilizzare nel vostro
bagaglio d'esperienza, nel caso in cui vogliate ottimizzare il codice.
Riprendiamo ora l'esempio discusso nel paragrafo del Casting:
intA = 300;
108
Compilatori differenti possono ottimizzare in maniera diversa, rendendo o meno l'utilizzo dello shift una soluzione valida per
ottimizzare il codice.
189
charA = intA;
In tale esempio si era messo in evidenza che trasferire un intero troppo grande in un
avrebbe portato alla perdita dei dati. Supponiamo ora di voler evitare la perdita di
dati, facendo uso di due variabili char:
char
intA = 300;
charA = intA;
charB = intA >> 8;
Grazie allo shift possibile recuperare il byte che andava perduto e caricarlo in charB.
Tale operazione risulta molto utile nel caso in cui si voglia per esempio memorizzare il
valore di un intero in memoria EEPROM, visto che la EEPROM composta da
locazioni di memoria da un byte. Altra situazione in cui potrebbe tornare utile
un'operazione di questo tipo potrebbe essere nel caso in cui si voglia inviare un itero sulla
porta seriale, visto che si pu trasmettere un solo byte alla volta.
Questo codice se pur funzionante non mette in evidenza il fatto che vi una
trasformazione in char dunque bene commentare tale operazione o far uso di un Casting
esplicito:
intA = 300;
charA = (unsigned char) intA;
charB = (unsigned char) (intA >> 8);
Fare il Casting non render il codice Assembly pi complesso, visto che implicitamente
comunque fatto, ma mette in evidenza che stiamo trasformando il nostro intero in char.
bene notare che in questa linea di codice l'operazione di shift messa tra parentesi.
charB = (unsigned char) (intA >> 8);
190
1
Blocco istruzioni
Espressione
Logica
Con il ciclo while possibile ottenere dei cicli infiniti qualora l'espressione sia sempre
verificata. Il modo pi semplice per ottenere un ciclo infinito si ha semplicemente
scrivendo:
while (1) {
// Istruzioni da eseguire all'infinito
}
191
while (a=5) {
// Istruzioni da eseguire all'infinito
}
// Istruzioni da eseguire
Nel caso in cui un loop diventi infinito, ovvero la condizione logica dovesse
essere sempre verificata, bene controllare con attenzione l'espressione logica e
verificare che non si siano commessi errori come per esempio l'utilizzo
dell'operatore assegnazione = piuttosto che l'operatore logico ==. Il
compilatore potrebbe generare delle Warning nel caso sospetti errori.
Una volta all'interno di un ciclo while, vi sono due modi per uscirne. Il primo modo
quello spiegato precedentemente, ovvero l'espressione logica non viene pi verificata.
Questo tipo di uscita non permette per di uscire da un ciclo infinito come quelli
precedentemente descritti, o comunque avere delle uscite premature dovute per esempio
al verificarsi di errori. 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, dunque esce dal ciclo while senza troppe domande. 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. Vediamo un
semplice esempio di utilizzo di un ciclo while :
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void) {
// Variabile usata per il conteggio da visualizzare su PORTD
unsigned char numero = 0;
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
192
La parte iniziale del programma non richiede particolari spiegazioni. Dal momento che
la variabile numero inizializzata a 0, il controllo nel ciclo while viene verificato e si entra
nel loop.
while (numero < 16)
Il loop viene ripetuto fino a quando la variabile numero minore di 16 ovvero fino a 15.
Tale valore anche l'ultimo valore visualizzato sui LED ovvero 0x0F, in binario
00001111. Eseguendo il programma, l'esecuzione talmente veloce che viene visualizzato
solo il valore 00001111 come se il conteggio non sia avvenuto. Per vedere il conteggio
necessario effettuare il Debug del programma ed eseguire le varie istruzioni passo passo.
Ora vediamo un altro esempio molto simile al precedente in cui si utilizza l'istruzione
per avere un'uscita prematura dal ciclo while.
break
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void) {
// Variabile usata per il conteggio da visualizzare su PORTD
unsigned char numero;
193
do {
// Codice da eseguire
} while ( espressione_logica)
Da un punto di vista logico rimane tutto uguale, ma l'istruzione while posta alla fine
del blocco istruzione da eseguire, che come detto sono eseguite certamente almeno una
volta. Una rappresentazione per mezzo di un diagramma di flusso riportata in Figura 89.
Blocco istruzioni
Espressione
Logica
Il ciclo for ( )
Come abbiamo appena visto per l'istruzione while, i programmi difficilmente sono
lineari, ovvero una semplice sequenza di istruzioni da compiere dall'inizio alla fine. Spesso
l'esecuzione del programma viene interrotta per fare dei test o ripetere delle porzioni del
programma stesso. Ripetere una sezione del programma equivale a fare dei salti
all'indietro in modo da riposizionare il registro PC all'inizio del ciclo.
Per eseguire un numero di volte prefissato una determinata istruzione, il modo pi
semplice per mezzo costrutto for(), il quale permette appunto di eseguire un numero
definito di volte una certa operazione o insieme di operazioni. La sua sintassi :
for (espressione1; espressione2; espressione3) {
// Blocco da ripetere
}
195
Si pu vedere che il ciclo for composto da un blocco con parentesi graffe al cui
interno sono scritte l'insieme di istruzioni da ripetere. Prima del blocco da ripetere, vi la
parola chiave for e tre espressioni tra parentesi109:
espressione1: Usata per l'inizializzazione della variabile di controllo del loop.
espressione2: Controllo della condizione di fine loop.
espressione3: Operazione da svolgere sulla variabile per il controllo del loop.
Da quanto appena detto si capisce che, sebbene un modo semplice per ripetere una
sequenza di istruzioni sia per mezzo del costrutto for, in realt questo comporta molte
pi istruzioni e complessit se paragonato al costrutto while. Il diagramma di flusso di un
ciclo for riportato in Figura 90.
Inizializzazione
Espressione
Logica
1
Incremento
o altra operazione
Blocco istruzioni
Ogni espressione pu essere in realt un gruppo di espressioni, ma si sconsiglia di utilizzare tale tecnica poich di pi
difficile lettura.
196
LATD = i;
// Ciclo infinito
while (1) {
}
La parte iniziale del programma non richiede particolari commenti. Si noti solo che si
voluto definire una costante MAX_VALUE, la quale viene utilizzata come limite per il
controllo del loop. Il suo utilizzo non obbligatorio, ma ritorna utile in applicazioni reali,
permettendo di cambiare il numero di volte che viene ripetuto il loop. Si noti inoltre che
PORTD stata impostata come output, in modo da visualizzare il valore i.
Iniziamo a vedere in maggior dettaglio le espressioni del ciclo for. La prima
espressione inizializza la variabile i, precedentemente dichiarata, questa utilizzata per il
conteggio delle volte per cui deve essere ripetuto il loop. Normalmente le variabili di
conteggio vengono inizializzate a 0 ma in base alle esigenze possibile iniziare da un
valore differente. La seconda espressione effettua ad ogni ciclo un controllo sullo stato
della variabile per vedere se la condizione verificata. In questo caso volendo fare un
ciclo con MAX_VALUE iterazioni si posto i = 0 come inizio e i < MAX_VALUE 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 MAX_VALUE bisogna incrementare di 1, dunque
si scritto i++. Se il punto di partenza fosse stato MAX_VALUE e quello di arrivo fosse stato
>0 si sarebbe dovuto scrivere i--110, ovvero:
for (i=MAX_VALUE; i > 0; i--) {
}
110
LATD = i;
In questo caso si parlato solo di i++ e i-- ma in realt si possono scrivere anche altre espressioni.
197
Le istruzioni che si vogliono ripetere per MAX_VALUE volte sono contenute all'interno
delle parentesi graffe. In questo caso si ha la sola istruzione che scrive il valore di i che si
scrive nel registro LATD. Per poter vedere il conteggio sui LED posti sulla PORTD
necessario eseguire il programma passo passo, ovvero in fase di Debug.
Sebbene il conteggio al contrario possa sembrare semplicemente un'alternativa al
caso di incremento, viene in realt utilizzato in applicazioni a batteria al fine di
risparmiare energia. Infatti per un microprocessore pi facile contare al
contrario e vedere se si arrivati a zero che non contare in vanti e vedere se si
arrivati al valore desiderato.
Vediamo un semplice esempio in cui si mette a confronto un semplice loop while, for sia
in avanti che indietro, usando una variabile i di tipi unsigned char.
// Esempio di ciclo for in avanti
for (i=0; i < MAX_VALUE; i++) {
LATD = i;
}
// Esempio di ciclo for indietro
for (i= MAX_VALUE; i > 0; i--) {
LATD = i;
}
// Esempio di ciclo while in avanti
i = 0;
while (i<MAX_VALUE){
LATD = i;
i++;
}
// Esempio di ciclo while indietro
i = MAX_VALUE;
while (i>0){
LATD = i;
i--;
}
6E01
0E00
6E02
5001
D003
2A02
0E55
6402
D7FA
198
7FB4
7FB6
37:
38:
39:
40:
7FC0
7FC2
7FC4
7FC6
7FC8
7FCE
7FD0
7FD2
41:
7FCA
7FCC
42:
43:
44:
45:
46:
7FD4
7FD6
7FD8
7FDA
47:
7FDC
7FE4
7FE6
7FE8
48:
7FDE
7FE0
49:
7FE2
50:
51:
52:
53:
7FEA
7FEC
7FEE
7FF0
54:
7FF2
7FFA
7FFC
55:
7FF4
7FF6
56:
7FF8
57:
C002
FF8C
6E01
0E56
6E02
5001
D003
0602
6602
D7FB
C002
FF8C
6E01
0E00
6E02
5001
D003
0E55
6402
D7FA
C002
FF8C
2A02
6E01
0E56
6E02
5001
D003
6602
D7FB
C002
FF8C
0602
MOVFF i, LATD
NOP
}
// Esempio di ciclo for indietro
for (i= MAX_VALUE; i > 0; i--) {
MOVWF 0x1, ACCESS
MOVLW 0x56
MOVWF i, ACCESS
MOVF 0x1, W, ACCESS
BRA 0x7FD0
DECF i, F, ACCESS
TSTFSZ i, ACCESS
BRA 0x7FCA
LATD = i;
MOVFF i, LATD
NOP
}
// Esempio di ciclo while in avanti
i = 0;
MOVWF 0x1, ACCESS
MOVLW 0x0
MOVWF i, ACCESS
MOVF 0x1, W, ACCESS
while (i<MAX_VALUE){
BRA 0x7FE4
MOVLW 0x55
CPFSGT i, ACCESS
BRA 0x7FDE
LATD = i;
MOVFF i, LATD
NOP
i++;
INCF i, F, ACCESS
}
// Esempio di ciclo while indietro
i = MAX_VALUE;
MOVWF 0x1, ACCESS
MOVLW 0x56
MOVWF i, ACCESS
MOVF 0x1, W, ACCESS
while (i>0){
BRA 0x7FFA
TSTFSZ i, ACCESS
BRA 0x7FF4
LATD = i;
MOVFF i, LATD
NOP
i--;
DECF i, F, ACCESS
}
Il codice lascia piuttosto delusi visto che al livello di ottimizzazione utilizzano non si
notano grandi differenze. Ciononostante se si contano il numero di istruzioni dentro il
blocco che viene effettivamente ripetuto, si pu notare che il ciclo for richiede circa 10
istruzioni contro 6 per il ciclo while. Questo mostra come un ciclo while possa tornare
pi leggero, soprattutto in cicli che non contengono molte istruzioni da ripetere e devono
199
x++, y++) {
Nonostante sia possibile una forma di programmazione di questo tipo, bene limitarne
l'uso visto che il codice diventa pi difficile da comprendere.
Vediamo un altro semplice esempio in cui si fa uso del ciclo for e in cui vengono
utilizzati anche gli Array, in modo da prendere dimestichezza con entrambi e vedere come
inserire anche un ritardo. In questo programma sono anche illustrate alcune pratiche di
programmazione che bene non usare, anche se il programma funziona comunque senza
problemi.
#include <xc.h>
#include "PIC18F4550_config.h"
#define MAX_VALUE 10
void main (void){
// Variabile per il Conteggio e Delay
unsigned char i;
unsigned int j;
// Definizione dell'Array
unsigned char mioArray [MAX_VALUE];
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Inizializzazione dell'Array
for (i = 0; i < MAX_VALUE; i++)
mioArray[i] = i;
200
LATD = mioArray[i];
// Ciclo infinito
while (1) {
}
Anche in questo caso non c' molto da dire per la parte iniziale del programma. Si noti
solo che la nostra costante MAX_VALUE stata adesso utilizzata in pi punti, dunque il
cambio del suo valore permette di aggiornare l'intero programma senza dover cambiare
pi punti del codice sorgente, questa proprio l'utilit delle costanti. L'Array
inizializzato utilizzando un ciclo for, per mezzo del quale possibile caricare il valore
dell'indice all'interno della variabile indicizzata dell'Array.
// Inizializzazione dell'Array
for (i = 0; i < MAX_VALUE; i++)
mioArray[i] = i;
ovvero facendo uso delle parentesi graffe. In questo modo si mette in evidenza che nel
ciclo for viene svolta solo questa operazione. Vediamo ora il nostro nuovo loop :
for (i=0; i < MAX_VALUE; i++) {
// Ritardo
for (j =0; j < 64000; j++);
}
LATD = mioArray[i];
programma in modalit passo-passo. Si noti che il ciclo for possiede il punto e virgola alla
fine della sua dichiarazione, visto che non possiede nessuna istruzione. Questa linea di
codice in realt meglio scriverla:
for (j =0; j < 64000; j++) {
// Non faccio nulla
}
Questo:
for (i = 0; i < 10; j++)
for (x = 0; x < 100; x++)
for (y = 0; y < 100; y++) {
mat[x][y] = (i * 3) + x;
}
o questo:
for (num_scan = 0; num_ scan < NUMERO_SCANSIONI_MAX; num_scan)
for (riga = 0; riga < MAX_RIGA; riga++)
for (colonna = 0; colonna < MAX_COLONNA; colonna++) {
matrice[riga][colonna] = (num_scan * 3) + riga;
}
In questo secondo caso, sebbene si faccia uso di nomi pi lunghi e il codice possa
sembrare pi lungo, viene in realt tradotto in Assembly come il codice con variabili meno
leggibili. Un'ultima nota sul ciclo for riguarda il caso particolare in cui non scritta
nessuna espressione, come sotto riportato:
for ( ; ;) {
//ciclo infinito
}
202
Allo stesso modo del ciclo while, l'istruzione break permette di interrompere il
ciclo ed uscire in maniera prematura. Oltre all'istruzione break, presente
l'istruzione continue, che permette di saltare il resto del codice presente nel
ciclo, sia esso for o while, e riprendere il codice dall'esecuzione del controllo
dell'istruzione condizionale.
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 () ovvero se (...). Per questa
istruzione sono presenti due diverse sintassi, la prima :
if (espressione_logica) {
}
Espressione
Logica
Blocco istruzioni
La seconda sintassi :
203
if (espressione_logica) {
// Programma da eseguire se l'espressione logica verificata
} else {
// Programma da eseguire se l'espressione logica non verificata
}
Per mezzo della seconda sintassi, in cui presente anche la parola chiave else,
possibile eseguire un secondo blocco di istruzioni qualora l'espressione logica non
verificata, il secondo blocco introdotto proprio per mezzo della parola chiave else
(altrimenti).
Il diagramma di flusso per l'istruzione semplice if riportato in Figura 92.
Espressione
Logica
Blocco istruzioni
Blocco istruzioni
if ( i = 3) {
LATD = i;
}
Il compilatore non segnala nessun errore e il programma viene anche eseguito dal PIC;
il problema sta nel fatto che quando viene eseguita l'operazione if (i=3) l'espressione
logica sempre verificata poich maggiore di 1, ovvero 3, dunque per il C vera.
Dunque come effetto collaterale si ha che PORTD viene impostata a 3 ogni volta che
viene eseguita l'istruzione if (i=3).
Vediamo ora un esempio completo in cui si effettua la lettura di un pulsante collegato
tra massa e RB4 e si pilotano dei LED sulla PORTD.
#include <p18f4550.h>
#include "PIC18F4550_config.h"
int main (void){
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Abilita i resistori di pull-up sulla PORTB
INTCON2bits.RBPU = 0x00;
// Ciclo infinito
for (;;) {
if (PORTBbits.RB4 == 0) {
// Ho premuto il pulsante su 0 su RB4
LATD = 0x0F;
}
else {
// Il pulsante aperto
LATD = 0xF0;
}
205
che setta a 0x00 il bit RBPU all'interno del registro INTCON2. Questa opzione abilita i
resistori di pull-up su tutti i pin della porta B settati come ingressi.
Per poter leggere il pulsante si provveduto a realizzare un numero infinito di letture 112
sul pin RB4 di PORTB facendo uso del ciclo for senza parametri, tanto per non usare il
tipico while (1).
All'interno del ciclo infinito si effettuato il controllo del bit RB4 di PORTB per
mezzo della variabile PORTBbits.RB4. Poich sono stati attivati i resistori di pull-up e il
pulsante collegato verso massa si ha che normalmente il valore di RB4 pari a 1 logico.
Se il controllo effettuato dall'if vale 0 viene eseguito il blocco di istruzioni dell' else,
dunque PORTD viene impostato con il valore 0xF0, ovvero i LED collegati sui quattro
bit pi significativi sono accesi, tali LED rimangono accesi fino a quando non si preme il
pulsante su RB4. Quando si preme il pulsante, il pin RB4 vale 0 logico (collegato a massa),
dunque la condizione if viene verificata e PORTD viene impostato a 0x0F, ovvero si
accenderanno i LED sulla PORTD associati ai bit meno significativi. Poich il ciclo for
infinito, RB4 viene continuamente testato, dunque quando si rilascia il pulsante, PORTD
viene impostato nuovamente a 0xF0.
Normalmente quando si leggono dei pulsanti la procedura ora utilizzata non
sufficiente, anche se si ha l'impressione che tutto funzioni correttamente. 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 spike, ovvero l'ingresso del PIC ha
un treno di 0 e 1, che se non opportunamente 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 stato effettivamente premuto. Questa tecnica pu essere
implementata sia per mezzo di hardware esterno che per via software, ed nota come
111
112
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. L'utilizzo dell'uno o dell'altro dipende da come si collega il
pulsante. Se si collega il pulsante a massa, bisogna usare un resistore di pull-up mentre nel caso in cui si collega il pulsante a
Vcc, si bisogna usare un resistore di pull-down.
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 microcontrollore libero di svolgere altre operazioni
invece di leggere continuamente un ingresso, poich viene avvisato (Interrupt) quando l'ingresso subisce una variazione.
206
filtro antirimbalzo. Nel nostro esempio il filtro implementato via software, visto che
possibile risparmiare componenti esterni.
Nel seguente esempio si incrementa una variabile e la si pone in uscita ad ogni pressione
del pulsante posto sul pin RB4:
#include <xc.h>
#include "PIC18F4550_config.h"
int main (void){
// Variabile usata per creare un conteggio fittizio di pausa
unsigned int i;
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Abilita i resistori di pull-up sulla PORTB
INTCON2bits.RBPU = 0x00;
// Ciclo infinito
for (;;) {
if (PORTBbits.RB4 == 0) {
// Pausa che filtra gli spike
for (i=0;i<32000; i++) {
}
// Controllo nuovamente il pulsante per vedere
// se ancora premuto
if (PORTBbits.RB4 == 0) {
}
}
l'introduzione della variabile i utilizzata per implementare una pausa per mezzo di un
ciclo for. possibile notare che all'interno del ciclo infinito il filtro antirimbalzo viene
realizzato nel seguente modo: si effettua prima la lettura del pulsante, qualora questo
risulti attivo c' la piccola pausa (conteggio fittizio) 113. Dopo il filtraggio viene nuovamente
riletto il pulsante per vedere se ancora premuto. Qualora sia ancora premuto vuol dire
che effettivamente si premuto il pulsante. In questo caso quello che viene fatto
incrementare il registro di uscita LATD, per cui si vede in uscita il conteggio binario da 0
a 255. Una volta che LATD arriva a 255 il conteggio riprende da 0. Inoltre per come
impostato il programma, tenendo premuto il pulsate, il conteggio viene fatto in
automatico (come quando si imposta l'ora dell'orologio digitale) e premendo il pulsante
troppo rapidamente la pressione viene ignorata114.
Qualora il conteggio (pausa) del ciclo for dovesse essere tolto, si pu notare
come a causa degli spike del pulsante il conteggio viene fatto quasi come se il
microcontrollore stia dando i numeri. L'incremento sempre di 1 ma vengono
contati tutti gli spike...creando cosi apparenti incrementi a passi di 50 e oltre...ma
in maniera casuale!
Si fa notare che spesso i pulsanti non vengono letti in polling piuttosto si fa uso delle
interruzioni. Questo argomento per oggetto di un altro Capitolo.
Frequentemente si ha l'esigenza di testare pi condizioni al fine di prendere una
decisione, in particolare se si devono controllare due condizioni logiche e decidere se
accendere un LED, un motore o qualsiasi altro dispositivo, ci si potrebbe porre la
domanda: condizione_1 e condizione_2 sono entrambe verificate? O ancora, si
verificato solo un evento?
Per rispondere alla prima domanda si potrebbe fare in questo modo, utilizzando due
in successione:
if
if (condizione_1 == 1)
if (condizione_2 == 1){
}
Un altro modo che si potrebbe utilizzare per mezzo dell'operatore logico AND
ovvero:
&&,
Si noti che ogni singola espressione posta all'interno di parentesi tonde. In particolare
possibile utilizzare l'operatore && anche con pi termini, ma bene, per ragioni di
113
114
In generale per ottenere un buon filtro antirimbalzo si raccomandano circa 5-10ms di attesa.
Si capisce che estendendo di molto il filtro antirimbalzo si pu creare la funzione tipica dei pulsanti ON-OFF, ovvero di
non accendere o spegnere il sistema se non si tiene premuto il pulsante per almeno 1-2 secondi. In questo modo si filtrano
oltre che agli spike, anche le pressioni accidentali.
208
leggibilit non eccedere con la fantasia. Oltre all'operatore && si pu utilizzare anche
l'operatore OR ||, o altro operatore logico o combinazione degli stessi.
Vediamo qualche dettaglio riguardo all'utilizzo della parola chiave
appena descritti:
else
negli esempi
In questo caso capire quando viene eseguito il blocco else piuttosto semplice poich
abbiamo a che fare con un solo if. Il blocco else viene eseguito se la condizione_1 e
condizione_2 non sono verificate, ovvero una sola condizione o nessuna delle due
verificata.
Riprendendo il primo esempio, in cui si invece fatto utilizzo di due if:
if (condizione_1 == 1)
if (condizione_2 == 1){
// Programma da eseguire se le condizioni sono entrambe verificate
} else {
// Programma da eseguire se la condizione 1 vera e la 2 no!
}
In questo secondo esempio, stata inserita la parentesi graffa anche per il primo if,
creando in questo modo una chiara evidenza a chi appartiene ogni else, l'indentazione
209
aiuta molto alla comprensione. Questo codice si sarebbe potuto scrivere anche in questo
secondo modo, ma lo sconsiglio poich ancor meno leggibile del primo.
if (condizione_1 == 1)
if (condizione_2 == 1){
// Programma da eseguire se le condizioni sono entrambe verificate
} else {
// Programma da eseguire se la condizione 1 vera e la 2 no!
}
else {
// Programma da eseguire se la condizione 1 falsa.
// Lo stato della 2 indifferente!
}
Da quanto detto si capisce che utilizzare due if non proprio come utilizzare
l'operatore AND, se non quando condizione_1 e condizione_2 sono entrambe verificate.
Un problema dell'utilizzo degli if in successione legata al fatto che possono rendere il
codice poco leggibile per tale ragione, in alcuni casi, piuttosto che concatenare molti if
meglio scrivere pi blocchi indipendenti, fare uso degli operatori logici, o se possibile far
uso del costrutto switch.
// Istruzioni da eseguire
switch
Tutti i casi da verificare sono posti al fianco della parola chiave case. Il valore che si vuole
controllare deve essere un valore costante ed intero, gi noto a priori, ovvero non si pu
porre come valore un'altra variabile. Questo fatto esclude l'utilizzo dell'istruzione
condizionale case qualora si vogliano fare confronti tra variabili. In questo caso si deve
infatti utilizzare l'istruzione condizionale if. Il diagramma di flusso per l'istruzione switch
riportato in Figura 93.
Espressione
Logica
Blocco istruzioni
+ break
Espressione
Logica
Blocco istruzioni
+ break
0
Blocco istruzioni
default
Il valore usato per il confronto pu essere scritto come intero, o come carattere; per
esempio:
case 128:
o anche
case 'b':
La costante potrebbe anche essere definita per mezzo della direttiva #define e si
potrebbe utilizzare il suo nome al posto del valore numerico. Questo secondo approccio
in generale quello consigliato in applicazioni professionali.
211
LED1
LED2
LED3
LED4
#define
#define
#define
#define
BT1
BT2
BT3
BT4
LATDbits.LATD0
LATDbits.LATD1
LATDbits.LATD2
LATDbits.LATD3
0b11100000
0b11010000
0b10110000
0b01110000
212
}
}
default:
LATD = 0x00;
Il programma oltre alle direttive classiche, fa in questo caso utilizzo della direttiva
#define per definire le costanti che sono utilizzate all'interno del blocco switch:
In particolare sono definite le costanti per la posizione dei LED
#define LED1 LATDbits.LATD0
213
BT1
BT2
BT3
BT4
0b11100000
0b11010000
0b10110000
0b01110000
possibile vedere che il pulsante BT1 equivale a porre a 0 il bit 5 ovvero RB4. Questo
dovuto al fatto che il pulsante ha un resistore di pull-up ovvero quando aperto il pin
RB4 viene letto come 1, mentre premendo il pulsante, ovvero collegando il pin RB4 a
massa, RB4 viene letto come 0.
Questa volta la lettura in polling dei pulsanti e il loro controllo stato posto all'interno
del ciclo while (1) che dagli altri esempi sembrava apparentemente utile solo per
bloccare il programma. All'interno del ciclo viene letta la PORTB e vengono azzerati i 4
bit meno significativi:
button = PORTB;
button = button & 0xF0;
La ragione per cui si azzerano i 4 bit meno significativi discende dal fatto che i pin non
utilizzati della PORTB, ovvero i quattro bit meno significativi, sono definiti come input.
Per fare questo si fa uso dell'operatore AND &; in particolare facendo button & 0xF0
vengono posti a 0 i bit meno significativi mentre si mantengono al proprio valore i bit
relativi ai pulsanti.
Una volta effettuata la lettura e pulizia della variabile button, si effettua il confronto tra
la variabile e le varie possibilit prese in considerazione, ovvero la pressione del singolo
pulsante. Questa operazione viene ripetuta in maniera continua per mezzo del ciclo while
all'interno del quale si messo l'intero codice. Caricando il codice nella scheda di
sviluppo, possibile vedere che i LED 0,1,2,3 sono inizialmente spenti, visto che non si
preme nessun pulsante. Questo discende dal fatto che button vale 11110000, dunque il
confronto con le costanti BT1-BT4 non viene verificato, per cui viene eseguita l'istruzione
all'interno del blocco default, in cui si spengono tutti i LED. Premendo un pulsante alla
volta si accende il LED associato al pulsante, il quale rimane acceso fino a quando si tiene
premuto il pulsante. Premendo in contemporanea un secondo pulsante si pu vedere che
viene eseguito il codice di default poich il programma non riconosce pi la pressione
del singolo pulsante.
Facciamo ora una digressione. Nel caso in cui si abbia l'esigenza di svolgere
un'operazione su di un registro e porre il risultato nel registro stesso, possibile far uso
dell'operatore di assegnazione composto:
button &= 0x0F;
Come visto, la ragione per cui si scrive il codice in questo modo per dare un aiuto al
compilatore permettendo di raggiungere un codice ottimizzato. Vediamo come viene
214
5001
0BF0
6E01
Effettivamente quello che ci si aspetta, visto che abbiamo una variabile unsigned
viene infatti eseguita l'operazione AND tra la costante e 0xF0 facendo uso
dell'accumulatore. Il risultato viene poi posto dall'accumulatore nella variabile button.
Vediamo ora come viene tradotto il codice aiutando il compilatore ad ottimizzare:
char,
49:
50:
7FC8
7FCA
0EF0
1601
0E00
90DF
92DF
94DF
96DF
Volevamo ottimizzare il codice sapendo che per sentito dire potevamo ottimizzarlo e
quello che abbiamo ottenuto un codice peggiore, almeno con il C18. interessante
vedere che il C18, piuttosto che fare l'AND pone a zero i bit meno significativi della
variabile button, che era proprio quello che volevamo fare usando l'AND. Quello che
successo che molte delle ottimizzazioni in realt possono perdere di significato quando
si ha a che fare con variabili unsigned char, ovvero di un solo byte.
Ancora una volta si mette in evidenza che non bisogna ottimizzare il codice senza
verificare i risultati e benefici che si ottengono con l'ottimizzazione. La sintassi:
button &=0x0F
215
Riassunto
In questo capitolo si introdotta la sintassi del C e relative parole chiave. Le principali
parole chiave sono state introdotte per mezzo di esempi mostrando il codice C e relativa
traduzione in codice Assembly. L'aver mostrato il codice Assembly ha permesso di
evidenziare come particolari costrutti e sintassi possano preferirsi rispetto ad altri
permettendo di raggiungere livelli di ottimizzazione differente.
Ogni esempio ha mostrato come la chiarezza del codice possa aiutare la fase di Debug
e debba essere favorita al tentativo di ottimizzare un codice senza averne l'effettiva
esigenza. In particolare sempre preferibile una soluzione semplice e chiara piuttosto che
una soluzione geniale e poco comprensibile. Nel caso si voglia proprio mostrare il proprio
genio sempre bene commentare accuratamente il codice, visto che il tempo arrugginisce
spesso la mente, impedendo la comprensione delle soluzioni precedentemente utilizzate.
potrebbero portare alla perdita delle informazioni dei registri coinvolti in una determinata
operazione. Questo stesso effetto si potrebbe per verificare anche nel caso contrario,
ovvero quando sarebbe necessario un Casting ma non lo si fa.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
}
17.
18.
19.
20.
21.
while (1) {
}
22.
218
Capitolo VII
VII
Le Funzioni
I programmi di esempio visti fino a questo momento non hanno presentato molte
difficolt, ed oggettivamente non svolgevano neanche compiti entusiasmanti. Un
programma o applicazione reale, contiene spesso molte centinaia o migliaia di righe di
codice. Poterlo organizzare in maniera opportuna permette di ottenere una chiarezza che
favorisce sia la sua scrittura che la fase di Debug. A questo scopo il C supporta le
funzioni, che come vedremo permettono di suddividere il codice in blocchi funzionali e
concettuali, permettendo di organizzare il software proprio con l'ordine a cui siamo ormai
abituati.
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 94 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo.
219
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 95: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 96: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
La scheda Freedom Light possiede i LED solo sui pin RD0 e RD1. Per poter
visualizzare gli altri LED necessario montarne altri sul connettore di
espansione, facendo per esempio uso della scheda di espansione Breadboard
PJ7011 o limitare il Debug su due soli LED.
220
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione software particolare oltre alla
semplice creazione di un progetto.
Gli esempi mostrati sono spiegati nel dettaglio, mostrando il codice Assembly,
per cui, a seconda delle impostazioni delle ottimizzazioni e della versione del
compilatore utilizzato, possono essere presenti delle differenze tra il codice
mostrato e quello che otterrete in fase di compilazione.
115
Questo non vero in ANSI C, dove la funzione main pu ricevere variabili in ingresso e restituire 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.
221
#include <xc.h>
#include "PIC18F4550_config.h"
// Funzione per sommare due numeri interi
signed char sommaInteri (signed char add1, signed char add2) {
signed char somma;
somma = add1+add2;
// Restituisco la somma
return(somma);
Come di consueto la parte iniziale del programma relativa alle direttive non cambiata,
ciononostante possibile vedere che prima della funzione main stata dichiarata la
funzione sommaInteri.
signed char sommaInteri ( signed char add1, signed char add2) {
signed char somma;
somma = add1+add2;
// Restituisco la somma
222
return(somma);
}
// Restituito la somma
return(add1+add2);
}
In questo modo si risparmia anche una variabile. Il fatto che la funzione sommaInteri
sia definita prima della funzione main non obbligatorio, ma si ritorner a breve su
questo argomento. Vediamo qualche dettaglio sulla definizione della funzione:
signed char sommaInteri ( signed char add1, signed char add2)
possibile vedere che si dichiarato, come tipo restituito, un signed char. Questo
discende dal fatto che la somma tra due interi sempre un intero 116 siano essi positivi o
negativi. I due addendi sono 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 sono rilasciate non appena la
funzione viene terminata, ovvero la memoria RAM utilizzata viene lasciata disponibile per
altro utilizzo. Le variabili dichiarate tra parentesi ospitano i valori degli addendi che
vengono 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 restituire il risultato della somma.
Qualora la funzione non abbia nessun valore restituito, ovvero il valore restituito sia void,
come potrebbe essere per una funzione di ritardo, la funzione return () non risulta
necessaria. In particolare se si dichiarasse il valore restituito di tipo void e si facesse
comunque uso della funzione return () verrebbe generato un errore:
error: (204) void function can't return a value
Allo stesso modo se si dichiare una funzione con un tipo restituito non void ma non si
richiama la funzione return ( ), si ha:
warning: (343) implicit return at end of non-void function
All'interno delle parentesi tonde della funzione return () si deve porre una variabile o
costante che sia dello stesso tipo di quella che stata dichiarata per la restituzione della
funzione. In particolare il valore restituito il risultato della nostra funzione.
Dopo un'istruzione return () si ha l'uscita dalla funzione e il programma riprende dal
punto in cui la funzione stata chiamata. All'interno di ogni funzione possono essere
presenti anche pi punti di uscita, ovvero return () ma solo uno quello che
effettivamente far uscire dalla funzione stessa. Se la funzione non restituisce alcun valore,
si ha l'uscita dalla funzione quando il programma giunge alle parentesi graffe. Quando
questo avviene il programma riprende dall'istruzione successiva alla chiamata della
116
In questo esempio non si sta considerando il caso in cui il risultato possa eccedere il valore massimo consentito da un intero.
223
funzione. Da quanto detto riguardo lo Stack Pointer e lo Stack Memory si capisce che la
chiamata ad una funzione equivale a posizionare il Program Counter al punto della memoria
in cui definita la funzione stessa. Lo Stack Pointer viene incrementato e l'indirizzo di
ritorno, ovvero l'indirizzo successivo a quello dal quale avviene la chiamata alla funzione,
viene memorizzato all'interno della Stack Memory. Questo significa che all'interno di una
funzione possibile richiamare altre funzioni ma bisogna tenere sempre a mente del limite
imposto dalla Stack Memory disponibile117.
La chiamata di una funzione avviene nel seguente modo:
LATD = sommaInteri (3,7);
Si noti che nel caso specifico si sono passati come parametri due costanti, ma si
possono pure passare due variabili di tipo intero. Dal momento che la funzione restituisce
un risultato bisogna scrivere sulla sinistra della funzione un'assegnazione, o comunque la
funzione deve essere parte di una espressione il cui risultato viene assegnato ad una
variabile; se cosi non si facesse il risultato verrebbe perso. Nel nostro caso il valore della
somma, pari a 10 (00001010), viene posto in uscita alla PORTD. Il valore restituito viene
spesso usato anche per notificare un codice di errore in maniera da avvisare chi chiama la
funzione, del fatto che si verificato un errore. Normalmente un valore restituito
negativo o nullo equivale alla presenza di un errore. Un valore maggiore di 0 equivale ad
una corretta esecuzione delle operazioni contenute nella funzione. In alcuni casi non si ha
interesse a gestire gli errori, anche se buona abitudine farlo, perci il valore restituito
pu non essere assegnato a nessuna variabile.
Come detto ogni funzione deve essere dichiarata prima della funzione main ma questo
non obbligatorio. Se si dichiara la funzione o funzioni dopo la funzione main il
compilatore fornisce un avviso o errori poich compilando la funzione main si trova ad
usare la funzione sommaInteri che non stata precedentemente dichiarata...ma che
presente dopo la funzione main. In particolare se si ripete il programma precedente
semplicemente spostando la funzione sommaInteri dopo la funzione main, si ottengono i
seguenti messaggi:
main.c:32: warning: (361) function declared implicit int
main.c:41: error: (984) type redeclared
main.c:41: error: (1098) conflicting declarations for variable
"sommaInteri" (main.c:32)
Per evitare i messaggi di errore, 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 dichiarata dopo la funzione main stessa.
La dichiarazione di un prototipo di funzione si effettua semplicemente per mezzo del
nome della funzione stessa e il tipo di variabili presenti, terminando il tutto per mezzo di
un punto e virgola, ovvero senza il corpo del programma associato alla funzione. Il
prototipo di funzione e relativo spostamento della funzione sommaInteri riportato nel
seguente esempio:
117
Si faccia riferimento al Capitolo relativo all'architettura dei PIC18 per maggiori informazioni.
224
#include <xc.h>
#include "PIC18F4550_config.h"
// Prototipo della funzione per sommare due numeri interi
signed char sommaInteri (signed char add1, signed char add2);
int main (void){
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Effettuo la somma tra 3 e 7
LATD = sommaInteri (3,7);
// Ciclo infinito
while(1){
}
// Restituisco la somma
return(somma);
225
Il prototipo della funzione richiede infatti il solo tipo delle variabile e potrebbe dunque
essere scritto nel seguente modo:
signed char sommaInteri (signed char, signed char);
Per convenzione il nome delle funzioni inizia spesso con la lettera minuscola.
Eventualmente parole composte possono essere scritte con la lettera maiuscola
come nel caso di sommaInteri. Un altro modo per scrivere il nome delle
funzioni facendo uso dell'undescore _ ovvero somma_interi.
Spiegata la funzione SommaInteri vi sarete resi certo conto di qualcosa di strano sul
tipo di variabili scelte. Cosa succederebbe facendo la somma 127 + 127?
attivaAllarme ();
Quando una variabile globale, vuol dire che il Linker associa una determinata quantit
di memoria RAM fissando un indirizzo fisso accessibile da ogni parte del programma. La
variabile risulta accessibile da ogni parte del programma proprio perch il suo indirizzo
118
In linguaggi di programmazione ad oggetti l'utilizzo di variabili globali sconsigliato in quanto crea delle relazioni di
dipendenza tra classi.
226
119
Alcune volte pur accedendo al contenuto di una variabile rilasciata il valore letto quello esatto, ma questo solo dovuto al
caso specifico e potrebbe non essere ripetibile.
227
Vediamo il seguente esempio, in cui la funzione conteggio grazie alla variabile statica
conta il numero di chiamate effettuate alla funzione stessa.
#include <xc.h>
#include "PIC18F4550_config.h"
// Funzione che conta le sue chiamate
unsigned char conteggio (void) {
// Dichiarazione variabile statica
static unsigned char contatore=0;
contatore++;
return (contatore);
}
int main (void){
// Variabile per il ciclo for
unsigned char i;
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
for (i = 0; i < 8; i++) {
}
conteggio ();
228
La dichiarazione della funzione fatta come nel caso precedente prima della funzione
main. possibile osservare che nel nostro caso non viene passata alcuna variabile.
unsigned char conteggio (void) {
// Dichiarazione variabile statica
static unsigned char contatore=0;
contatore++;
return (contatore);
}
static
conteggio ();
Dopo il ciclo
PORTD.
for
conteggio()
viene scritto su
Dal momento che per scrivere il valore restituito si chiamata nuovamente la funzione
si ha che il valore finale 9 ovvero 00001001. E' interessante notare che la funzione
conteggio () pur restituendo un valore pu essere richiamata senza dover
necessariamente leggere il suo valore.
Pu essere interessante rimuovere ora la parola chiave static e vedere che in uscita si
ha sempre il valore 00000001. La ragione legata al fatto che la variabile conteggio viene
inizializzata sempre a zero non essendo pi static .
Dagli esempi fin ora visti, si fatto restituire alla funzione sempre un solo valore, ma
come detto ci possono essere dei casi in cui si abbia l'esigenza di restituire variabili pi
complesse o un gruppo di variabili. In questa situazione si potrebbero usare i puntatori al
fine di restituire il puntatore ad un Array o una struttura. Al fine di garantire che lo spazio
di memoria RAM assegnato all'Array o struttura non sia perso, necessario dichiarare tali
strutture come statiche. Nel caso di un Array se non si dovesse dichiararlo come static si
avrebbe una Warning:
229
Un esempio potrebbe essere il seguente, in cui si scrivono dei valori nell'Array e poi si
restituisce l'Array, o meglio il puntatore a quest'ultimo, per poi scrivere il suo contenuto
sulla PORTD.
#include <xc.h>
#include "PIC18F4550_config.h"
// Prototipo di funzione
unsigned char * inizializza_array (void);
int main (void){
unsigned int delay = 0;
unsigned int i = 0;
unsigned char *pArray;
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
pArray = inizializza_array();
// Ciclo infinito
while(1){
for (i=0; i<=10; i++){
for (delay=0; delay<64000; delay++){
//Semplice Delay
}
}
LATD = pArray[i];
}
}
230
La forma non dovrebbe sorprendere molto. bene anche notare che l'Array dichiarato
come static:
static unsigned char
mio_array[11] ;
231
e si implementa la funzione :
void resetta_valore (unsigned char registro){
registro = 0;
}
scrivendo 0 nella variabile registro non comporta l'aver scritto 0 nella variabile
numero_a. Quanto detto rappresenta il cosiddetto passaggio di una variabile per valore,
ovvero viene creata una variabile locale alla funzione, in questo caso nominata registro,
e al suo interno viene caricato il valore della variabile numero_a. Nella funzione
resetta_valore, non si a conoscenza dell'indirizzo della variabile numero_a per cui
scrivere nella variabile registro non equivale a scrivere nella variabile numero_a.
Per avere la possibilit di scrivere nella variabile numero_a dalla funzione
resetta_valore, necessario passare la variabile per riferimento e non per valore. Da
quanto detto si capisce che passare la variabile per riferimento equivale a passare
l'indirizzo della variabile stessa, ovvero la funzione deve avere come variabile in ingresso
un puntatore.
Riscrivendo la funzione:
void resetta_valore (unsigned char * registro){
*registro = 0;
}
232
numero_a
direttamente
*registro = 0;
Da quanto detto si capisce che passare pi variabili per riferimento potrebbe anche
essere considerato un modo per restituire pi valori, superando il limite di un solo valore
di ritorno. In questo modo il valore di ritorno principale potrebbe essere utilizzato per la
gestione degli errori. Vediamo qualche dettaglio in pi per comprendere le due possibilit
con cui possibile passare dei valori ad una funzione.
#include <xc.h>
#include "PIC18F4550_config.h"
// Prototipo di funzione
unsigned char funzione_test
numero_b);
(unsigned
char
numero_a,
unsigned
char
233
unsigned char
numero_b) {
funzione_test
(unsigned
char
numero_a,
unsigned
char
Il programma di esempio mostra come sia possibile passare due valori alla funzione, in
particolare uno per valore e uno per riferimento. Inoltre la funzione restituisce anche un
errore nel caso in in cui il valore della variabile numero_a sia maggiore di 3 (numero
magico!). La chiamata alla funzione viene effettuata fornendo l'indirizzo della variabile
numero, visto che tale variabile passata per riferimento.
LATDbits.LATD7 = funzione_test (i, &numero);
Dopo la chiamata alla funzione, il valore dell'errore viene visualizzato sul pin 7 di
PORTD mentre i bit meno significativi visualizzano il conteggio:
LATDbits.LATD7 = funzione_test (i, &numero);
LATD |= numero;
una libreria basta creare un altro file, 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 :
#include <nome_file>
dove si scrive il file da includere tra i due simboli di minore e maggiore. Un secondo
formato :
#include "nome_libreria"
Si capisce subito che l'utilit di una libreria sta nel fatto che fornisce tutte le funzioni (o
almeno questa sempre la speranza) di cui si pu aver bisogno per gestire la nostra
periferica. Se la libreria ben fatta permette anche di raggiungere un livello di astrazione
per l'utilizzo della periferica tale per cui non bisogna sapere molto della periferica stessa.
Oltre a raccogliere le funzioni, una libreria ha l'utilit di rappresentare un file comune
per diverse applicazioni, permettendo di aggiornare un unico file qualora si dovesse essere
trovato un errore o si volessero aggiungere nuove funzioni. Se infatti si dovesse creare per
ogni programma un file c con le funzioni per la gestione di un display LCD,
semplicemente copiando ed incollando le funzioni da un altro progetto precedentemente
scritto, si avrebbero due copie delle funzioni. Facendo ulteriori copia ed incolla, il numero
di copie delle nostre funzioni diventerebbe tale che se si volesse modificare una sola
funzione dovremo andare a modificare uno per uno i sorgenti dei vari progetti. Creando
una libreria si ha un'unica raccolta che tutti i progetti possono includere, dunque ogni
modifica si ripercuote automaticamente su ogni progetto. Nonostante si possa modificare
la libreria in un solo punto, si deve comunque di ricompilare ogni progetto che fa uso
della libreria.
Un ultimo aspetto associato all'utilizzo delle librerie, e questo vale in generale, come
anche gli altri punti, quello di permettere di mantenere il codice sorgente nascosto. Per
utilizzare una libreria si pu infatti necessitare solo del file header .h con i prototipi delle
funzioni e il file .lib ovvero il file oggetto con il codice compilato. Il file sorgente .c non
necessario poich compilato nel file .lib. In questo modo molte societ nascondono il file
sorgente permettendo di implementare librerie ottimizzate che i competitori avranno
difficolt a copiare. Nel caso di semplici applicazioni osservando il codice Assembly
possibile risalire pi o meno al codice originale ma richiede comunque un certo sforzo. Le
librerie LuarTec non sono scritte con l'intento di nascondere il codice, per cui sono
fornite con il file header .h e il file sorgente .c.
Le direttive
Come detto una direttiva non rappresenta un'istruzione che il compilatore tradurr in
linguaggio macchina, bens una guida per il compilatore o meglio pre-compilatore (ovvero
colui che viene prima del compilatore). In particolare le direttive servono per far capire al
pre-compilatore come organizzare la compilazione che verr effettuata dal compilatore.
Abbiamo gi incontrato alcune direttive che per completezza sono ripetute in maniera da
avere una lista pi completa. Le direttive che sono descritte sono solo alcune di quelle
disponibili, in particolare sono le pi usate. Ogni direttiva come gi visto introdotta dal
carattere # e non termina con il solito punto e virgola necessario per le istruzioni C.
Vediamo le direttive:
#include
Questo formato viene utilizzato per includere file, siano essi .c , .h o altri formati
236
in cui siano scritti contenuti riconoscibili dal compilatore. Il file viene ricercato tra i
percorsi di ricerca impostati nelle propriet del progetto, alla voce Include Search Path.
Qualora si voglia fare riferimento ad un percorso assoluto o alla directory dove si
trova anche il nostro progetto, si pu far uso del formato:
#include "percorso_file/nome_file"
La parte del percorso file pu anche essere rimossa, qualora non si abbia interesse
ad un percorso assoluto. In questo caso i percorsi che sono utilizzati per la ricerca
del file sono prima la directory corrente e successivamente gli Include Search Path. In
generale si sconsiglia di utilizzare i percorsi assoluti in modo da rimanere pi
flessibili in termini di salvataggio ed esecuzione del progetto.
Nel testo ho seguito la pratica di usare il formato di inclusione con <
Microchip, mentre il formato " " per le librerie LaurTec.
>
per le librerie
#define
Nel primo esempio si definisce la costante MAX_VALUE pari al valore 12. L'utilizzo
della costante MAX_VALUE permette di utilizzare tale nome, ovvero valore, in
qualunque parte del programma, ma nel caso in cui si dovesse cambiare il suo valore
baster cambiare la riga di definizione.
Nel secondo esempio si assegna il nome LED al pin LATDbits.LATD4. In
questo modo qualora si voglia accendere il LED si pu scrivere:
LED = 0x01;
piuttosto che:
LATDbits.LATD4 = 0x01;
Questo risulta molto utile nei casi in cui la posizione del LED dovesse cambiare.
In tale circostanza basterebbe infatti cambiare solo la riga in cui viene definito il
nome LED e non tutti i punti del codice dove viene effettivamente usato.
La direttiva #define pu essere anche utilizzata per definire semplicemente un
nome senza dare un valore:
#define LED
Questo risulta utile per esempio nel caso si usi la direttiva #ifndef.
#ifndef
La direttiva
#ifndef
determinata costante non sia stata definita. Il codice che deve essere compilato deve
essere terminato dalla direttiva #endif. Questa funzione ritorna molto utile per
evitare di includere due volte uno stesso file. Qualora un file sia incluso due volte si
ha infatti il problema di codice duplicato ed in particolare di nomi di funzioni e
variabili duplicate. Questa situazione crea in generale molti errori di compilazione,
facilmente identificabile dal fatto che il compilatore segnala la presenza di variabili e
funzioni gi dichiarate. Un esempio in cui la direttiva #ifndef permette di
controllare ed evitare inclusioni multiple potrebbe essere:
#ifndef LIBRERIA_LCD
#define LIBRERIA_LCD
// Codice della mia libreria o funzioni
#endif
#endif
#warning
La direttiva
codice.
#warning
#error
238
Funzione di lettura
L'indirizzo della cella di memoria da cui leggere viene passato per valore. Il valore
restituito dalla funzione deve essere il valore letto dall'indirizzo di memoria richiesto. Il
formato della funzione deve essere:
unsigned char read_internal_EEPROM (unsigned char address);
Successivamente bisogna creare un file.c con il nome della nostra libreria, nel nostro
caso intEEPROM.c e un file .h (noto come header file) intEEPROM.h.
Nel file .c viene scritto il codice vero e proprio con cui si implementano le funzioni,
mentre nel file .h sono scritti solo i prototipi delle funzioni con loro descrizione.
Questa pratica utilizzata anche nella programmazione C e C++ per PC o altri
239
/**
* This function reads a byte from the internal EEPROM.
*
*
* @param address Address where the byte must be read [min: 0, max: 255]
*
* @return data Value read from the internal EEPROM.
*
*/
unsigned char read_internal_EEPROM (unsigned char address);
#endif
Questo permette di avere pi inclusioni dello stesso file, senza avere conflitti ed errori
di compilazione, infatti come detto, il codice viene incluso e compilato una sola volta.
Il nome del flag dichiarato per l'inclusione spesso uguale al nome della libreria con
l'aggiunta di _H, ma un nome vale l'altro, si deve solo garantire che nel progetto non
siano presenti definizioni uguali.
240
Questo permette alla libreria di essere compatibile sia con il vecchio compilatore C18 che
con il nuovo XC8. In particolare viene controllato se il nome __XC8 definito. Qualora
lo sia vorrebbe dire che stiamo compilando facendo uso del compilatore XC8. Infatti
__XC8 automaticamente definito dall'ambiente di sviluppo. In particolare se il
compilatore XC8, viene incluso il file xc.h.
Dopo queste prime direttive, sono definiti i prototipi di funzione. Ogni funzione
preceduta da un commento in cui si riporta una breve descrizione della mansione svolta
dalla funzione e la descrizione dei parametri che bisogna passare e che vengono restituiti.
In questo modo l'utilizzatore ha tutte le informazioni necessarie per un suo corretto
utilizzo. Qualora si dovessero avere dei problemi con la funzione e il suo utilizzo, l'header
file un buon punto per iniziare la ricerca della documentazione sulle funzioni fornite. La
documentazione scritta facendo uso di un formato un po' particolare, ovvero conforme
a Doxygen. Doxygen uno strumento che permette di estrarre la documentazione
direttamente dal codice, evitando di dover scrivere file diversi per la documentazione.
Estrarre la documentazione direttamente dal codice, in un certo qual modo garantistiche
che sia sempre aggiornata, maggiori dettagli su Doxygen sono dati a breve.
La dichiarazione delle due funzioni seguita dalla direttiva #endif, ovvero la fine del
codice associato alla dichiarazione dei prototipi di funzioni.
Nel file .c scritto il sorgente vero e proprio. In questo file, diversamente dal file .h
non vengono normalmente scritte le informazioni associate alla funzione. Infatti se si
dovessero utilizzate librerie precompilate, si potrebbe non avere affatto il codice sorgente,
ovvero i file.c e in pi si avrebbe il problema di avere due punti in cui dover aggiornare la
documentazione. Il file .c rappresenta un file sorgente tradizionale come quelli creati con
la funzione main, ma in questo caso si implementano solo le funzioni dichiarate
nell'header file, ovvero non abbiamo nessuna funzione main.
Un modo per implementare le funzioni potrebbe essere:
#ifdef __XC8
#include <xc.h>
#endif
#include "intEEPROM.h"
//************************************************************
//
write_internal_EEPROM function implementation
//************************************************************
unsigned char write_internal_EEPROM (unsigned char address, unsigned char
data) {
// Flag used to store the GIE value
241
242
#ifdef _PIC18
if (flagGIEH == 1) {
INTCONbits.GIEH = 1;
}
if (flagGIEL == 1) {
INTCONbits.GIEL = 1;
}
#endif
// Disable the writing process
EECON1bits.WREN = 0x00;
// Check if the data has been properly written,
// a simple read back is done
if (read_internal_EEPROM (address) == data) {
return (1);
} else {
}
return (0);
}
//************************************************************
//
read_internal_EEPROM Function Implementation
//************************************************************
unsigned char read_internal_EEPROM (unsigned char address) {
unsigned char data = 0;
// Set the memory address that will be read
EEADR = address;
// EEPROM memory is pointed
EECON1bits.EEPGD = 0;
#ifdef _PIC18
// EEPROM access enable
EECON1bits.CFGS = 0;
#endif
// Initiate reading
EECON1bits.RD = 0x01;
// Data is read from the register
data = EEDATA;
return (data);
}
Si pu subito notare che due semplici funzioni di lettura e scrittura possono essere
piuttosto complicate qualora debbano essere compilate in ambiente C18, XC8 e come
anche PIC16 e PIC18. Questa esigenza non contemplata nelle specifiche per cui non
243
sarebbe stato necessario ma in una libreria di uso pratico in cui si responsabili delle
specifiche, bisogna cercare di coprire anche esigenze extra che permettano alla libreria di
essere robusta. In realt le specifiche appena aggiunte non rendono la libreria pi robusta
ma semplicemente di pi ampio utilizzo. Guardando nel dettaglio la libreria ci sono per
alcuni dettagli in pi che rendono la libreria robusta contro particolari eventi ma si
comprenderanno solo alla fine del prossimo Capitolo.
All'interno del file .c viene incluso l'header file:
#include "intEEPROM.h"
Questo permette di includere il file header nel quale sono dichiarati i prototipi di
funzione e si sono inclusi gli altri file necessari alla compilazione. Ogni altro file
necessario deve essere incluso nel file di header, ed indirettamente viene incluso nel file
sorgente. Il non seguire questa pratica non crea nessun problema, ma un modo ordinato
per mostrare i file inclusi in un solo punto e segue la pratica di programmazione in C e
C++. Il programma in C ripete i passi gi spiegati quando stata introdotta la memoria
EEPROM, il cui codice era stato per scritto in Assembly. Unica differenza sta nella
funzione di scrittura in cui si gestito il caso di disabilitazione delle interruzioni e
confronto del byte scritto con quello da scrivere.
Arrivati a questo punto si potrebbe compilare il codice al fine di creare un file binario.
Sebbene questo era quanto avevo fatto in passato con il compilatore C18, al fine di restare
compatibile con la pratica della programmazione dei sistemi embedded C e C++, in realt
dal momento che nella stessa famiglia PIC18 ci possono essere vari modelli non
compatibili tra loro che richiederebbero una ricompilazione delle libreria ho iniziato a
preferire il lasciare la libreria non compilata.
244
La libreria scritta come esempio appartiene alle librerie LaurTec per PIC18 per cui tra i
percorsi da includere al fine di compilare l'esempio in maniera corretta, bisogna scrivere
tra le propriet del progetto:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.2.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.2.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.2.0\src
Questo non pratica comune visto che dovrebbe bastare includere il solo file .h.
L'includere il solo file .h richiederebbe per un file di libreria precompilato e il dover
includere il percorso del file di libreria. Inoltre si dovrebbero avere file multipli
precompilati di libreria per i vari microcontrollori, rendendo l'aggiornamento della libreria
piuttosto complicato. Come detto in precedenza, preferisco ora lasciare la compilazione
della libreria in fase di progetto. Per tale ragione, al fine di testare la libreria sempre
necessario scrivere un semplice programma di esempio nel quale includere la libreria
stessa.
Nel caso si debbano includere le librerie Microchip, basta includere il solo file .h.
Infatti Microchip fornisce i file di libreria precompilati che sono inclusi in
maniera trasparente. Nella guida al compilatore XC8 si sconsiglia comunque di
creare file precompilati per le ragioni citate sopra.
245
246
I parametri della funzione devono essere introdotti dalla parola chiave @param
/**
Questa funzione svolge la somma tra due interi
@param A primo addendo [min. 0 max. 255]
@param B secondo addendo [min. 0 max. 255]
*/
unsigned int somma_interi (unsigned char A, unsigned char B);
Nel caso siano presenti nelle note, warning possibile aggiungerle facendo uso
delle parole chiavi @note e @warning.
/**
Questa funzione svolge la somma tra due interi
@param A primo addendo [min. 0 max. 255]
@param B secondo addendo [min. 0 max. 255]
@return somma tra i due addendi passati alla funzione
@warning Gli addendi devono essere solo numeri maggiori di 0
@note Una qualunque nota...
*/
unsigned char somma_interi (unsigned char A, unsigned char B);
247
bene notare che i vari parametri sono anche descritti da un valore massimo e
minimo. Questo non obbligatorio ma un buon aiuto per il programmatore.
Sebbene abbia scritto frequentemente la parola deve essere, in realt la sintassi di
Doxygen piuttosto flessibile e sono supportati diversi formati.
Prima di eseguire Doxygen necessario impostare alcune impostazioni, in particolare
dove trovare i file sorgenti e quali file documentare. possibile anche scegliere il formato
della documentazione, che spesso semplicemente HTML. Doxygen utilizzato per
documentare codice sorgenti scritti in molti linguaggi di programmazione ed di fatto
quasi uno standard. Alcuni IDE, grazie al Parser interno, durante la scrittura del nome di
una funzione visualizzano anche la descrizione della funzione e dei parametri, prelevando
le informazioni direttamente dalla documentazione in formato Doxygen.
Nel caso delle librerie LaurTec, un esempio di impostazioni di Doxygen, facendo uso
dell'interfaccia grafica per Windows, riportato in Figura 97.
La documentazione estratta in formato HTML, pu essere letta facendo uso di un
qualunque Browser, come riportato in Figura 98. Per maggiori dettagli sull'utilizzo di
Doxygen si rimanda alla documentazione ufficiale che possibile trovare al sito
www.doxygen.org .
248
Figura 98: Esempio di documentazione estratta dal codice sorgente della libreria intEEPROM.
Riassunto
In questo capitolo si messo in evidenza l'importanza di organizzare in maniera
corretta un programma, in particolare mostrando come scrivere delle funzioni e
organizzare il tutto in librerie. Si sono mostrati anche altri esempi di utilizzo di puntatori,
in particolare per il valore restituito dalla funzione e i parametri in ingresso. Alcuni dettagli
sono stati forniti per quanto riguarda il passaggio di variabili per valore e per riferimento,
mostrando alcuni vantaggi e svantaggi nell'utilizzare l'uno o l'altro metodo.
Variabile globale: Una variabile si definisce globale quando il suo scope rappresentato
da tutto il programma, ovvero visibile da ogni funzione. Per dichiarare una variabile in
modo globale bisogna definire la stessa prima della funzione main. Allo stesso modo di
una variabile definita static, una variabile globale possiede un indirizzo fisso durante
tutta l'esecuzione del programma.
Header file: L'header file, rappresenta il file con estensione .h all'interno del quale sono
definiti i vari prototipi di funzione. Contiene generalmente anche la documentazione delle
funzioni stesse, rappresentando il primo punto in cui guardare nel caso in cui si cerchino
informazioni sulla funzione e suoi parametri. La divisione in file .h e .c viene fatta spesso
per le librerie, fornendo un modo per separare i prototipi di funzione, dal codice sorgente
vero e proprio. Qualora la libreria sia fornita in formato compilato (binary file), il file header
rappresenta l'unica fonte di documentazione visto che il codice sorgente non leggibile,
in particolare deve essere incluso insieme al file .bin.
Doxygen: Doxygen un'applicazione multi-piattaforma che pu essere utilizzata per
estrarre la documentazione direttamente dal codice sorgente. La documentazione inclusa
nel file header, al fine di poter essere estratta da Doxygen, deve essere scritta seguendo una
particolare sintassi. L'utilizzo di Doxygen divenuto uno standard per la documentazione
di molte applicazioni software e supporta molti linguaggi di programmazione.
250
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
251
Capitolo VIII
VIII
Le Interruzioni
Quanto studiato nei Capitoli precedenti ci permette gi di scrivere applicazioni
piuttosto complesse, ma certamente ci si resi conto che manca qualcosa che permetta di
armonizzare varie parti del programma, sebbene queste possano essere scritte in funzioni.
Nella programmazione dei microcontrollori un ruolo importantissimo coperto dalla
interruzioni, che permettono appunto di interrompere il programma principale e di saltare
in altre parti della nostra applicazione. In questo Capitolo si vedremo i dettagli su cosa sia
un'interruzione e l'importanza delle stesse nello sviluppo di un'applicazione software.
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 99 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo.
252
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 100: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 101: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
La scheda Freedom Light possiede i LED solo sui pin RD0 e RD1. Per poter
visualizzare gli altri LED necessario montarne altri sul connettore di
espansione, facendo per esempio uso della scheda di espansione Breadboard
PJ7011 o limitare il Debug su due soli LED.
254
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione software particolare oltre alla
semplice creazione di un progetto.
Le interruzioni
Da quanto fin ora spiegato si capisce che i microcontrollori hanno la possibilit di
essere utilizzati in sistemi molto complessi in cui sia richiesto di svolgere molte operazioni
in contemporanea. In realt qualunque microcontrollore, per quanto veloce sia, svolger
un'operazione alla volta; le sue velocit di esecuzione sono tali per da dare l'impressione
che siano svolte molte operazioni in contemporanea. Ogni qual volta si abbia l'esigenza
di svolgere pi operazioni bene strutturare il programma in modo da poter gestire il
tutto in maniera snella e funzionale. La tecnica del Polling potrebbe creare qualche
255
problema poich si perde molto tempo a controllare periferiche che magari sono inattive.
La gestione di pi funzioni o processi notevolmente migliorata per mezzo dell'utilizzo
degli interrupt, ovvero segnali inviati dall'hardware quando viene a verificarsi un
determinato evento. I PIC18 possiedono, per ogni modulo hardware interno varie
modalit e tipologie d'interrupt in maniera da permettere l'utilizzo dell'hardware non solo
per mezzo della tecnica del Polling ma anche per mezzo degli interrupt. Per esempio la
PORTB per i pin RB4-RB7 genera un evento d'interrupt qualora ci sia il cambio del valore
logico sul pin, che potrebbe essere causato per esempio dalla pressione di un pulsante.
Un'interruzione di cui si fatto inconsapevolmente uso e presente in 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 generate da periferiche interne, come per esempio il convertitore analogico
digitale (ADC), 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 (interrupt Vector)
Quando avviene un'interruzione da parte delle periferiche, prima di saltare al vettore
d'interruzione, l'Hardware120 si preoccupa di salvare tutte le informazioni necessarie per
poter riprendere dal punto in cui il programma stato interrotto. Allo stesso modo delle
funzioni, come visto in precedenza, il PC viene salvato nello Stack in maniera da
riprendere il programma dal punto in cui stato interrotto 121.
Il PIC18F4550 come ogni PIC18, 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.
Ogni interruzione a bassa priorit pu interrompere il normale flusso del programma
permettendo di svolgere la particolare funzione associata con la gestione delle stessa. Le
istruzioni ad alta priorit possono interrompere sia il normale flusso del programma sia
l'esecuzione della routine associata alle interruzioni a bassa priorit. Da questa possibilit
si capisce per quale ragione questa seconda interruzione viene detta ad alta priorit. Lo
svolgimento della funzione associata alle interruzioni ad alta priorit non pu essere
invece interrotta n da un'interruzione ad alta priorit n da un'interruzione a bassa
priorit. Ciononostante, 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; il Reset in un certo qual modo ad altissima priorit.
Il verificarsi di un'interruzione, dopo essere stata accettata, causa il salvataggio del
registro PC e il suo cambio in modo da puntare l'interrupt Vector di competenza. Oltre a
questa operazione vengono in realt salvati i registri WREG, BSR e lo Status Register. La
modalit di salvataggio degli stessi differisce a seconda del tipo d'interruzione. Si capisce
intanto che il posto dove vengono salvati deve essere differente per i due tipi
d'interruzione, perch se cosi non fosse l'interruzione ad alta priorit potrebbe causare la
perdita di dati associati ad un'interruzione a bassa priorit.
Nel caso d'interruzione a bassa priorit i registri ora citati vengono salvati nello Stack
per mezzo di normali operazioni MOVFF, MOVWF e MOVF. Nel caso in cui si verifichi
120
121
In realt anche il software interviene spesso per il salvataggio di variabili particolari che altrimenti non verrebbero salvate.
L'eventuale istruzione caricata nell'ISR viene comunque terminata.
256
RCON
INTCON
INTCON2
INTCON3
PIR1, PIR2
PIE1, PIE2
IPR1, IPR2
In realt a seconda delle periferiche che sar necessario gestire solo alcuni registri
dovranno essere propriamente impostati, in particolare solo alcuni bit. Ciononostante il
registro RCON ed INTCON hanno al loro interno i bit fondamentali per la gestione delle
interruzioni. All'interno di tali registri sono presenti tutti i bit ovvero le impostazioni
necessarie per la gestione delle interruzioni 123. Per maggiori informazioni sui singoli
registri si rimanda al Datasheet del PIC utilizzato.
Ogni tipo d'interruzione che pu essere generata da una periferica, possiede tre bit di
controllo posizionati in punti diversi nei registri ora citati:
Bit di Enable
Bit per il livello Priorit
Bit di Flag
Il bit di Enable permette di abilitare l'interruzione della periferica stessa. Infatti non
sempre si vuole che un evento generi un'interruzione del programma principale. A
seconda della gestione di una periferica si potrebbe gestire la stessa sia in Polling che per
mezzo delle interruzioni. Il bit di Enable abilita la possibilit di generare l'interruzione,
ovvero di poter interrompere il flusso del programma principale, ma necessario
impostare anche il livello di priorit. Abbiamo infatti visto che i PIC18 possiedono due
livelli di priorit. Una corretta scelta del livello di priorit permette o meno di armonizzare
la gestione delle periferiche nella particolare applicazione che si sta scrivendo.
Il bit di Flag segnala il fatto che l'evento che pu generare un'interruzione si
verificato. Per esempio il Flag relativo alla ricezione di un byte sulla porta seriale (UART)
viene abilitato ogni qual volta che si riceve un byte.
122
123
Si ricorda che un sistema Real Time non un sistema necessariamente veloce, ma un sistema che garantisce lo svolgimento
di determinate operazioni o compiti in un tempo predefinito. Conoscere il tempo di latenza permette di valutare il tempo
richiesto ad una routine prima di essere chiamata. Oltre al tempo di latenza possono poi intervenire altri ritardi derivanti
dalla gestione dei Task da parte del Sistema Operativo.
Il modulo USB un po' un mondo a parte per quanto riguarda le interruzioni. La sua gestione esula dallo scopo di tale testo,
per cui si rimanda al Datasheet per ulteriori informazioni.
257
Il bit di Flag vengono attivati indipendentemente dal fatto che la periferica abbia
o meno le interruzioni abilitati, per cui possono essere usati anche per mezzo del
Polling per controllare il verificarsi di un certo evento. La generazione di
un'interruzione da parte del Flag richiede che le interruzioni della periferica siano
abilitate.
Oltre a questi tre bit associati ad ogni tipologia d'interruzione, sono presenti anche altri
bit per la gestione globale delle interruzioni. I bit per la gestione globale delle interruzioni
sono i bit GIE e PEIE del registro INTCON e il bit IPEN del registro RCON. I PIC18
pur potendo gestire le interruzioni ad alta e bassa priorit possono anche lavorare in
modalit compatibile con i PIC16. Per lavorare in modalit compatibile bisogna impostare
a 0 il bit IPEN, questo per altro il valore di default.
Quando IPEN vale zero, il bit GIE permette di abilitare in maniera globale le
interruzioni mentre PEIE permette di abilitare/disabilitare le interruzioni delle
periferiche. Quando i bit valgono 1 la funzione abilitata, mentre quando valgono 0
disabilitata. Quando il PIC18 lavora in maniera compatibile con i PIC16 l'Interrupt Vector
posto all'indirizzo 0x08, cio come se fosse abilitata l'interruzione ad alta priorit.
Per lavorare in maniera compatibile sono richieste le seguenti istruzioni:
// Abilito modalit compatibile (di default vale gi 0)
RCONbits.IPEN = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
// Abilito interrupt per periferiche
INTCONbits.PEIE = 1 ;
In questa configurazione i bit per selezionare la priorit non devono essere impostati,
poich di default valgono 1, ovvero abilitati per funzionare ad alta priorit.
Nel caso si voglia fare uso delle funzionalit avanzate d'Interrupt offerte dall'architettura
PIC18, bisogna settare i bit precedenti in maniera differente. In particolare necessario
porre ad 1 il bit IPEN. Quando questo bit viene abilitato i bit IPEN e GIE del registro
INTCON assumono un altro significato. Il bit GIE, anche nominato GIEH, permette, se
posto ad 1, di abilitare le interruzioni ad alta priorit. Il bit PEIE, anche nominato GIEL,
se posto ad 1, abilita tutte interruzioni a bassa priorit. In questo caso si hanno i due
vettori d'interruzione posti all'indirizzo 0x08 (alta priorit) e 0x18 (bassa priorit). Quando
si lavora con i due livelli di priorit necessario impostare i bit dei registri di priorit IPR
e IPR2. Per abilitare le interruzioni con i due livelli di priorit si deve procedere come
segue:
// Abilito modalit interruzione a due livelli alta e bassa
RCONbits.IPEN = 1;
// Abilito gli interrupt ad alta priorit
INTCONbits.GIEH = 1;
// Abilito gli interrupt a bassa priorit
INTCONbits.GIEL = 1 ;
258
(void) {
high_priority
low_priority
Fatto questo bisogna specificare il nome della funzione che utilizzeremo per la gestione
dell'interruzione. Come si vede dalla dichiarazione sopra, entrambi le funzioni devono
essere dichiarate con parametri di chiamata e di restituzione di tipo void. Il nome della
funzione pu essere arbitraria, ma dal momento che viene specificato il livello di
259
Per esempio per modificare il bit GIE presente nel registro INTCON si pu scrivere:
INTCONbits.GIE = 1;
Vediamo un esempio in cui si voglia gestire la pressione del tasto BT1 collegato alla
linea RB4 del microcontrollore. In particolare si gestir tale interruzione in modalit
compatibile con i PIC16, ovvero facendo uso dell'Interrupt Vector posto all'indirizzo 0x08
(alta priorit).
#include <xc.h>
#include "PIC18F4550_config.h"
// Funzione per la gestione dell'interruzione
__interrupt (high_priority) void ISR_alta (void) {
// Indice per il ciclo di pausa
unsigned int i;
// Controllo che l'interrupt sia stato generato da PORTB
if (INTCONbits.RBIF == 1 ) {
//pausa filtraggio spike
for (i=0; i<10000; i++){
}
// Controllo la pressione di RB4
if (PORTBbits.RB4 == 0) {
// Inverto lo stato del LED 0
LATDbits.LATD0 = ~LATDbits.LATD0;
}
260
In questo esempio si vede che possibile gestire la pressione di un pulsante senza che
il microcontrollore stia di continuo a leggere il valore del pin a cui collegato il pulsante.
In particolare la funzione main praticamente uguale alla funzione main degli altri
progetti. Unica differenza che prima di entrare nel ciclo while a non far nulla, si
261
impostano ed abilitano le interruzioni. Per poter utilizzare i pulsanti mostrati nello schema
di Figura 99 necessario attivare i resistori di pull-up interni al PIC:
// Abilita i resistori di pull-up sulla PORTB
INTCON2bits.RBPU = 0x00;
Dopo aver attivato i resistori di pull-up si abilitano le interruzioni associate ai pin della
PORTB, ovvero si setta il bit RBIE del registro INTCON:
INTCONbits.RBIE = 1;
Una volta impostato l'hardware per la gestione delle periferiche, vengono abilitate le
interruzioni:
// Resetto il flag d'interrupt prima di attivare le interruzioni
INTCONbits.RBIF = 0;
// Abilito modalit compatibile (di default vale gi 0)
RCONbits.IPEN = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
// Abilito l'interrupt periferiche
INTCONbits.PEIE = 1 ;
Nel caso specifico il filtro inserito nei pulsati annullerebbe eventuali interruzioni visto che al controllo del pulsante non
verrebbe trovato alcun pulsante premuto.
262
}
// Resetto il flag d'interrupt per permettere nuove interruzioni
INTCONbits.RBIF = 0;
}
Una volta assodato che l'interrupt stato generato dalla pressione di un pulsante, viene
posto un filtro per eliminare gli spike, semplicemente facendo un conteggio a vuoto. Un
conteggio di questo tipo viene detto bloccante, poich durante il conteggio non
possibile fare altre cose.
Dopo la pausa viene controllato se il pulsante ancora premuto, qualora dovesse
essere ancora premuto il pin RD0, collegato con il LED 0, viene invertito di stato per
mezzo dell'operatore bitwise ~.
LATDbits.LATD0 = ~LATDbits.LATD0;
Dunque se il LED spento viene acceso, mentre se acceso viene spento. Una volta
svolta l'operazione di gestione dell'interrupt viene resettato il bit RBIF.
INTCONbits.RBIF = 0;
Si noti che tale istruzione appartiene al blocco if e non alla funzione principale.
Questo fondamentale qualora si gestiscano anche altre interruzioni. Infatti se si pulissero
tutti i flag d'interruzione alla fine della funzione di gestione dell'interruzione, qualora una
periferica avesse generato un interrupt durante l'esecuzione di istruzioni associate ad
un'altra interruzione, tale informazione verrebbe persa; dunque ogni flag deve essere
posto a 0 all'interno del blocco if che ha controllato il suo stato. A fine gestione di un
interrupt, qualora qualche altra periferica avesse richiesto un interruzione, potr essere
gestita mettendo nuovamente in attesa il programma principale. Infatti ogni interruzione
abilitata sia essa ad alta o bassa priorit risulta a priorit pi alta rispetto all'esecuzione del
programma principale.
Dal momento che non sono ancora note molte periferiche interne al PIC, non sono
presentati altri esempi sulla gestione delle interruzioni abilitando sia l'alta che la bassa
priorit. Esempi con ambedue le modalit attive saranno comunque mostrati nei prossimi
Capitoli.
263
Questa variabile cosi definita potrebbe essere utilizzata dalla funzione main come
anche da qualunque altra funzione. Qualora tale variabile sia utilizzata dalla funzione
di gestione delle interruzione (qualunque sia la priorit) eventuali assunzioni che il
compilatore potrebbe fare per ottimizzare il codice non varrebbero pi, poich la
variabile temperatura potrebbe essere variata in qualunque momento dalla funzione
d'interrupt.
Spesso il compilatore cerca infatti di ottimizzare il codice cambiando l'ordine delle
istruzioni o utilizzando il contenuto di un vecchio registro piuttosto che ripetere
un'operazione che presumibilmente porterebbe allo stesso risultato.
Se il risultato di un'operazione potesse essere cambiato da un ISR si capisce che il
suo valore potrebbe cambiare inaspettatamente, infatti il compilatore non pu per
esempio prevedere quando verr premuto un pulsante o si ricever un dato sulla
porta seriale.
Per tale ragione, al fine di mettere in guardia il compilatore e obbligarlo a non
fare assunzioni di alcun tipo nei confronti della variabile d'interesse, bisogna
dichiarare la variabile in questo modo.
volatile unsigned int temperatura = 0;
applicazioni Real Time, non avere il controllo del tempo di esecuzione potrebbe
facilmente causare dei problemi. Si pensi per esempio che un ISR impieghi 1ms e si
garantisce ad un cliente che entro 2ms si riesce a servire un'interruzione di un'altra
periferica; fin qui tutto bene. Ma se la nostra ISR dovesse diventare troppo
complessa, potremmo facilmente eccedere i 2ms uscendo dunque fuori specifica. Al
fine di garantire che il tempo dell'ISR non vari al variare della complessit dell'ISR
consigliabile impostare delle variabili base nell'ISR e svolgere le operazioni pi
pesanti al di fuori dell'ISR stessa.
Tipiche cose da evitare sono l'utilizzo di funzioni bloccanti all'interno di un ISR,
come per esempio funzioni di delay, usate per esempio nell'esempio precedente.
Altre tecniche per permettere lo svolgimento di operazioni al di fuori dell'ISR per
mezzo delle macchine a stati, ovvero un programma le cui operazioni da svolgere
sono scandite non direttamente dagli eventi ma dagli stati, i quali vengono invece
cambiati dagli eventi.
Per esempio un sistema molto semplice con due soli stati ON e OFF (acceso e
spento) come nel caso dell'esempio precedente, potrebbe essere scritto:
#include <xc.h>
#include "PIC18F4550_config.h"
#define STATE_OFF
#define STATE_ON
#define STATE_BUTTON_PRESSED
0x00
0x01
0x02
}
//*********************************************
// Funzione principale
//*********************************************
int main (void){
unsigned int i;
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
265
TRISB = 0x10;
// Imposto PORTC tutti ingressi
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Abilita i resistori di pull-up sulla PORTB
INTCON2bits.RBPU = 0x00;
// Abilito le interruzioni su PORTB
INTCONbits.RBIE = 1;
// Abilito modalit compatibile (di default vale gi 0)
RCONbits.IPEN = 0;
// Resetto il flag d'interrupt prima di attivare le interruzioni
INTCONbits.RBIF = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
// Abilito l'interrupt periferiche
INTCONbits.PEIE = 1 ;
// Ciclo infinito
while(1){
switch (state) {
case STATE_BUTTON_PRESSED:
//pausa filtraggio spike
for (i=0; i<10000; i++){
}
// Controllo la pressione di RB4
if (PORTBbits.RB4 == 0) {
if (LATDbits.LATD0 == 1){
state = STATE_OFF;
} else {
state = STATE_ON;
}
}
break;
case STATE_ON:
LATDbits.LATD0 = 1;
break;
case STATE_OFF:
LATDbits.LATD0 = 0;
break;
266
bene pulire il Flag delle interruzioni subito dopo l'aver identificato il flag delle
interruzioni stesse. In questo modo eventuali ulteriori interruzioni da parte della
stessa periferica possono essere identificate anche durante l'esecuzione dell'ISR.
Infatti se durante l'esecuzione dell'ISR un nuovo evento di interruzioni si dovesse
verificare, porrebbe il Flag delle interruzioni ad 1 ed uscendo dall'ISR verrebbe
nuovamente eseguita la funzione delle interruzioni senza aver perso l'evento
stesso.
bene pulire il Flag solo alla fine delle istruzioni nel caso in cui si debba garantire
che altre interruzioni siano ignorate se si stanno eseguendo ancora le operazioni
associate all'interruzione precedente.
questo Capitolo si sono viste le ISR ma si sono controllati solo i flag relativi alle
periferiche d'interesse. In applicazioni professionali si effettua spesso il controllo dei
flag delle interruzioni anche di periferiche non abilitate, in maniera da poter
individuare eventuali problemi ed eventualmente decidere se effettuare un Reset di
sistema al fine di far ripartire il sistema da uno stato noto.
Riassunto
In questo Capitolo abbiamo visto come utilizzare le interruzioni al fine di armonizzare
eventi da parte dei moduli interni al microcontrollore. Si sono visti i due livelli di priorit
utilizzabili per poter gestire due gruppi di periferiche ai quali viene assegnato un livello di
importanza diverso. Il Capitolo, sebbene non si sia introdotta la gestione multipla di
interruzioni, ha messo in evidenza diverse considerazioni da tenere a mente quando si
scrivono applicazioni con le interruzioni. In particolare si introdotta la variabile di tipo
volatile al fine di limitare problemi con l'utilizzo delle variabili globali.
268
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
269
Capitolo IX
Utilizziamo un Display alfanumerico LCD
In questo Capitolo si introduce una periferica esterna al PIC, ovvero il display
alfanumerico LCD. Il suo utilizzo cosi frequente nonch importante, che bene
parlarne subito. Infatti il modulo LCD torner utile anche negli esempi che seguiranno,
fornendo un'interfaccia grafica sulla quale visualizzare i dati. I display LCD alfanumerici
permettono infatti con pochi soldi di creare un'interfaccia grafica professionale,
permettendo ai nostri programmi di colloquiare con l'utilizzatore finale. In particolare, al
fine di mantenere la discussione quanto pi semplice possibile, l'utilizzo del display viene
mostrato facendo uso della libreria LaurTec. In questo modo, con poche righe,
saluteremo nuovamente il mondo!
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 102 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo.
270
Freedom II
Al fine di poter eseguire in maniera corretta l'esempio presentato in questo capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 103: Impostazioni dei Jumper per il Progetto presentato nel Capitolo .
Freedom Light
Al fine di poter eseguire in maniera corretta l'esempio presentato in questo capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 104: Impostazioni dei Jumper per il Progetto presentato nel Capitolo .
272
Impostazioni software
I programmi, facendo uso della libreria grafica LCD_44780.h richiede che siano
propriamente impostati i percorsi per la sua inclusione, ovvero:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
Per semplificare la scrittura del programma, i vari esempi presentati fanno uso della
seguente funzione di inizializzazione delle varie porte di ingresso e uscita:
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi, RC1 come output
LATC = 0x00;
TRISC = 0b11111101;
// Imposto PORTD tutte uscite, RD0 come ingresso
LATD = 0x00;
TRISD = 0b00000001;
273
126
Ogni carattere contenuto all'interno di una piccola matrice di punti per mezzo dei quali si ottiene la forma del carattere
stesso o anche di un simbolo.
La Figura estratta dal Datasheet Hitachi, relativo al controllore HD44780.
274
127
Le linee di Enable possono anche essere due ma per il 16x2, 16x1 si ha una sola linea di Enable.
275
Il compilatore XC8 fornisce una libreria dedicata per il controllo dei display LCD ma
per ragioni di semplicit e uniformit delle librerie LaurTec, nel Capitolo far uso della
libreria che ho personalmente realizzato128 ovvero la libreria inclusa nei file:
LCD_44780.h
LCD_44780.c
[2105]
[2105]
[2105]
[2105]
[2105]
[2105]
[2105]
[2105]
Questi messaggi stanno ad indicare che le varie costanti interne non sono state
128
129
130
276
impostate e per cui saranno usate quelle di default. In particolare quelle di default
rappresentato proprio quelle per poter lavorare correttamente con Freedom II e Freedom
Light.
Come detto bene sempre compilare un programma senza alcun messaggio di
Warning, dunque la libreria prevede un modo per non far visualizzare tali messaggi. Il
modo consiste nel comunicare alla libreria l'intenzione di utilizzare i valori di default. Per
fare questo necessario definire il nome LCD_DEFAULT prima della chiamata della libreria,
ovvero:
#define LCD_DEFAULT
#include "LCD_44780.h"
#include "LCD_44780.c"
In questo modo i controlli interni della libreria sapranno che l'utente consapevole che
i valori utilizzati dalla libreria sono quelli di default.
Oltre alla libreria necessario impostare, per mezzo dei registri TRISx del PIC, i vari
pin utilizzati dalla libreria stessa in modo opportuno. In particolare i pin utilizzati dalla
libreria sono:
277
#define
#define
#define
#define
#define
#endif
LCD_D3 LATDbits.LATD7
LCD_RS LATDbits.LATD2
LCD_E LATDbits.LATD3
LCD_RW LATDbits.LATD1
LCD_LED LATCbits.LATC1
ed
#ifndef _PIC18
#define LCD_D0 PORTDbits.RD4
#define LCD_D1 PORTDbits.RD5
#define LCD_D2 PORTDbits.RD6
#define LCD_D3 PORTDbits.RD7
#define LCD_RS PORTDbits.RD2
#define LCD_E PORTDbits.RD3
#define LCD_RW PORTDbits.RD1
#define LCD_LED PORTCbits.RC1
#endif
I due diversi blocchi vengono a dipendere dal fatto che la libreria supporta sia i PIC18 che
i PIC16. Nel caso in cui si stia usando un PIC18, cosa che dovrebbe essere, visto il libro
che state leggendo, dovrete modificare il primo blocco di costanti. Nel caso in cui state
usando un PIC16, dovrete cambiare il secondo blocco.
Ogni costante fa riferimento ad un pin del PIC. proprio questo riferimento che deve
essere aggiornato; si noti che la modalit supportata solo quella a 4 bit. Nel caso dei
PIC18 si fa uso del registro LATD, mentre nel caso dei PIC16 si fa uso del registro
PORTD, non essendo presente il registro ausiliario LATD.
La costante LCD_D0, ovvero il bit D0, rappresentato in realt dal bit D4 del display,
infatti i bit del display D0-D3 non sono utilizzati, quelli utilizzati in modalit 4 bit sono
solo i bit D4-D7. Il bit RW non viene utilizzato dalla libreria se non per il fatto che viene
posto a 0; per tale ragione in molte applicazioni tale pin collegato a massa. L'ultimo bit
da impostare il bit LCD_LED ovvero il bit che controlla il LED di retroilluminazione.
La libreria supporta un semplice Output, ma la linea RC1 del PIC18F4550 ha l'opzione di
avere un segnale PWM in uscita, fornendo dunque la possibilit di regolare la
retroilluminazione del display stesso. Questa opzione deve per essere gestita al di fuori
della libreria stessa.
Nel caso si decidesse di modificare la libreria si consiglia di crearne una copia e
modificare la stessa, lasciando invariata quella originale, la quale potr essere
ripristinata in ogni momento.
278
ID
Funzione
Descrizione
LCD_initialize
LCD_clear
LCD_cursor
LCD_backlight
LCD_home
LCD_goto_line
LCD_goto_xy
LCD_shift
LCD_shift_cursor
10
LCD_write_char
11
LCD_write_string
12
LCD_write_integer
13
LCD_write_message
La libreria supporta, per ogni funzione, il nome LCD sia come all'inizio che alla
fine del nome stesso. Per esempio LCD_initialize e initialize_LCD. In
particolare il primo formato torna utile quando non si ricorda il nome della
funzione, basta infatti scrivere LCD e premere Control + Space per avere i
suggerimenti di ogni funzione inclusa nella libreria.
Per facilitare l'utilizzo della libreria sono definite le seguenti costanti:
#define LCD_LEFT 0
#define LCD_RIGHT 1
#define LCD_TURN_ON_LED 1
#define LCD_TURN_OFF_LED 0
#define LCD_TURN_ON_CURSOR 1
#define LCD_TURN_OFF_CURSOR 0
#define LCD_BLINKING_ON 1
#define LCD_BLINKING_OFF 0
279
LCD_DEFAULT,
in modo da
280
#define LCD_DEFAULT
#include "LCD_44780.h"
#include "LCD_44780.c"
Tra le impostazioni dei pin non c' nulla di nuovo, ma si osservino le impostazioni dei
pin della PORTD e PORTC contenuti della funzione di inizializzazione
board_initialization.
// Imposto PORTC tutti ingressi, RC1 come output
LATC = 0x00;
TRISC = 0b11111101;
// Imposto PORTD tutte uscite, RD0 come ingresso
LATD = 0x00;
TRISD = 0b00000001;
Una volta inizializzati i pin possibile inizializzare il nostro display LCD e scrivere il
messaggio Hello World 1 sulla prima riga.
LCD_initialize (20);
LCD_backlight (TURN_ON_LED_LCD);
LCD_write_message (FRASE_1);
Questo viene fatto spesso, in maniera da avere le frasi scritte in un unico posto. Non
inusuale che sia creato anche un file .h separato solo per la definizione delle frasi o
messaggi.
Dopo aver scritto il primo messaggio sulla prima riga si pu scrivere sulla seconda,
semplicemente con l'aggiunta delle due righe:
LCD_goto_line (2);
LCD_write_message ("Hello World 2");
la funzione:
LCD_goto_line (2);
qualora si stia usando un display con 4 righe, permette anche di posizionare il cursore
sulla riga 3 e 4.
281
Il display LCD permette di scrivere frasi sul display, ma come visto nell'esempio
precedente il modo con cui si pu usare la funzione write_message_LCD pu essere
leggermente differente in base a come viene definito il messaggio stesso. Infatti si pu
scrivere sia:
LCD_write_message (FRASE_1);
che
LCD_write_message ("Hello World 2");
282
LCD_goto_line (2);
LCD_write_string (frase);
// Ciclo infinito
while (1) {
}
}
283
typedef struct {
unsigned char nome [20];
unsigned char cognome [20];
} persona;
int main (void){
// Variabile tizio di tipo persona
persona tizio;
board_initialization();
// Inizializzo il display LCD con quarzo a 20MHz
LCD_initialize (20);
LCD_backlight (TURN_ON_LED_LCD);
// Scrivo i dati nella variabile tizio
copy (tizio.nome, "Mauro");
copy (tizio.cognome, "Laurenti");
LCD_write_string (tizio.nome);
LCD_goto_line (2);
LCD_write_string (tizio.cognome);
while (1) {
}
//*************************************
//
Implementazione della funzione
//*************************************
void copy (unsigned char * dest, const unsigned char * parola) {
while(*parola) {
// Copio il carattere dentro l'array
*dest = *parola;
// Incremento il puntatore
parola++;
dest++;
L'inizio del programma simile al primo esempio, nulla di nuovo. In particolare oltre
alle inclusioni dei file gi visti si dichiara il prototipo della funzione copy e la struttura dati
persona. Riguardo alla funzione copy basta sapere che viene utilizzata per copiare una
stringa costante, tipo Ciao Mamma all'interno del nostro Array di caratteri.
typedef struct {
284
All'inizio del
tizio :
main
persona
Il programma diviene poi come il precedente, ovvero si inizializzano i vari pin del PIC
in modo da poter utilizzare propriamente la libreria e si inizializza il display LCD.
Successivamente viene richiamata la funzione copy, utilizzata per copiare una stringa
all'interno della nostra struttura.
// Scrivo i dati nella variabile tizio
copy (tizio.nome, "Mauro");
copy (tizio.cognome, "Laurenti");
La ragione per cui deve essere richiamata la funzione legata al fatto che il C, non
supportando in maniera nativa le stringhe, non permette istruzioni del tipo:
nome = "Piero"
Per poter scrivere un nome o una qualunque frase all'interno di un Array necessario
scrivere elemento per elemento, ovvero carattere per carattere, il nome o frase. Per
agevolare il programmatore la Microchip fornisce la libreria standard C string.h, 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, quindi possibile
accedere i singoli caratteri di questi due campi per mezzo di questa sintassi:
a = tizio.nome[2];
b = tizio.cognome[3];
Questa volta d non deve essere semplicemente un carattere! Infatti con questa sintassi
senza parentesi quadre, come spiegato in precedenza, si intende l'indirizzo di memoria
285
dove inizia il nostro Array 131 tizio.nome, ovvero abbiamo a che fare con un puntatore.
Per cui la variabile d deve essere definita come:
unsigned char * d;
possibile notare che la prima variabile della funzione copy rappresenta proprio un
// Incremento il puntatore
parola++;
dest++;
Al suo interno viene effettuato un ciclo while che termina quando il valore puntato dal
131
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.
286
puntatore parola vale '\0', ovvero alla fine della stringa. Fino a che tale valore diverso
da tale carattere vengono eseguite le istruzioni interne al ciclo.
La prima istruzione nel ciclo copia il carattere puntato da parola nell'indirizzo puntato
da dest, ovvero si copia un elemento dall'origine alla destinazione. L'istruzione successiva
incrementa l'indirizzo contenuto nella variabile parola in modo da puntare il carattere
successivo della parola d'origine. Allo stesso modo viene incrementato l'indirizzo
contenuto nella variabile dest in modo da poter copiare il nuovo carattere nella cella
successiva, il ciclo si ripete fino a che la parola non termina.
Usciti dal ciclo, all'ultimo elemento puntato da dest, si scrive il valore '\0', infatti tale
valore non viene trasferito poich il ciclo while termina quando questo viene trovato
all'interno della parola, come detto fondamentale che questo valore sia mantenuto.
In ultimo, una volta che il nome e cognome sono caricati all'interno della nostra
struttura, si possono scrivere semplicemente facendo uso della funzione di libreria
LCD_write_string, al quale viene passato il puntatore dell'Array:
LCD_write_string (tizio.cognome);
Luce
Lingua
Luce pu assumere il valore accesa e spenta, mentre il valore di Lingua assume il nome
delle tre lingue. I tre menu nelle tre diverse lingue sono:
Italiano
Inglese
Tedesco
Il passaggio da un menu ad un altro deve avvenire attraverso la pressione del tasto BT1
mentre il cambio del parametro deve avvenire per mezzo del tasto BT2.
287
MESSAGE_0
MESSAGE_1
MESSAGE_2
MESSAGE_3
MESSAGE_4
0x00
0x01
0x02
0x03
0x04
STATE_LANGUAGE_ITALIANO
STATE_LANGUAGE_ENGLISH
STATE_LANGUAGE_DEUTSCH
STATO_LANGUAGE_MAX
#define STATE_MENU_1
#define STATE_MENU_2
#define STATE_MENU_MAX
0x00
0x01
0x02
0x03
0x01
0x02
0x03
288
#define ACTIVATED
#define DEACTIVATED
0x01
0x00
#define PRESSED
0x00
if (state_menu == STATE_MENU_MAX)
state_menu = STATE_MENU_1;
if (state_language == STATO_LANGUAGE_MAX)
state_language = STATE_LANGUAGE_ITALIANO;
}
// Resetto il flag d'interrupt per permettere nuove interruzioni
INTCONbits.RBIF = DEACTIVATED;
}
289
//*************************************
//
Programma principale
//*************************************
int main (void){
board_initialization ();
// Inizializzo il display LCD con quarzo a 20MHz
LCD_initialize (20);
// Ciclo infinito
while (1) {
//*************************************
//
Controllo Lingua
//*************************************
if (state_language == STATE_LANGUAGE_ITALIANO) {
language = ( unsigned char **) &italian;
}
if (state_language == STATE_LANGUAGE_ENGLISH) {
language = ( unsigned char **) &english;
}
if (state_language == STATE_LANGUAGE_DEUTSCH) {
language = ( unsigned char **) &german;
}
LCD_goto_xy (2,1);
LCD_write_string ((unsigned char *) language[MESSAGE_0]);
LCD_write_string ((unsigned char *) language[MESSAGE_1]);
//*************************************
//
Controllo Cursore
//*************************************
if (state_menu == STATE_MENU_1){
LCD_goto_xy (1,1);
LCD_write_char (126);
LCD_goto_xy (1,2);
LCD_write_char (' ');
} else {
LCD_goto_xy (1,1);
LCD_write_char (' ');
LCD_goto_xy (1,2);
LCD_write_char (126);
}
//*************************************
//
Controllo Luce
//*************************************
LCD_goto_xy (2,2);
LCD_write_string ((unsigned char *) language[MESSAGE_2]);
if (state_light == STATE_LIGHT_ON) {
LCD_backlight (LCD_TURN_ON_LED);
LCD_write_string ((unsigned char *) language[MESSAGE_3]);
} else {
290
}
}
LCD_backlight (LCD_TURN_OFF_LED);
LCD_write_string ((unsigned char *) language[MESSAGE_4]);
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC tutti ingressi, RC1 come output
LATC = 0x00;
TRISC = 0b11111101;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Abilita i resistori di pull-up sulla PORTB
INTCON2bits.RBPU = 0x00;
// Abilito le interruzioni su PORTB
INTCONbits.RBIE = 1;
// Abilito modalit compatibile (di default vale gi 0)
RCONbits.IPEN = 0;
// Resetto il flag d'interrupt prima di attivare le interruzioni
INTCONbits.RBIF = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
// Abilito l'interrupt periferiche
INTCONbits.PEIE = 1 ;
}
291
bene vedere i vari dettagli del programma al fine di comprendere il tutto e fare
propri alcuni aspetti di programmazione. Si consiglia di simulare anche il programma o
parte di esso, per la verifica e studio dell'Array multidimensionale che contiene le stringhe
del menu. In particolare per la gestione degli Array, si fatto anche uso di un puntatore a
puntatore.
Riassunto
In questo Capitolo abbiamo visto come utilizzare il modulo LCD basato su controllore
HD44780. Grazie all'utilizzo della libreria LaurTec i vari programmi di esempio sono
piuttosto compatti e chiari. Il progetto finale ha messo in evidenza come un programma
possa diventare pi organico anche se apparentemente le funzioni svolte non sono molte.
Il progetto mostrato mette anche in evidenza l'utilizzo dei puntatori in applicazioni pi
complesse e come il loro utilizzo permetta di cambiare facilmente lingua semplicemente
cambiando l'indirizzo dell'Array indirizzato.
292
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
293
Capitolo X
Utilizziamo un Display Grafico LCD
In questo Capitolo si introduce nuovamente una periferica esterna al PIC. Il suo
utilizzo particolarmente frequente, estendendo le possibilit di un Display Alfanumerico,
ovvero il Display Grafico. Il Capitolo prende come esempio i Display Grafici basati su
controllore KS0108B ed una risoluzione 128x64. Sebbene sia di tipo bianco e nero e la
risoluzione non paragonabile con i classici cellulari che si soliti avere in tasca, le
applicazioni e tipologia di dati che possibile visualizzare, fornisce un ulteriore strumento
professionale alle nostre applicazioni.
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 107 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo. Si noti che il Display Grafico non presente
sulle schede di sviluppo presentate, per cui il montaggio del Display GLCD deve essere
esterno.
294
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 108: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 109: Impostazioni dei Jumper per i Progetti presentati nel Capitolo .
296
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione software particolare oltre alla
semplice creazione di un progetto.
Il programma facendo uso della libreria grafica GLCD_KS0108B.h richiede che siano
propriamente impostati i percorsi per la sua inclusione, ovvero:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
Per semplificare la scrittura del programma, i vari esempi presentati, fanno uso della
seguente funzione di inizializzazione delle varie porte di ingresso e uscita:
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0x07;
// Set Analog Input come I/O
ADCON1 = 0x0F;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFD;
// Imposto PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
297
Figura 110: Schema a blocchi di un display GLCD 128x64 basato su controllore KS0108B.
Sul mercato possibile trovare GLCD con risoluzione maggiore ottenuti con tre
controllori KS0108B. Da quanto detto si capisce che pilotare un Display Grafico basato
su controllore GLCD KS0108B consiste nel pilotare due controllori. Il Display Grafico
suddiviso in due parti come riportato in Figura 111. La parte I controllata dal primo
controllore mentre la parte II controllata dal secondo controllore. Da questa prima
rappresentazione si capisce anche che se si volesse disegnare un punto con coordinata X
pari a 80, bisogna scrivere nel secondo controllore con coordinata X pari a 15. Di questo
132
298
non ci si dovr preoccupare troppo perch la libreria utilizzata, tratter con questi dettagli
mentre l'utilizzatore pu utilizzare funzioni semplici.
Sebbene la Figura 111 mostri che la risoluzione di ogni settore di 64x64 pixel, per
accedere ad ogni punto non basta semplicemente inviare dei comandi per un punto di
coordinate x,y compreso tra 0 e 63. Il controllore KS0108B divide infatti ogni settore in 8
pagine, come riportato in Figura 112. Ogni pagina possiede a sua volta una risoluzione
64x8 e pu essere scritta solo 8 punti alla volta (verticalmente).
Questa divisione, che si ritrova anche in altri controllori, discende dal fatto che per
ogni pixel non associato un byte ma un solo bit, per cui un byte controlla 8 bit. Per tale
ragione 8 punti il numero minimo di pixel che possibile scrivere. Per poter disegnare
un pixel necessario dunque impostare, il controllore (settore), la pagina e l'indirizzo
(nominato Y address). Dal momento che possibile scrivere solo 8 punti alla volta, si
capisce che, qualora si voglia modificare un solo pixel, necessario prima leggere il valore
del byte in cui raggruppato il nostro pixel e solo successivamente scrivere il valore del
nuovo pixel senza alterare il valore degli altri.
Sebbene per ogni pixel si debba posizionare controllore, pagina e indirizzo, qualora si
debbano scrivere pi punti consecutivi si pu sfruttare il fatto che l'indirizzo interno al
controllore viene automaticamente incrementato ad ogni scrittura. In questo modo fin
tanto che non si debba cambiare controllore o pagina, si pu scrivere l'indirizzo iniziale e
procedere da sinistra verso destra alla scrittura degli altri bit, evitando di dover effettuare
299
CS1, CS2 : Tali linee rappresentano i Chip Select dei due controllori. In particolare
le linee precedentemente spiegate sono usate dall'uno o l'altro controllore a
seconda del Chip Select abilitato.
Si fa notare che la nomenclatura utilizzata in questa descrizione pu variare
leggermente a seconda del Datasheet utilizzato ma dalla descrizione della
funzione del pin stesso si pu facilmente individuare la corrispondenza con
quelli appena descritti.
300
I vari tempi associati alle sequenze scrittura/lettura, sono riportati in Tabella 13.
133
134
301
Parametro
Simbolo
Min.
Tipico
Max.
Unit
Ciclo E (Enable)
tCYC
1000
--
--
ns
E livello alto
PWEH
450
--
--
ns
E livello basso
PWEL
450
--
--
ns
tr
--
--
25
ns
tf
--
--
25
ns
tAS
140
--
--
ns
tAH
10
--
--
ns
tDSW
200
--
--
ns
tDDR
--
--
320
ns
tDHW
10
--
--
ns
tDHR
20
--
--
ns
Qualora i tempi di scrittura lettura non sono rispettati possibile che i comandi non
vengano propriamente riconosciuti, per cui l'inizializzazione o la rappresentazione di
oggetti sullo schermo avviene in maniera disordinata (punti mancanti o punti in posizioni
errate). Una mancata inizializzazione possibile verificarla osservando un effetto neve
sullo schermo, ovvero i pixel dello schermo hanno un valore casuale che riflette il
contenuto random della memoria RAM interna al controllore (alcune volte possibile
vedere una correlazione tra i pixel accesi). La sequenza d'inizializzazione del Display,
ovvero dei controllori, tra le varie operazioni compiute, ha il compito di porre a 0x00 o
0xFF il contenuto della memoria RAM, il che equivale a pulire lo schermo.
Al fine di poter utilizzare propriamente il Display sono presenti altre linee/pin, in
particolare per il LED di retroilluminazione, pin per il contrasto e pin di alimentazione. La
piedinatura tipica per un Display GLCD basato su controllore KS0108B riportata in
Tabella 2 (normalmente il pin 1 riportato sulla serigrafia del PCB sul quale montato il
display):
Per il controllo della griglia per polarizzare i cristalli interni necessaria una
tensione negativa. Alcuni Display possiedono il convertitore DC-DC o charge
pump, direttamente sul modulo, richiedendo per cui un solo trimmer esterno.
Alcuni Modelli di display richiedono invece una tensione negativa esterna, la
quale deve essere opportunamente ricavata dai 5V (normalmente disponibili).
302
PIN
Simbolo
Livello
Funzione
VSS
--
Ground
VDD
--
VO
--
D/I
H/L
H: Data Input
L: Istruzione
R/W
H/L
H: Data Read
L: Data Write
H, H -> L
7-14
DB0-DB7
H/L
Enable
15
CS1
Chip Select 1
16
CS2
Chip Select 2
17
RST
Reset
18
VOUT
--
19
LED+
--
LED retroilluminazione
20
LED-
--
LED retroilluminazione
Data Bus
Nel file header sono presenti le informazioni relative al collegamento del modulo
GLCD. Sebbene sia possibile variare a proprio piacimento i collegamenti rispetto allo
schema elettrico presentato in Figura 107 necessario mantenere il Data bus su un'unica
303
GLCD_DATA_WRITE
GLCD_DATA_READ
GLCD_DATA_DIRECTION
GLCD_D_I
GLCD_R_W
GLCD_E
GLCD_CS1
GLCD_CS2
GLCD_RST
GLCD_LED
LATD
PORTD
TRISD
LATAbits.LATA3
LATAbits.LATA4
LATAbits.LATA5
LATEbits.LATE0
LATEbits.LATE1
LATEbits.LATE2
LATCbits.LATC1
304
ID
Funzione
Descrizione
GLCD_initialize
GLCD_clear
Pulisce il Display.
GLCD_backlight
GLCD_set_display
GLCD_set_vertical_offset
GLCD_plot_xy
GLCD_draw_horizontal_line
GLCD_draw_vertical_line
GLCD_draw_window
10
GLCD_draw_box
11
GLCD_draw_picture
12
GLCD_write_char
13
GLCD_write_string
14
GLCD_write_message
15
GLCD_write_integer
16
GLCD_set_font
0b00000001
0b00000000
#define GLCD_TURN_ON_DISPLAY
#define GLCD_TURN_OFF_DISPLAY
0b00000001
0b00000000
#define GLCD_HIGH
#define GLCD_LOW
0b00000001
0b00000000
#define GLCD_ENABLE
#define GLCD_DISABLE
0b00000001
0b00000000
#define GLCD_ENABLE_RESET
#define GLCD_DISABLE_RESET
0b00000000
0b00000001
siamo pi abituati.
Facendo riferimento all'esempio riportato a fine articolo possibile avere una migliore
comprensione sul concetto di origine dell'oggetto, posto all'angolo in basso a sinistra
dell'oggetto stesso.
Prima di poter utilizzare una qualunque funzione della libreria necessario
richiamare la funzione di inizializzazione del modulo GLCD.
Per non includere la tabella dei font e risparmiare memoria Flash, basta commentare la
riga in cui viene definito ENABLE_FONT_5x7.
Allo stesso modo dei font possibile includere delle immagini, a loro volta incluse in
Array memorizzati nella memoria Flash. Per abilitare o meno le immagini possibile
306
307
GLCD_backlight (GLCD_TURN_ON_LED);
while (1) {
GLCD_draw_picture (logo_1);
GLCD_draw_window ( 0, 0, 127, 63, GLCD_FILLING_BLACK);
GLCD_draw_window ( 2, 2, 123, 59, GLCD_FILLING_BLACK);
delay_ms (2000);
GLCD_clear (GLCD_FILLING_WHITE);
GLCD_write_message (25,28,"Loading...
%");
Si noti che sono passati vari parametri. Facendo riferimento alla documentazione:
/**
* This function plots an empty window either with black or white colour
borders.
* Origin and length and height can be set by the user.
*
* @param x_origin Window X origin [0..127].
*
* @param y_origin Window Y origin [0..63].
*
* @param length Window length [0..127].
*
* @param length Window height [0..63].
*
* @param filling_color Filling color allows to set the dot either on
white or black color [GLCD_FILLING_WHITE, GLCD_FILLING_BLACK]
308
*
* @return void
*
* @warning It is user responsibility to make sure that the window length
and height are valid, depending on x_origin, y_origin.
*
*/
void GLCD_draw_window (unsigned char x_origin, unsigned char y_origin,
unsigned char length, unsigned char height, unsigned char filling_color);
si pu vedere che i primi due valori rappresentano le coordinate x,y del punto di
riferimento dell'oggetto stesso, ovvero la sua origine. Come detto questo fa riferimento al
punto in basso a sinistra. Successivamente si passano i parametri relativi alla lunghezza e
all'altezza. In ultimo si definisce il colore della cornice stessa, che pu essere solo:
#define GLCD_FILLING_WHITE 0x00
#define GLCD_FILLING_BLACK 0xFF
Diversamente dal Display LCD, anche quando si scrive un messaggio bisogna fornire
l'origine della stringa stessa, ovvero x ed y:
GLCD_write_message (25,28,"Loading...
%");
309
#include "GLCD_KS0108B.h"
#include "GLCD_KS0108B.c"
#include "delay.h"
#include "delay.c"
#define DISPLAY_WIDTH 128
#define
#define
#define
#define
MAIN_WINDOW_ORIGIN_X 0
MAIN_WINDOW_ORIGIN_Y 0
MAIN_WINDOW_LENGTH 127
MAIN_WINDOW_HEIGHT 63
#define INFO_WINDOW_ORIGIN_X 0
#define INFO_WINDOW_ORIGIN_Y 12
#define INFO_WINDOW_LENGTH 127
#define NUMBER_OF_CH 4
#define
#define
#define
#define
#define
CHX_ORIGIN_Y
CH1_ORIGIN_X
CH2_ORIGIN_X
CH3_ORIGIN_X
CH4_ORIGIN_X
2
8
40
73
104
#define COLUMN_WIDTH 15
//*************************************
//
Prototipi delle funzioni
//*************************************
void board_initialization (void);
void draw_main_window (void);
void plot_histogram (unsigned char *data);
//*************************************
//
Programma principale
//*************************************
int main (void){
unsigned char data_buffer[4] = {25, 10,45, 20};
board_initialization ();
GLCD_initialize ();
GLCD_backlight (GLCD_TURN_ON_LED);
draw_main_window ();
plot_histogram (data_buffer);
while (1) {
}
//*************************************
//
Implementazione della funzione
//*************************************
310
(CH1_ORIGIN_X,
(CH2_ORIGIN_X,
(CH3_ORIGIN_X,
(CH4_ORIGIN_X,
CHX_ORIGIN_Y,"CH1");
CHX_ORIGIN_Y,"CH2");
CHX_ORIGIN_Y,"CH3");
CHX_ORIGIN_Y,"CH4");
(i*(DISPLAY_WIDTH)/NUMBER_OF_CH,
//*************************************
//
Implementazione della funzione
//*************************************
void plot_histogram (unsigned char *data) {
GLCD_draw_box
data[0], FILLING_BLACK);
GLCD_draw_box
data[1], FILLING_BLACK);
GLCD_draw_box
data[2], FILLING_BLACK);
GLCD_draw_box
data[3], FILLING_BLACK);
(CH1_ORIGIN_X,INFO_WINDOW_ORIGIN_Y,COLUMN_WIDTH,
(CH2_ORIGIN_X,INFO_WINDOW_ORIGIN_Y,COLUMN_WIDTH,
(CH3_ORIGIN_X,INFO_WINDOW_ORIGIN_Y,COLUMN_WIDTH,
(CH4_ORIGIN_X,INFO_WINDOW_ORIGIN_Y,COLUMN_WIDTH,
311
Riassunto
In questo Capitolo abbiamo visto come utilizzare il modulo GLCD basato su
controllore KS0108B spiegando in dettaglio come viene realizzato in pratica il Display
stesso. In particolare si messa in evidenza l'esigenza dell'uso di due controllori al fine di
ottenere una risoluzione pari a 128x64 pixel. L'uso della libreria presentata ha mostrato
nuovamente come l'uso della stessa permetta di realizzare un sistema complesso, senza
dover entrare nei dettagli di ogni sub modulo.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
e
unsigned char data_buffer[4] = {10, 30,20, 40};
passare i due Array alla funzione plot_histogram a distanza di 1000ms in maniera ciclica.
9.
312
Capitolo XI
XI
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 117 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo. I collegamenti mostrati sul connettore DB9
implementano gi una connessione Null-Modem, per cui, per il collegamento con il PC si
pu usare un cavo per modem normale.
313
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 118: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 119: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light non possiede il transceiver MAX232 e il connettore RS232, per cui
devono essere montati esternamente. Per poter testare tutti i LED, se necessario,
richiesto montare anche i relativi LED aggiuntivi.
314
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione software particolare oltre alla
semplice creazione di un progetto.
Alcuni esempi fanno uso delle librerie LaurTec, per cui necessario impostare i percorsi
di libreria riportati di seguito:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
Il protocollo RS232
Lo standard RS232 rappresenta un protocollo di comunicazione che permette senza un
eccessivo hardware, la comunicazione tra due sistemi elettronici. Lo standard descrive
una trasmissione asincrona e in modalit full-duplex. Il protocollo impone dei vincoli sia
di natura meccanica che elettrica, oltre che al formato dei dati stessi.
Il connettore utilizzato nei Personal Computer di due tipi, il DB25 maschio e il DB9
maschio. Ai suoi albori il connettore utilizzato era il DB25 rimpiazzato successivamente
con il DB9. In particolare lo standard RS232 associato al connettore DB25 mentre lo
standard RS232C associato al connettore DB9. Relativamente ai segnali base, i due
standard non differiscono.
I nomi che il sistema operativo d alle seguenti porte COM1 e COM2, ma non
inusuale avere nomi anche con un numero superiore a 100.
In Tabella 16 sono riportate, in funzione della numerazione che visibile sulla parte
frontale del connettore stesso, il significato dei vari pin, sia nel caso di connettore DB25
che del connettore DB9.
DB25
DB9
in/out
Sigla
TX Trasmissione Dati
RX Ricezione Dati
20
22
RI Ring Indicator
315
E' possibile subito osservare che sono presenti pi delle due semplici linee di
trasmissione e ricezione. Questo dovuto al fatto che nel protocollo RS232 sono presenti
anche regole per Handshaking (stretta di mano) ovvero linee che permettono al
trasmettitore e al ricevitore di avere informazioni dirette l'uno dell'altro. I modem fanno
uso di queste linee, ma si pu ottenere una trasmissione full-duplex con il solo utilizzo
delle linee TX, RX e GND.
Alcune volte le altre linee di output vengono utilizzate per prelevare l'alimentazione del
dispositivo collegato al computer. Questo viene fatto per esempio dai mouse seriali 135.
Bisogna comunque stare attenti e placare ogni entusiasmo poich questo non significa che
si possa alimentare ogni circuito. Le correnti che possibile prelevare dalle linee di output
sono dell'ordine del mA e non neppure specificato sul manuale, per cui non bene
affidarsi a questa alimentazione a meno di non richiedere correnti molto inferiori al mA.
Vediamo ora a cosa servono le altre linee non di trasmissione.
DTR: La linea DTR viene attivata quando il PC pronto per il collegamento con
una periferica esterna.
RTS: La linea RTS viene posta alta dal PC quando pronto per la trasmissione
dati verso la periferica esterna.
CTS: La linea CTS viene attivata in risposta al segnale RTS quando la periferica
pronta per ricevere dati. Prima di questo evento il PC non inizia la trasmissione.
RI: Il segnale RI viene attivato dal modem quando rileva il trillo di una chiamata
telefonica.
Quanto fin ora descritto rappresenta la parte pi grammaticale del protocollo, ovvero
la descrizione dei segnali e il loro significato in una trasmissione. In realt le linee che si
sono appena descritte sono controllabili anche via software il che permette di utilizzarle
anche con un significato differente creando cosi un proprio protocollo.
Quello che per non potremmo variare sono le specifiche elettriche a cui devono
soddisfare i segnali utilizzati per la trasmissione dei dati.
Questo discende semplicemente dal fatto che l'hardware per il protocollo RS232
progettato per soddisfare queste specifiche. In particolare i livelli di tensione sono
riportati in Tabella 17.
135
Anche i mouse USB prendono l'alimentazione direttamente dal computer, ma in questo caso la presa USB prevede di suo le
linee di alimentazione.
316
Livello Logico
Trasmettitore
Ricevitore
da +5V a +15V
da +3V a +25V
da -5V a -15V
da -3V a -25V
non definito
da -3V a +3V
Come possibile osservare l'intervallo di tensione che caratterizza ogni livello logico
molto ampio e differisce dai livelli di tensione disponibili sul microcontrollore.
Per tale ragione tra il microcontrollore e il connettore necessario avere un traslatore di
livello adatto, ovvero un RS232 Transceiver. Per convertire il livello logico TTL (5V) a
RS232 il tipico integrato utilizzato il MAX232, come mostrato in Figura 117.
Confrontando i livelli di tensione di questo protocollo con lo standard TTL in cui i livelli
di tensione sono compresi tra 0V (0 logico) e 5V (1 logico) possibile notare che un
livello di tensione alto corrisponde ad uno zero logico.
Nonostante l'ampiezza dell'intervallo di tensioni ammissibili nel protocollo RS232,
generalmente si usano livelli di tensione compresi tra 12V.
In ultimo, prima di fare un esempio di trasmissione utilizzando le linee TxD e RxD,
bene ricordare alcune modalit con cui possibile accorgersi di un errore.
Supponiamo di dover trasmettere il byte 10010011, sia che si trasmetta serialmente un bit
alla volta sia che si trasmetta tutti i bit in parallelo, c' sempre la possibilit che qualche bit
possa cambiare di livello prima di giungere a destinazione a causa del rumore elettrico.
L'errore, di suo, non sempre eliminabile, ma si pu limitare la possibilit che questo si
verifichi. Per limitare il verificarsi di un errore si provvede alla schermatura del cavo e al
suo intreccio in modo da limitare gli effetti nocivi dovuti ad eventuali campi
elettromagnetici che si possono accoppiare con il nostro cavo. Nonostante questi
accorgimenti un errore in trasmissione o ricezione pu ancora avvenire.
Subentrano a questo punto le tecniche per la correzione. Queste presumono in primo
luogo che sia possibile accorgersi dell'errore. Per fare questo necessario aggiungere,
come si dice in gergo, della ridondanza, ovvero degli ulteriori bit che permettano di fare
dei controlli sulle informazioni ricevute.
Maggiore l'informazione ridondante che viene aggiunta, maggiore la probabilit di
accorgersi e correggere l'errore. Con questo si vuole dire che nonostante si faccia uso di
ridondanza, il verificarsi di un errore pu ancora una volta solo essere limitato.
Il protocollo RS232 prevede la possibilit di utilizzare le seguenti tecniche per la
rilevazione degli errori :
Parit pari
Parit dispari
Mark
Space
318
140
141
Il livello scende automaticamente a -12V se la trasmissione gestita da hardware dedicato. Se la trasmissione implementata
per mezzo di un software bisogna abbassare il livello a -12V manualmente.
Non tutte le USART supportano tutte le lunghezze (forato dati). La lunghezza di 7 ed 8 bit generalmente quella standard
poich permette di inviare i caratteri ASCII e un byte completo.
Si specificato linee dati poich generalmente nella trasmissione dell'informazione sono presenti anche delle linee di
319
comunicazione tra il PIC e il modulo LCD; questo tipo di collegamento permette velocit
particolarmente elevate. Nonostante le apparenti brevi distanze che separano il
microprocessore dalle varie periferiche, necessario attendere un tempo non nullo
affinch tutti i bit che costituiscono l'informazione da trasmettere arrivino alla fine del
bus. Per tale ed altre ragioni la trasmissione parallela non viene utilizzata per lunghe
distanze, anche se in realt la massima distanza viene a dipendere pure dalla massima
frequenza con la quale si vuole trasmettere l'informazione. A tal proposito si pensi alla
porta parallela utilizzata per la stampante, che pu raggiungere distanze dei 3m senza
molti problemi. Questo non sarebbe possibile se si volesse trasmettere le informazioni alla
stampante alla stessa velocit con cui il microprocessore pu leggere o scrivere nella
memoria RAM.
Per lunghe distanze si tende ad utilizzare una trasmissione seriale; per trasmissione
seriale si intende che l'informazione pu viaggiare su un'unica linea dati 142. In questa classe
di trasmissioni rientrano per esempio lo standard RS232 e RS485, entrambi gestiti per
mezzo del modulo interno al PIC nominato EUSART (Enhanced Universal Synchronous
Asynchronous Receiver Transmitter).
Normalmente i nomi dei moduli per gestire la RS232 e RS485 sono nominati USART o
UART a seconda delle opzioni disponibili, in particolare la S sta per trasmissione
Sincrone. Molti PIC gestiscono per alcune opzioni aggiuntive per migliorare il loro
utilizzo in applicazioni particolari, per cui stata aggiunta la E.
Sia che la trasmissione sia parallela o seriale si parla di trasmissioni simplex, half-duplex e
full-duplex. Con questi nomi si caratterizza ulteriormente la tipologia della trasmissione
stessa. In particolare si ha una trasmissione simplex quando i dati viaggiano in un unico
senso, ovvero si invia un'informazione senza preoccuparsi di nessuna risposta da parte del
ricevitore. Con questo tipo di trasmissione non dunque possibile sapere se
l'informazione effettivamente giunta a destinazione ed inoltre non c' un vero scambio
di informazioni143.
Per trasmissione half-duplex si intende una trasmissione che pu avvenire in entrambi i
sensi ma non contemporaneamente. Il fatto che la trasmissione non debba avvenire
contemporaneamente pu essere dovuta alla presenza di un solo cavo per la trasmissione
dati o poich le risorse hardware non permettono di ottenere una comunicazione
bidirezionale nello stesso intervallo di tempo. Un esempio tipico rappresentato dal
protocollo RS485.
In ultimo vi la trasmissione full-duplex che permette una comunicazione in entrambi i
sensi e in contemporanea, come per esempio il protocollo RS232. Questo non
necessariamente significa che siano presenti due linee dati, una per la trasmissione e una
per la ricezione, infatti con la cosiddetta multiplazione di frequenza, utilizzata per esempio
in telefonia, possibile trasmettere pi informazioni in contemporanea su un unico cavo.
Due altri sottogruppi di trasmissioni che possibile individuare sia tra quelle parallele
che quelle seriali l'insieme delle trasmissioni sincrone e quelle asincrone. Per
trasmissione sincrona si intende che assieme alle linee dati viene trasmesso anche un
142
143
320
segnale di Clock in modo da sincronizzare le varie fasi di trasmissione e ricezione dati 144,
questo pu o meno essere fornito per mezzo di linea dedicata. Con trasmissioni sincrone,
avendo a disposizione una base dei tempi, possibile raggiungere velocit di trasmissione
pi alte che non nel caso asincrono. Anche se letteralmente asincrono vuol dire senza
sincronismo, non significa che questo non sia presente. Per asincrono si intende solo che
il sincronismo non viene trasmesso per mezzo di un Clock presente su una linea ausiliaria.
Nelle trasmissioni asincrone, sia il trasmettitore che il ricevitore sono preimpostati a
lavorare ad una certa velocit. Quando il ricevitore si accorge che il trasmettitore ha
iniziato a trasmettere, conoscendo la sua velocit, sapr interpretarlo, lo start bit del
protocollo RS232 serve proprio per permettere la sincronizzazione in una trasmissione
asincrona.
Per l'invio di dati sono presenti molti tipi di trasmissioni sia seriali che parallele, come
anche in ogni lingua affinch i due interlocutori si possano capire necessario che parlino
la stessa lingua. Ogni idioma ha una struttura grammaticale che permette una corretta
trasmissione dell'informazione. In termini tecnici si parla di protocollo per intendere
l'insieme di regole e specifiche che il trasmettitore e il ricevitore devono avere affinch si
possano comprendere. Le varie regole si traducono per lo pi in impostazioni del PIC
stesso ma coinvolgono anche alcuni aspetti elettrici, per cui sono spesso necessari dei
transceiver ad hoc, a seconda del protocollo che si vuole utilizzare.
Infatti il modulo EUSART e protocollo RS232 sono due cose distinte, in particolare il
protocollo RS232 impone alcune regole di tipo meccanico (connettore) ed elettrico al fine
da garantire uno standard sul cosiddetto physical layer (livello fisico) ed anche sul formato
dei dati. Il modulo EUSART rispetta il formato dei dati che il protocollo RS232 richiede,
ma per poter rispettare l'intero protocollo necessario aggiungere il connettore
opportuno145 e soprattutto un traslatore di livello (transceiver) per convertire i segnali TTL
del PIC in RS232 e viceversa. Lo stesso modulo EUSART potrebbe essere utilizzato
anche per una trasmissione RS485, cambiando semplicemente il transceiver o anche LIN.
Per cui a parit di impostazioni del modulo EUART, si potrebbe soddisfare sia il
protocollo RS232 che RS485, semplicemente cambiando il transceiver.
144
145
321
Nome
TXSTA
RCSTA
BAUDCON
TXSTA
RCSTA
BAUDCON
SPBRGH
SPBRG
BIT7
BIT6
BIT5
BIT4
BIT3
BIT2
BIT1
BIT0
CSRC
TX9
TXEN
SYNC
SENDB
BRGH
TRMT
TX9D
SPEN
RX9
SREN
CREN
ADDEN
FERR
OERR
RX9D
ADOVF
RCIDL
RXDTP
TXCKP
BRG16
WUE
ABDEN
SPBRGH
SPBRG
Il contenuto dei registri presentati nel capitolo sono forniti per semplificare la
comprensione delle librerie e del programma ma non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
322
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R-1
R/W-0
CSRC
TX9
TXEN
SYNC
SENDB
BRGH
TRMT
TX9D
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
Bit 7
W = Writable bit
1 = Bit is set
S : Settable bit
x = Bit is unknown
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
323
R/W-0
R/W-0
R/W-0
R/W-0
R-0
R-0
R/W-0
SPEN
RX9
SREN
CREN
ADDEN
FERR
OERR
RX9D
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7
Bit 6
Bit 5
S : Settable bit
x = Bit is unknown
Modalit Asincrona
Ignorato
Modalit Sincrona
1 : Ricezione singola abilitata
0 : Ricezione singola disabilitata
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
324
R-1
R/W-0
R/W-0
R/W-0
U-0
R/W-0
R/W-0
ADOVF
RCIDL
RXDTP
TXCKP
BRG16
WUE
ABDEN
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7
Bit 6
Bit 5
S : Settable bit
x = Bit is unknown
Modalit Asincrona
1 : I dati RX sono invertiti
0 : I dati RX non sono invertiti
Modalit Sincrona
1 : Clock CK invertito
0 : Clock CK non invertito
Bit 4
Bit 3
Bit 2
Non implementato
Bit 1
Bit 0
325
Dal momento che la libreria valida indipendentemente dal PIC18 utilizzato, si far uso
di quest'ultima. Vediamo un riassunto delle funzioni principali della libreria usart.h:
Funzioni
Descrizione
char BusyxUSART(void)
void ClosexUSART(void )
Chiude l'USART.
char DataRdyxUSART(void)
Inizializza l'USART.
void WritexUSART(char)
Si noti subito che ogni funzione possiede una x prima della parola USART. La x deve
essere cambiata con il numero del modulo interno al PIC. I PIC18 possiedono infatti, a
seconda del modello, fino a 3 USART interne. Nel caso di modelli con una sola USART,
la x va tolta.
char BusyUSART ( void )
326
Per mezzo di questa funzione viene chiuso il modulo USART precedentemente aperto.
Parametri:
void
Restituisce:
void
Esempio:
// Chiudi il modulo USART
CloseUSART();
Interruzione di Trasmissione:
USART_TX_INT_ON
Interruzione TX ON
USART_TX_INT_OFF Interruzione TX OFF
Interruzione in ricezione:
USART_RX_INT_ON
Interruzione RX ON
USART_RX_INT_OFF Interruzione RX OFF
Modalit USART:
USART_ASYNCH_MODE
USART_SYNCH_MODE
Modalit Asincrona
Modalit Sincrona
Larghezza dati:
USART_EIGHT_BIT
USART_NINE_BIT
8-bit
9-bit
Modalit Slave/Master:
USART_SYNC_SLAVE Modalit Slave sincrona (si applica solo in modalit sincrona)
USART_SYNC_MASTER Modalit Master sincrona (si applica solo in modalit sincrona)
Modalit di ricezione:
USART_SINGLE_RX
Ricezione singola
USART_CONT_RX
Ricezione multipla
Baud rate:
USART_BRGH_HIGH
USART_BRGH_LOW
spbrg: 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 si 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 Datasheet del microcontrollore che si sta utilizzando. In
Tabella 19 sono riportate quelle di maggior interesse ovvero per il caso asincrono alto
baud rate e basso baud rate.
Le prime due Tabelle fanno riferimento all'opzione basso baud rate; ogni colonna delle
Tabelle fa riferimento a diverse frequenze del 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.
Per poter utilizzare il modulo EUSART i seguenti bit devono essere cosi
impostati:
328
Quarzo 20MHz
Formato 8 bit
1 bit di stop
Baud rate 19200bit/s
Interruzioni disattive
64);
Si noti che per evitare di scrivere tutte le impostazioni su di una linea si formattato il
tutto su pi linee. Per il C questo non un problema, poich il compilatore, per capire il
termine della riga cerca il punto e virgola. L'approccio con AND di parametri molto
usato, normalmente lo si implementa per mezzo di una struttura enum all'interno della
quale si definiscono le costanti da utilizzare.
146
Il bit SPEN abilita il modulo seriale ovvero l'EUSART (Serial Port Enable).
329
Fosc = 40 MHz
Fosc = 20 MHz
Fosc = 10 MHz
Fosc = 8 MHz
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
300
1200
1221
1.73
255
1202
0.16
129
1201
-0.16
103
2400
2441
1.73
255
2404
0.16
129
2404
0.16
64
2403
-0.16
51
9600
9615
0.16
64
9766
1.73
31
9766
1.73
15
9615
-0.16
12
19200
19531
1.73
31
19531
1.73
15
19531
1.73
57600
56818
-1.36
10
62500
8.51
52083
-9.58
115200
125000
8.51
104167
-9.58
78125
-32.18
Fosc = 4 MHz
Fosc = 2 MHz
Actual
Rate
Error
%
SPBRG
300
300
0.16
1200
1202
0.16
2400
2404
9600
8929
19200
20833
57600
115200
Fosc = 1 MHz
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
207
300
-0.16
51
1201
-0.16
103
300
-0.16
51
25
1201
-0.16
12
0.16
25
2403
-6.99
-0.16
12
8.51
62500
8.51
62500
-45.75
Fosc = 40 MHz
Fosc = 20 MHz
Fosc = 10 MHz
Fosc = 8 MHz
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
300
1200
2400
2441
1.73
255
2403
-0.16
207
9600
9766
1.73
255
9615
0.16
129
9615
0.16
64
9615
-0.16
51
19200
19231
0.16
129
19231
0.16
64
19531
1.73
31
19230
-0.16
25
57600
58140
0.94
42
56818
-1.36
21
56818
-1.36
10
55555
3.55
115200
113636
-1.36
21
113636
-1.36
10
125000
8.51
Fosc = 4 MHz
Fosc = 2 MHz
Fosc = 1 MHz
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
Actual
Rate
Error
%
SPBRG
300
300
-0.16
207
1200
1202
0.16
207
1021
-0.16
103
1201
-0.16
51
2400
2404
0.16
103
2403
-0.16
51
2403
-0.16
25
9600
9615
0.16
25
9615
-0.16
12
19200
19231
0.16
12
57600
62500
8.51
115200
125000
8.51
330
Per mezzo di questa funzione possibile leggere un byte ricevuto dalla porta seriale; il
valore letto, ovvero restituito dalla funzione, di tipo char.
Parametri:
void
Restituisce:
Dato letto
Esempio:
unsigned char data = 0;
// Aspetta per la ricezione di dati (tecnica del polling)
while (!DataRdyUSART());
// Leggo il dato ricevuto
data = ReadUSART ();
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 bit.
Parametri:
data: Dato da inviare
Restituisce:
void.
Esempio:
unsigned char data = 0x0A;
// Invio il dato
WriteUSART(data);
Per ulteriori informazioni sulle altre funzioni disponibili nella libreria usart.h si rimanda
alla documentazione ufficiale della Microchip. Tra le funzioni non trattate in questo
paragrafo ma che possono ritornare utili, si ricorda la famiglia di funzioni put e get che
permettono di scrivere e leggere, singoli caratteri o stringhe, sia da variabili che da
stringhe costanti.
331
Formato: 8 bit
Baud Rate: 19200 bit/s
Stop bit: 1 bit stop
Bit di parit: 0 bit
Controllo Flusso: Nessuno
Tale configurazione deve essere utilizzata anche nel caso in cui si faccia uso di altri
terminali per il controllo della porta seriale.
#include <xc.h>
#include "PIC18F4550_config.h"
#include <usart.h>
#define LCD_DEFAULT
#include "LCD_44780.h"
#include "LCD_44780.c"
#include "delay.h"
#include "delay.c"
int main (void){
// Variabile per salvare il dato restituito
unsigned char data = 0;
// Inizializzo PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Inizializzo PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Inizializzo PORTC
LATC = 0x00;
TRISC = 0b11111101;
// Inizializzo PORTD tutte uscite
147
332
LATD = 0x00;
TRISD = 0x00;
// Inizializzo PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
LCD_initialize (20);
LCD_backlight (LCD_TURN_ON_LED);
//
//
//
//
//
Configura l'USART
8 bit
19200 bit/s
1 bit stop
0 bit parit
333
// Chiudo l'USART
CloseUSART();
LCD_clear ();
LCD_write_message ("RS232 : Closed");
// Ciclo infinito
while (1) {
}
}
Arrivati a questo punto del testo inutile ripetere sempre le stesse cose, pi
interessante andare al sodo!
Come prima cosa necessario collegare la scheda di sviluppo al PC per mezzo della porta
seriale ed avviare il programma RS232 Terminal. Una volta programmata la scheda di
sviluppo, se tutto impostato propriamente, viene mostrato il messaggio in Figura 121.
334
ovvero:
// Attendo di ricevere un dato dal PC
while(!DataRdyUSART( ));
336
}
}
//*************************************************
//
Programma Principale
//*************************************************
int main (void){
// Inizializzo PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Inizializzo PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Inizializzo PORTC
LATC = 0x00;
TRISC = 0b11111101;
// Inizializzo PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Inizializzo PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
LCD_initialize (20);
LCD_backlight (LCD_TURN_ON_LED);
//
//
//
//
//
//
Configura l'USART
8 bit
19200 bit/s
1 bit stop
0 bit parit
Interruzione RX abilitata
337
64 );
// Invio la stringa al terminale
putrsUSART ("...start writing: ");
// Invio la stringa all'LCD
LCD_write_message ("...start writing");
// Resetto gli Interrupt vecchi
PIR1bits.RCIF = 0;
// Abilito modalit compatibile (di default vale gi 0)
RCONbits.IPEN = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
// Abilito interrupt per periferiche
INTCONbits.PEIE = 1 ;
// Ciclo infinito
while (1) {
}
}
148
Variabili che hanno il compito di memorizzare un determinato stato, quale per esempio acceso/spento vengono spesso
chiamate Flag.
338
Formato: 8 bit
Baud Rate: 19200 bit/s
Stop bit: 1 bit stop
Bit di parit: 0 bit
Controllo Flusso: Nessuno
339
//*************************************************
__interrupt (high_priority) void ISR_alta (void) {
// Variabile che conterr i dati ricevuti
unsigned char data;
// Flag per controllare la ricezione del primo carattere
static unsigned char header_file_Flag = HEADER_NOT_RECEIVED;
// Controllo che l'interrupt sia stato generato dall'USART
if (PIR1bits.RCIF == ACTIVATED ) {
// Resetto gli Interrupt EUSART
PIR1bits.RCIF = DEACTIVATED;
// Leggo il dato in ingresso
data = ReadUSART();
// Controllo la ricezione del primo carattere
if (header_file_Flag == HEADER_NOT_RECEIVED) {
if (data == HEADER_FILE) {
// Memorizzo la ricezione dell'header byte
header_file_Flag = HEADER_RECEIVED;
}
} else {
LATD = data;
header_file_Flag = HEADER_NOT_RECEIVED;
}
}
//*************************************************
//
Programma Principale
//*************************************************
int main (void){
// Inizializzo PORTA tutti ingressi
LATA = 0x00;
TRISA = 0xFF;
// Inizializzo PORTB tutti ingressi
LATB = 0x00;
TRISB = 0xFF;
// Inizializzo PORTC
LATC = 0x00;
TRISC = 0xFF;
// Inizializzo PORTD tutte uscite
LATD = 0x00;
TRISD = 0x00;
// Inizializzo PORTE tutti ingressi
LATE = 0x00;
TRISE = 0xFF;
// Configura l'USART
// 8 bit
// 19200 bit/s
340
// 1 bit stop
// 0 bit parit
// Interruzione RX abilitata
OpenUSART( USART_TX_INT_OFF &
USART_RX_INT_ON &
USART_ASYNCH_MODE &
USART_EIGHT_BIT &
USART_CONT_RX &
USART_BRGH_HIGH,
64 );
// Resetto gli Interrupt vecchi
PIR1bits.RCIF = DEACTIVATED;
// Abilito modalit compatibile (di default vale gi 0)
RCONbits.IPEN = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
// Abilito interrupt per periferiche
INTCONbits.PEIE = 1 ;
// Ciclo infinito
while (1) {
}
}
Macros
possibile impostare dei pulsanti per l'accensione dei vari LED, come riportato in
Figura 123.
341
In particolare possibile scrivere il codice di Header nella casella dedicata nel campo
Prepend Data, in maniera che venga inviato alla pressione di ogni pulsante. Ad ogni
pulsante possibile associare un nome ed impostare il valore da inviare. Nel caso
specifico si associato un LED a pulsante per cui ad ogni codice inviato per il campo yy
viene attivato un solo bit.
Riassunto
In questo Capitolo abbiamo visto il funzionamento del modulo EUSART utilizzandolo
in applicazioni basate sul protocollo RS232. In particolare si visto come controllare una
comunicazione seriale sia facendo uso della tecnica del polling che delle interruzioni. Si
introdotto anche l'applicazione RS232 Terminal, al fine di poter gestire la porta seriale dal
lato PC e poter controllare il sistema Hardware senza dover realizzare GUI dedicate.
fornito durante la trasmissione dei dati per cui il ricevitore pu adattarsi a varie frequenze.
Trasmettere un clock non necessariamente implica una linea dedicata allo stesso, infatti i
dati possono inglobale tale informazione. Nonostante il clock sia fornito dal sistema che
trasmette i dati, il ricevitore in generale pu comunque avere un clock locale che viene
modificato in funzione del clock ricevuto.
Trasmissione Full Duplex: Una trasmissione di definisce Full Duplex quando il sistema
supporta la trasmissione e ricezione dei dati in contemporanea. In generale questo
necessita di una linea dedicata alla trasmissione ed una per la ricezione ma ci sono
comunque tecniche per mezzo delle quali potrebbe essere sufficiente una sola linea dati.
Trasmissione Half Duplex: Una trasmissione di definisce Half Duplex quando il sistema
supporta la trasmissione e ricezione dei dati in tempi separati.
Trasmissione Simplex: Una trasmissione di definisce Simplex quando il sistema
trasmette informazioni ad uno o pi ricevitori, senza avere la possibilit di sapere se la
ricezione sia avvenuta o meno.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
343
Capitolo XII
XII
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 124 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo.
344
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 125: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 126: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
346
Impostazioni software
Gli esempi mostrati non richiedono alcuna impostazione software particolare oltre alla
semplice creazione di un progetto.
Alcuni esempi fanno uso delle librerie LaurTec, per cui necessario impostare i percorsi
di libreria riportati di seguito:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
La versione della libreria inclusa nel percorso potrebbe variare in base a quella utilizzata.
Il protocollo I2C
Il protocollo I2C uno standard brevettato dalla Philips (ora NXP). Venne ideato nel
1980 (Il brevetto scaduto) per superare le difficolt inerenti all'utilizzo di Bus paralleli
per la comunicazione tra un'unit di controllo e le varie periferiche.
In quegli anni, tra le soluzioni maggiormente adottate per permettere la comunicazione
tra un microcontrollore e delle periferiche esterne vi era la comunicazione parallela. Per
tale ragione il bus149 su cui doveva viaggiare l'informazione era costituito da molti fili.
Fin quando bisogna collegare una sola periferica al microcontrollore i problemi legati alla
presenza di molte linee possono essere tenuti sotto controllo. Qualora le periferiche
dovessero essere pi di una, far giungere il Bus ad ogni periferica pu diventare un
problema.
Un semplice Bus ad otto linee comporta comunque la presenza ad ogni integrato di
almeno altrettanti pin necessari per la comunicazione. Questo significa che le dimensioni
dell'integrato vengono ad essere dipendenti dalla dimensione del bus stesso. Ci comporta
che lo stesso PCB (Print Circuit Board) sul quale deve essere montato l'integrato sar pi
grande e quindi pi costoso.
Questi problemi vengono interamente superati dal Bus I 2C, che permette una
comunicazione tra periferiche con due sole linee 150; questa la ragione per cui non raro
avere integrati con Bus I2C a soli otto pin.
Il Bus I2C uno standard seriale che a differenza del protocollo RS232, permette di
collegare sullo stesso Bus un numero elevato di periferiche, ognuna individuata da un proprio
indirizzo, in gergo tale comunicazione si definisce multipoint.
Un notevole vantaggio dei dispositivi che fanno uso del Bus I2C quello della loro
semplicit d'uso. Infatti tutte le regole del protocollo che bisogna rispettare per una
corretta comunicazione vengono gestite a livello hardware, dunque il progettista non si
deve preoccupare di nulla.
Da quando nato, tale protocollo stato aggiornato al fine di adeguarlo alle diverse
149
150
Per Bus si intende semplicemente un insieme di linee sui cui viaggia un segnale elettrico.
Alle due linee bisogna comunque aggiungere la linea di massa comune.
347
Le periferiche che fanno uso del Bus I 2C per comunicare con un'unit di controllo sono
tipicamente: memorie EEPROM, RAM, Real Time Clock Calendar (RTC), LCD,
potenziometri digitali, convertitori A/D, sintonizzatori radio, controller per toni DTMF,
periferiche generiche per estendere il numero degli ingressi o delle uscite (PCF8574,
MCP2300x), sensori per la temperatura, controllori audio e molto altro.
Un altro vantaggio del Bus I2C quello di poter aggiungere o togliere delle periferiche
dal Bus senza influenzare il resto del circuito. Questo si traduce in una facile scalabilit
verso l'alto del sistema, ovvero si pu migliorare un sistema aggiungendo nuove
caratteristiche senza dover toccare l'hardware151.
(Serial Clock) pi la linea di massa. Ambedue le linee sono bidirezionali 152. La prima
utilizzata per il transito dei dati che sono in formato ad 8 bit, mentre la seconda
utilizzata per trasmettere il segnale di Clock necessario per la sincronizzazione della
trasmissione. Il Bus I2C permette la connessione di pi periferiche sullo stesso Bus ma
permette la comunicazione tra due soli dispositivi alla volta.
Chi trasmette le informazioni chiamato trasmettitore mentre chi le riceve chiamato
ricevitore. L'essere il trasmettitore o il ricevitore non una posizione fissa, ovvero, un
trasmettitore pu anche divenire ricevitore in una differente fase della trasmissione dati.
In ogni comunicazione invece fissa la posizione del cosiddetto Master (Padrone) e del
cosiddetto Slave (Schiavo). Il Master il dispositivo che inizia la comunicazione ed lui a
terminarla, lo Slave pu solo ricevere o trasmettere informazioni su richiesta del Master.
Non tutti i dispositivi possono essere dei Master del bus I2C. Per esempio una memoria
per il mantenimento dei dati non sar un Master del Bus, mentre ragionevole supporre
che un microcontrollore lo possa essere153.
Su uno stesso bus inoltre possibile la presenza di pi Master, ma solo uno alla volta
151
152
153
Naturalmente il software dell'unit di controllo deve essere cambiato affinch possa riconoscere la nuova periferica. Quanto
detto non vale se il software predisposto per accettare la nuova periferica, la quale pu esser dunque inserita senza alcuna
modifica n hardware n software.
Dal momento che la linea dati bidirezionale si ha che il sistema half-duplex.
Molti microcontrollori integrano al loro interno l'hardware necessario per la gestione del bus I2C sia in modalit Master che
Slave.
348
ricoprir questo ruolo. Se per esempio due microcontrollori iniziano una comunicazione,
anche se potenzialmente potrebbero essere ambedue dei Master, solo uno lo sar, in
particolare il Master sar quello che ha iniziato la comunicazione, mentre l'altro sar uno
Slave.
Ogni periferica inserita nel Bus I2C possiede un indirizzo che sul Bus la individua in
modo univoco. Questo indirizzo pu essere fissato dal produttore in sede di
fabbricazione o fissato dal progettista, spesso solo parzialmente. L'indirizzo costituito da
7 bit nelle versioni standard o da 10 bit nelle versioni estese.
Nel caso di indirizzamento a 7 bit si ha potenzialmente la possibilit di indirizzare 128
periferiche mentre nel caso di 10 bit si ha la possibilit di indirizzare fino a 1024
periferiche. Il numero di periferiche ora citate non sono tutte raggiungibili dal momento
che alcuni indirizzi, come si vedr, sono riservati a funzioni speciali.
Nel caso in cui l'indirizzo che l'integrato ha all'interno del bus I 2C venga fissato
dall'industria, comporta che su un Bus non possono essere presenti due integrati dello
stesso tipo.
Questa soluzione viene generalmente scelta per quelle periferiche come il Real Time
Clock Calendar, ovvero per gli orologi. E' ragionevole infatti presumere che in un circuito,
e in particolare sullo stesso bus, sia presente un solo orologio che mantenga ora e data.
Tale scelta ha il vantaggio di permettere di avere un integrato con meno pin. Se proprio
si dovesse avere la necessit di inserire due orologi 154, o comunque due periferiche con
stesso indirizzo necessario dividere il bus in due, questo pu per esempio essere
ottenuto con l'integrato PCA9544 della NXP.
Qualora si utilizzino delle memorie esterne, un eventuale limite come per l'orologio
potrebbe essere notevole. Per tale ragione in questo caso sia ha la possibilit di impostare
l'indirizzo dell'integrato intervenendo su alcuni bit. Ad esempio la memoria 24LC256155
della Microchip, possiede tre pin in uscita nominati A0, A1, A2, per mezzo dei quali
possibile impostare i tre bit meno significativi dell'indirizzo che caratterizza la memoria.
Questo significa che possibile mettere fino ad otto memorie dello stesso tipo sullo
stesso Bus. Un limite sul numero massimo di periferiche che possibile connettere sul
Bus dunque imposto dall'indirizzo, ovvero il numero dei pin assegnati all'indirizzo.
Un altro vincolo a cui non sempre si pensa, imposto dalla capacit totale della linea che
deve essere limitata a non pi di 400 pF. Il valore di questa capacit viene a dipendere dal
numero di dispositivi e dalla lunghezza del bus stesso. Potenzialmente, dal momento che
una linea tipicamente introduce una capacit parassita di circa 80 pF/m, potr essere
lunga fino a 5m, ma queste distanze non sono in realt quelle per cui pensato il
protocollo. Bisogna inoltre tenere a mente che ogni dispositivo aggiunto sul Bus
introduce ulteriore capacit a causa del buffer di ingresso e uscita. I pin del package
dell'integrato possono inoltre aggiungere altri 1-2pF, per cui moltiplicando solo questo
valore per il numero potenziale di periferiche, si capisce che il numero massimo
piuttosto teorico ed fornito per permettere di segmentare le periferiche in maniera
opportuna e non per avere realmente 128 o 1024 integrati su uno stesso Bus.
Il limite imposto dalla capacit viene a dipendere anche dalla velocit con cui devono
avvenire le transizioni dei bit dal livello basso al livello alto (rise time).
Qualora si debbano raggiungere distanze elevate, o il numero dei dispositivi supera la
capacit totale permessa sul Bus, possibile spezzare il Bus, in due o pi parti, per mezzo
154
155
Il PCF8583 possiede un pin, nominato A0, per impostare il bit meno significativo dell'indirizzo. Dunque possibile inserire
due Real Time Clock Calendar sullo stesso bus.
La Microchip produce una vasta gamma di memorie che supportano il protocollo I2C.
349
di ripetitori (Repeater) quali il PCA9515, PCA9516 e il PCA9518 della NXP. Per mezzo di
questi integrati possibile avere 400 pF per ogni segmento del Bus.
Da quanto appena esposto si comprende che un Bus che possa funzionare a 100Kbit/s
non necessariamente deve lavorare a tale frequenza. il Master, opportunamente
impostato, a scandire il sincronismo e dunque la velocit di trasmissione.
La necessit di dividere il Bus in due la si pu avere anche nel caso in cui siano presenti
dispositivi con diversi standard di velocit o livelli di tensione.
La divisione del Bus necessaria solo nel caso in cui si voglia far comunicare le
periferiche a pi alta velocit sfruttandole al massimo della velocit. Nel caso ci si adegui
alla periferica pi lenta non necessario dividere il Bus. Gli integrati che sfruttano il
protocollo I2C possono lavorare a diverse tensioni, dai tipici 5V a 3.3V ed anche 2.5V 156
permettendo consumi cosi ridotti da poter essere montati anche su dispositivi portatili
alimentati a batteria.
Ultima nota, prima di vedere come avviene una comunicazione sul Bus I2C, riguarda le
linee SDA e SCL. Queste linee devono essere implementate per mezzo di uscite open
drain/open collector o emularle. Questa nota particolarmente importante qualora si voglia
implementare il protocollo I2C interamente via Software, ovvero senza sfruttare moduli
dedicati. Tale caratteristica rende necessaria una resistenza di pull-up per ogni linea, ovvero
di una resistenza collegata tra la linea e Vcc, come riportato in Figura 127. Questo
significa che quando le linee SDA e SCL non sono utilizzate, sono a livello alto.
Valori tipici per le resistenze di pull-up sono compresi tra 2K e 10K. Il primo valore
utilizzato per una frequenza del Bus fino a 400Kbit/s mentre il secondo per velocit fino
di 100Kbit/s.
Il valore corretto viene comunque a dipendere, oltre che dalla frequenza di
trasmissione, anche dalla capacit totale di linea.
Nel caso di trasmissioni a 100Kbit/s la resistenza pull-up sufficiente fino a capacit
totali di linea pari a 400 pF. Nel caso di velocit a 400 Kbit/s la resistenza di pull-up
sufficiente fino a capacit totali di linea non superiori a 200 pF, mentre per velocit a 3.4
Mbits/s la resistenza idonea fino a capacit non superiori a 100 pF.
Cosa significa questo limite? Il problema della capacit di linea legato al tempo di
salita che si riesce ad ottenere per una variazione dal livello logico basso a livello logico
alto. Nel caso in cui si faccia uso di una resistenza, i fronti di salita hanno un andamento
esponenziale, tipico della carica e scarica di un condensatore.
156
350
Figura 127: Collegamento dei resistori di pull-up e dettaglio dell'indirizzamento di dispositivi dello stesso tipo.
All'aumentare della capacit, il tempo di salita tende ad aumentare, a tal punto da non
rispettare pi le specifiche elettriche I 2C. Per porre rimedio a questo limite bisogna porre,
in sostituzione della resistenza di pull-up, un generatore di corrente costante.
Tale generatore, avendo una corrente indipendente dal livello di carica della capacit di
linea, permette di ottenere dei fronti di salita la cui pendenza rimane costante 157. La
differenza dei fronti di salita ottenuti nel caso di resistenza di pull-up e di generatore
costante sono riportati in Figura 128.
157
Figura 128: Differenza tra i fronti d'onda nel caso si usi una resistenza di pull-up e di un generatore di corrente.
Questo pu essere un modo per ottenere un segnale a forma triangolare. Normalmente si preferisce per generare un
segnale ad onda quadra e poi provvedere all'integrazione di quest'ultimo.
351
3.
4.
5.
6.
7.
8.
Il Master controlla se le linee SDA e SCL non siano attive, ovvero siano poste
ambedue a livello alto158.
Se il Bus libero, il master invia il messaggio che fa capire alle altre periferiche che
il Bus ora occupato. Le altre periferiche si metteranno allora in ascolto per
comprendere con chi il Master ha intenzione di comunicare.
Il Master provvede all'invio del segnale di sincronizzazione sulla linea SCL, che
rappresentato da un onda quadra, non necessariamente periodica.
Il Master invia l'indirizzo della periferica con la quale vuole parlare.
Il Master segnala poi se la comunicazione che vuole intraprendere verso la
periferica di lettura o scrittura.
Il Master attende la risposta da parte della periferica che nella chiamata ha
riconosciuto il suo indirizzo. Se nessuna periferica risponde, il Master libera il Bus.
Dopo l'avvenuto riconoscimento della periferica, il Master inizia lo scambio dei
dati. Lo scambio avviene inviando pacchetti di 8 bit. Ad ogni pacchetto si deve
attendere il segnale che avvisa dell'avvenuta ricezione.
Quando la trasmissione terminata il Master libera il Bus inviando un segnale di
stop.
158
Fase 1-2
L'hardware del microcontrollore, dedicato alla gestione dell'interfaccia I 2C,
controlla le linee SDA e SCL per un tempo superiore al massimo periodo di
trasmissione consentito dall'hardware. Se il bus risulta libero, il Master invia la
sequenza di Start, che consiste nel portare la linea SDA a livello basso quando SCL
a livello alto. L'andamento delle due linee SDA e SCL, per segnalare lo Start,
riportata in Figura 129.
Il livello alto, in termini di tensione, viene a dipendere dagli integrati che si sta utilizzando. Se Vcc=3.3V, i resistori di pull-up
porteranno le linee SDA e SCL al livello pari a 3.3. Diversamente se l'alimentazione ottenuta con 5V il livello alto di 5V.
352
Fase 3
Dopo la transizione della linea SDA da alto a basso, il Master invia il segnale di
sincronizzazione per le altre periferiche. A differenza della sequenza di Start e di
Stop, la linea SDA assume un valore valido solo se la linea SCL a livello basso.
Questo vuol dire che non sono ammesse transizioni di livello della linea SDA
durante il livello alto della linea SCL, se non da parte del Master per inviare un
nuovo Start o uno Stop.
Fase 4-5
Il formato dell'indirizzo pu essere sia a 7 bit che a 10 bit, per semplicit si
considera prima il formato a 7 bit.
I 7 bit dell'indirizzo vengono inviati dal bit pi significativo al bit meno
significativo. In coda a questo indirizzo viene aggiunto un bit per segnalare se il
Master vuole intraprendere, con la periferica individuata da tale indirizzo, una
comunicazione di scrittura o di lettura. In particolare se tale bit 0 vuol dire che il
Master vuole scrivere sulla periferica, se il bit 1 vuol dire che il Master vuole
leggere dalla periferica. Il formato del byte (8 bit) riportato in Figura 130 a). Nel
caso in cui l'indirizzo della periferica individuato da 10 bit, si capisce che
necessario inviare un secondo byte. Per mantenere la compatibilit con il formato a
7 bit per necessario qualcosa che permetta alle periferiche in ricezione di capire
che il primo byte non rappresenta in realt il formato d'indirizzo a 7 bit. Questo lo si
ottenuto proibendo alcuni indirizzi nel formato a 7 bit.
353
Se gli indirizzi proibiti159 dovessero essere utilizzati vorrebbe dire che il primo
byte serve per indirizzare una periferica con indirizzo a 10 bit.
Gli indirizzi proibiti che individuano il formato d'indirizzamento a 10 bit sono
rappresentati dalle combinazioni 1111xxx, dove x pu valere sia 0 che 1.
In particolare la prima x vicino all'1 viene posta a 0 cosi con il primo byte si inviano
solo i due bit pi significativi dell'indirizzo a 10 bit.
L'ottavo bit rappresenta come nel caso ad indirizzamento a 7 bit la condizione di
lettura o scrittura. Gli altri 8 bit, dell'indirizzo a 10 bit, sono inviati con il secondo
byte. Il formato dell'indirizzamento a 10 bit riportato in Figura 130 b).
159
160
161
Fase 6
L'invio dell'indirizzo a 7 bit e della modalit del colloquio (lettura/scrittura),
avviene grazie ad otto transizioni, da livello alto a basso, della linea SCL. Al nono
impulso della linea SCL il Master si aspetta una risposta di un bit da parte della
periferica che ha chiamato160. La risposta della periferica chiamata consiste nel
mantenere a livello basso la linea SDA, per la durata di un ciclo SCL. In gergo si dice
che il Master attende l'Acknowledge da parte della periferica chiamata. Una sola
periferica risponder alla chiamata del Master.
Nel caso di indirizzamento a 10 bit si pu anche avere che pi periferiche
rispondano al primo byte, ma solo una risponder anche al secondo.
Qualora la periferica non sia presente il Master libera il bus permettendo ad
eventuali altri Master di prenderne il controllo.
Fase 7
Dopo l'avvenuto riconoscimento, avviene lo scambio dei dati verso la periferica,
nel caso di scrittura, o dalla periferica al Master, in caso di lettura. In una
comunicazione si possono avere sia fasi di scrittura che di lettura. Si prenda ad
esempio una fase di lettura da memoria. In primo luogo bisogner scrivere dei byte
per impostare l'indirizzo che si vuole leggere, poi si potr effettivamente leggere il
byte161. Ad ogni invio di un byte necessario l'Acknowledge del byte inviato o
ricevuto. In particolare se il Master invia un byte allo Slave si aspetta, dopo l'ottavo
bit, un bit basso sulla linea SDA. Se lo Slave sta inviando un byte al Master si aspetta
In realt sono presenti altri gruppi d'indirizzi proibiti che individuano altre funzioni speciali o sono stati lasciati per future
estensioni dello standard I2C.
Si capisce che colui che scrive il software deve conoscere a priori gli indirizzi delle periferiche montate o che possono essere
montate.
Le memorie permettono istruzioni speciali per saltare la fase d'indirizzamento qualora si debbano leggere pi indirizzi
consecutivi. Comunque l'indirizzo d'inizio dovr essere inviato.
354
che quest'ultimo invii un bit alto dopo aver ricevuto il byte. La mancanza
dell'Acknowledge determina un errore di comunicazione.
Fase 8
Quando la comunicazione terminata, il Master libera il Bus, inviando la
sequenza di Stop.
Questa consiste nella transizione dal livello basso ad alto della linea SDA, quando la
linea SDL alta. In Figura 131 riportata la sequenza di Stop generata dal Master.
Se il Master deve effettuare un'altra comunicazione con un altro Slave, piuttosto che
liberare il bus e rischiare di perdere il diritto del controllo, pu inviare una nuova sequenza
di Start. Il vecchio Slave comprender che la comunicazione con lui terminata.
Le potenzialit dell'interfaccia I2C consistono nel fatto di poter disporre di una larga
variet di dispositivi che possiedono al loro interno l'hardware necessario per la gestione
automatica del protocollo.
Master
Multi-Master Mode
Slave
La modalit Multi-Master rappresenta il caso in cui nello stesso Bus siano presenti pi
Master, per cui necessario controllare che non avvenga nessun conflitto durante la
scrittura dei dati sul Bus.
355
I vari registri associati al modulo MSSP assumono un significato differente in base alla
configurazione del modulo stesso, ovvero se lo stesso impostato come modulo I2C o
SPI, ciononostante il nome dei registri rimane lo stesso. Il Diagramma a blocchi del
modulo MSSP in configurazione I2C riportato in Figura 132.
Dal Diagramma a blocchi possibile vedere il registro per la trasmissione seriale dei
dati, ovvero SSPSR. Tale registro non direttamente scrivibile, infatti il suo accesso
controllato per mezzo di un sistema a doppio Buffer, rappresentato dal registro SSPBUF.
In particolare per scrivere nel registro SSPSR, quello che bisogna fare scrivere nel
registro SSPBUF. Dal lato di ricezione, SSPBUF viene automaticamente scritto con il
contenuto del registro SSPSR, una volta che viene riempito da un nuovo byte, sia esso il
valore di un dato o un registro, in particolare SSPSR rappresenta uno shift register, ovvero
registro a scorrimento.
Nel Diagramma a blocchi possibile vedere anche il registro per il riconoscimento
dell'indirizzo, necessario nel caso in cui il microcontrollore dovesse essere impostato
come Slave. In particolare il contenuto del registro SSPSR viene confrontato con il
registro SSPADD, che contiene il valore dell'indirizzo della periferica. Il valore di tale
registro pu essere singolo o rappresentare un gruppo, configurabile per mezzo del
registro Address Mask. I bit da mascherare, ovvero ignorare sono contenuti nel registro
SSPCON2, ovvero nei bit 2-5 del relativo registro. Il valore dell'indirizzo rappresentato,
nel caso dello standard a 7 bit dal primo byte trasmesso. A livello software per
riconoscere se il valore del byte ricevuto sia un Byte indirizzo o dati, possibile usare il bit
A/D' contenuto nel registro SSPSTAT. In maniera analoga, per distinguere se l'accesso al
modulo I2C sia per una lettura o scrittura di dati, possibile usare il bit R/W', sempre del
registro SSPSTAT.
356
Nel caso in cui il modulo MSSP sia configurato come Master, il diagramma a blocchi che
meglio lo rappresenta riportato in Figura 133.
Figura 133: Diagramma a blocchi del modulo MSSP in configurazione I2C Master.
In questo nuovo dettaglio del modulo, possibile notare la presenza delle funzioni per
il controllo della sequenza di Start, di Stop, collisione e Acknowledge, che avviene
controllando le linee SCL e SDA. In particolare la linea dati e clock possiede un buffer
con Trigger di Schmitt, il quale permette di limitare eventuali disturbi derivanti dal rumore
presente sulle rispettive linee di comunicazione.
SSPCON1
SSPCON2
SSPSTAT
SSPBUF
SSPSR (non accessibile direttamente)
SSPADD
357
Nome
BIT7
BIT6
BIT5
BIT4
BIT3
BIT2
BIT1
BIT0
GIE/GIEH
PEIE/GIEL
TMR0IE
INT0IE
RBIE
TMR0IF
INT0IF
RBIF
PIR1
SPPIF
ADIF
RCIF
TXIF
SSPIF
CCP1IF
TMR2IF
TMR1IF
PIE1
SSPPIE
ADIE
RCIF
TXIF
SSPIE
CCP1IE
TMER2IE
TMR1IE
IPR1
SPPIP
ADIP
RCIP
TXIP
SSPIP
CCP1IP
TMR2IP
TMR1IP
PIR2
OSCFIF
CMIF
USBIF
EEIF
BCLIF
HLVDIF
TMR3IF
CCP2IF
PIE2
OSCFIE
CMIE
USBIE
EEIE
BCLIE
HLVDIE
TMR3IE
CCP2IE
IPR2
OSCFIP
CMIP
USBIP
EEIP
BCLIP
HLVDIP
TMR3IP
CCP2IP
TRISC
TRISC7
TRISC6
TRISC2
TRISC1
TRISC0
TRISD7
TRISD6
TRISD5
TRISD4
TRISD3
TRISD2
TRISD1
TRISD0
INTCON
TRISD
SSPBUF
SSPADD
SSPCON1
WCOL
SSPOV
SSPEN
CKP
SSPM3
SSPM2
SSPM1
SSPM0
SSPCON2
GCEN
ACKSTAT
ACKDT
ACKEN
RCEN
PEN
RSEN
SEN
SSPSTAT
SMP
CKE
D/A'
R/W'
UA
BF
Il contenuto dei registri presentati nel Capitolo sono forniti per semplificare la
comprensione delle librerie e dei programmi, ma non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
All'abilitazione del modulo, le linee SDA e SCL sono automaticamente impostate come
Open Drain.
Oltre alla configurazione dei singoli pin necessario avere i resistori di pull-up al fine di
permettere il corretto funzionamento del sistema. Sul Bus devono essere presenti solo due
resistori di pull-up, uno per linea. Nel caso in cui due sistemi aventi i resistori di pull-up
siano collegati tra loro, in generale necessario disattivare/eliminare i resistori da un
sistema. Nel caso in cui il valore dei resistori dovesse essere elevato (circa 10K) potrebbe
non essere necessario, ma sempre bene verificare la qualit del segnare ed avere due soli
resistori nel sistema finale.
La configurazione dei pin riportata valida solo per il PIC18F4550 e per la
maggior parte dei PIC18 con modulo USB integrato. I PIC18 senza modulo
USB possiedono le linee SDA ed SCL rispettivamente sui pin RC4 ed RC3, RD2
ed RD3. Ci sono Modelli per cui tramite Configuration bit possibile configurare
anche altri pin alternativi.
358
R/W-0
R-0
R-0
R-0
R-0
R-0
R-0
SMP
CKE
D/A
R/W
UA
BF
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
Bit 7
W = Writable bit
1 = Bit is set
S : Settable bit
x = Bit is unknown
Bit 6
Bit 5
Bit 4
P : Stop bit
1: Indica che stata rilevata la sequenza del bit di Stop
0: Bit di Stop non rilevato
Bit 3
S : Start bit
1: Indica che stata rilevata la sequenza del bit di Start
0: Bit di Start non rilevato
Bit 2
Bit 1
Bit 0
359
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
WCOL
SSPOV
SSPEN
CKP
SSPM3
SSPM2
SSPM1
SSPM0
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
Bit 7
W = Writable bit
1 = Bit is set
S : Settable bit
x = Bit is unknown
Bit 6
Bit 5
Bit 4
Bit 3-0
360
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
GCEN
ACKSTAT
ACKDT
ACKEN
RCEN
PEN
RSEN
SEN
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
Bit 7
W = Writable bit
1 = Bit is set
S : Settable bit
x = Bit is unknown
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
361
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
GCEN
ACKSTAT
ACKDT
ACKEN
RCEN
PEN
RSEN
SEN
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
Bit 7
W = Writable bit
1 = Bit is set
S : Settable bit
x = Bit is unknown
Bit 6
Bit 5-2
Bit 1
Bit 0
362
Descrizione
Controlla che il bus sia in stato di Idle (non ci sono attivit in corso).
StartI2Cx ( )
StopI2Cx ( )
NotAckI2Cx ( )
Questa funzione deve essere eseguita prima di utilizzare qualunque funzione della
libreria I2C, infatti permette di attivare il modulo per un suo corretto utilizzo. Tale
funzione potrebbe non essere richiamata solo nel caso in cui la libreria del
dispositivo I2C utilizzata, fornisca un'altra funzione per attivare ed impostare il
modulo I2C.
Parametri:
sync_mode:
SLAVE_7
Restituisce:
void.
Oltre ad attivare il modulo necessario impostare la frequenza di lavoro del
modulo stesso. Questo possibile farlo per mezzo del registro SSPADD, il cui valore
determina appunto la frequenza operativa che verr utilizzata qualora il modulo sia
impostato come Master. L'equazione da usare per il calcolo del valore da inserire in
SSPADD :
SSPADD=
F OSC
1
4F BUS
Dove FOSC rappresenta la frequenza del quarzo mentre FBUS rappresenta il valore
della frequenza che si vuole avere per la trasmissione dei dati sul bus I2C.
void CloseI2Cx (void)
Questa funzione deve essere eseguita per richiudere il modulo I2C. Tale funzione
potrebbe non essere mai richiamata all'interno del programma, qualora non si avesse
mai l'esigenza di chiudere il modulo.
Parametri:
void.
Restituisce:
void.
void IdleI2Cx (void)
Questa funzione permette di controllare che non sia in corso alcuna attivit sul
bus ed in particolare siano terminate le operazioni interne al modulo. Questa
funzione deve essere richiamata per evitare che si verifichino sovrapposizioni nella
sequenza delle operazioni svolte dal modulo. Ogni operazione deve essere svolta
solo al termine della precedente. La funzione di tipo bloccante e termina solo
quando il modulo I2C pu svolgere altre operazioni, per questa ragione non accetta
n restituisce alcun valore.
Parametri:
364
void
Restituisce:
void
StartI2C ()
SSPCON2bits.SEN=1;while(SSPCON2bits.SEN)
Parametri:
void
Restituisce:
void
StopI2C ()
SSPCON2bits.PEN=1;while(SSPCON2bits.PEN)
Parametri:
void
Restituisce:
void
NotAckI2Cx ()
Parametri:
365
void
Restituisce:
void
unsigned char WriteI2Cx (unsigned char)
162
Sebbene molti siano portati a trasmissioni sempre pi veloci, bisogna tenere a mente che all'aumentare delle frequenze in
gioco il layout del PCB diventa sempre pi importante e le considerazioni da tenere a mente non sono banali. Il sistema che
si progetta non deve essere visto solo da una prospettiva software ma anche hardware. In particolari errori di trasmissione
potrebbero derivare da un layout non corretto e non da problemi software.
366
0xF0
#define
#define
#define
#define
BUTTON_1
BUTTON_2
BUTTON_3
BUTTON_4
0x01
0x02
0x04
0x08
#define
#define
#define
#define
BUTTON_1_PRESSED
BUTTON_2_PRESSED
BUTTON_3_PRESSED
BUTTON_4_PRESSED
0b11100000
0b11010000
0b10110000
0b01110000
//*************************************
//
Prototipi delle funzioni
//*************************************
void board_initialization (void);
void write_data (unsigned char data);
//*************************************
//
I2C Master
//*************************************
int main(void) {
367
board_initialization ();
//Inizializzazione del modulo I2C
//Baudrate 400kHz @20MHz
OpenI2C(MASTER,SLEW_OFF);
SSPADD = 12;
//Controllo della pressione del pulsante
while (1) {
switch (PORTB & BUTTON_MASK) {
case BUTTON_1_PRESSED : write_data (BUTTON_1);
LATD = 0x00;
break;
case BUTTON_2_PRESSED : write_data (BUTTON_2);
break;
case BUTTON_3_PRESSED : write_data (BUTTON_3);
break;
case BUTTON_4_PRESSED : write_data (BUTTON_4);
break;
}
}
}
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFD;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
//Attivo i restitori di pull-up su PORTB
INTCON2bits.RBPU = 0;
}
//*************************************
//
Implementazione della funzione
//*************************************
void write_data (unsigned char data){
368
369
370
Descrizione
signed char I2C_EEPROM_write_check (unsigned char, unsigned int , unsigned Scrive un byte nella cella di memoria indirizzata e
char )
controlla che sia stata scritto propriamente
signed char I2C_EEPROM_read ( unsigned char , unsigned int , unsigned char )
void I2C_EEPROM_initialize(unsigned
int baud_rate_KHz)
char
crystal_frequency_MHz,
unsigned
371
372
La funzione delay utilizzata per introdurre l'attesa necessaria al ciclo di scrittura per essere completato. Il tempo richiesto,
specificato nel Datasheet, dell'ordine di qualche ms.
373
Parametri:
control: Tale valore rappresenta il parametro di controllo che identifica la memoria.
Questo valore lo si trova scritto sul Datasheet della memoria utilizzata. Molte
memorie i2c EEPROM della Microchip possiedono come valore di controllo il
valore 10100xxx dove le x rappresentano il valore dei pin d'indirizzo esterni. Per
esempio Freedom II possiede i pin d'indirizzo della memoria EEPROM collegati a
massa, cio pari a 0; dunque il valore del byte di controllo 0xA0 (10100000).
address: Tale valore rappresenta l'indirizzo della locazione di memoria. Tale
indirizzo viene scomposto internamente alla funzione in due byte. L'utilizzatore
deve accertarsi che l'indirizzo sia sempre nei limiti della memoria utilizzata.
data: Tale valore rappresenta l'indirizzo della variabile di tipo
all'interno della quale viene scritto il valore letto.
unsigned char,
Restituisce:
1: Il byte stato scritto senza errori (il controllo del valore non effettuato)
-1: Errore Bus Collision
-2: Errore Not Ack Error
-3: Errore Write Collision
Esempio:
unsigned char data;
signed char errore;
errore = I2C_EEPROM_read(0xA0, 0x00, &data);
Si noti che la variabile data all'interno della quale viene scritto il dato letto, viene
passata per mezzo dell'operatore & che permette di passare l'indirizzo della memoria data.
Vediamo ora un esempio in cui si scrive un byte nella memoria EEPROM e lo si legge
subito dopo per visualizzarlo sui LED.
#include <xc.h>
#include "PIC18F4550_config.h"
#include "i2cEEPROM.h"
#include "i2cEEPROM.c"
#include "delay.h"
#include "delay.c"
#define ADDRESS
#define DATA_TO_STORE
0x00
0xA3
374
#define CONTROL_BYTE
0xA0
Il programma inizia includendo i file di libreria, si noti che oltre al file di libreria
i2cEEPROM.h si incluso anche il file di libreria delay.h. Il resto delle impostazioni
piuttosto standard. Come prima istruzione si richiama la seguente funzione:
// Inizializza il modulo I2C a 400KHz @20MHz
I2C_EEPROM_initialize (20, 400);
Questo richiesto per impostare correttamente la funzione delay.h e il modulo I2C per
lavorare a 400 KHz.
Solo dopo aver inizializzato il modulo I2C possibile utilizzare la libreria
i2cEEPROM.
375
Si osservi che il valore viene scritto all'interno della variabile data, la quale deve essere
passata per riferimento, ovvero deve essere passato il suo indirizzo. Si ricorda infatti che i
valori passati ad una funzione sono per valore, ovvero la funzione crea delle variabili
temporanee all'interno delle quali pone i valori passati. Questo quello che in generale
voluto, ma nel nostro caso dovendo scrivere il valore nella variabile passata necessario
sapere il suo indirizzo reale e non costruire una variabile temporanea. Per fare questo la
funzione accetta un puntatore al tipo di variabile che si vuole modificare (nel nostro caso
unsigned char). Per passare l'indirizzo della variabile si fa uso dell'operatore & prima
della variabile, da non confondere con l'operatore and. Nel caso si volesse passare
l'indirizzo di un Array, come detto, si sarebbe fatto uso del nome dell'Array senza
parentesi quadre e non dell'operatore &. Tale apparente complicazione stata utilizzata
poich il valore restituito utilizzato per segnalare eventuali errori. In ultimo, il valore
della variabile data viene scritto sui LED.
164
Il Datasheet della memoria 24LC32 mette come valore massimo 5ms. Si scelto di usare 10ms quale forma di garanzia per
un corretto funzionamento.
376
Funzione
Descrizione
signed char RTCC_set_day_of_the_week_alarm (unsigned char, Imposta il giorno della settimana per l'allarme.
unsigned char );
signed char RTCC_enable_alarm_interrupt (void);
#include <xc.h>
#include <i2c.h>
#include "PIC18F4550_config.h"
#include "PCF8563.h"
#include "PCF8563.c"
#define LCD_DEFAULT
#include "LCD_44780.h"
#include "LCD_44780.c"
#define DELAY_INTERRUPT_BLOCKING
#include "delay.h"
#include "delay.c"
#define
#define
#define
#define
BT1
BT2
BT3
BT4
0b11100000
0b11010000
0b10110000
0b01110000
378
//*************************************
//
Prototipi delle funzioni
//*************************************
void board_initialization (void);
//******************************************
//
Gestione Interrupt
//******************************************
// Funzione per la gestione dell'interruzione
__interrupt (high_priority) void ISR_alta (void) {
unsigned char button;
// Controllo che l'interrupt sia stato generato da PORTB
if (INTCONbits.RBIF == 1 ) {
//pausa filtraggio spike
delay_ms (30);
button = PORTB;
button = button & 0xF0;
// Controllo del tasto premuto
switch(button) {
case BT1 & BT3:
RTCC_increment_minutes();
break;
}
//******************************************
//
Programma Principale
//******************************************
int main (void){
board_initialization ();
// Inizializza il modulo I2C a 400KHz @20MHz
PCF8563_initialize(20,400);
379
");
LCD_write_string (RTCC_get_time());
LCD_goto_line(2);
LCD_write_message ("Date :
");
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void){
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
380
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFD;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
//Attivo i restitori di pull-up su PORTB
INTCON2bits.RBPU = 0;
}
nel fatto che la funzione delay ha delle variabili interne che vengono alterate in due punti
diversi, rendendo il programma imprevedibile. In particolare incrementando e
decrementando le variabili interne in maniera incontrollata si possono verificare delle
condizioni di stallo legate al fatto che determinate condizioni possono non essere pi
verificate, per cui il programma non procede oltre.
Per tale ragione, usando la funzione delay sia nel main che nell'ISR necessario
dichiarare la seguente riga di codice prima della libreria delay:
#define DELAY_INTERRUPT_BLOCKING
Riassunto
In questo Capitolo abbiamo visto il funzionamento del modulo I2C, descrivendo il
protocollo stesso. In particolare si mostrato come utilizzare direttamente la libreria
Microchip al fine di gestire la comunicazione Master Slave tra due microcontrollori. Oltre
alla libreria base si sono introdotte anche le librerie per la gestione EEPROM e del Real
Time Clock Calendar, mostrando come l'utilizzo delle librerie possa rendere il programma
particolarmente chiaro e di facile realizzazione. In ultimo si sono accennate brevemente
le problematiche ad applicazioni multitasking, ovvero la chiamata di una funzione in pi
punti del programma.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
Quali sono le ragioni per cui stato introdotto il protocollo seriale I2C?
Quali sono le differenze tra il protocollo I2C e il protocollo RS232?
Quali sono le differenze tra il protocollo I2C e il protocollo RS485?
possibile per i PIC18 eseguire un codice direttamente da una memoria esterna
EEPROM? (spiegare la risposta)
possibile per un PIC18F4550 realizzare un RTCC senza far uso di un PCF8563 esterno?
(descrivere due soluzioni, una basata su un solo quarzo ed una basata su due)
Che problemi si potrebbero avere richiamando una funzione in pi parti del programma in
maniera asincrona?
Che cosa significa che una funzione Thread Safe?
Riscrivere il programma del RTCC facendo uso di una state machine, evitando i ritardi
all'interno dell'Interrupt Service Routine.
Modificare la libreria EEPROM al fine che possa funzionare anche per EEPROM ad un
solo byte d'indirizzo.
Realizzare una sveglia, estendendo il programma di esempio, in cui sia possibile impostare
un orario per la sveglia.
383
Capitolo XIII
XIII
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 134 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo.
384
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 135: Impostazioni dei Jumper per il Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 136: Impostazioni dei Jumper per il Progetti presentati nel Capitolo.
Il DAC MCP4822 non presente sulle schede di sviluppo e deve per tanto
essere montato sulle schede di sviluppo esterne o su Breadboard. Per montaggi
in cui sia richiesto un bus di lunghezza di circa 10cm o pi, si consiglia di
mettere un resistore in serie ad ogni linea dell'interfaccia SPI, del valore di
120ohm, in prossimit della scheda di sviluppo.
386
Impostazioni software
Gli esempi proposti fanno uso delle librerie LaurTec, per cui necessario impostare i
percorsi di libreria riportati di seguito:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
La versione della libreria inclusa nel percorso potrebbe variare in base a quella utilizzata.
Il protocollo SPI
Il protocollo SPI o meglio l'interfaccia SPI, venne originariamente ideata dalla
Motorola (ora Freescale) a supporto dei propri microprocessori e microcontrollori. A
differenza dello standard I2C ideato dalla Philips, linterfaccia SPI non stata mai
standardizzata; ciononostante divenuta di fatto uno standard. Il non avere delle regole
ufficiali ha portato allaggiunta di molte caratteristiche ed opzioni che vanno
opportunamente selezionate ed impostate al fine di permettere una corretta
comunicazione tra le varie periferiche interconnesse.
Linterfaccia SPI descrive una comunicazione singolo Master singolo Slave, ed di
tipo sincrono e full-duplex. Il clock viene trasmesso con una linea dedicata (non
necessariamente una trasmissione sincrona ha una linea dedicata al clock) ed possibile
sia trasmettere che ricevere dati in contemporanea. In Figura 137 riportato uno schema
base di collegamento tra due periferiche che fanno uso dellinterfaccia SPI.
Figura 137: Schema base di collegamento tra periferiche facenti uso dellinterfaccia SPI.
Dalla Figura 137 possibile subito notare quanto appena detto, ovvero che la
comunicazione avviene generalmente tra un Master ed uno Slave. Linterfaccia presenta
inoltre 4 linee di collegamento (esclusa la massa comunque necessaria), per cui lo standard
SPI anche noto come 4 Wire Interface. compito del Master avviare la comunicazione e
fornire il clock allo Slave. La nomenclatura delle varie linee presenti nellinterfaccia SPI
normalmente la seguente:
Oltre a questa nomenclatura, pi o meno standard, sono presenti altre sigle. Per
esempio i microcontrollori quali i PIC18 della Microchip o gli MSP430 della Texas
Instruments, fanno uso di una nomenclatura leggermente differente ma comunque
intuitiva.
Per esempio la linea MOSI viene anche nominata:
388
Unaltra osservazione da tenere a mente sia sulla Figura 137 che Figura 138 che la
linea SS attiva bassa. Seppur questo non uno standard, i microcontrollori della
Motorola vennero pensati con questa opzione.
Il segnale Slave Select (SS) non sempre presente nei microcontrollori che possiedono
linterfaccia SPI e per la sua implementazione necessario distinguere due casi:
Il microcontrollore impostato per lavorare come Master
Lo Slave Select deve essere opportunamente abilitato prima dellavvio della
comunicazione con lo Slave. Il suo corretto utilizzo pu cambiare a seconda della
modalit di trasmissione utilizzata (si veda di seguito). Frequentemente i
microcontrollori in modalit Master fanno uso di semplici I/O generici che
vengono assegnati uno per periferica.
Il microcontrollore impostato per lavorare come Slave
Lo Slave Select ha lo scopo di abilitare il microcontrollore al fine di permettergli
una corretta comunicazione. Quando il segnale SS non presente si fa spesso uso di
un ingresso con interruzione, in maniera da poter prontamente individuare la
richiesta del Master di avviare la comunicazione. Si ricorda che il segnale SS, qualora
pi Slave dovessero condividere la linea MISO, quando disabilitato, dovrebbe
portare luscita MISO dello Slave in uno stato di alta impedenza.
Figura 139: Connessione tra Master e Slave mettendo in evidenza i registri a scorrimento interni.
microcontrollori hanno lopzione di trasmettere il byte in uscita non a partire dal bit pi
significativo ma da quello meno significativo. Come ogni opzione utilizzata si deve avere
la premura di controllare che lo Slave sia compatibile con tale opzione. La pratica di
trasmettere il bit dal meno significativo a quello pi significativo viene normalmente
utilizzata nella comunicazione 3 Wire (half-duplex).
Come detto la rappresentazione con un solo registro a scorrimento non una
semplificazione a solo scopo didattico, infatti viene spesso utilizzata. Ciononostante molti
microcontrollori offrono anche un registro a scorrimento per direzione di trasmissione,
come anche un buffer dati al fine da poter caricare il dato ricevuto e permettere una
nuova ricezione limitando eventuali perdite d'informazione. Il modulo SPI spesso
supportato da interruzioni in maniera da poter armonizzare lutilizzo dello stesso con il
resto delle attivit della CPU.
Si noti che quanto descritto non fa riferimento ad alcun protocollo o formattazione dei
dati. Il byte trasmesso un semplice byte e il suo significato dipender dallapplicazione.
Questo in contrapposizione con standard come lI2C e CAN, in cui i vari byte possiedono
un determinato significato. La mancanza di un protocollo fa dellinterfaccia SPI una
soluzione semplice da implementare anche senza lausilio di un modulo dedicato. Daltro
lato la mancanza di un protocollo di supervisione, impedisce allinterfaccia SPI di sapere
se il Master effettivamente collegato ad uno Slave; questo nello Standard I2C
implementato per mezzo del meccanismo di Acknowledgment. Per mezzo dellinterfaccia
SPI comunque possibile controllare la presenza di uno Slave, ma necessario
implementarlo, per esempio creando un byte domanda del tipoci sei?
Una volta compreso come realizzato un modulo con interfaccia SPI, possibile
comprendere il terzo modo con cui si possono collegare tra loro pi Slave. In Figura 140
riportato il cosiddetto collegamento in daisy chain degli Slave.
Dalla Figura 140 si capisce che la catena di Slave ottenuta collegando luscita MISO
del primo Slave allingresso MOSI del Secondo Slave e cosi via. Lultimo Slave avr il
MISO collegato effettivamente al Master. La catena di Slave viene attivata da una sola
391
linea SS, quindi si ha la possibilit di risparmiare diversi pin altrimenti necessari per ogni
singolo Slave. Per comunicare con un dispositivo necessario questa volta inviare sempre
tre byte (uno per periferica) anche se il dispositivo con cui si vuole colloquiare il primo
(in lettura obbligatorio, mentre in sola scrittura si potrebbe evitare). Questa
configurazione, seppur attraente non permette un numero indefinito di periferiche. A
seconda della frequenza del segnale di clock e dalla tipologia delle periferiche utilizzate
potrebbe essere necessario dover rispettare dei tempi minimi e massimi di lettura o
scrittura oltre i quali non possibile andare.
La tecnica daisy chain la stessa utilizzata dallinterfaccia JTAG (Joint Test Action Group)
molto simile allinterfaccia SPI, ovvero basata su registri a scorrimento. Linterfaccia
JTAG divenuta uno standard per simulare circuiti integrati montati su sistemi complessi,
ma viene sempre pi frequentemente utilizzata anche per programmare i dispositivi quali
microcontrollori, FPGA e CPLD.
CPOL
CPHA
Si fa presente che alcuni microcontrollori, come per esempio i PIC18 e MSP430 hanno
il significato di CPHA invertito. Quando uno Slave richiede quindi la modalit 0 secondo
lo standard Freescale CPOL=0 e CPHA=0, per alcuni microcontrollori potrebbe essere
392
CPOL=0 e CPHA=1. Frequentemente il nome dei bit non lo stesso per cui bisogna
prestare ulteriore attenzione. Vediamo in maggior dettaglio le varie modalit.
In Figura 141 riportato il diagramma temporale nel caso in cui CPHA=1.
Si noti che il caso CPOL=0 e CPOL=1 sono inclusi nello stesso diagramma e
comporta, come detto, solo linversione dei fronti sul quale avviene una determinata
operazione. Nella trattazione che segue si considera il caso CPOL=0 (modalit 1). La
trasmissione dei dati avviene secondo la seguente sequenza:
In questa modalit e nella 3 (CPOL=1, CPHA=1) la linea Slave Select rimane attiva per
tutta la trasmissione. Se ulteriori byte devono essere inviati, la linea SS pu rimanere
ininterrottamente attiva, cosa non vera se CPHA = 0. Questo significa che moduli con
interfaccia SPI con registri a 8 bit, possono leggere e scrivere periferiche che hanno
registri a scorrimento 16 bit (come anche 3 o pi byte), ma solo se si fa uso della modalit
1 o 3. In Figura 142 riportato il caso in cui CPHA sia uguale a 0. Come nellanalisi
precedente, il caso CPOL=0 e CPOL=1 sono inclusi nello stesso diagramma temporale.
393
Come per la Figura 141, anche in questo caso si analizza il caso CPOL=0 ovvero la
modalit 0.
Per la modalit 0 e 2 necessario che ad ogni invio di byte la linea SS venga disattivata
e riattivata. Infatti il bit MSB viene posto rispettivamente sulle linee MOSI e MISO sul
fronte di discesa della linea stessa. In queste modalit si capisce che non si pu fare a
meno della linea SS e che Master con registri interni a 8 bit possono colloquiare solo con
Slave che hanno il registro interno di ugual dimensione.
massimo viene a dipendere dalle periferiche di cui si sta facendo uso. Per esempio molti
microcontrollori supportano frequenze massime fino a 10MHz. Per frequenze pi alte si
fa spesso uso di macchine a stati implementate dentro CPLD o FPGA, che permettono di
raggiungere le frequenze massime sopra citate, che per altro potrebbero anche essere
superate. Anche DSP di fascia alta permettono di superare il limite dei 10MHz che
caratterizza molti microcontrollori. Il limite massimo della frequenza in generale legato
alla frequenza di clock e dalla tecnologia con cui vengono realizzati gli integrati.
Linterfaccia SPI, oltre a non specificare la frequenza operativa non impone alcun
vincolo sulle tensioni che caratterizzano i livelli logici. Ciononostante si capisce che due
dispositivi al fine di comunicare tra loro debbano lavorare con gli stessi livelli logici,
altrimenti non si potrebbero capire. Qualora due dispositivi lavorino a tensioni diverse,
per esempio uno a 3.6V e uno a 5V, necessario far uso di traslatori di livello. Valori
tipici di tensione sono: 1.8V, 3.3V, 3.6V e 5V.
396
Qualora ci si trovi ad implementare un protocollo via software si preferisce in generale implementare l'interfaccia SPI
piuttosto che il protocollo I2C, che in generale richiede maggior attenzioni.
397
Nome
INTCON
SSPCON1
SSPSTAT
SSPBUF
SSPSR (non accessibile direttamente)
BIT7
BIT6
BIT5
BIT4
BIT3
BIT2
BIT1
BIT0
GIE/GIEH
PEIE/GIEL
TMR0IE
INT0IE
RBIE
TMR0IF
INT0IF
RBIF
PIR1
SPPIF
ADIF
RCIF
TXIF
SSPIF
CCP1IF
TMR2IF
TMR1IF
PIE1
SSPPIE
ADIE
RCIF
TXIF
SSPIE
CCP1IE
TMER2IE
TMR1IE
IPR1
SPPIP
ADIP
RCIP
TXIP
SSPIP
CCP1IP
TMR2IP
TMR1IP
TRISA
TRISA7
TRISA6
TRISA5
TRISA4
TRISA3
TRISA2
TRISA1
TRISA0
TRISB
TRISB7
TRISB6
TRISB5
TRISB4
TRISB3
TRISB2
TRISB1
TRISB0
TRISB
TRISC7
TRISC6
TRISC2
TRISC1
TRISC0
SSPBUF
SSPCON1
WCOL
SSPOV
SSPEN
CKP
SSPM3
SSPM2
SSPM1
SSPM0
SSPSTAT
SMP
CKE
D/A'
R/W'
UA
BF
Il contenuto dei registri presentati nel Capitolo sono forniti per semplificare la
comprensione delle librerie e del programma ma non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
398
In particolare tra i due diversi casi, le linee SCK e SS assumono un valore diverso, vista la
differente funzione delle linee.
La configurazione dei pin riportata valida solo per il PIC18F4550 e per la
maggior parte dei PIC18 con modulo USB integrato. La posizione delle linee
potrebbe cambiare a seconda del PIC utilizzato.
399
R/W-0
R-0
R-0
R-0
R-0
R-0
R-0
SMP
CKE
D/A
R/W
UA
BF
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
Bit 7
W = Writable bit
1 = Bit is set
S : Settable bit
x = Bit is unknown
Bit 6
Bit 5
Bit 4
P : Stop bit
Utilizzato solo in modalit I2C.
Bit 3
S : Start bit
Utilizzato solo in modalit I2C.
Bit 2
Bit 1
Bit 0
400
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
WCOL
SSPOV
SSPEN
CKP
SSPM3
SSPM2
SSPM1
SSPM0
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
Bit 7
W = Writable bit
1 = Bit is set
S : Settable bit
x = Bit is unknown
Bit 6
Bit 5
Bit 4
Bit 3-0
401
Descrizione
void OpenSPIx (unsigned char, unsigned char, unsigned Apre il modulo SPIx.
char )
void CloseSPIx (void)
Questa funzione deve essere eseguita prima di utilizzare qualunque funzione della
libreria SPI, infatti permette di attivare il modulo per un suo corretto utilizzo. Tale
funzione potrebbe non essere richiamata solo nel caso in cui la libreria del
dispositivo SPI utilizzata, fornisca un'altra funzione per attivare ed impostare il
modulo SPI.
Parametri:
sync_mode: Contiene i parametri utilizzati per la configurazione del registro
SSPCON1.
SPI_FOSC_4
402
SPI_FOSC_16
SPI_FOSC_64
SPI_FOSC_TMR2
SLV_SSON
SLV_SSOFF
bus_mode: Modalit del modulo SPI
MODE_00
MODE_01
MODE_10
MODE_11
smp_phase: Delezione del campionamento
SMPEND
SMPMID
Restituisce:
void
void CloseSPIx (void)
Questa funzione deve essere eseguita per richiudere il modulo SPI. Tale funzione
potrebbe non essere mai richiamata all'interno del programma, qualora non si avesse
mai l'esigenza di chiudere il modulo.
Parametri:
void
Restituisce:
void
unsigned char WriteSPIx (unsigned char data_out)
403
404
0xF0
#define
#define
#define
#define
BUTTON_1
BUTTON_2
BUTTON_3
BUTTON_4
0x01
0x02
0x04
0x08
#define
#define
#define
#define
BUTTON_1_PRESSED
BUTTON_2_PRESSED
BUTTON_3_PRESSED
BUTTON_4_PRESSED
0b11100000
0b11010000
0b10110000
0b01110000
//*************************************
//
Prototipi delle funzioni
//*************************************
void board_initialization (void);
void write_data (unsigned char data);
//*************************************
//
SPI Master
//*************************************
int main(void) {
board_initialization ();
//Inizializzazione del modulo SPI
//Baudrate Fosc/16 @20MHz -> 312.5 KHz
OpenSPI(SPI_FOSC_64,MODE_01,SMPMID);
//Controllo della pressione del pulsante
while (1) {
switch (PORTB & BUTTON_MASK) {
case BUTTON_1_PRESSED : write_data (BUTTON_1);
LATD = 0x00;
break;
case BUTTON_2_PRESSED : write_data (BUTTON_2);
break;
case BUTTON_3_PRESSED : write_data (BUTTON_3);
break;
case BUTTON_4_PRESSED : write_data (BUTTON_4);
break;
}
405
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0b11111101;
// Imposto PORTC
LATC = 0x00;
TRISC = 0b01111111;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
// Tutte le porte Analogiche sono impostate come I/O
ADCON1 = 0x0F;
//*************************************
//
Implementazione della funzione
//*************************************
void write_data (unsigned char data){
unsigned char dummy_read;
WriteSPI(data);
dummy_read = ReadSPI ();
}
0xF0
0x01
0x02
0x04
406
#define BUTTON_4
#define
#define
#define
#define
0x08
BUTTON_1_PRESSED
BUTTON_2_PRESSED
BUTTON_3_PRESSED
BUTTON_4_PRESSED
0b11100000
0b11010000
0b10110000
0b01110000
//*************************************
//
Prototipi delle funzioni
//*************************************
void board_initialization (void);
//*************************************
//
SPI Slave
//*************************************
int main(void) {
board_initialization ();
//Inizializzazione del modulo SPI
//Baudrate Fosc/16 @20MHz -> 312.5 KHz
OpenSPI (SLV_SSOFF,MODE_01,SMPMID);
//Controllo della pressione del pulsante
while (1) {
LATD = ReadSPI();
}
}
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0b11111111;
// Imposto PORTC
LATC = 0x00;
TRISC = 0b01111111;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
// Tutte le porte Analogiche sono impostate come I/O
ADCON1 = 0x0F;
407
1.5V
1.8V
3.3V
Per mezzo di un quarto pulsante si deve porre l'uscita in alta impedenza, ovvero spegnere
l'uscita. Reprimendo il pulsante, l'uscita deve essere riposta all'ultimo valore di tensione
prima dello spegnimento dell'uscita.
Tra un'accensione ed un altra si preveda un ritardo di un 1 secondo.
Lo stato dell'uscita deve essere segnalato dall'accensione e spegnimento di un LED.
REFERENCE_0000
REFERENCE_1500
REFERENCE_1800
REFERENCE_3300
0
1500
1800
3300
//*************************************
//
Costanti
//*************************************
#define STATUS_ON 0x01
166
Il DAC, ovvero Digital to Analog Converter, permette di convertire un segnale digitale in uno analogico. In particolare in
base al numero di bit (risoluzione) e il valore di riferimento usato, permette di controllare il livello analogico con un valore di
precisione pari al valore di riferimento diviso il numero di sequenze numeriche supportate (2 RISOLUZIONE). Il passo minimo
viene definito quanto.
408
if (PORTBbits.RB4 == 0) {
MCP4822_set_amplitude (REFERENCE_1500, DAC_A);
last_voltage = REFERENCE_1500;
}
//*************************************
//
Pulsante BT2
//*************************************
if (PORTBbits.RB5 == 0) {
delay_ms (DEBOUNCE_TIME);
if (PORTBbits.RB5 == 0) {
MCP4822_set_amplitude (REFERENCE_1800, DAC_A);
last_voltage = REFERENCE_1800;
}
//*************************************
//
Pulsante BT3
//*************************************
if (PORTBbits.RB6 == 0){
delay_ms (DEBOUNCE_TIME);
409
if (PORTBbits.RB6 == 0){
MCP4822_set_amplitude (REFERENCE_3300, DAC_A);
last_voltage = REFERENCE_3300;
}
}
//*************************************
//
Pulsante BT4
//*************************************
if (PORTBbits.RB7 == 0 && system_status == STATUS_ON){
delay_ms (DEBOUNCE_TIME);
if (PORTBbits.RB7 == 0){
MCP4822_set_options (MCP4822_GAIN_2, MCP4822_SHUTDOWN_ON,
MCP4822_DAC_A);
MCP4822_set_amplitude (REFERENCE_0000, DAC_A);
STATUS_LED = STATUS_OFF;
system_status = STATUS_OFF;
delay_ms (1000);
}
STATUS_LED = STATUS_ON;
system_status = STATUS_ON;
delay_ms (1000);
}
}
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFF;
410
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
// Tutte le porte Analogiche sono impostate come I/O
ADCON1 = 0x0F;
//Attivo i resistori di pull-up su PORTB
INTCON2bits.RBPU = 0;
}
Riassunto
In questo Capitolo abbiamo visto il funzionamento del modulo SPI, mostrando come
con i due soli registri SSPCON1 e SSPSTAT sia possibile configurare un modulo seriale
di particolare importanza. L'interfaccia SPI si presta infatti alla configurazioni di molti
dispositivi complessi come ADC, DAC ma anche per leggere e scrivere memorie Flash,
EEPROM. Il Capitolo ha anche mostrato come poter utilizzare l'interfaccia SPI per poter
mettere in comunicazione due microcontrollori ed utilizzare il DAC a doppia uscita
MCP4822.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
Quali sono le ragioni per cui stato introdotto il protocollo seriale SPI?
Quali sono le differenze tra il protocollo SPI e il protocollo I2C?
Quali sono le massime velocit raggiungibili dal protocollo SPI?
Quali sono le massime distanze raggiungibili dal protocollo SPI?
Che cos' un DAC?
Modificare l'esempio Master e Slave presentato, permettendo la visualizzazione da parte del
Master dei pulsanti premuti dallo Slave.
Modificare il Progetto, facendo uso delle interruzioni per il controllo dei tasti premuti.
Modificare il Progetto presentato inserendo altri tre pulsanti per il DAC_B, estendendo il
sistema a due uscite.
412
Capitolo XIV
XIV
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 144 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo.
413
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 145: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 146: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
414
Impostazioni software
Gli esempi proposti fanno uso delle librerie LaurTec, per cui necessario impostare i
percorsi di libreria riportati di seguito:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
La versione della libreria inclusa nel percorso potrebbe variare in base a quella utilizzata.
I tempo di latenza uno dei parametri fondamentali nell'analisi del time budget.
I Timer vengono spesso utilizzati nei sistemi operativi per la realizzazione dello scheduling dei processi. Nel seguente
Capitolo non si affronteranno le problematiche associate a questo utilizzo poich la trattazione richiede conoscenze che
vanno oltre la semplice lettura di un Datasheet o un libro.
415
applicazioni specifiche.
Per esempio il Timer1 pu essere utilizzato come oscillatore secondario ed in
particolare possedendo la circuiteria per far oscillare un quarzo a bassa frequenza, pu
essere utilizzato per far oscillare un quarzo da 32768KHz, utilizzato per la realizzazione di
un Real Time Clock Calendar. Il Timer2 e Timer3 sono spesso associati con i moduli CCP
utilizzati per la generazione del segnale PWM. Quando un Timer viene associato ad una
periferica non pu essere utilizzato in applicazioni generiche salvo forte restrizioni.
Normalmente i Timer vengono fatti lavorare per mezzo di Interrupt, in maniera tale da
avere un conteggio senza bloccare il resto del programma. Infatti i Timer sono
indipendenti dall'esecuzione del programma, ovvero una volta impostati non richiedono
nessun'altra azione da parte del programma. Se sono attivate le interruzioni il Timer
interrompe il programma principale e sar solo a questo punto che il programma deve
gestire l'evento associato al Timer. Ogni Timer, indipendentemente dal modulo a cui
viene associato, permette di generare un'interruzione di overflow al passaggio da 0xFF a
00 (se ad 8 bit) o da 0xFFFF a 0x0000 (se a 16 bit).
Per esempio, nel caso del Timer0, per abilitare le interruzioni necessario abilitare il bit
TMR0IE del registro INTCON. All'interno dello stesso registro anche presente il bit
TMR0IF che permette di visualizzare l'evento d'interruzione associato al Timer1. Si
ricorda che tale bit deve essere posto a 0 via software al termine della routine di gestione
dell'interruzione. In ultimo a seconda della modalit d'interruzione che viene utilizzata
bisogna impostare il bit TMR0IP. Bisogna impostare il bit a 0 per abilitarlo ad interruzioni
a bassa priorit, mentre deve essere impostato ad 1 per utilizzare le interruzioni ad alta
priorit o modalit compatibile. Si ricorda che tale bit di default vale 1, ovvero la
periferica gestita ad alta priorit. Questo permette di ignorare tale bit quando viene
utilizzata la modalit compatibile PIC16 che equivale a trattare le interruzioni ad alta
priorit, ovvero con l'Interrupt Vector impostato posto all'indirizzo 0x08.
In Figura 147 riportato lo schema a blocchi del modulo Timer0 in modalit ad 8 bit
mentre in Figura 148 riportato lo schema a blocchi in modalit a 16 bit.
416
Dallo schema a blocchi subito possibile vedere che sulla sua sinistra possibile
selezionare la sorgente del clock che permette l'incremento del contatore. In particolare
possibile selezionare una sorgente esterna ovvero entrante dal pin TOKI o la sorgente
interna Fosc/4 ovvero la frequenza di esecuzione delle istruzioni. Successivamente
presente un percorso diretto verso un secondo multiplexer di selezione, ed un percorso
attraverso un Prescaler ovvero un divisore di frequenza. Questo permette rallentare la
frequenza principale, sia essa quella interna o esterna. La divisione pu essere selezionata
da 2 a 256 con passi di potenze di 2, ovvero, 2, 4, 8, 16, 32, 64, 128, 256. Successivamente
presente una linea di ritardo di due cicli di clock; questo permette di sincronizzare il
segnale con il clock interno. Il clock che si viene a generare dopo questo percorso andr
ad incrementare il contatore Timer0, sia che sia impostato a 8 bit ovvero con conteggio
massimo fino a 255, sia che sia impostato in modalit a 16 bit, ovvero con conteggio
massimo fino a 65535. Lo schema a blocchi a 16 bit risulta leggermente pi complesso
poich il secondo registro associato al Timer0, ovvero TMR0H, deve essere scritto e letto
in maniera opportuna in maniera da garantire il corretto funzionamento del Timer stesso.
In particolare il registro TMR0H rappresenta un registro ombra, dal momento che non
possibile leggere e scrivere direttamente il suo contenuto.
Per leggere il registro contatore dato dall'unione TMR0H e TMR0L si deve leggere prima
il registro TMR0L. Alla lettura del registro TMR0L, il registro ombra TMR0H viene
automaticamente caricato con il valore reale del registro TMR0 per cui si pu leggere in
maniera accurata il valore del registro all'istante in cui si avviata la lettura del registro
417
TMR0L. In maniera simile, per poter scrivere nel registro a 16 bit dato dall'unione
TMR0H e TMR0L, si procede prima alla scrittura del registro TMR0H, il cui valore viene
per trasferito solo alla scrittura del registro TMR0L. In questo modo si garantisce che i
16bit vengono caricati nello stesso ciclo di clock.
Si fa notare che al fine di garantire la corretta esecuzione temporale dei tempi
impostati, il Prescaler viene azzerato ogni volta che si scrive nei registri del Timer0. Il suo
Reset non cambia per il rapporto di divisione che stato impostato.
Scrivere nel contatore TMR0 inibisce il conteggio per due cicli di clock. Questa
perdita sistematica del clock pu essere compensata caricando il valore
opportuno all'interno del Timer.
T0CON
TMR0L
TMR0H
BIT7
BIT6
BIT5
BIT4
BIT3
TMR0L
TMR0H
INTCON
GIE/GIEH PEIE/GIEL
INTCON2
BIT2
BIT1
BIT0
TMR0IE
INT0IE
RBIE
TMR0IF
INTIF
RBIF
RBPU
INTEDG0
INTEDG1
INTEDG2
TMR0IP
RBIP
T0CON
TMR0ON
T08BIT
T0CS
T0SE
PSA
T0PS2
T0PS1
T0PS0
TRISA
TRISA6
TRISA5
TRISA4
TRISA3
TRISA2
TRISA1
TRISA0
Il contenuto dei registri presentati nel Capitolo sono forniti per semplificare la
comprensione delle librerie e dei programmi, e non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
R/W-1
R/W-1
R/W-1
TMR0ON
T08BIT
T0CS
Bit 7
Bit 6
Bit 5
Leggenda
R = Readable bit
-n = Value at POR
R/W-1
R/W-1
R/W-1
R/W-1
T0SE
PSA
T0PS2
T0PS1
T0PS0
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
W = Writable bit
1 = Bit is set
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2-0
S : Settable bit
x = Bit is unknown
419
Timer a 16 bit.
I registri del Timer possono essere sia letti che scritti.
Possibilit di utilizzare il Prescaler.
Sorgente di Clock selezionabile (interna od esterna).
Possibilit di generare un Interrupt per overflow.
Evento speciale modulo CCP.
Stato del clock (T1RUN).
BIT7
BIT6
BIT5
BIT4
BIT3
TMR1L
TMR1H
INTCON
GIE/GIEH PEIE/GIEL
TMR0IE
INT0IE
RBIE
BIT2
BIT1
BIT0
TMR0IF
INTIF
RBIF
PIR1
SPPIF
ADIF
RCIF
TXIF
SSPIF
CCP1IF
TMR2IF
TMR1IF
PIE1
SSPPIE
ADIE
RCIF
TXIF
SSPIE
CCP1IE
TMER2IE
TMR1IE
IPR1
SPPIP
ADIP
RCIP
TXIP
SSPIP
CCP1IP
TMR2IP
TMR1IP
T1CON
RD16
T1RUN
T1CKPS1
T1CKPS0
T1OSCEN
T1SYNC
TMR1CS
TMR1ON
TRISA
TRISA6
TRISA5
TRISA4
TRISA3
TRISA2
TRISA1
TRISA0
Dopo un Reset
Il contenuto dei registri presentato nel capitolo sono forniti per semplificare la
comprensione delle librerie e del programma ma non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
R-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
RD16
T1RUN
T1CKPS1
T1CKPS0
T1OSCEN
T1SYNC
TMR1CS
TMR1ON
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7
Bit 6
Bit 5-4
Bit 3
Bit 2
S : Settable bit
x = Bit is unknown
TMR1CS = 1
1: Non sincronizzare il Clock esterno
0: Sincronizza il Clock esterno
TMR1CS = 0
bit ignorato
Bit 1
Bit 0
422
Dallo schema a blocchi si pu notare che possibile selezionare solo Fosc/4 come
sorgente del Clock ma si ha la flessibilit di avere sia il Prescaler che il Postscaler. Il Postscaler
divide l'uscita del comparatore, ovvero il numero di eventi d'uguaglianza tra il registro
TMR2 e PR2. La caratteristica introdotta dal Timer2 proprio quella di avere un secondo
registro nel quale caricare un valore da confrontare con il TMR2. Ogni volta che avviene
l'uguaglianza viene generato un Interrupt. L'Interrupt del Timer2 differisce dunque dagli
altri Timer, che generano un Interrupt all'overflow del contatore stesso. Tale funzione
pu essere sfruttata in applicazioni personali ma nasce principalmente per supportare il
modulo PWM e il modulo MSSP. Nel primo caso permette infatti di avere un modo facile
di impostare la frequenza del PWM, infatti il segnale di uguaglianza resetta anche il
TMR2. Nel caso del modulo MSSP si ha per esempio la possibilit di avere un clock per il
modulo SPI configurabile a valori differenti dai pochi valori rappresentati da Fosc/4,
Fosc/16 e Fosc/64.
423
T2CON
PRS2
BIT7
BIT6
BIT5
TMR2
INTCON
BIT4
BIT3
BIT2
BIT1
BIT0
Registro Timer2
GIE/GIEH
PEIE/GIEL
TMR0IE
INT0IE
RBIE
TMR0IF
INTIF
RBIF
PIR1
SPPIF
ADIF
RCIF
TXIF
SSPIF
CCP1IF
TMR2IF
TMR1IF
PIE1
SSPPIE
ADIE
RCIF
TXIF
SSPIE
CCP1IE
TMER2IE
TMR1IE
IPR1
SPPIP
ADIP
RCIP
TXIP
SSPIP
CCP1IP
TMR2IP
TMR1IP
T2OUTPS3
T2OUTPS2
T2OUTPS1
T2OUTPS0
TMR2ON
T2CKPS1
T2CKPS0
T2CON
PRS2
Il contenuto dei registri presentati nel Capitolo sono forniti per semplificare la
comprensione delle librerie e dei programmi, e non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
424
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
T2OUTPS3
T2OUTPS2
T2OUTPS1
T2OUTPS0
TMR2ON
T2CKPS1
T2CKPS0
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7
- : Non implementato
Bit 6-3
Bit 2
Bit 1-0
S : Settable bit
x = Bit is unknown
425
Timer a 16 bit.
I registri del Timer possono essere sia letti che scritti.
Possibilit di utilizzare il Prescaler.
Sorgente di Clock selezionabile (interna od esterna).
Possibilit di generare un Interrupt per overflow.
Evento speciale modulo CCP.
Alla sinistra dello schema a blocchi possibile selezionare la sorgente del clock usata
per il conteggio. In particolare possibile selezionare una sorgente esterna ovvero
entrante dal pin T13CKI o la sorgente interna Fosc/4 ovvero la frequenza di esecuzione
delle istruzioni. Oltre a questa opzione presente un buffer interno con il quale
possibile ottenere l'oscillazione di un quarzo a bassa frequenza, tipicamente da 32KHz
(32768Hz). L'oscillatore in questione lo stesso attivabile per il Timer1 e non un secondo
modulo.
I contatore dato dall'unione di due registri ad 8 bit TMR3L e TMR3H, in particolare
il registro TMR3H rappresenta l'ombra del registro TMR3. Per leggere il registro
contatore dato dall'unione TMR3H e TMR3L si deve leggere prima il registro TMR3L.
Alla lettura del registro TMR3L, il registro ombra TMR3H viene automaticamente
caricato con il valore reale del registro TMR3 per cui si pu leggere in maniera accurata il
426
valore del registro all'istante in cui si avviata la lettura del registro TMR3L.
In maniera simile, per poter scrivere nel registro a 16 bit dato dall'unione TMR3H e
TMR3L, si procede prima alla scrittura del registro TMR3H, il cui valore viene per
trasferito solo alla scrittura del registro TMR3L. In questo modo si garantisce che i 16 bit
vengono caricati nello stesso ciclo di clock. Oltre a queste funzioni di Timer, il Timer3
pu essere affiancato al modulo Capture e Compare, di cui si parler nel capitolo dedicato
al modulo CCP (Capture Compare PWM).
T3CON
TMR3L
TMR3H
BIT7
BIT6
BIT5
BIT4
BIT3
TMR3L
TMR3H
INTCON
GIE/GIEH PEIE/GIEL
BIT2
BIT1
BIT0
TMR0IE
INT0IE
RBIE
TMR0IF
INTIF
RBIF
PIR2
OSCIF
CMIF
USBIF
EEIF
BCLIF
HLVDIF
TMR3IF
CCP2IF
PIE2
OSCIE
CMIE
USBIE
EEIE
BCLIE
HLVDIE
TMR3IE
CCP2IE
IPR2
OSCIP
CMIP
USBIP
EEIP
BCLIP
HLVDIP
TMR3IP
CCP2IP
T1CON
RD16
T1RUN
T1CKPS1
T1CKPS0
T1OSCEN
T1SYNC
TMR1CS
TMR1ON
T3CON
RD16
T3CCP2
T3CKOS1
T3CKOS0
T3CCP1
T3SYNC
TMR3CS
TMR3ON
TRISC
TRISC7
TRISC6
TRISC5
TRISC4
TRISC3
TRISC2
TRISC1
TRISC0
Il contenuto dei registri presentati nel Capitolo sono forniti per semplificare la
comprensione delle librerie e dei programmi, e non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
427
R-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
RD16
T3CCP2
T3CKOS1
T3CKOS0
T3CCP1
T3SYNC
TMR3CS
TMR3ON
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7
Bit 6 e 3
Bit 5-4
Bit 2
S : Settable bit
x = Bit is unknown
TMR3CS = 1
1: Non sincronizzare il Clock esterno
0: Sincronizza il Clock esterno
TMR3CS = 0
bit ignorato
Bit 1
Bit 0
428
429
}
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xF0;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
//Attivo i restitori di pull-up su PORTB
INTCON2bits.RBPU = 0;
}
Si osservi subito che il pulsante viene letto solo dopo le pause. Caricando il programma
sulla scheda Freedom II vi renderete presto conto che la pressione del pulsante non fa
accendere e spegner il LED 1 in maniera molto fluida, visto che sono presenti delle pause
pi o meno lunghe in cui il programma non effettua il controllo. Un modo per ovviare a
tale limite sarebbe quello di eseguire il controllo all'interno di ogni ciclo di ritardo, in
modo da controllare la pressione del pulsante. Alla luce delle nostre esperienze passate e
dalla brutta esperienza o brutta figura attuale, sappiamo che un modo professionale per
gestire il tutto, per mezzo delle interruzioni. In questo caso specifico, si potrebbe
evitare, ma se oltre al lampeggio bisogna usare un LCD alfanumerico sul quale scrivere i
dati che arrivano dalla porta seriale...leggere la temperatura e controllare che sia suonata la
sveglia...in questo caso non c' altra soluzione snella che l'utilizzo delle interruzioni.
Utilizzare le interruzioni permette di inserire altre funzionalit senza sconvolgere il
programma principale, cosa che nel caso dell'utilizzo dei cicli for e letture continue del
pulsante, sarebbe stato probabilmente necessario fare.
Vediamo allora un altro esempio, in cui si fa lampeggiare il LED0 e si accende il LED1
alla pressione del pulsante BT1. Si considera che il lampeggio del LED a bassa priorit
poich ha solo lo scopo di visualizzare all'utente che il programma in esecuzione,
mentre la pressione del pulsante viene gestita ad alta priorit poich si considera che alla
sua pressione corrisponder l'accensione del LED1, che corrisponde all'apertura di una
porta d'emergenza.
430
#include <xc.h>
#include "PIC18F4550_config.h"
#include "delay.h"
#include "delay.c"
//*************************************
//
Costanti
//*************************************
#define BUTTON_1 PORTBbits.RB4
#define LED_0 LATDbits.LATD0
#define LED_1 LATDbits.LATD1
//*************************************
//
Prototipi delle funzioni
//*************************************
void board_initialization (void);
//*************************************
//
ISR Alta Priorita'
//*************************************
__interrupt (high_priority) void ISR_alta (void) {
// Controllo che l'interrupt sia stato generato da PORTB
if (INTCONbits.RBIF == 1 ) {
//pausa filtraggio spike
delay_ms(10);
// Controllo la pressione di RB4
if (BUTTON_1 == 0) {
// Accendo il LED 1
LED_1 = ~LED_1;
}
// Resetto il flag d'interrupt per permettere nuove interruzioni
INTCONbits.RBIF = 0;
}
}
//*************************************
//
ISR Bassa Priorita'
//*************************************
__interrupt (low_priority) void ISR_bassa (void) {
// Controllo che l'interrupt sia stato generato dal Timer0
if (INTCONbits.TMR0IF == 1 ) {
// Resetto il flag d'interrupt per permettere nuove interruzioni
INTCONbits.TMR0IF = 0;
// Inverto lo stato del LED 0
LED_0 = ~LED_0;
}
431
}
//*************************************
//
main
//*************************************
int main(void) {
board_initialization ();
// Ciclo infinito
while (1){
}
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xF0;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
//************************************************************
// Abilito i pulsanti per le interruzioni ad alta priorit
//************************************************************
//Attivo i resistori di pull-up su PORTB
INTCON2bits.RBPU = 0;
// Abilito le interruzioni su PORTB
INTCONbits.RBIE = 1;
// Abilito le interruzioni su PORTB come alta priorit
INTCON2bits.RBIP = 1;
//************************************************************
// Abilito il Timer0 per funzionare a bassa priorit
//************************************************************
// Modalit a 16 bit
T0CONbits.T08BIT = 0;
432
// Clock interno
T0CONbits.T0CS = 0;
// Abilito Prescaler
T0CONbits.PSA = 0;
// Prescaler 32
T0CONbits.T0PS0 = 0;
T0CONbits.T0PS1 = 0;
T0CONbits.T0PS2 = 1;
// Abilito le interruzioni del Timer0
INTCONbits.TMR0IE = 1;
// Abilito le interruzioni del Timer0 come bassa priorit
INTCON2bits.TMR0IP = 0;
// Abilito il Timer0
T0CONbits.TMR0ON = 1;
//************************************************************
// Abilito le interruzioni
//************************************************************
// Abilito modalit interruzione a due livelli alta e bassa
RCONbits.IPEN = 1;
// Abilito gli interrupt ad alta priorit
INTCONbits.GIEH = 1;
Nel caso del Timer0, la sua impostazione un po' pi laboriosa, visto che
intervengono altri parametri per inizializzare il Timer stesso. Ciononostante i passi per
l'abilitazione delle interruzioni sono sempre gli stessi.
// Modalit a 16 bit
T0CONbits.T08BIT = 0;
433
// Clock interno
T0CONbits.T0CS = 0;
// Abilito Prescaler
T0CONbits.PSA = 0;
// Prescaler 32
T0CONbits.T0PS0 = 0;
T0CONbits.T0PS1 = 0;
T0CONbits.T0PS2 = 1;
// Abilito le interruzioni del Timer0
INTCONbits.TMR0IE = 1;
// Abilito le interruzioni del Timer0 come bassa priorit
INTCON2bits.TMR0IP = 0;
// Abilito il Timer0
T0CONbits.TMR0ON = 1;
I passi qui proposti possono essere anche cambiati, l'importante che vengano
comunque fatti prima dell'abilitazione del Timer. Nel nostro caso si scelta la modalit a
16 bit in maniera da avere ritardi dell'ordine di mezzo secondo senza problemi. Sul come
impostare il registro per avere dei tempi stabiliti si parler nel prossimo paragrafo. Si noti
in particolare che per rallentare ulteriormente il conteggio si fatto uso del Prescaler,
impostato per dividere il clock di un fattore 32. In ultimo, dopo aver abilitato le
interruzioni e selezionata la bassa priorit, viene abilitato il Timer, il quale inizier il
conteggio da 0. A questo punto il programma, uscito dalla funzione di inizializzazione,
non fa altro che stare in un ciclo infinito in attesa delle interruzioni.
In questa applicazione si noti che sono state definite le funzioni sia per gestire le
interruzioni ad alta che a bassa priorit. In entrambe le funzioni la prima cosa che viene
fatta il controllo del bit di stato IF (Interrupt Flag), per accertarsi che l'interruzione sia
stata generata dalla periferica d'interesse. Questo di fondamentale importanza quando
sono presenti pi periferiche che generano interruzioni ad un certo livello. Si ricorda che,
a parit di livello di priorit, la periferica pi importante sempre bene metterla al primo
controllo in modo da limitare il tempo di latency. Una volta verificato che la periferica
d'interesse ha generato l'interruzione, vengono svolte le operazioni necessarie.
Eseguendo il programma si pu vedere che il lampeggio ed il riconoscimento della
pressione del pulsante avvengono in maniera fluida. Inoltre il programma per come
organizzato potrebbe facilmente gestire altre interruzioni senza compromettere la fluidit
del programma originale.
La Microchip mette a disposizione una libreria per i Timer includendo il file
timers.h, ma visto che il Timer possiede un solo registro di controllo non si
fatto uso di alcuna libreria. Per maggiori informazioni sulla libreria timers.h si
rimanda alla documentazione ufficiale che possibile trovare nella cartella docs
presente nel cartella d'installazione del compilatore.
434
1
F OSC
1
=0.05 s
20106
Nel nostro esempio il periodo di 0.2s viene in realt rallentato dal Prescaler che stato
impostato a 32. Questo significa che il periodo del clock in uscita dal Prescaler 32 volte
maggiore:
T PRESCALER=T OSC432=0.05128=6.4s
Il clock in uscita dal Prescaler effettivamente quello utilizzato per far incrementare il
nostro Timer0, il quale impostato per lavorare in modalit a 16 bit. Questo significa che
conter da 0 a 65535. Quando avviene l'overflow, ovvero al passaggio da 65535 a 0, viene
generato un Interrupt che permette l'esecuzione dell'ISR nella quale si fa cambiare lo stato
al LED 0, quindi il nostro LED viene cambiato di stato ogni 0.42 secondi 169.
Cosa bisogna fare per ottenere 0.5 secondi, o un tempo prefissato?
Un modo di procedere il seguente. Considerando i tempi elevati bisogner
sicuramente far uso del Prescaler, che come detto ritarda il clock ottenendo un periodo di
6.4s che come abbiamo appena visto non sufficiente per ottenere il tempo desiderato
neanche facendo contare fino al massimo il Timer0 impostato in modalit a 16 bit.
Questo significa che bisogna utilizzare il Prescaler dividendo per 64 piuttosto che per 32,
ottenendo in questo modo :
T PRESCALER=T OSC464=0.05128=12.8s
In questo caso moltiplicando il nuovo periodo per 65536 (si considerato anche lo 0)
si ha che il nostro ritardo totale 0.839s, ovvero il doppio di prima! Questa volta il nostro
Timer0 rallenta troppo. Per ottenere esattamente 0.5s, quello che si fa non far partire il
Timer0 da 0, ovvero si carica un valore nel timer in modo che raggiunga l'overflow in minor
tempo. Questo significa che potremo in realt ottenere il nostro tempo...
Per capire il valore da caricare nel Timer0 si divide il tempo che si vuole ottenere per il
periodo del clock ottenuto in uscita dal Prescaler, ovvero:
169
Nel conteggio del nostro tempo si e sono trascurati i due cicli di clock di ritardo richiesti per la sincronizzazione del clock
presenti alla prima impostazione del Timer.
435
CONTEGGI =
0.5
T PRESCALER
0.5
=39062.5
0.0000128
TIMER0=2 139062.5=6553639062.5=26473.5
Dal momento che nel Timer0 si possono caricare solo numeri interi necessario
approssimare o troncare il numero, in questo caso dunque il numero da caricare 26473.
Va bene troncare il numero poich questo non sarebbe comunque preciso. Come
precedentemente detto per avere tempi precisi sarebbe necessario anche avere un
oscillatore preciso.
Qualche altra nota importante. Il valore 26473 deve essere caricato all'interno di due
registri ovvero TMR0L e TMR0H. Questo significa che bisogna spezzare il valore 26473
in due byte, per fare questo si deve convertire il numero in binario (per esempio usando la
calcolatrice di Windows). Il numero binario : 110011101101001. Tale numero deve esser
poi suddiviso in due byte:
01100111 01101001
Si noti che la divisione in due gruppi stata fatta partendo da destra. Il primo byte di
destra deve essere caricato nel registro TMR0L, mentre l'altro deve essere caricato nel
registro TMR0H, ovvero:
TMR0H = 0b01100111;
TMR0L = 0b01101001;
Si osservi che il registro TMR0H stato scritto per primo. Questo passo obbligatorio
poich il registro TMR0H non rappresenta effettivamente gli otto bit pi significativi del
Timer0 in modalit 16 bit, bens un buffer (registro di supporto), il cui valore viene
caricato nel registro TMR0H quando avviene la scrittura del registro TMR0L. Questo
significa che per caricare un valore in modalit a 16 bit necessario caricare il valore in
TMR0H prima di scrivere in TMR0L. Per maggiori dettagli si faccia riferimento al
Datasheet del PIC utilizzato.
Le istruzioni sopra citate per caricare il valore nel Timer0 devono essere eseguite subito
dopo aver attivato il Timer0 dunque subito dopo l'inizializzazione del Timer0. Oltre a
questo bisogna ricaricare tale valore ogni volta che si entra nella funzione d'Interrupt del
Timer0170.
Dal momento che il Timer0 pu essere gestito anche in modalit ad 8 bit si pu anche
prendere in considerazione il fatto di far lavorare il registro in tale modalit. Il tutto
dipende dai tempi che dobbiamo ottenere. Nel nostro caso per esempio, pur mettendo il
170
Si noti che il tempo da caricare nella funzione d'Interrupt dovrebbe in realt considerare anche il tempo di latenza per l'avvio
della routine di gestione e il tempo di esecuzione per caricare il valore stesso. Si noti che il tempo di latenza potrebbe non
essere trascurabile se la direttiva #pragma viene dichiarata con l'opzione save, ovvero con l'opzione di salvataggio di un
blocco di memoria.
436
Prescaler a 256 si sarebbe potuto ottenere un ritardo massimo di circa 13ms, per cui il
formato a 16 bit obbligatorio.
In ultimo si fa notare che qualora si debbano ottenere ritardi ben superiori al secondo,
per esempio 1 minuto, ovvero in casi in cui la combinazione del Timer e il Prescaler non
sono sufficienti per ottenere il ritardo voluto; quello che si fa inserire un ulteriore
conteggio all'interno della procedura d'Interrupt 171. Se il tempo dovesse raggiungere valori
dell'ora la soluzione proposta va ancora bene, ma si potrebbe anche prendere in
considerazione l'utilizzo di un Clock Calendar esterno o ottenuto per mezzo del Timer1 ed
un quarzo da 32768KHz.
1230050 Hz
171
Un'alternativa potrebbe essere anche quella di diminuire la frequenza del Clock principale, ma questo non sempre
possibile, visto che alcune periferiche richiedono delle frequenze minime di funzionamento, si pensi per esempio al modulo
USB, I2C e USART.
437
Il Timer1 viene invece usato per il conteggio vero e proprio della frequenza. Infatti il
Timer1 impostato per accettare un clock esterno, ovvero proveniente dall'ingresso RC0.
Il conteggio non avviene in maniera continua da 0 fino all'overflow ma da 65536-10000. In
questo modo il Timer1 genera un'interruzione ogni 10000 impulsi.
Ad ogni Interrupt avviene l'incremento:
frequency_high = frequency_high + 1;
Che contiene dunque il valore della frequenza a partire dalla quinta cifra.
Il valore delle quattro cifre meno significative viene calcolato nell'ISR del Timer0,
secondo questa formula:
frequency_low = 10000 - (65536 - ((buffer_TMR1H << 8) + buffer_TMR1L)) ;
La scelta del dividere la frequenza in due variabili discende dal fatto che la gestione delle
stesse diventa pi semplice nel caso della visualizzazione sul display. Infatti la libreria per
il display fornisce una funzione per la visualizzazione di interi a 16 bit.
La visualizzazione delle due variabili intere avviene in maniera apparentemente complessa,
ma la ragione per tale complessit solamente di tipo estetico e serve per eliminare gli 0
superflui e mantenere allineato il valore visualizzato, indipendentemente dal valore
numerico.
Si noti che l'ISR contiene al suo interno delle operazioni piuttosto complesse, cosa che
in generale si detto che bene evitare. Ciononostante in questo caso non si ha un grosso
problema, infatti nell'ISR i Timer vengono bloccati per cui non si rischia di influenzare
l'accuratezza della misura successiva.
Riassunto
Il Capitolo ha fornito una visione d'insieme dei vari Timer presenti all'interno del
microcontrollore PIC18F4550 mostrando la loro utilit in applicazioni generiche. I
quattro Timer interni al PIC18F4550 permettono di fornire funzioni speciali oltre al
semplice conteggio, ma in base al Timer si hanno o meno certe caratteristiche. Il Timer0
il pi generico e fornisce pi opzioni per quanto riguarda la divisione della frequenza del
clock principale. Il Timer1 e Timer3 permetto come fonte esterna del Clock, l'utilizzo di
un cristallo da 32768Hz, permettendo di realizzare RTCC. Il Timer2 permette di generare
trigger speciali per l'ADC e anche il segnale PWM utile in molte applicazioni analogiche o
per per il controllo dei motori. Oltre a queste ed altre funzioni tutti i Timer possono
essere utilizzati per semplice conteggio, fornendo un interruzione all'overflow o al verificarsi
di un uguaglianza (Timer2).
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
439
Capitolo XV
XV
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 152 o far uso delle schede di sviluppo impostate
come descritto nel paragrafo successivo.
440
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 153: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 154: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
441
Impostazioni software
Gli esempi proposti fanno uso delle librerie LaurTec, per cui necessario impostare i
percorsi di libreria riportati di seguito:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
La versione della libreria inclusa nel percorso potrebbe variare in base a quella utilizzata.
Per semplicit si riporta anche il codice della funzione di inizializzazione del PIC usata nei
vari esempi.
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFF;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
}
442
In particolare il modulo CCP2 ha un pin opzionale che pu essere impostato o sul pin
RC2 o RB2 per mezzo dei configuration bit, ovvero in tempo di compilazione.
Il numero dei moduli CCP dipende dal microcontrollore utilizzato. In particolare per il
PIC18F4550 il modulo CCP1 pu anche lavorare in configurazione Enhanced CCP. In
questa modalit le funzioni Capture e Compare funzionano allo stesso modo ad eccezione
degli eventi di Trigger speciali. La modalit Enhanced PWM introduce molte funzioni
aggiuntive specifiche per applicazioni in cui sia necessario controllare un ponte H o due
mezzi ponte H, come per esempio necessario in un sistema di controllo motori o
convertitori DC-DC. Nel seguito del Capitolo si tratter solo il modulo CCP standard,
lasciando i dettagli del modulo ECCP al Datasheet.
172
Il pin CCP2 posto di default in RC2 potrebbe essere configurato per lavorare con il pin RB3.
443
Lo schema a blocchi mostra come ogni modulo CCPx possieda il relativo pin in
ingresso CCPx e possieda un Prescaler per dividere gli impulsi in ingresso. Successivamente
avviene il rilevamento del fronte di salita o discesa. L'evento che pone ad 1 il Flag
CCPxIF lo stesso che permette di caricare il valore dei registri TMR3H-TMR3L oppure
TMR1H-TMR1L in CCPRxH-CCPRxL. La selezione di un Timer o l'altro avviene tramite
il registro di configurazione T3CON del timer Timer3. Il Timer selezionato deve lavorare
in modalit sincrona al fine di garantire il corretto funzionamento del modulo stesso.
La configurazione Capture viene frequentemente utilizzata per la misura del tempo
percorso tra due eventi. In particolare questo potrebbe essere utilizzato per la misura della
frequenza misurando un solo periodo del segnale. Non insolito che all'interno dell'ISR
assegnato al modulo CCP venga invertito il fronte a cui il modulo risulta sensibile. In
questo modo si potrebbe anche misurare la larghezza di un impulso e non soltanto il suo
periodo.
Rimanere invariato.
173
1
T
Nel caso si faccia uso direttamente dell'uscita del microcontrollore, un segnale PWM assume come livello 0 il valore di
GND, mentre come livello 1 il valore di Vcc.
445
T ON
T
Dove TON rappresenta il tempo che l'impulso rimane a livello alto. Si capisce che T ON
risulter sempre minore o uguale al periodo T. Frequentemente il duty cycle viene espresso
in percentuale:
d=
T ON
100
T
In Figura 157 possibile vedere diversi impulsi di ugual periodo ma con diverso duty
cycle. Nel caso in cui il segnale PWM venga generato per mezzo di un microcontrollore, un
altro parametro caratteristico rappresentato dal numero di bit con il quale viene
generato. Se per esempio il numero di bit 8, il periodo T verr suddiviso in 256
intervalli. La durata di un intervallo calcolata facendo il rapporto tra il periodo T e il
numero d'intervalli. Nel caso particolare del PIC18F4550 si ha che il modulo PWM ha
una risoluzione di 10 bit174 ovvero si hanno 1024 intervalli di tempo per il quale il nostro
periodo pu essere diviso. A seconda del periodo utilizzato si ha che l'intervallo temporale
base avr una durata differente.
Figura 157: Due segnali PWM di pari frequenza ma duty cycle differente.
Facciamo ora un passo indietro e ripensiamo a cosa significa PWM cio Pulse Width
Modulation ovvero modulazione della larghezza dell'impulso.
Un segnale si dice modulato con PWM quando ha un'ampiezza a due livelli, una
174
In realt 10 bit rappresenta la risoluzione massima. La risoluzione effettiva viene a dipendere dal rapporto tra la frequenza
del clock con cui lavora il modulo PWM e la frequenza del segnale PWM generato.
446
frequenza fissa, ed un duty cycle variabile. Modulare il segnale in PWM significa appunto
variare la larghezza del nostro impulso ovvero il duty cycle. Vediamo ora un semplice
esempio che mette in evidenza una delle ragioni per cui si dice che il PWM efficiente
ovvero permette di risparmiare energia. Si consideri la Figura 158, di un LED con in serie
un resistore.
Questo schema normalmente usato in molte applicazioni, ma posso dire che questo
non il modo con cui i LED di retroilluminazione dello schermo del vostro cellulare
sono realmente controllati. La ragione che la resistenza che viene posta per limitare la
corrente dissipa molta energia. Considerando per esempio un LED con una caduta di
potenziale pari ad 1.6V e sul quale si voglia far scorrere una corrente di 10mA,
alimentando il tutto con 5V, si ha che la resistenza in serie che deve essere utilizzata 175:
R=
Si capisce dunque che la potenza dissipata dal resistore, per il solo compito di limitare
la corrente del diodo LED pari a:
P LED=V RI LED =V CCV LED I LED =3.40.01=0.034 W
Il totale dell'energia utilizzata per accendere il LED dunque 0.05W ma solo 0.016W
sono effettivamente convertiti in luce dal LED 176, per cui si ha che il sistema ha
un'efficienza pari a:
=
P LED
0.016
100=
100=32 %
PTOT
0.05
176
Il valore ottenuto non rientra nel valore standard della serie E12. Il valore pi vicino 330 ohm. Questo valore far per
scorrere una corrente lievemente maggiore pari a 10.3mA.
In realt il LED ha anche una propria efficienza per quanto riguarda la conversione dell'energia elettrica in energia luminosa.
447
Il PWM ci viene in aiuto grazie al fatto che modulando il segnale ovvero accendendo e
spegnendo il LED possibile controllare l'energia che viene trasferita. L'energia viene a
dipendere dall'area del nostro segnale quando posto ad 1. Si ricorda che la potenza
rappresenta l'energia consumata da un dispositivo nell'unit di tempo. Dunque se con un
segnale PWM riusciamo a controllare l'energia che trasferiamo ad un dispositivo, quello
che stiamo facendo controllare la potenza di quest'ultimo! Questo significa che
controllando in maniera opportuna il duty cycle del nostro segnale PWM possibile
controllare un LED senza aver bisogno del resistore, ovvero si risparmierebbe la potenza
dissipata sul resistore stesso. Il duty cycle deve essere opportunamente controllato, poich
se eccede i limiti che un determinato dispositivo riesce a sopportare, porterebbe alla
rottura di quest'ultimo.
Nonostante non sia presente la resistenza per limitare la corrente comunque
necessario che il LED sia inserito in un circuito con filtro passa basso al fine di garantire
che la tensione non ecceda i limiti del diodo stesso. Normalmente i driver per LED
comandati in PWM non sono altro che convertitori DC-DC di tipo switching
(normalmente in modalit buck o boost), ed il filtro passa basso rappresentato
dall'induttore e la resistenza di feedback 177 e/o Ron dei MOS utilizzati per il controllo dei
LED stessi.
La tecnica del PWM viene utilizzata in molte altre applicazioni, si pensi per esempio
agli alimentatori switching con cui si ricaricano i cellulari moderni, avrete notato che sono
divenuti pi leggeri che in passato! Larga applicazione anche fatta in dispositivi
alimentati a batteria grazie alle alte efficienze dei regolatori switching rispetto ai vecchi
regolatori lineari178. Per mezzo della modulazione PWM anche possibile realizzare
sistemi di comunicazione tra due sistemi, alla pari di altre tecniche di trasmissione o
modulazione. La modulazione PWM trova ampio utilizzo anche nei sistemi di controllo
per motori, o sistemi di potenza in generale, in cui bisogna controllare la potenza di un
carico. Nel caso di motori grazie alla modulazione PWM possibile controllare la
corrente che scorre nel motore per cui si controlla la coppia.
In ultimo bene ricordare che la tecnica PWM permette di realizzare un convertitore
DAC (Digital to Analog Converter) permettendo per esempio di far parlare un PIC,
convertendo un file .wav o realizzare un generatore di segnali arbitrari.
Da quanto abbiamo studiato sui microcontrollori e sulla modulazione PWM, si forse
giunti alla conclusione che un microcontrollore potrebbe realizzare un segnale PWM
semplicemente controllando un qualunque pin del microcontrollore stesso.
Effettivamente questo possibile, per per ottenere la stessa risoluzione offerta dal
modulo PWM del PIC, ovvero 10bit, il software per il controllo richiederebbe un utilizzo
elevato della CPU, la quale non riuscirebbe pi a compiere altre mansioni. Per mezzo del
modulo CPP invece possibile con poche impostazioni generare un segnale PWM, senza
richiedere ulteriore intervento della CPU, infatti il modulo CCP lavora in maniera
indipendentemente. La CPU deve intervenire solo per cambiare il duty cycle quando
177
178
I LED sono spesso nominati componenti in corrente poich vengono normalmente pilotati in corrente piuttosto che in
tensione. Il controllo della corrente che attraversa il LED, ovvero il controllo del duty cycle del segnale PWM, effettuato
grazie alla resistenza di feedback collegata in serie con il LED stesso. Il valore della resistenza di feedback dell'ordine
dell'ohm, dunque la potenza dissipata dal resistore esigua se confrontata al caso in cui la resistenza viene usata per limitare
la corrente.
Gli alimentatori lineari quali la serie 78xx, hanno ancora ampio utilizzo grazie al basso costo e semplicit di utilizzo. In
applicazioni di precisione i regolatori lineari, siano LDO (Low Drop Out) o meno, sono pi usati degli alimentatori switching
visto che generano meno rumore di quest'ultimi.
448
richiesto. In Figura 159 riportato lo schema a blocchi del modulo CCP in modalit
PWM.
Lo schema a blocchi mette in evidenza che la risoluzione a 10 bit viene ottenuta, dal
lato Timer, unendo 8 bit del registro TMR2 e 2 bit del Prescaler. I dieci bit dal lato del duty
cycle, vengono ottenuti unendo il registro CCPRxL e i bit 5 e 4 del registro CCPxCON. Il
valore con cui viene confrontato il Timer non direttamente CCPRxL ma CCPRxH. Per
cui CCPRxL rappresenta solo il buffer di scrittura. Il valore del registro CCPRxL viene
caricato nel registro CCPRxH solo all'evento di uguaglianza del Timer2 e il registro PR2.
In questo modo si evita che al variare del duty cycle si possano verificare dei glitches. Al
verificarsi dell'uguaglianza tra TMR2 e il registro CCPRxH pi i due bit di estensione,
l'uscita CCPx viene posta a 0, ovvero il latch S-R viene resettato. L'uscita viene posta ad 1
quando TMR2 uguale a PR2.
Da questo si capisce che il periodo del segnale PWM viene a dipendere dal valore
impostato in PR2 mentre il duty cycle dal valore scritto in CCPRxL pi i bit 5 e 4 del
registro CCPxCON. In particolare PR2 legato al valore del periodo del segnale PWM
nel seguente modo:
Periodo PWM =[(PR2 )+1]4T OSCTMR2 prescaler
Da questa relazione si capisce che per poter utilizzare il segnale PWM bisogna anche
attivare 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 Clock generato dal nostro quarzo. Per calcolare questo
basta fare l'inverso della frequenza del quarzo stesso, qualora questo sia generato per
mezzo di un quarzo esterno, si ha T OSC =1/ F OSC . Vediamo la formula inversa per il
calcolo del registro PR2 una volta note le altre grandezze:
449
PR2=
Periodo PWM
1
4T OSCTMR2 prescaler
Per poter controllare il timer TMR2 si pu far uso della libreria XC8 timers.h, piuttosto
che controllare i singoli registri come fatto negli esempi di controllo del Timer0.
E' importante notare che sebbene il modulo PWM supporti 10 bit di risoluzione, quella
effettiva viene a dipendere dal valore della frequenza dell'oscillatore e quella del segnale
PWM generato, ovvero:
log
Risoluzione Bit=
( )
F OSC
F PWM
log ( 2 )
bits
In particolare se la risoluzione in bit dovesse essere di 8 bit ovvero con un duty cycle
massimo pari a 255, se si dovesse scrivere un duty cycle pari a 512, che apparentemente
sarebbe il 50% per una risoluzione a 10 bit, il segnale PWM sarebbe invece del 100%
ovvero l'uscita sarebbe fissa ad 1. Per cui non bisogna mai scrivere valori del duty cicle
maggiori del valore della risoluzione effettiva, al fine di evitare di avere il pin CCPx fisso
ad 1.
CCPxCON
CCPR2L
CCPR2H
In particolare la x deve essere sostituita con 1 o 2 a seconda del modulo che si sta
configurando. In Tabella 29 sono riportati i vari registri e bit associati al modulo CCP.
450
Nome
BIT7
BIT6
BIT5
BIT4
BIT3
CCP2RL
CCP2RH
INTCON
GIE/GIEH PEIE/GIEL
BIT2
BIT1
BIT0
TMR0IE
INT0IE
RBIE
TMR0IF
INTIF
RBIF
PIR1
SPPIF
ADIF
RCIF
TXIF
SSPIF
CCP1IF
TMR2IF
TMR1IF
PIE1
SSPPIE
ADIE
RCIF
TXIF
SSPIE
CCP1IE
TMER2IE
TMR1IE
IPR1
SPPIP
ADIP
RCIP
TXIP
SSPIP
CCP1IP
TMR2IP
TMR1IP
PIR2
OSCIF
CMIF
USBIF
EEIF
BCLIF
HLVDIF
TMR3IF
CCP2IF
PIE3
OSCIE
CMIE
USBIE
EEIE
BCLIE
HLVDIE
TMR3IE
CCP2IE
IPR2
OSCIP
CMIP
USBIP
EEIP
BCLIP
HLVDIP
TMR3IP
CCP2IP
CCP1CON
P1M1179
P1M0180
DC1B1
DC1B2
CCP1M3
CCP1M2
CCP1M1
CCP1M0
CCP2CON
DC1B1
DC1B2
CCP2M3
CCP2M2
CCP2M1
CCP2M0
TRISB
TRISB7
TRISB6
TRISB5
TRISB4
TRISB3
TRISB2
TRISB1
TRISB0
TRISC
TRISC7
TRISC6
TRISC2
TRISC1
TRISC0
Il contenuto dei registri presentati nel Capitolo sono forniti per semplificare la
comprensione delle librerie e dei programmi, e non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
I registri riportati sopra escludono quelli associati al Timer1, Timer3 e Timer2. In
particolare in modalit Capture e Compare bisogna opportunamente configurare il registro
T3CON ed eventualmente anche T1CON, qualora il Timer1 sia utilizzato come
riferimento. Nel caso in cui il modulo CCP dovesse essere utilizzato in configurazione
PWM, bisogna impostare in maniera opportuna il Timer2 ovvero T2CON.
Il bit 6 e 7 non sono presenti nei PIC a 28 pin della famiglia PIC18F4550.
Il bit 6 e 7 non sono presenti nei PIC a 28 pin della famiglia PIC18F4550.
451
La linea RC2 rappresenta il segnale CCP1 e deve essere impostato come Output.
Nella modalit Compare le linee RC1 e RC2 potrebbero anche non essere usate
qualora si volesse solo sfruttare l'interrupt e la chiamata all'ISR.
La linea RC1, associata al modulo CCP2 pu essere alternativamente assegnata sul pin
RB3. Per fare questo bisogna impostare il registro di configurazione CONFIG3H per
mezzo della direttiva #pragma.
In particolare includendo il file di configurazione:
#include PIC18F4550_config.h
452
U-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
182
DC1B1
DC1B2
CCP1M3
CCP1M2
CCP1M1
CCP1M0
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
181
Bit 7
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7-6
Bit 5-4
S : Settable bit
x = Bit is unknown
181
182
Il bit 6 e 7 non sono presenti nei PIC a 28 pin della famiglia PIC18F4550.
Il bit 6 e 7 non sono presenti nei PIC a 28 pin della famiglia PIC18F4550.
453
Descrizione
d'interesse
Parametri:
void
Restituisce:
void
Esempio:
// Chiude il modulo 1
ClosePWM1 ();
Per mezzo di questa funzione possibile impostare il periodo del segnale PWM. Si
ricorda che il periodo l'inverso della frequenza f =1/T .
Parametri:
period : valore del registro PR2 calcolato in funzione del periodo del segnale PWM
183
La libreria supporta le varie versioni del modulo PWM relative ai vari PIC. Di questo non si parlato ma bello pensare che
la libreria penser a tutto!
454
Restituisce:
void
Esempio:
Si veda il Progetto come esempio
void SetDCPWMx (unsigned int dutycycle)
Per mezzo di questa funzione possibile impostare il duty cycle del segnale PWM.
Parametri:
dutycycle : Il duty cycle pu variare da un minimo di 0 a un massimo 1023 (10bit).
Un duty cycle pari a 0 vincola il segnale PWM a 0 mentre un duty cycle pari a 1023
vincola il segnale PWM a 1.
Restituisce:
void
Esempio:
// Aggiorno il duty cycle
SetDCPWM2 (820);
455
//*************************************
//
Prototipi delle funzioni
//*************************************
void board_initialization (void);
//*************************************
//
Costanti
//*************************************
#define RISING_EDGE 0x01
#define FALLING_EDGE 0x02
#define NONE_EDGE 0x03
#define PULSE_WIDTH 2
//*************************************
//
Variabili globali
//*************************************
unsigned char edge = RISING_EDGE;
unsigned int start = 0;
unsigned int stop = 0;
signed int time = 0;
//*************************************
//
ISR Alta Priorita'
//*************************************
__interrupt (high_priority) void ISR_alta (void) {
unsigned int buffer = 0;
if (PIR1bits.CCP1IF == 1 ) {
// Resetto il flag d'interrupt per permettere nuove interruzioni
PIR1bits.CCP1IF = 0;
switch (edge){
case RISING_EDGE :
edge = FALLING_EDGE;
break;
case FALLING_EDGE:
456
//*************************************
//
main
//*************************************
int main(void) {
board_initialization ();
delay_ms(1000);
//Genero un impulso
LATDbits.LATD0 = 0x01;
delay_ms(PULSE_WIDTH);
LATDbits.LATD0 = 0x00;
while (1){
if (edge == NONE_EDGE) {
// Disabilito l'interrupt globale
INTCONbits.GIE = 1;
// Disabilito il modulo CCP
CCP1CONbits.CCP1M0 = 0;
CCP1CONbits.CCP1M1 = 0;
CCP1CONbits.CCP1M2 = 0;
CCP1CONbits.CCP1M3 = 0;
LCD_clear ();
LCD_write_message ("T : ");
if (stop >= start) {
time = (stop - start)/5;
} else {
time = ((65535 - start) + stop)/5;
}
LCD_write_integer (time, 5, ZERO_CLEANING_ON);
LCD_write_message (" us");
while (1);
}
}
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xF0;
457
// Imposto PORTC
LATC = 0x00;
TRISC = 0b11111101;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
//************************************************************
// Imposto LCD
//************************************************************
LCD_initialize (20);
LCD_write_message ("Trigger...");
LCD_backlight (TURN_ON_LED_LCD);
//************************************************************
// Imposto CCP1
//************************************************************
// Imposto la modalita' Capture sul fronte di salita
CCP1CONbits.CCP1M0 = 1;
CCP1CONbits.CCP1M1 = 0;
CCP1CONbits.CCP1M2 = 1;
CCP1CONbits.CCP1M3 = 0;
// Abilito le interruzioni ad alta priorita'
PIE1bits.CCP1IE = 1;
IPR1bits.CCP1IP = 1;
//************************************************************
// Imposto il Timer3
//************************************************************
// Utilizzo il Timer3 per entrambi i moduli CCP
T3CONbits.T3CCP2 = 1;
T3CONbits.T3CCP1 = 1;
// Imposto il Prescaler ad 1
T3CONbits.T3CKPS1 = 0;
T3CONbits.T3CKPS0 = 0;
// Imposto il clock a Fosc/4
T3CONbits.TMR3CS = 0;
// Attivo il Timer
T3CONbits.TMR3ON = 1;
//************************************************************
// Abilito le interruzioni
//************************************************************
//Resetto i Flag delle interruzioni d'interesse
PIR1bits.CCP1IF = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
458
Dal codice possibile notare come la misura del tempo avvenga per mezzo del Timer3,
il cui clock proviene dal cristallo principale ovvero Fosc/4. Per tale ragione la risoluzione
del tempo 0.2s. Per la misura del tempo si imposta il modulo CCP per essere sensibile
sul fronte dei salita in maniera tale da rilevare il tempo nel momento in cui viene posta ad
1 l'uscita RD0. Nel momento in cui avviene l'evento di Capture, viene salvato il tempo
nella variabile start, e viene invertito il fronte a cui sensibile il modulo Capture, ovvero
viene impostato il fronte di discesa. Quando viene posto a 0 il pin RD0, viene a verificarsi
il secondo evento di Capture e viene salvato il tempo nella variabile stop. Tra le altre
funzioni, la variabile di stato edge, viene posta su NONE_EDGE in maniera da permettere al
loop principale di accorgersi del termine della misura. Una volta che sia il tempo di start
che stop sono disponibili, sono disabilitate le interruzioni e il modulo CCP1. Il tempo
viene calcolato tenendo conto del valore di start e di stop e si divide il valore per 5 al
fine di avere una risoluzione di 1s. Il valore risultante viene poi visualizzato sul display
LCD.
// Azzero il Timer 3
TMR3H = 0x00;
TMR3L = 0x00;
459
//*************************************
//
main
//*************************************
int main(void) {
board_initialization ();
}
while (1);
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xF0;
// Imposto PORTC
LATC = 0x00;
TRISC = 0b11111101;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
//************************************************************
// Imposto CCP2
//************************************************************
// Imposto la modalita' Toggle
CCP2CONbits.CCP2M0 = 0;
CCP2CONbits.CCP2M1 = 1;
CCP2CONbits.CCP2M2 = 0;
CCP2CONbits.CCP2M3 = 0;
// Imposto i Compare Register per 50Hz
CCPR2L = 0x6A;
CCPR2H = 0x18;
// Abilito le interruzioni ad alta priorita'
PIE2bits.CCP2IE = 1;
IPR2bits.CCP2IP = 1;
//************************************************************
// Imposto il Timer3
//************************************************************
// Utilizzo il Timer3 per entrambi i moduli CCP
460
T3CONbits.T3CCP2 = 1;
T3CONbits.T3CCP1 = 1;
// Imposto il Prescaler ad 8
T3CONbits.T3CKPS1 = 1;
T3CONbits.T3CKPS0 = 1;
// Imposto il clock a Fosc/4
T3CONbits.TMR3CS = 0;
// Attivo il Timer
T3CONbits.TMR3ON = 1;
//************************************************************
// Abilito le interruzioni
//************************************************************
//Resetto i Flag delle interruzioni d'interesse
PIR2bits.CCP2IF = 0;
// Abilito l'interrupt globale
INTCONbits.GIE = 1;
// Abilito l'interrupt periferiche
INTCONbits.PEIE = 1 ;
}
Dalla soluzione si pu notare che non si fatto uso di alcuna libreria. Infatti i registri
da configurare sono solo due. Al fine di usare l'uscita RC2, il relativo pin stato
impostato come uscita. Il modulo CCP2 stato impostato per lavorare in modalit Toggle,
ovvero al raggiungimento dell'uguaglianza viene invertito il valore di RC1. Il valore per il
confronto caricato nei relativi registri CCPR2H e CCPR2H.
// Imposto i Compare Register per 50Hz
CCPR2L = 0x6A;
CCPR2H = 0x18;
Il valore discende dal fatto che il Timer3 esegue il conteggio per mezzo di Fosc/4.
Considerando un cristallo a 20MHz si ha che Fosc/4 pari a 5MHz ovvero con periodo
da 0.2s. Dal momento che il Prescaler impostato ad 8 si ha che il clock reale fornito al
Timer3 ha un periodo di 1.6s. Volendo raggiungere una frequenza di 50Hz, ovvero un
periodo di 0.02s, si ha che sono necessari 12500 cicli del clock in uscita dal Prescaler. In
realt dal momento che ogni periodo richiede due Toggle, bisogna dividere il numero di
cicli per 2, ovvero 6250 cicli, che in esadecimale equivale a 0x186A. Dividendo questo
valore nei due registri, si ha la configurazione sopra.
Dopo la configurazione del modulo CCP2 viene configurato il Timer3. Questo
necessario sia per la configurazione del Timer3 sia per associare il Timer3 al modulo
CCP2. Si noti che il Timer3 non impostato per generare alcun interrupt, anche se
formalmente potrebbe generarne uno ad ogni overflow.
Diversamente dal Timer3, per il modulo CCP2 si sono attivate le interruzioni. In
particolare nell'ISR viene resettato il valore del Timer3 al fine da permettere un nuovo
confronto con i registri CCPR2H e CCPR2H. Se cosi non si facesse il Timer3 andrebbe
sempre in overflow prima di cominciare nuovamente il conteggio, impedendo di ottenere la
frequenza desiderata.
461
462
SetDCPWM2 (duty_cycle);
// Incremento il duty cycle
duty_cycle++;
// Controllo che non sia maggiore di 2^10
if (duty_cycle > 1023) {
duty_cycle =0;
}
delay_ms (2);
}
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0b11111101;
// Imposto PORTD
LATD = 0x00;
TRISD = 0xFF;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
}
Come prima cosa, si noti che diversamente dagli altri programmi si sono incluse le
seguenti librerie:
#include <pwm.h>
#include <timers.h>
Questo non strettamente necessario, visto l'esiguo numero dei registri, ma pu essere
considerato un semplice esempio d'uso della libreria. Si ricorda che per utilizzare il pin
CCP2 sul pin RC1 bisogna specificare la seguente configurazione (gi presente nel file di
configurazione):
#pragma config CCP2MX = ON
Per quanto riguarda le impostazioni dei pin si noti che il pin RC1 stato impostato
come uscita, in modo da permettere il corretto funzionamento del modulo PWM.
// Imposto PORTC tutti ingressi ed RC1 come uscita
463
LATC = 0x00;
TRISC = 0b11111101;
Come passo successivo si abilitato il modulo Timer2 per poter impostare il Prescaler
utilizzato dal modulo CCP2.
// Apro il TMR2 per il PWM
OpenTimer2( TIMER_INT_OFF & T2_PS_1_1 & T2_POST_1_1);
Si noti che il il Prescaler impostato ad 1:1 come anche il Postscaler. Questo significa
che ai fini del PWM in realt ininfluente, perlomeno nel nostro caso.
Dopo aver impostato il Timer2, si imposta il periodo del modulo PWM e lo si attiva.
period = 249;
// Apro il moldulo PWM
OpenPWM2(period);
si ha che il periodo pari a 0.05ms ovvero pari ad una frequenza di 20KHz. Una volta
impostato il periodo e avviato il modulo PWM si effettua la modulazione del segnale
all'interno del ciclo infinito. La modulazione consiste semplicemente nell'incrementare il
duty cycle e aggiornare il suo valore nel modulo PWM. In questo modo si ha che l'intensit
del LED di retroilluminazione tender ad aumentare d'intensit fino a raggiungere il
valore massimo. Per rendere la variazione del LED pi lenta, si posto un semplice
ritardo per mezzo della funzione di delay_ms. All'interno del nostro ciclo anche
presente il seguente controllo:
// Controllo che non sia maggiore di 2^10
if (duty_cycle > 1023) {
duty_cycle =0;
}
Tale controllo necessario dal momento che il modulo PWM del PIC ha un
risoluzione a 10 bit. Raggiunto il valore massimo, il duty cycle viene posto nuovamente a 0
per iniziare un nuovo conteggio. Da un punto di vista visivo si ha che il LED di
retroilluminazione viene spento.
Qualora si facesse uso di frequenze PWM di valore maggiori a 20KHz, si deve
verificare l'effettiva risoluzione del modulo PWM, per cui potrebbe essere
necessario il confronto con un valore pi piccolo.
464
465
if (PIR1bits.TMR2IF == 1) {
PIR1bits.TMR2IF = 0;
set_duty_cycle (sample_value);
repeat++;
if (repeat == REPEATING_FACTOR) {
repeat = 0;
sample_index++;
if (sample_index >= NUMBER_OF_SAMPLES) {
if (sample_index > NUMBER_OF_SAMPLES + DELTA_FOR_1_SECOND)
{
sample_index = 0;
} else {
sample_value = BASE_LINE;
}
}
}
} else {
sample_value = ecg_signal[sample_index];
}
//*************************************
//
Main
//*************************************
int main (void){
board_initialization ();
// Imposta la frequenza PWM a 20KHz
PR2 = 249;
set_duty_cycle (BASE_LINE);
// Abilita il TMR2 eimposta Prescaler a 1:1
T2CON = 0x04;
turn_on_PWM1 ();
// Attiva le interruzioni del Timer 2
PIE1bits.TMR2IE = 1;
PIR1bits.TMR2IF = 0;
// Standard interrupt
RCONbits.IPEN = 0;
// Attiva global interrupt
INTCONbits.GIE = 1;
// Attiva Peripheral Interrupt
INTCONbits.PEIE = 1 ;
while (1){
466
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0b11111010;
// Imposto PORTD
LATD = 0x00;
TRISD = 0xFF;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
}
//************************************
//
Imposta il Duty Cycle
//************************************
void set_duty_cycle (unsigned int duty_cycle) {
CCPR1L = (unsigned char) (duty_cycle >> 2);
if (duty_cycle & 0x0001)
CCP1CONbits.DC1B0 = 0x0001;
else
CCP1CONbits.DC1B0 = 0x0000;
if (duty_cycle & 0x0002)
CCP1CONbits.DC1B1 = 0x0001;
else
CCP1CONbits.DC1B1 = 0x0000;
}
//************************************
//
Attiva il modulo PWM
//************************************
void turn_on_PWM1 (void) {
CCP1CON = CCP1CON | 0b00001100;
}
467
F PWM
20000Hz
=
=200
F Campione
100Hz
200
All'interno dell'ISR associata al Timer2, ogni campione, ovvero valore del duty cycle, viene
ripetuto per tale fattore. Visualizzati tutti i campioni dell'ECG contenuti nell'Array , non
viene ripetuto nuovamente il primo campione della tabella, visto che bisogna ancora
impostare la frequenza del battito cardiaco a 60Hz. Per fare questo sono introdotti
ulteriori campioni per giungere ad 1s. Dal momento che il segnale dura circa 0.64s, sono
inseriti altri 36 campioni.
184
Controllando lo spettro di un segnale ECG si pu vedere che gran parte dell'energia contenuta in circa 30Hz per cui con
una banda di circa 50Hz si pu effettivamente ben rappresentare l'informazione associata a tale tipologia di segnale.
468
Il valore assunto dal campione inserito per il raggiungimento del periodo di 1 secondo,
pari al valore della linea isoelettrica, che nel caso dei campioni disponibili posta a :
#define BASE_LINE 103
Che nel caso specifico ha un nome che fa capire che per il raggiungimento del periodo
di 1 s. Quindi nel caso di una frequenza cardiaca, a frequenza maggiore si mantiene
costante il segnale base e si aggiunge solo un ritardo opportuno. Nel caso di segnali di
altra natura questo non in generale vero. Infatti cambiare la frequenza vorrebbe dire
cambiare il periodo con cui vengono riprodotti i vari campioni.
Per testare il programma, necessario aggiungere il circuito come riportato in Figura 152,
ovvero collegare il filtro passa basso all'uscita RC2 (modulo CCP1). In particolare la
frequenza di taglio del filtro stata posta a circa 30Hz, ovvero R=5600 e C= 0.1F.
Per la verifica del segnale generato si pu far uso di un oscilloscopio o del software
Data Scope185 abbinato al software da caricare nella relativa scheda di sviluppo. In
particolare le impostazioni da utilizzare per un PIC18F4550 o PIC18F14K50 sono:
185
469
I dati visualizzati sullo schermo, ponendo una base dei tempi pari a 400ms per divisione,
sono riportati in Figura 161.
Figura 161: Misura del segnale in uscita dal filtro passa basso.
Riassunto
Il Capitolo ha introdotto il modulo CCP spiegando le sue diverse impostazioni in
modalit Capture, Compare e PWM. In particolare la modalit Capture permette di misurare
il tempo tra due eventi di Trigger configurabili nel modulo stesso. La modalit Compare
permette di generare dei tempi di riferimento confrontando il Timer1 o Timer3 con i
registri di configurazione del modulo CCP. La modalit PWM permette di generare un
470
segnale PWM utilizzabile per il controllo della luminosit di LED, velocit di motori o
convertitori digitali analogici.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
471
Capitolo XVI
XVI
Schema elettrico
Per poter seguire gli esempi presentati in questo Capitolo, richiesto realizzare su
Breadboard lo schema elettrico di Figura 162 o far uso delle schede di sviluppo
impostate come descritto nel paragrafo successivo.
472
Freedom II
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo Capitolo la
scheda di sviluppo Freedom II deve avere i Jumper posizionati nel seguente modo:
Figura 163: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Freedom Light
Al fine di poter eseguire in maniera corretta gli esempi presentati in questo capitolo la
scheda di sviluppo Freedom Light deve avere i Jumper posizionati nel seguente modo:
Figura 164: Impostazioni dei Jumper per i Progetti presentati nel Capitolo.
Per la scheda Freedom Light la posizione del Jumper JP01 del trimmer,
mutuamente esclusiva tra l'utilizzo del modulo LCD (regolazione contrasto) e
l'uso del trimmer con il modulo ADC.
473
Impostazioni software
Gli esempi proposti fanno uso delle librerie LaurTec, per cui necessario impostare i
percorsi di libreria riportati di seguito:
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\conf
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\inc
[percorso radice della libreria]\LaurTec_PIC_libraries_v_3.3.0\src
La versione della libreria inclusa nel percorso potrebbe variare in base a quella utilizzata.
//*************************************
//
Implementazione della funzione
//*************************************
void board_initialization (void) {
// Imposto PORTA
LATA = 0x00;
TRISA = 0xFF;
// Imposto PORTB
LATB = 0x00;
TRISB = 0xFF;
// Imposto PORTC
LATC = 0x00;
TRISC = 0xFD;
// Imposto PORTD
LATD = 0x00;
TRISD = 0x00;
// Imposto PORTE
LATE = 0x00;
TRISE = 0xFF;
}
474
Figura 165: Esempio di segnale tempo continuo (a) e tempo discreto (b)
475
convertire poi in valore numerico il campione. Rappresentando con uno schema a blocchi
quanto appena detto, si ha:
Nello schema a blocchi l'interruttore rappresenta quello che noto come SH ovvero
Sample and Hold (campiona e mantieni). La sua realizzazione viene generalmente ottenuta
per mezzo di un interruttore realizzato in tecnologia MOS, per mezzo del quale si fa
caricare un condensatore al valore della tensione d'ingresso. L'interruttore viene aperto e
chiuso ad intervalli regolari in modo da campionare il segnale e permettere
successivamente la sua conversione. In particolare la conversione avviene durante la fase
in cui l'interruttore viene riaperto e il condensatore stato propriamente caricato al valore
della tensione in ingresso.
Spesso il SH integrato nel modulo ADC, al quale dunque possibile applicare
direttamente il segnale. Quanto appena spiegato rappresenta una versione molto
semplicistica di quello che sta dietro la teoria di conversione analogico digitale.
Il convertitore ADC che permette di convertire in valore numerico il nostro segnale
pu essere realizzato in molti modi a seconda delle applicazioni. Tra le tipologie pi note
si ricorda il convertitore ADC flash, ADC a rampa, ADC a doppia rampa ed ADC ad
approssimazione successive (SAR, Successive Approximation Register). In particolare i PIC18
possiedono un convertitore ad approssimazioni successive.
Ogni ADC caratterizzato da alcuni parametri per mezzo dei quali si pu verificare se
lo stesso idoneo per determinate applicazioni. Uno dei parametri principali il numero
di bit, ovvero il numero di valori per mezzo dei quali si pu rappresentare un segnale. Un
ADC a 10bit pu per esempio convertire una grandezza per mezzo di 10bit ovvero 1024
valori, da 0 a 1023. La discretizzazione normalmente di tipo lineare ovvero i vari valori
sono tra loro equidistanti.
Un altro parametro importante dell'ADC il valore di fondo scala, ovvero il valore
massimo di tensione che pu accettare. Dividendo il valore di fondo scala per il numero
di valori per i quali l'ADC pu convertire un segnale, si ha il valore minimo del segnale
rilevabile, noto come quanto q. L'errore massimo che un ADC commette nel convertire
un segnale analogico in digitale di q/2, pi altri errori quali INL (Integral non Linearity),
DNL (Differential non Linearity). Un ADC, a seconda della tipologia, converte un campione
del segnale d'interesse in tempi differenti. In particolare l'ADC, al fine di poter essere
sincronizzato con il mondo esterno possiede al suo interno della logica digitale per mezzo
della quale effettua la conversione numerica e pone il risultato sulle linee di uscita. Dal
momento che un ADC SAR una sistema digitale a stati, necessita di un Clock per poter
svolgere la sua attivit. Nel caso di un convertitore ad approssimazioni successive
476
187
188
In realt si cerca di evitare di raggiungere full scale, spesso i Datasheet specificano i vari parametri a -1dBFS ovvero un dB al
disotto del fondo scala.
Il teorema di Shannon, generalizzando il teorema del campionamento per segnali non in banda base, permette di avere
anche segnali a banda finita non centrati nello zero. In questi casi per limitare la banda si fa uso di filtri passa banda e non
non passa basso.
477
registri a 8 bit, ovvero ADRESH e ADRESL. Il valore pu essere giustificato sia a destra
che a sinistra. In particolare con la giustificazione a sinistra, i due bit meno significativi
sono in ADRESL, per cui facendo uso del solo registro ADRESH come se si facesse
una conversione a 8 bit e non a 10 bit, visto che si ignorerebbero i due bit meno
significativi.
Dalla Figura 167 si pu anche notare che il canale da convertire pu essere selezionato
per mezzo dei bit CHS3:CHS0. Il modulo interno fornisce inoltre la possibilit di
cambiare la tensione di riferimento dal valore di alimentazione (Vdd-Vss) ad un valore
esterno. Usare una sorgente di riferimento esterna permette in generale di ottenere
performance migliori da un punto di vista dell'accuratezza, ma per un ADC a 10 bit
questo non spesso necessario. Normalmente si fa uso di riferimenti esterni qualora si
stiano effettuando misure con risoluzioni a 12 bit o superiori 189. Nel caso di ADC a 10 bit
si pu pensare di usare un riferimento di tensione esterno, qualora si voglia cambiare il
valore di fondo scala dell'ADC e adattarlo al sensore, senza le pretese di ottenere una
miglior accuratezza dal lato della sorgente di riferimento.
Come accennato in precedenza ci sono delle frequenze di campionamento minime che
bisogna rispettare, in base alla banda del segnale che si sta misurando. Nel caso di misure
DC, ovvero a frequenza 0Hz o lentamente variabili, come la temperatura, un semplice
filtro con una capacit da 0.1uF-1uF in parallelo con il sensore, pu in generale essere
sufficiente per limitare la banda ma soprattutto il rumore che si verrebbe a misurare
qualora la banda non fosse limitata. Questa esigenza ci fa capire che il modulo ADC ed in
particolare il clock utilizzato dal modulo stesso, deve essere opportunamente selezionato.
In particolare la frequenza di clock da utilizzare pu essere selezionata per mezzo del
registro ADCON2 e i bit ADCS2:ADCS0. Tra le varie opzioni si pu selezionare il clock
interno RC o Fosc, opportunamente diviso per ottenere una frequenza inferiore. Il
periodo del clock selezionato rappresenta il cosiddetto Tad. Una volta impostato il
modulo ADC e selezionato il canale da convertire, si pu avviare una conversione per
mezzo del bit GO/DONE. Tale bit rimane ad 1 fino a quando non termina la
conversione. Terminata la conversione oltre ad essere resettato il bit GO/DONE viene
posto ad 1 il flag ADIF, e se abilitate, viene generata un'interruzione di fine conversione.
Per capire meglio cosa avviene durante la fase della conversione, vediamo in maggior
dettaglio il modello d'ingresso del modulo ADC, come riportato in Figura 168.
Ci sono tecniche che rientrano nella cosiddetta teoria dei segnali, che permettono di ottenere anche 12bit di risoluzione a
partire da un ADC con risoluzione inferiore.
479
esterno, con in serie una resistenza (impedenza della sorgente). In particolare viene
mostrata la capacit CHOLD, posizionata subito dopo l'interruttore di SS. Questa, prima di
poter avviare la conversione da analogico a digitale, deve essere caricata al valore della
tensione di ingresso. Per fare questo, dopo aver avviato una conversione per mezzo del
bit GO/DONE, viene chiuso l'interruttore SS per un tempo definito come tempo di
acquisizione. Questo tempo variabile ed configurabile per mezzo dei bit
ACQT2:ACQT0 presenti nel registro ADCON2. Per capire il significato dei bit 0-5
necessario comprendere meglio il modello del pin analogico. infatti tale comprensione
che permette di scegliere il valore ottimale dei bit 0-5. Diciamo pure che mettendosi nel
caso peggiore, ovvero ponendo i bit 3-5 tutti ad 1, e i bit 0-2 a 011 si sta pi o meno
coperti, senza per sapere il perch! Dalla Figura 168 possibile vedere che il generatore
VAIN rappresenta il modello del nostro segnale s(t), mentre alla destra del modello
presente il nostro interruttore che permette di campionare il segnale e far caricare il
condensatore al valore assunto dal segnale in quel determinato momento. Il modulo ADC
provveder poi al calcolo del valore numerico da associare.
Quando si applica una tensione ad un condensatore, spero sia noto che il condensatore
si caricher al valore di tensione a cui stato sottoposto. Se tra il generatore di tensione ed
il condensatore presente una resistenza, il condensatore richieder del tempo prima di
caricarsi al valore finale, in particolare l'andamento della carica del condensatore di tipo
esponenziale. Si capisce che affinch il condensatore si carichi al valore d'interesse
necessario che l'interruttore rimanga chiuso un tempo minimo, che sar funzione della
resistenza totale presente nel percorso di carica. Non considerando la corrente di leakage,
ovvero di perdita, la resistenza totale pari alla somma delle resistenze R S, RIC, ed RSS. In
particolare il tempo di acquisizione risulta pari a:
T ACQ =Amplifier Settling TimeHolding Capacitor Charging TimeTeperature Coefficient
ovvero:
T ACQ =T AMPT C T COFF
Il valore TAMP viene a dipendere dal settling time dell'operazionale eventualmente utilizzato
come buffer, al fine di realizzare un filtro passa basso e diminuire l'impedenza della
sorgente. Il valore di TC pari a:
T C =C HOLD R IC RSS R S ln
1
2048
RSS = 2K @ VCC = 5V
RIC = 1K (caso peggiore)
Temp = 85C (temperatura per applicazioni industriali)
Calcolando i vari termini si ha che:
TAMP = 0 s
TC = 1.05 s
TCOFF = 1.2 s
dunque il tempo di acquisizione totale pari a:
T ACQ =T AMPT C T COFF =01.051.2=2.25 s
Ora torniamo ai bit 0-2 del registro ADCON2. Questi bit stabiliscono la frequenza a
cui il nostro ADC elaborer le informazioni ovvero il suo Clock. Il PIC18F4550 come
detto ha un ADC con approssimazioni successive dunque per effettuare la conversione
richieder un tempo pari 11 T AD. In realt il tempo richiesto di 12 T AD poich un
periodo viene utilizzato per scaricare il condensatore CHOLD190. Come visibile dal registro
ADCON2 il Clock pu essere derivato dal Clock principale o da quello RC interno.
Utilizzando il Clock RC interno possibile far lavorare il convertitore anche quando il
microcontrollore in stato di SLEEP. Formalmente si potrebbe utilizzare la frequenza
operativa pi alta, in generale pari a FOSC/2. Tale valore deve per essere scelto in maniera
tale che il numero di T AD impostati per il tempo di acquisizione, ovvero il valore dei bit 35, sia tale da poter garantire il valore T ACQ precedentemente calcolato e che TAD sia almeno
superiore a 0.7s191.
Considerando per esempio una frequenza operativa del PIC pari a 20MHz,
selezionando FOSC/2 si ha che TAD pari a 0.1 s, questo valore inferiore al minimo T AD
di 0.7s, per cui non pu essere selezionato, indipendentemente dal valore dei bit
ACQT2:ACQT0. Per garantire il TAD minimo usando 20MHz necessario impostare i bit
ADCS2:ADCS0 al valore 101 ovvero a Fosc/16. In questo modo il TAD pari a 0.8s,
ovvero il TAD minimo viene rispettato. Volendo raggiungere T ACQ di 2.25s calcolato, si
necessita di almeno 3 TAD per cui bisogna impostare il tempo di acquisizione al valore
maggiore disponibile che pi lo avvicina, ovvero 4.
Avendo calcolato il nostro tempo di acquisizione per una resistenza di sorgente pari a
2.5K, si capisce che rallentando ulteriormente il tempo di acquisizione si potrebbero
anche avere resistenze maggiori di 2.5K. Si ricorda che il Datasheet sconsiglia
comunque di non avere valori di RS superiori a 2.5K, in particolare con RS pari a circa
25K, considerando le correnti di leakage, si verrebbe ad avere un errore pari a q/2 (met
del quanto). Qualora il numero di TAD non fossero sufficienti o si volesse comunque
inserire un tempo di acquisizione personale, possibile impostare i bit 3-5 del registro
ADCON2 a 0. Questo valore comporta l'obbligo che prima di avviare un'acquisizione,
190
191
Questa una tecnica che permette di ottimizzare il SH, facendo in modo che questo si trovi sempre a caricare il
condensatore, piuttosto che far seguire a quest'ultimo il nuovo valore di tensione.
Tale parametro varia in base al modello del PIC utilizzato, per cui bene far riferimento al Datasheet del PIC utilizzato. I
modelli LF, all'interno della stessa famiglia hanno anche valori differenti dal modello F.
481
non potranno pi essere utilizzati come digitali. Gli altri pin analogici possono invece
essere utilizzati sia come analogici che digitali, cambiando la loro funzione durante
l'esecuzione del programma. Si noti che dalla Tabella di assegnazione pin, non possibile
assegnare un pin come analogico in qualunque combinazione. Se per esempio si volesse
utilizzare il pin AN3, sar comunque necessario avere impostati AN2 e AN1 come
analogici. Questo comporta che durante la fase progettuale in cui si assegnano i pin
analogici sempre bene fare le giuste considerazioni. Impostando i bit PCFG3:PCFG0
tutti ad 1 si ha che tutti i pin sono assegnati come I/O digitali, ciononostante sempre
necessario impostare il registro config per i pin della PORTB. Tale architettura e/o limite
non presente in ogni PIC18, in alcuni infatti si ha la totale flessibilit nella scelta e
abilitazione degli ingressi analogici.
Nome
INTCON
ADRESH
ADRESL
ADCON0
ADCON1
ADCON2
BIT7
BIT6
GIE/GIEH PEIE/GIEL
BIT5
BIT4
BIT3
BIT2
BIT1
BIT0
TMR0IE
INT0IE
RBIE
TMR0IF
INTIF
RBIF
PIR1
SPPIF
ADIF
RCIF
TXIF
SSPIF
CCP1IF
TMR2IF
TMR1IF
PIE1
SSPPIE
ADIE
RCIF
TXIF
SSPIE
CCP1IE
TMER2IE
TMR1IE
IPR1
SPPIP
ADIP
RCIP
TXIP
SSPIP
CCP1IP
TMR2IP
TMR1IP
PIR2
OSCIF
CMIF
USBIF
EEIF
BCLIF
HLVDIF
TMR3IF
CCP2IF
PIE2
OSCIE
CMIE
USBIE
EEIE
BCLIE
HLVDIE
TMR3IE
CCP2IE
IPR2
OSCIP
CMIP
USBIP
EEIP
BCLIP
HLVDIP
TMR3IP
CCP2IP
ADCON0
CHS3
CHS2
CHS1
CHS0
GO/DONE
ADON
ADCON1
VCFG1
VCFG0
PCFG3
PCFG2
PCFG1
PCFG0
ADCON2
ADFM
ACQT2
ACQT1
ACQT0
ADCS2
ADCS1
ADCS0
TRISA
TRISA6
TRISA5
TRISA4
TRISA3
TRISA2
TRISA1
TRISA0
TRISB
TRISB7
TRISB6
TRISB5
TRISB4
TRISB3
TRISB2
TRISB1
TRISB0
TRISE
TRISE3
TRISE2
TRISE1
TRISE0
ADRESH
ADRESL
Il contenuto dei registri presentati nel Capitolo sono forniti per semplificare la
comprensione delle librerie e dei programmi, e non rappresentano una fonte
alternativa al Datasheet. A seconda del modello del PIC utilizzato i registri
descritti potrebbero essere differenti o avere bit con significato differente.
483
484
U-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
CHS3
CHS2
CHS1
CHS0
GO/DONE
ADON
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7-6
Bit 5-2
S : Settable bit
x = Bit is unknown
Bit 0
Si fa presente che il registro ADCON0 non deve essere scritto in una sola
istruzione per attivare il modulo e avviare la conversione. Questo deve essere
fatto in due istruzioni separate, ovvero prima si attiva il modulo ponendo ad 1 il
bit 0, poi si avvia la conversione. I passi completi verranno comunque visti a
breve.
485
U-0
R/W-0
R/W-0
R/W-0
R/W1
R/W1
R/W1
VCFG1
VCFG0
PCFG3
PCFG2
PCFG1
PCFG0
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7-6
Bit 5
Bit 4
Bit 3-0
S : Settable bit
x = Bit is unknown
PCFG3:PCFG0
AN12
AN11
AN10
AN9
AN8
AN7
AN6
AN5
AN4
AN3
AN2
AN1
AN0
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
A = Ingresso Analogico
D = I/O Digitale
1) Il valore POR (Power On Reset) dipende dal valore assunto dal bit di configurazione PBADEN.
PBADEN = 1 : PCFG,3:0> = 0000
PBADEN = 0 : PCFG,3:0> = 0111
486
U-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
R/W-0
ADFM
ACQT2
ACQT1
ACQT0
ADCS2
ADCS1
ADCS0
Bit 7
Bit 6
Bit 5
Bit 4
Bit 3
Bit 2
Bit 1
Bit 0
Leggenda
R = Readable bit
-n = Value at POR
W = Writable bit
1 = Bit is set
Bit 7
Bit 6
Bit 5-3
Bit 2-0
S : Settable bit
x = Bit is unknown
487
Dal momento che la libreria rimane valida per i vari PIC18 indipendentemente dal
modulo ADC interno, quest'ultima pu tornare pi utile, ma l'utilizzo diretto dei registri
non dovrebbe essere troppo complicato. Vediamo un riassunto delle funzioni principali
della libreria adc.h:
Funzioni
Descrizione
void OpenADC (unsigned char config, unsigned char config2 Attiva il modulo ADC con le relative impostazioni. Questa
,unsigned char portconfig)
funzione varia a seconda delle versioni del modulo ADC. Questa
relativa alla versione 5 a cui appartiene il PIC18F4550.
int ReadADC (void)
Si fa presente che i vari PIC sono classificati in base alla versione del modulo ADC. Il
PIC18F4550 appartiene al gruppo della versione 5. Le funzioni della libreria riportate in
Tabella 31 potrebbero differire qualora il PIC utilizzato appartenga ad un altro gruppo.
Per vedere a che gruppo appartiene un particolare PIC si faccia riferimento alla
documentazione della libreria che possibile trovare nella directory docs del compilatore.
char BusyADC (void)
Per mezzo di questa funzione possibile avviare la conversione del canale analogico
precedentemente selezionato. Al suo interno la funzione non fa altro che settare il bit
GO/DONE.
Parametri:
void
Restituisce:
void
Esempio:
// Avvia la conversione del canale analogico precedentemente selezionato
ConvertADC();
void OpenADC (unsigned char config, unsigned char config2 ,unsigned char
portconfig)
Parametri:
config: Tale parametro risulta un bitmask dei seguenti valori:
Sorgente del Clock per il modulo ADC:
ADC_FOSC_2
Fosc / 2
ADC_FOSC_4
Fosc / 4
ADC_FOSC_8
Fosc / 8
ADC_FOSC_16
Fosc / 16
ADC_FOSC_32
Fosc / 32
ADC_FOSC_64
Fosc / 64
ADC_FOSC_RC
Oscillatore RC interno
Giustificazione a destra
Giustificazione a sinistra
Canale analogico:
ADC_CH0
ADC_CH1
ADC_CH2
ADC_CH3
ADC_CH4
ADC_CH5
ADC_CH6
ADC_CH7
ADC_CH8
ADC_CH9
ADC_CH10
Canale 0
Canale 1
Canale 2
Canale 3
Canale 4
Canale 5
Canale 6
Canale 7
Canale 8
Canale 9
Canale 10
490
ADC_CH11
ADC_CH12
ADC_CH13
ADC_CH14
ADC_CH15
Canale 11
Canale 12
Canale 13
Canale 14
Canale 15
Stato Interruzioni:
ADC_INT_ON Interruzioni abilitate
ADC_INT_OFF Interruzioni disabilitate
Selezione VREF+ e VREF- :
ADC_REF_VDD_VREFMINUS
ADC_REF_VREFPLUS_VREFMINUS
ADC_REF_VREFPLUS_VSS
ADC_REF_VDD_VSS
491
Per mezzo di questa funzione possibile leggere il valore della conversione. Come
detto tale valore in realt memorizzato all'interno di due registri di 8 bit. La funzione si
preoccupa di unire i due registri e restituire un unico valore sotto forma di intero. La
funzione cosi implementata:
unsigned int ReadADC(void){
return (((unsigned int)ADRESH)<<8)|(ADRESL);
}
Si noti che viene effettuato un casting e poi traslato il registro ADRESH, al quale viene
poi aggiunto il valore del registro ADRESL per mezzo dell'operatore bitwise or |. Tale
funzione viene normalmente chiamata dopo che ci si accertati che la conversione
terminata.
Parametri:
void
Restituisce:
Restituisce il valore della conversione
Esempio:
unsigned int valore_conversione = 0;
// Impostazioni ADC...
// ...
// Avvia la conversione del canale analogico selezionato
ConvertADC();
// Aspetta la fine della conversione
while (BusyADC());
// leggo il risultato della conversione
valore_conversione = ReadADC ();
Per mezzo di questa funzione possibile impostare il canale analogico, ovvero pin, del
quale si vuole effettuare la conversione del segnale applicato ai suoi capi. Tale funzione
accetta vari parametri che possono essere al di fuori della validit del PIC18 utilizzato.
Parametri:
channel: Canale del quale si effettuer la conversione.
ADC_CH0
ADC_CH1
Canale 0
Canale 1
492
ADC_CH2
ADC_CH3
ADC_CH4
ADC_CH5
ADC_CH6
ADC_CH7
ADC_CH8
ADC_CH9
ADC_CH10
ADC_CH11
ADC_CH12
ADC_CH13
ADC_CH14
ADC_CH15
ADC_CH_CTMU
ADC_CH_VDDCORE
ADC_CH_VBG
Canale 2
Canale 3
Canale 4
Canale 5
Canale 6
Canale 7
Canale 8
Canale 9
Canale 10
Canale 11
Canale 12
Canale 13
Canale 14
Canale 15
Canale 13
Canale 14
Canale 15
Restituisce:
void
Esempio:
// Selezione AN2 per la conversione
SetChanADC (ADC_CH2);
493
//*************************************
void board_initialization (void);
int main (void){
// Variabile per salvare la sommatoria dei dati letti
unsigned int sommatoria = 0;
// Variabile per salvare il valore della conversione
unsigned int lettura = 0;
// Variabile utilizzata per il numero delle letture
unsigned char i;
board_initialization ();
// Abilito A0-A1 come ingressi analogico
// VREF sono impostate a massa e VCC
ADCON1 = 0b00001101;
// Seleziono AN1 come ingresso
ADCON0 = 0b00000100;
// Imposto i tempi di conversione e giustificazione a destra
// TAD : FOSC/16
// TACQ: 16 TAD (4 min.)
// Giustificazione sinistra
ADCON2 = 0b00110101;
// Abilito l'ADC
ADCON0 |= 0b00000001;
//attesa prima di iniziare la prima conversione
//basterebbero 2us
delay_ms (1);
// Ciclo infinito
while (1) {
sommatoria = 0;
// Effettuo 8 letture per fare una media
for (i =0; i<NUMERO_CAMPIONI; i++){
// Avvio la conversione Analogico/Digitale
ADCON0bits.GO = 1;
// Attendo la fine della conversione
while(ADCON0bits.GO);
// Prelevo il valore della conversione
// ignoro ADRESL perche' ho una giustificazione a sinistra
lettura = ADRESH;
// Sommatoria delle letture fatte
sommatoria = sommatoria + lettura;
}
// Calcolo della media
sommatoria = sommatoria / NUMERO_CAMPIONI;
494
// Visualizzo la lettura
LATD = (unsigned char) (sommatoria);
}
Come possibile vedere, facendo uso direttamente dei registri non necessario
includere il file adc.h. Dal momento che PORTB non utilizzata per i suoi ingressi
analogici, si impostato:
#pragma config PBADEN = OFF
Spero che il codice non sia nulla di sorprendente, ma certamente per la sua
comprensione necessario controllare il significato dei vari bit. Si noti che per usare AN1
necessario impostare anche AN0 come ingresso analogico. Dopo l'inizializzazione del
modulo ADC necessario attendere per almeno 2us, prima che il modulo sia operativo
(settling time). Questa pausa necessario solo dopo aver attivato il modulo e non ad ogni
conversione.
Una volta impostato il modulo si effettua la lettura dello stesso. Questo viene fatto in
maniera continua all'interno del ciclo infinito while. Si noti in particolare che all'interno
del ciclo for la lettura ripetuta per 8 volte.
// Effettuo 8 letture per fare una media
for (i =0; i<NUMERO_CAMPIONI; i++){
192
Si rimanda alla documentazione di Freedom II per maggiori informazioni tecniche relative allo schema elettrico.
495
Il fatto di ripetere la lettura permette di calcolare un valore medio piuttosto che uno
istantaneo che potrebbe creare delle fluttuazioni. Il numero di letture normalmente
utilizzato per effettuare una media normalmente una potenza di 2, in modo da poter poi
fare la divisione per mezzo dell'operatore shift, o permettere al compilatore di ottimizzare
il codice. Nel codice si preferita la chiarezza dell'operazione matematica, ma senza
ottimizzazioni del codice il compilatore esegue semplicemente una divisione e non uno
shift, introducendo un codice piuttosto complesso:
71:
72:
7FD8
7FDA
7FDC
7FDE
7FE0
7FE2
7FE4
7FE6
7FE8
7FEA
7FEC
7FEE
7FF0
7FF2
7FF4
7FF6
7FF8
C00E
F009
C00F
F00A
90D8
320A
3209
90D8
320A
3209
90D8
320A
3209
C009
F00E
C00A
F00F
La lettura del valore avviene semplicemente per mezzo del registro ADRESH,
ignorando il registro ADRESL. Infatti dovendo visualizzare il valore ad 8 bit, si
possono ignorare direttamente i due bit meno significativi, che equivale appunto
a dividere il valore a 10 bit per 4, ovvero ottenere una risoluzione ad 8 bit.
L'ottenimento di una risoluzione ad 8 bit si raggiunge semplicemente
giustificando a sinistra i 10 bit.
496
La presenza della protezione del regolatore 7805, presente in alcune schede di sviluppo, potrebbe portare ad avere Vcc
leggermente inferiore a 5V, ma per l'accuratezza che si vuole ottenere e le basse temperature, non un problema.
497
board_initialization ();
// Inizializzazione LCD
LCD_initialize (20);
LCD_backlight (LCD_TURN_ON_LED);
LCD_write_message (" Temp :
LCD_write_char (223);
LCD_write_char ('C');
");
LCD_shift_cursor (LCD_LEFT,5);
// Abilito AN0-AN1-AN2 come ingressi analogici
// VREF sono impostate a massa e VCC
ADCON1 = 0b00001101;
// Seleziono AN2 come ingresso
ADCON0 = 0b00001000;
// Imposto i tempi di conversione e giustificazione a destra
// TAD : FOSC/16
// TACQ: 16 TAD
// Giustificazione destra
ADCON2 = 0b10110101;
// Abilito l'ADC
ADCON0 |= 0b00000001;
//attesa prima di iniziare la prima conversione
//basterebbero 2us
delay_ms (1);
// Ciclo infinito
while (1) {
sommatoria = 0;
// Effettuo NUMERO_CAMPIONI letture per fare una media
for (numero_letture =0; numero_letture<NUMERO_CAMPIONI;
numero_letture++){
// Avvio la conversione Analogico/Digitale
ADCON0bits.GO = 1;
// Attendo la fine della conversione
while(ADCON0bits.GO);
// Prelevo il valore della conversione
lettura = (((unsigned int) ADRESH) << 8) | ADRESL;
498
LCD_shift_cursor (LEFT,2);
delay_ms (2000);
}
Il software non molto diverso dal precedente, si aggiunto solo il seguente codice
per inizializzare il display:
// Inizializzazione LCD
LCD_initialize (20);
LCD_backlight (LCD_TURN_ON_LED);
LCD_write_message (" Temp :
LCD_write_char (223);
LCD_write_char ('C');
");
LCD_shift_cursor (LCD_LEFT,5);
Scrivere il carattere 223 permette di scrivere . Tale valore pu essere trovato nella
tabella dei caratteri del Datasheet del controllore HT44780. Si noti che dopo la scrittura
dei vari caratteri si fatto uno spostamento verso sinistra del cursore, in modo da
localizzarlo nelle xx del seguente esempio:
Temp : xx
In questo modo ogni volta che si aggiorna la temperatura di devono solo aggiornare le
xx e non tutto il display. Questa tecnica pu ritornare comoda quando si hanno molte
scritte di cui solo una piccola parte deve essere aggiornata; altrettanto valida risulta nel
caso in cui si utilizzino display pi grandi. In questo esempio riscrivere sempre tutto
quanto non sarebbe stato un grande problema.
L'impostazione del modulo ADC identica al caso precedente, per questa volta si
sono abilitati come ingressi analogici AN0-AN2 e si selezionato AN2 come ingresso per
la misura. La giustificazione dei dati a destra, visto che si vogliono leggere tutti e dieci i
bit. La lettura e la media anche svolta come del caso precedente ma il numero delle
letture stato portato a 64. Quando si hanno valori per la media cosi alti sempre bene
accertarsi che il registro che contiene la sommatoria sia sempre delle dimensioni
opportune per contenere la somma dei campioni richiesti. Oltre alla divisione per 64
presente anche una seconda divisione per 2, al fine di ottenere il valore in gradi centigradi,
questo stato separato solo per chiarezza.
Il valore della temperatura in gradi viene scritto per mezzo della funzione della libreria
del display:
LCD_write_integer (sommatoria,2, LCD_ZERO_CLEANING_ON);
Questa permette di convertire in stringa un valore numerico intero ed assegna due digit
con giustificazione a destra, al numero finale. Questo significa che potenzialmente si
potrebbero misurare temperature fino a 99 C. Una volta aggiornato il display si effettua
uno shift a sinistra in maniera da riposizionare il cursore sulle xx della temperatura; si
inserito anche un ritardo di due secondi in maniera da rallentare le letture. Questo ritardo
senza far nulla accettabile in un'applicazione come questa in cui non si fa altro che
499
500
LCD_shift_cursor (LEFT,4);
501
120
25
964
Riassunto
In questo Capitolo abbiamo visto il funzionamento del modulo ADC e qualche
dettaglio sulle varie architetture disponibili. In particolare si sono descritte le impostazioni
necessarie per impostare il modulo interno, al fine di garantire il suo corretto
funzionamento da un punto di vista temporale, ovvero permettere il corretto
funzionamento del Sample and Hold interno. Per fare questo il tempo di carica del
condensatore interno, deve essere opportunamente dimensionato in funzione
dell'impedenza vista dal condensatore stesso. Gli esempi hanno inoltre mostrato come
effettuare letture multiple dei canali dell'ADC, facendo uso sia della tecnica del polling che
delle interruzioni.
banda finita, ovvero limitata. Il teorema di Nyquist analizza solo il caso di un segnale in
banda base, ovvero intorno a 0Hz. Il teorema Teorema di Nyquist generalizzato dal
teorema di Shannon, che considera anche segnali non in banda base, dimostrando che la
frequenza di campionamento non necessariamente deve essere il doppio della frequenza
massima contenuta nel segnale da campionare ma il doppio della banda del segnale stesso.
Nel caso in cui il segnale sia in banda base il teorema di Nyquist e Shannon coincidono
mentre per segnali a banda traslata, il teorema di Nyquist richiederebbe frequenze di
campionamento molto maggiori di quella minima realmente necessaria.
Special Event Trigger: I PIC18 come anche molti altri microcontrollori permettono di
avviare la conversione analogica per mezzo del modulo CCP2, ovvero dei timer. Questo
permette di ottenere frequenze di campionamento con frequenza nota ma soprattutto
senza l'intervento della CPU. In sistemi che contengono anche il modulo DMA (Direct
Memory Access), possibile ulteriormente diminuire l'intervento della CPU, avviandola solo
dopo la raccolta dei dati richiesti. Si ricorda che i PIC18 non hanno per il modulo DMA.
Domande ed Esercizi
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
504
Bibliografia
Internet Link
[1] www.LaurTec.it : sito dove scaricare il testo originale del libro XC8 Step by Step
e la documentazione tecnica delle schede di sviluppo.
[2] www.microchip.com : sito ufficiale della Microchip, dove poter scaricare i
Datasheet dei PIC e i software descritti nel testo.
[3] www.zilog.com : sito della Zilog, societ leader nella produzione di
microcontrollori.
[4] www.ti.com : sito della Texas Instruments, societ leader nella produzione di
microcontrollori e componenti analogici/digitali. Sito dove trovare il Datasheet del
sensore LM35.
[5] www.analog.com : sito della Analog Devices, societ leader nella produzione di
microcontrollori e componenti analogici/digitali.
[6] www.maximintegrated.com : sito della Maxim IC, societ leader nella produzione di
microcontrollori e componenti analogici/digitali. Sito dove trovare il Datasheet dei
Transceiver MAX232 e MAX485.
[7] www.atmel.com : sito della Atmel, societ leader nella produzione di
microcontrollori.
[8] www.st.com : sito della ST Microelectronics, societ leader nella produzione di
microcontrollori e componenti analogici/digitali.
[9] www.renesas.com : sito della Renesas, societ leader nella produzione di
microcontrollori.
[10] www.nxp.com : sito della NXP, societ leader nella produzione di microcontrollori
e componenti analogici/digitali. .
[11] www.nxp.com/acrobat_download/applicationnotes/AN10216_1.pdf :
documento sulle specifiche I2C.
Indice Alfabetico
0
Address Mask.............................................356
ADIE..........................................................482
ADIF..................................................479, 482
ADRESH....................................479, 483, 492
ADRESL....................................479, 483, 492
1
Advanced Architecture.................................18
1 Wire.........................................................390
Advanced RISC Machine.............................20
128x64 pixel...............................................298
alta impedenza......................................74, 388
18f4550_g.lkr...............................................29
Altera............................................................19
18f4550.html..............................................104
ALU..................................................20, 55, 78
2
Always Break.............................................126
24LC256155...............................................349
AMBA..........................................................24
24LC32.......................................................371
AMD............................................................14
24MHz..........................................................63
American Standard Code for Information
3
Interchange...............................................161, 216
3 Wire communication...............................390
Analisi temporale.......................................132
32768Hz.......................................................64
Analog Devices............................................22
4
Analog to Digital Converter.........20, 475, 503
4 Wire Interface..........................................387
and..............................................................185
48MHz..........................................................63
ANSI C.......................................................146
6
Architettura avanzata....................................18
6MHz............................................................63
Architettura base..........................................18
8
architettura base dei PIC18..........................44
8051..............................................16, 22 e seg.
architettura di von Neumann........................44
8052..............................................................22
Architettura media........................................18
80x86............................................................21
Architettura media potenziata......................18
A
Arithmetic Logic Unit............................20, 78
A/D'............................................................356
ARM...................................................19 e seg.
accumulatore..........................55, 57, 135, 177
ARM Holding Ltd........................................20
Accumulatore...............................................78
ARMv7-M....................................................21
Acknowledge..............................................354
Array..................................................160, 216
ACQT0.......................................................482
ASCII.................................100, 107, 161, 216
ACQT2.......................................................482
Assembler.....................................................31
ACQT2:ACQT0...............................480 e seg.
Assembly..........................26, 31, 42, 121, 132
ADC.......................20, 75, 256, 390, 472, 503
AT91SAM....................................................21
ADC a doppia rampa..................................476
ATMEL........................................................21
ADC a rampa..............................................476
Atom.............................................................17
ADC ad approssimazione successive.........476
auto baud rate.............................................394
ADC flash...................................................476
auto-programmazione...................................49
adc.h...................................................474, 488
AVR.............................................................21
ADCON0.........................................482 e seg.
AVR32.........................................................21
ADCON0bits.GO.......................................503 B
ADCON1.............................75, 77, 482 e seg.
Back up System............................................65
ADCON2.......................................480 e segg.
banchi di memoria........................................54
ADCS2:ADCS0.................................479, 481
band gap.......................................................69
Add Watch.................................................129
bank..............................................................54
ADDRESH.................................................482
Base Architecture.........................................18
ADDRESL.................................................482
Basic...........................................................204
Address bus................................................128
BCD............................................................378
Address Bus.......................................382, 411
BCF............................................57 e seg., 149
0008h............................................................52
0018h............................................................52
0x08 (alta priorit)......................................258
0x18 (bassa priorit)...................................258
BF.......................................................359, 400
bin.................................................................29
Binary.................................................130, 163
bit................................................................148
bit C..............................................................78
Bit Clear F....................................................57
bit DC...........................................................78
bit N..............................................................78
bit OV...........................................................78
bit Z..............................................................78
bitmask.......................................................490
Bob Bemer.........................................161, 216
Boolean......................................................149
booleana.....................................................149
bootloader.....................................................33
bootloader USB............................................29
bootloader.....................................................50
BOR........................................................65, 68
BOREN1:BOREN0......................................69
BORV1:BORV0..........................................69
BP...............................................................121
Breadboard.......................................36, 38, 40
break...........................................193, 203, 212
Breakpoint 117, 119, 121, 124 e seg., 133, 142
BRG............................................................327
BRGH.........................................................328
Brown Out Reset..........................................65
BSF.......................................................58, 149
BSR......................................................54, 256
BSR3:BSR0..................................................54
buffer overflow...........................................160
bug................................................................72
BUILD SUCCEEDED.................................98
C
C...................................................................78
C ..................................................................31
C18.......................................................28, 104
C2000...........................................................22
C5000...........................................................22
C6000...........................................................22
cache.............................................................46
CALL...........................................................50
CAN.....................................................50, 391
cane da guardia.............................................70
Capacit di carico.........................................63
Capture.......................................................443
Capture Compare PWM.......76, 427, 440, 443
Carry.............................................................78
case.............................................................211
case sensitive..............................................147
casting........................................171, 180, 492
Casting................................................182, 216
Categories.....................................................83
CC2538........................................................22
CCI.....91, 104, 111, 149, 155 e seg., 159, 259
CCP......................................76, 427, 440, 443
CCP....................................................420, 426
CCP1..................................................443, 464
CCP1IE......................................................451
CCP1IF.......................................................451
CCP1IP.......................................................451
CCP2................................443, 464, 502 e seg.
CCP2IE......................................................451
CCP2IF.......................................................451
CCP2IP.......................................................451
CCPR2H.............................................450, 461
CCPR2L.....................................................450
CCPRxH.....................................................444
CCPRxH-CCPRxL.....................................444
CCPRxL.............................................444, 449
CCPx..........................................................444
CCPxCON........................................449 e seg.
CCPxIE............................................443 e seg.
CCPxIF.............................................443 e seg.
CCPxIP.......................................................443
CCPxM0.....................................................445
CCPxM3.....................................................445
CE...............................................................388
Central Processing Unit..........................20, 44
CFGS................................................56 e segg.
char.....................................................165, 215
Char............................................................130
Chip Enable................................................388
Chip Select.................................................388
CHOLD......................................................481
CHS3:CHS0...............................................479
ciclo infinito...............................................202
circuiteria di Reset........................................67
CISC.............................................................46
CKE....................................................359, 400
CKP....................................................360, 401
Cleaning.......................................................98
CLK............................................................388
Clock Stretching........................................362
Clock Calendar...........................................437
clock invertito.............................................392
Clock Phase................................................392
Clock Polarity.............................................392
Clock RC....................................................481
Clock Recovering.......................................341
clock secondario...........................................63
Clock Stretch..............................................360
CLRF..........................................................135
CLRWDT.....................................................70
CMOS..........................................................14
codice ASCII..............................................274
Coding Stile................................................137
commenti....................................................106
Common C Interface............91, 104, 111, 149
Comparatore...............................................439
Compare...........................................443 e seg.
Compilatore..................................................42
Compiler Toolchains....................................85
Complex Instruction Set Computer.............46
Complex Programmable Logic Device........19
comunicazione parallela.............................319
config..........................................................104
CONFIG...............................................60, 104
CONFIG1H......................................60, 64, 66
CONFIG1L..................................................60
CONFIG2L..................................................69
CONFIG3H................................................452
CONFIG4L..................................................71
Configuration Bits......................................131
Configuration Words....................................70
continue......................................................203
Continue.....................................................119
controllo dei motori......................................76
controllore HD44780.................................274
Convertitore Analogico digitale.................503
corrente di leakage.....................................480
Cortex A.......................................................21
Cortex M0..........................................21 e seg.
Cortex M0+..................................................22
Cortex M3....................................................22
Cortex M4F..................................................22
Cortex R.......................................................21
cos(x)..........................................................175
costante.......................................................167
Count..........................................................126
CPHA.........................................................392
CPLD............................................................19
CPOL..........................................................392
CPU..............................................................44
CPUDIV.......................................................63
CS...............................................................388
CTRL+B.....................................................104
CTS............................................................316
CVS............................................................142
D
D/A.....................................................359, 400
DAC...........................408, 411, 448, 468, 471
daisy chain..................................................391
Dallas..........................................................390
Dashboard....................................88, 100, 121
dat.................................................................29
DOS............................................................110
double.........................................................155
DOUT.........................................................388
Doxygen.............................241, 246, 250, 305
DSC..............................................................17
DSP..............................................17 e seg., 20
dsPIC............................................................18
dsPIC30........................................................18
dsPIC33........................................................18
DSR............................................................316
DTR............................................................316
Dual in Line..................................................40
dump...........................................................125
duty cycle...................445, 448, 455, 464, 468
E
EC.................................................................61
ECG............................................................470
echo............................................................335
ECIO............................................................61
ECPIO..........................................................62
ECPLL..........................................................62
EE Data Memory........................................131
EEADR..................................................56, 58
EECON1......................................................56
EECON2......................................................56
EEDATA................................................56, 58
EEPGD.............................................56 e segg.
EEPROM..................50, 56, 70, 131, 371, 395
EEPROM Memory.......................................49
Effective number of bit..............................477
efficienza......................................................63
Electrically Erasable Programmable Read
Only Memory.....................................................56
else..............................................................204
Enhanced CCP...........................................443
Enhanced Midrange Architecture................18
Enhanced PWM...................................76, 443
ENOB.........................................................477
enum...................................................168, 329
EPROM........................................................49
Errata............................................................72
Escape Character........................................161
esponente....................................................155
Ethernet..................................................14, 50
EUSART..............................................76, 320
Event must Occur Count Times.................126
Execute.........................................................53
Extra Low Power..........................................65
F
Fail Safe Clock Monitor...............................64
FALSE........................................................149
famiglia Midrange........................................47
Hard Disk.....................................................14
Interfaccia SPI............................................411
Hard Real Time..........................................415
International Organization for Standardization
Hardware Description Language..................16..................................................................161, 216
Harvard.........................................................44
interrupt............................................255 e seg.
HD44780....................................................275
Interrupt Flag..............................................434
HDC44780.................................................292
Interrupt Service Routine.....52, 259, 264, 268
header.........................................................341
interrupt Vector..........................................256
header file...........................................235, 239
Interrupt Vector..........................51 e seg., 268
Header file..................................................250
interruzione........................................255, 388
Hexadecimal...............................................130
Interruzione................................................268
HI-TECH......................................................28
interruzione a bassa priorit.......................256
High Priority Interrupt Vector......................51
interruzione ad alta priorit........................256
High Speed.............................................59, 63
INTHS..........................................................62
High Speed Mode.......................................348
INTIO...........................................................62
Hitachi................................................274, 292
INTOSC.............................................66 e seg.
HITECH.......................................................28
INTRC....................................................64, 70
Hold in Reset..............................................103
INTXT..........................................................62
HS.........................................................61, 105
IP..................................................................56
HSPLL..........................................................61
IPEN...........................................................258
HT44780....................................................499
IPR..............................................................258
HTML........................................................248
IPR1............................................................257
I
IPR2.................................................257 e seg.
I/O................................................................72
IR..................................................................53
I2C................................................56, 347, 387
IRCF.............................................................70
i2c.h....................................................363, 371
ISO.....................................................161, 216
i2cEEPROM.h............................................371
ISO646...............................................161, 216
IBM....................................................161, 216
ISR........................................52, 256, 259, 381
ICD 2............................................................34
Istruction Register........................................53
ICD 3....................................................34, 116
istruzione break..........................................192
IDE...................................26, 33, 42, 138, 140
istruzione condizionale switch...................210
IDLEN....................................................65, 67
istruzione if ().........................................203
IIR................................................................20
Istruzione RESET...................................68, 71
INCF...........................................................177
istruzione return ( ).....................................223
inches............................................................41
istruzione while (...)...................................190
INCKO.........................................................62
Istruzioni bloccanti.....................................110
include..........................................................29
istruzioni condizionali................................184
Include Directories.......................................94
IXYS............................................................22
Include Path................................................111 J
Include Search Path....................................237
Joint Test Action Group.............................392
Infinite Impulse Response............................20
JTAG..........................................................392
inizializzazione delle variabili....................158 K
INL.............................................................476
Kernel Linux..............................................111
Input/Output.................................................72
Keyword.....................................................146
int........................................................107, 165
KS0107B....................................................298
INTCON 257 e seg., 260, 262 e seg., 416, 418 KS0108B............................................294, 298
INTCON2.....................................75, 257, 418 L
INTCON3...................................................257
LAN............................................................313
Integral non Linearity.................................476
LAT..............................................................73
Integrated Development Environment...26, 42
LATB.........................................................135
Intel......................................................14, 111
Latch.............................................................73
Intel HEX...........................................100, 111
latency........................................................434
Intellectual Propriety....................................56
Latency.......................................................257
LCD....................................................274, 292
LCD_44780................................................276
LCD_44780.c.............................................276
LCD_44780.h.............................................276
LCD_DEFAULT........................................277
LDO............................................................448
Le direttive.................................................236
leakage........................................................504
Leakage Current...........................................71
Least Significant Bit...................................318
left value.....................................................182
legacy...........................................................30
lettura di un pulsante..................................205
lib..................................................................30
LIN.............................................................321
linguaggio macchina....................................31
linker......................................................29, 71
Linker.............................................42, 93, 100
linker file......................................................29
lint..............................................................137
Linux......................................................17, 26
Linux embedded...........................................23
Liquid Crystal Display...............................292
little indian.................................................148
Livelli di priorit........................................268
livello fisico................................................321
lkr.................................................................29
LM35D.......................................................497
Local History......................................138, 140
log(x)..........................................................175
loop.............................................................190
Low Drop Out............................................448
Low Priority Interrupt Vector......................51
Low Speed....................................................63
Low Voltage Differential Signal................320
Low Voltage Programming..........................43
LPM4.5.........................................................69
LSB............................................................318
LSFR............................................................50
lvalue..........................................................182
LVDS.........................................................320
LVP......................................................43, 106
M
MAC.............................................................26
macro..........................................................167
magic number.....................................167, 338
main............................................................107
Main Significant Bit...........................318, 390
manual.pdf....................................................29
mapping file...............................................100
mark............................................................318
Master.........................................................348
MOVWF....................................................256
MPLAB IDE..........................................26, 34
MPLAB SIM..............................................116
MPLAB X....................................................26
MPU.............................................................22
MSB...................................................318, 390
MSP340........................................................70
MSP430..............22, 46, 63, 71, 128, 388, 390
MSSP..........................................355, 397, 423
multipoint...................................................347
multitasking................................................381
MUX............................................................59
N
N...................................................................78 P
Navigate.....................................................138
P..........................................................359, 400
Netbeans.......................................................88
p18f4550.h ..................................................30
Netbeens.......................................................26
PAL..............................................................19
New Project..................................................83
parallelismo..................................................47
New Watch.................................................130
parser..........................................................137
NOT............................................................392
Parser..................................................141, 248
NULL.........................................................161
Pascal..........................................................204
null-modem................................................319
Passaggio di variabili per valore e riferimento
NXP..............................22, 347, 350, 376, 382..........................................................................232
Nyquist...............................................477, 504
Pause..........................................................119
O
PBADEN......................................75, 106, 482
One Time Programmable.............................49
PC...................................................50, 78, 256
open collector.............................................350
PCA9515....................................................350
open drain...................................................350
PCA9516....................................................350
OpenPWM1................................................464
PCA9518....................................................350
OpenPWM2................................................464
PCA9544....................................................349
operatore ++...............................................177
PCF8563.......................................64, 376, 420
operatore AND...........................................185
PCF8563.c..................................................376
operatore binario AND...............................185
PCF8563.h .................................................376
operatore binario OR..................................185
PCFG3:PCFG0.................................482 e seg.
operatore binario XOR...............................185
PCI express...................................................20
operatore complemento a 1........................185
PEIE...................................................258, 418
operatore di decremento.............................177
Pentium II..................................................17
operatore divisione.....................................175
percorso radice.............................................28
operatore logico AND................................184
percorso standard d'installazione..................28
operatore logico di uguaglianza ................184
Periferica Master................................382, 411
operatore logico diverso.............................184
Periferica Slave..................................382, 411
operatore logico maggiore..........................184
persistent....................................................159
operatore logico maggiore o uguale...........184
Phase Lock Loop..........................................59
operatore logico minore.............................184
Philips...................................22, 347, 376, 382
operatore logico minore o uguale...............184
Philips, .......................................................387
operatore logico OR...................................184
physical layer......................................321, 396
operatore moltiplicazione...........................175
PIC10......................................................18, 30
operatore NOT...........................................187
PIC10F.........................................................26
operatore OR..............................................186
PIC12......................................................18, 30
operatore somma........................................175
PIC12F1.......................................................18
operatore sottrazione..................................175
PIC16..................................18, 30, 33, 47, 258
operatore XOR...........................................186
PIC16Cxxx...................................................49
PIC16F1.......................................................18
production..................................................100
PIC18..........................................................388
Program Counter. .50, 67, 78, 119 e seg., 125,
PIC18F14K50............................................469134, 224
PIC18F4550.................................................67
Program Memory...........................44, 49, 131
PIC18F4550_config.h................................105
Programmable Brown-out Reset..................68
pic18f4550.h...............................................104
Programmable Brown-out Reset (BOR)......69
pic18f4550.h ..............................................104
programmatore.......................................33, 49
PIC24............................................................18
Programmer Logic Analyzer........................33
PIC32......................................................19, 26
Project Folder...............................................86
PICKIT 2........................................33, 83, 116
Project Location...........................................86
PICKIT 3..............34, 83, 101 e seg., 116, 120 Project Name................................................86
PIE1............................................................257
Projects.........................................................83
PIE2............................................................257
Protocollo di comunicazione......................342
pipeline.........................................................53
Protocollo I2C............................................382
Pipeline.........................................................79
prototipo di funzione..................................224
PIR1....................................................257, 398
PRS2...........................................................424
PIR2............................................................257
PSA..................................................418 e seg.
Pixel............................................................312
pull-down...............................40, 76, 109, 206
PLD..............................................................19
pull-up..........................40, 75 e seg., 109, 206
plib................................................................30
Pulse Width Modulation............................445
PLL.......................................................59, 132
puntatore.....................................................230
PLLDIV........................................................59
Puntatore....................................................216
pollici............................................................41
punto di arresto...........................................124
polling..................59, 190, 206, 214, 332, 482
put...............................................................331
Polling................................................255, 258
PWM....................................76, 443, 445, 471
ponte H.......................................................443
pwm.h.........................................................454
POR......................................68 e seg., 71, 486 Q
PORT............................................................73
quanto.................................................408, 476
PORTA.................................................75, 482
QVGA..........................................................18
PORTB.................................75, 106, 256, 482 R
PORTC.........................................................76
R/W....................................................359, 400
PORTD.................................................76, 109
R/W'............................................................356
PORTDbits...................................................30
RA6....................................................61 e seg.
PORTE.........................................................77
RAM.................14, 46, 53, 131, 227, 320, 395
post-scaler..................................................133
RAM ..........................................................148
postscaler......................................................63
Random Access Memory.............................53
Postscaler............................................423, 464
RB4-RB7....................................................256
potenza dissipata..........................................63
RB6........................................................35, 39
power cycle..................................................66
RB7........................................................35, 39
Power Cycle.................................................58
RBIE...........................................................262
Power On Reset..........................68 e seg., 486
RBIF...........................................................263
Power On Reset ...........................................68
RBPU...................................................75, 418
Power-Up.....................................................58
RC_IDL........................................................66
PR2.............................................................423
RC_RUN......................................................66
pre-scaler....................................................133
RC6............................................................329
Preprocessing and Messages........................99
RCIF...........................................................335
preprocessore..............................................103
RCON.........................................68, 257 e seg.
Prescaler...416 e seg., 420, 423, 426, 434, 449 RCSTA.......................................................329
Preview.......................................................140
RD................................................................58
PRI_IDL.......................................................66
Real Time...................................................415
PRI_RUN.....................................................66
Real Time Clock Calendar...64, 376, 416, 420
Print Circuit Board.....................................347
Real Time Operative System..................17, 52
real-time Debugger.......................................34
Reduced Instruction Set Computer..............44
Refactor............................................139 e seg.
Refactoring.......................138 e seg., 141, 338
Registri.......................................................398
registri shadow...........................................257
registro a scorrimento.................................389
Registro di Stato.....................................55, 78
registro speciale PC......................................50
registro speciale W.......................................55
Relay..........................................................339
Rename.......................................................139
Renesas...................................................23, 25
Repeater......................................................350
Reset...................................................119, 256
RESET..........................................................71
Reset Hardware............................................72
Reset Vector...............51 e seg., 67, 119 e seg.
resistori di pull-up................................75, 262
resto divisione tra interi..............................175
RETFIE........................................................78
RETURN......................................................78
return ().......................................................223
RI................................................................316
RISC.......................................20 e seg., 44, 46
risorse del sistema......................................121
Robert Noyce...............................................16
router............................................................17
RRNCF.......................................................188
RS232.....................................33, 50, 313, 320
RS232 Terminal.........................................503
RS232 Transceiver.....................................317
RS232C......................................................315
RS485,........................................................320
RTCC...........................................64, 420, 438
RTOS......................................................17, 52
RTS............................................................316
Run to Cursor.....................................119, 125
Run To Cursor............................................124
Run Build and Clean Main Project..........96
S
S..........................................................359, 400
SAM.............................................................21
Sample and Hold......................476, 503 e seg.
sampling.....................................................393
sampling rate..............................................477
Samsung.....................................................298
SAR....................................................476, 478
save.............................................................436
scheduling dei processi..............................415
SCK............................................................388
SCL....................................................348, 352
SCLK..........................................................388
scope.................................................148 e seg.
Scope di una variabile................................249
scope di variabili........................................226
SCS1:SCS0......................................63, 65, 67
SDA....................................................348, 352
SDI.............................................................388
SDO............................................................388
SEC_IDL......................................................66
SEC_RUN....................................................66
segnale analogico.......................................475
segnale tempo continuo..............................475
Self-programming under software control...49
Semplici regole di programmazione..........136
sen(x)..........................................................175
sequenza numerica.....................................475
Serial Clock........................................348, 388
Serial Data..................................................348
Serial Data In..............................................388
Serial Data Out...........................................388
Serial In......................................................388
Serial Out...................................................388
Serial Port Enable.......................................329
Set as main project.......................................86
Set PC at Cursor.........................................119
SETF..........................................................135
settling time........................................480, 495
SFDR..........................................................477
SFR.........................................................56, 78
SFRs.....................................................55, 131
SH.......................................................476, 504
Shannon..............................................477, 504
Shark............................................................22
shift.............................................................496
shift a destra.......................................180, 185
shift a sinistra.....................................180, 185
Shift Register..............................................389
Show Local History....................................140
SI................................................................388
Signal to Noise And Distortion..................477
Signal to Noise Ratio.................................477
signed char.................................................148
signed int ...................................................148
signed long.................................................148
signed long long.........................................148
signed short................................................148
signed short long........................................148
simplex.......................................................320
Simulatore..................................................116
SINAD........................................................477
sincrono......................................................387
Single Ended..............................................477
Tab Projects..................................................88
Tool Bar...........................................101 e seg.
Tad..............................................................479
tool chain......................................................25
TAD............................................................481
traboccamento..............................................71
target.............................................................35
Trasmissione Asincrona.............................342
Target Board...............................................102
Trasmissione Full Duplex..........................343
task.............................................................382
trasmissione full-duplex.............................320
tasto Continue.............................................125
Trasmissione Half Duplex..........................343
tasto Debug.................................................118
trasmissione half-duplex............................320
tasto Pause..................................................119
trasmissione parallela.................................319
tasto Reset..................................................120
trasmissione seriale....................................320
tecniche di predizione dei salti.....................46
trasmissione simplex..................................320
tempo di latenza.........................................257
Trasmissione Simplex................................343
Teorema del Campionamento....................477
trasmissione sincrona.................................321
Teorema di Nyquist....................................503
Trasmissione Sincrona...............................342
Teorema di Shannon..................................477
trasmissioni asincrone................................321
TEPT4400..........................................500, 504
Trigger........................................................462
Terminal.....................................................503
Trigger di Schmitt........................77, 357, 397
Texas Instruments....................22, 46, 63, 388
TRIS.............................................................73
Thread........................................................382
TRISA........................................................418
Thread Save................................................382
TRISB........................................................135
three states..................................................388
TRISD........................................................277
TI..................................................................22
TRUE.........................................................149
time budget.................................................415
TTL............................................................317
Timer..........................................................438
TX..............................................................329
Timer0........................................................418
typedef struct..............................................165
Timer1................................421, 437, 444, 503 U
Timer2................................................444, 464
UA......................................................359, 400
Timer3................................................427, 444
UART.................................................320, 502
timers.h...............................................434, 450
ULP..............................................................65
tipi primitivi...............................................165
Ultra Low Power..............................22, 63, 65
TIVA............................................................22
underflow.....................................................71
TMER2IE...................................................424
Unita Logica Aritmetica...............................78
TMR0H..............................................418, 436
unsigned char.............................................148
TMR0IE.............................................416, 418
unsigned int................................................148
TMR0IF..............................................416, 418
unsigned long.............................................148
TMR0IP..............................................416, 418
unsigned long long.....................................148
TMR0L...............................................418, 436
unsigned short............................................148
TMR0ON.........................................418 e seg.
unsigned short long....................................148
TMR1H......................................................421
USART...............................................256, 320
TMR1H-TMR1L........................................444
usart.h.................................................326, 331
TMR1L.......................................................421
USB............................................33, 50, 59, 76
TMR2.........................................................449
User ID Memory........................................131
TMR2IF......................................................424
Utilizzo della Breadboard.............................40
TMR2IP......................................................424
UV EPROM.................................................49
TMR2ON.........................................424 e seg. V
TMR3.........................................................426
Value..........................................................129
TMR3H......................................................427
Variabile globale........................................250
TMR3H-TMR3L........................................444
variabile static............................................227
TMR3IE.....................................................427
Variabile static............................................249
TMR3IF......................................................427
Variabile volatile........................................268
TMR3IP......................................................427
Variables....................................................130
TMR3L.......................................................427
Verilog..........................................................16
Verilog HDL................................................16
vettore d'interruzione..................................256
VHDL...........................................................16
VHDL3.........................................................16
VHSIC..........................................................16
virus............................................................160
Visibilit delle variabili..............................226
void.............................................................223
volatile........................................................264
von Neumann.......................................46, 128
W
W............................................................57, 78
Warning..................................93, 98, 104, 136
Warning -1................................................137
Watchdog...................................................106
Watchdog Timer...............................65, 68, 70
Watchdog Timer (WDT)..............................70
Watches......................................................128
WCOL................................................360, 401
WDT.....................................................68, 106
Windows........................................17, 26, 110
Workaround..................................................72
WR...............................................................58
WREG........................................................256
WREN..........................................................58
WRERR........................................................59
X
XC................................................................28
XC Linker.....................................................93
xc.h.....................................................104, 108
Xilinx............................................................19
XLP..............................................................65
XT................................................................61
XTPLL.........................................................61
Y
Y address....................................................300
Z
Z...................................................................78
Z80.........................................................16, 22
Zigbee...........................................................22
Zilog.............................................................22
Logic Unit...................................................55
_
__persistent................................................159
-..................................................................175
.
.hex.............................98, 100 e seg., 105, 111
.hex ..............................................................99
.lkr..............................................................100
.map............................................................100
(
(Stack Overflow)..........................................68
*
*..................................................................175
/
/...................................................................175
#
#define......................167 e seg., 211, 213, 237
#endif..................................................238, 241
#error..........................................................238
#ifndef........................................................237
#include..........................103 e seg., 235 e seg.
#include "nome_file"..................................235
#include <nome_file>................................235
#pragma......................................104, 436, 452
#warning.....................................................238
%
%................................................................175
+
+..................................................................175