Sei sulla pagina 1di 58

POLITECNICO DI BARI

A.A. 2009/2010

Corso di Laurea Specialistica in Ingegneria Elettronica (SEI)

Corso di Sistemi Digitali Programmabili Prof. Ing. P. Dello Russo

PROGETTO DI UN MISURATORE DI TEMPERATURA

Columbo Lorenzo
1

Introduzione
Obiettivi e caratteristiche generali del progetto
Il progetto descritto nella presente relazione ha come prima finalit la comprensione delle possibilit offerte dall'impiego di un microcontrollore in un sistema rivolto ad una specifica applicazione, tale che richieda lo sviluppo di una determinata logica ma che non si esaurisca in essa. Obiettivo successivo ma non secondario l'apprendimento delle tecniche e l'utilizzo dei tools disponibili per la programmazione del microcontrollore. Infine, stato posto l'obiettivo di sviluppare il flusso di progetto tipico di una scheda PCB, dalla rappresentazione dell'idea mediante schema a blocchi fino alla generazione del layout. In generale, un microcontrollore (MCU) un dispositivo elettronico integrato su singolo chip, nato come evoluzione alternativa al microprocessore ed utilizzato per applicazioni specifiche di controllo digitale. Il primo processore a 8 bit (lo 8008, in Fig.1) venne proposto sul mercato da Intel all'inizio degli anni '70, ed era costituito unicamente da una semplice ALU e da una unit di
Figura 1: Il primo microprocessore a 8 bit (Intel 8008).

controllo in grado di gestire il flusso di dati e indirizzi tra la ALU e la circuiteria esterna di supporto. Lo

sviluppo tecnologico di questo tipo di dispositivi fu rapidissimo, e caratterizzato naturalmente da una sempre crescente potenza di calcolo e complessit della struttura interna. Nonostante ci, i microprocessori (brevemente, CPU) continuarono nel tempo a richiedere l'utilizzo di unit periferiche esterne per l'espletamento di funzioni che non riguardassero strettamente l'elaborazione e il calcolo [1]. Proprio questo tipo di esigenza condusse allora alla realizzazione dei microcontrollori (il primo dei quali, l'8048 della Intel, rappresentato in Fig.2, fu rilasciato nel 1975): si trattava di sistemi completi che integravano in uno stesso chip il processore, la memoria, i canali di I/O ed eventuali altri blocchi in grado di svolgere funzioni altamente specifiche. Di conseguenza, a differenza dei microprocessori classici, di fatto sistemi general purpose, i microcontrollori furono (e sono tutt'ora) progettati per ottenere la massima autosufficienza funzionale ed ottimizzare il rapporto prezzo-prestazioni in una specifica applicazione. Il successo commerciale di questo genere di prodotto fu enorme. Tra i fattori che lo determinarono, figurano sicuramente [2]: 2
Figura 2: Il primo microcontrollore (Intel 8048).

Il basso costo dei sistemi che consentono di realizzare, integrando al loro interno unit funzionali che dovrebbero altrimenti essere introdotte mediante ulteriori dispositivi;

Ampia scalabilit di prestazioni, in termini di potenza di calcolo e di complessit funzionale, nonch elevata variet di dotazioni in periferiche e moduli specializzati;

Facilit di programmazione, garantita dall'esistenza di numerosi tools di sviluppo e dalla larga disponibilit di documentazione, librerie e codici di esempio.

Non di rado, una volta individuato il produttore e il modello del MCU che si intende impiegare, la scelta dell'ambiente di sviluppo cade sul relativo IDE (Integrated Devolopment Environment) proprietario: il caso ad esempio degli affermati PIC della Microchip e del relativo ambiente MPLAB. Una scelta diversa stata operata nell'ambito del presente progetto: il microcontrollore impiegato infatti il pi tradizionale (e meno complesso) 80C32 della Philips, per la programmazione del quale stato utilizzato il semplice e potente ambiente di sviluppo KeiluVision. Tale IDE si infatti rivelato in grado di unire ad un efficiente compilatore C utilissimi tools quali un disassembler chiaro e preciso e un pratico debugger, completo di visuale su memoria interna e periferiche del micro. Ulteriori dettagli relativi alle caratteristiche tecniche del micro e dell'IDE impiegati sono riportati nei capitoli successivi. Per quanto riguarda il flusso di progetto della PCB, il software CAD utilizzato il popolare pacchetto Orcad nella sua versione 9.2: in particolare, sono stati impiegati i tools Orcad Capture e Orcad Layout. Il principale punto di forza di questa soluzione CAD infatti proprio la funzione di interfacciamento tra i due tools, che consente di passare rapidamente dal disegno schematico alla disposizione dei dispositivi e dei componenti sulla scheda, quindi allo sbroglio automatico e alla stampa su lucido di piste, vias e pad (nonch alla generazione di quei file Gerber impiegati dalle macchine CNC per la produzione del circuito stampato).

Descrizione generale del sistema


La specifica applicazione scelta la rilevazione simultanea di misure di temperatura da un array di sensori, disposti in linea in modo tale da consentire successive misure derivate di valore medio e gradiente termico. Si richiede inoltre che le misure derivate vengano mostrate in tempo reale su un display LCD, e che invece quelle dirette vengano eventualmente, su richiesta dell'utente, trasmesse ad un personal computer connesso alla PCB mediante standard RS-232. Il sistema complessivo includer allora necessariamente un applicativo in grado di interfacciarsi alle porte seriali ed effettuare semplici operazioni di elaborazione, rappresentazione e archiviazione delle misure acquisite. Ulteriori dettagli relativi allo sviluppo di tale applicativo sono riportati in appendice. Lo schema a blocchi generale del sistema progettato quindi riportato in Fig.3:

Figura 3: Schema a blocchi del sistema realizzato.

Gi in questa prima rappresentazione si comprende che si avr a che fare con una parte analogica estremamente ridotta, costituita essenzialmente dai quattro sensori di temperatura e da un blocco che implementi il riferimento di tensione necessario (come descritto pi avanti, esso sar ottenuto semplicemente dall'impiego opportuno di un integrato basato su diodo zener). Come evidenziato, il micro dovr fornire all'ADC e al MUX gli opportuni segnali di controllo, oltre che acquisire le misurazioni di volta in volta prodotte dal convertitore, per il quale si scelta la dimensione di 8 bit (adeguata alle porte di I/O del micro). Allo stesso tempo, il micro produrr in uscita i segnali di dati e di controllo necessari a rappresentare sul display LCD le misure indirette calcolate e a trasmettere sulla seriale le misure dirette acquisite.

Presentazione dello schematico


Rispetto allo schema a blocchi gi mostrato, lo schematico in Fig.4 chiarisce alcuni importanti dettagli implementativi.

Figura 4: Schematico del circuito realizzato.

Innanzitutto, in questa versione dello schematico, finalizzata alla sola documentazione, si preferito non evidenziare, per comodit di rappresentazione, su tutti gli integrati la presenza dei pin VCC e GND e delle relative connessioni alle linee di alimentazione (ad esempio, il caso tra gli altri dei quattro sensori di temperatura TSENSX). Nell'implementare lo schema a blocchi descritto in un circuito, si voluto rendere il riferimento di tensione fornito all'ADC preciso ed eventualmente regolabile mediante l'impiego di un trimmer da 1 k, tale cio da garantire che gran parte della corrente erogata dalla alimentazione sul ramo relativo a R6 (resistenza di limitazione) finisca nel trimmer e non nel carico su cui si vuole realizzare il riferimento. Come si evince dallo schematico, inoltre, per la trasmissione dei bit convertiti tra l'ADC e il micro si scelto di adottare la tecnica della mappatura in memoria di una periferica: in pratica, la porta dati dell'ADC stata posta sulla stessa porta sulla quale il micro legge dalla ROM gli 8 bit relativi alle istruzioni che deve ad ogni colpo di clock eseguire. I dettagli relativi all'implementazione di questa tecnica sono riportati nella sezione relativa al codice C sviluppato. Per il momento, si osserva soltanto come le porte di I/O del micro P0 e P2 siano di fatto speciali, perch preposte dalla stessa struttura interna del micro a interfacciare quest'ultimo con le porte indirizzo e dati della ROM contenente il codice macchina da eseguire. Diversamente dall'ADC, il display LCD stato posto direttamente su una porta di I/O semplice, che dedica ad esso 6 dei suoi 8 pin. In particolare, i pin da P1.0 a P1.3 sono destinati a scambiare con il display i byte di dati: in generale, questi byte potrebbero viaggiare sia dal micro al display, rappresentando quindi ad esempio i caratteri che il display dovr mostrare (ma, come si vedr in seguito, non solo), sia dal display al micro. La direzionalit di queste linee viene allora istante per istante ad essere determinata dal valore assunto dal bit R/W (sempre in lettura sul relativo pin del display): il fatto di mantenere tale bit forzato a 0 implica che il display si porr sempre in lettura sulla porta dati. Le altre due linee, En e RS, trasportano segnali di controllo diretti dal micro al display: la loro funzione sar chiarita nella sezione relativa al display impiegato. Una sezione del tutto assente nello schema a blocchi e introdotta a questo livello invece l'array di diodi LED in basso a destra nello schematico, ciascuno dei quali in serie ad una propria resistenza di limitazione da 1.2 k (adatta cio ad una alimentazione di 5 V e ad una corrente erogata sul carico dell'ordine di qualche mA). Ciascuno di questi LED (che nel layout finale della scheda verranno posti accanto al display LCD) atto a segnalare uno specifico stato del sistema. In particolare: D1 (verde) indica quando acceso lo stato di ON del sistema, quando spento lo stato di OFF; D2 (rosso) indica quando acceso che il valore medio di temperatura calcolato supera un certo livello di soglia superiore prefissato, viceversa quando spento; D3 (blu) indica quando acceso che il

valore medio di temperatura calcolato si trova al di sotto di un certo livello di soglia inferiore prefissato, viceversa quando spento. Il dispositivo D-LATCH svolge la funzione di garantire al micro l'accesso alla memoria programma esterna ROM (e ad un eventuale memoria dati che per non prevista nella nostra applicazione). L'idea implementata la seguente: quando il micro deve accedere al contenuto di una memoria esterna, ad un assegnato indirizzo, esso innanzitutto produrr sulla porta P0 la parte bassa dell'indirizzo stesso; successivamente, invier un impulso sull'ingresso di controllo del latch, ottenendo e produrr sulla porta P2 la parte alta dell'indirizzo. A questo punto, la porta P0 in lettura e l'indirizzo completo all'ingresso della ROM (oppure, eventualmente, di entrambe le ROM, nel caso in cui sia prevista anche una memoria dati esterna). L'operazione successiva del micro quindi l'invio di un impulso sulla linea di controllo della ROM: tale segnale produce in uscita alla ROM il byte che era presente all'indirizzo di memoria selezionato, che nel nostro caso proprio l'istruzione che il micro dovr eseguire. Essa viene quindi letta dal micro sulla porta P0. Facciamo infine qualche considerazione relativa alla componentistica passiva. La cella RC costituita da R5 e C6 ha unicamente la funzione di consentire all'ADC la generazione di un segnale di clock di frequenza opportuna (tutto ci sar pi chiaro nella sezione relativa all'ADC impiegato). La cella RC costituita da R7 e C2 ha invece la funzione di garantire al microcontrollore il necessario reset iniziale, a seguito della accensione (per questo motivo, tale blocco non stato realizzato in fase di emulazione, quando il sistema di emulazione stesso che provvede al reset del micro). I due condensatori C3 e C4, assieme al componente XTAL e all'amplificatore invertente integrato nel micro tra i due pin indicati, costituiscono un oscillatore Colpitts quarzato che fornisce al micro stesso il necessario segnale di clock (in questo caso, a 11.0592 MHz: il perch di tale specifico valore sar chiaro nella sezione relativa al microcontrollore e al baud-rate della UART). Le quattro capacit elettrolitiche C5, C6, C7, C8 da 1 F sono semplicemente richieste dal convertitore di protocollo HIN232 per il suo normale funzionamento (il motivo per il quale esse non sono state integrate nel dispositivo probabilmente la difficolt di realizzare in forma integrata capacit di dimensioni cos elevate). Il componente indicato con la sigla CON9 semplicemente il tipico connettore femmina DE-9 dello standard RS-232, del quale, come evidente, il sistema impiega unicamente i pin 2, 3 e 5 (ovvero quelli relativi rispettivamente ai segnali in ricezione, in trasmissione e al riferimento di massa).

Analisi dei dispositivi impiegati


Premessa
Nel presente capitolo verr fornita una descrizione del principio di funzionamento, delle specifiche funzionali e della struttura interna di ciascuno dei dispositivi impiegati (naturalmente, verranno assunti i datasheet come principale riferimento bibliografico), con un grado di dettaglio sufficiente a comprendere la collocazione del dispositivo nel sistema complessivo e le modalit di interazione (eventuali segnali di controllo scambiati, timing, componentistica passiva di supporto, ecc.). Non si ritenuto opportuno approfondire nel dettaglio il funzionamento della ROM e del D-Latch impiegati e riportati nello schematico, considerato il fatto che il loro utilizzo non attiene strettamente alla specifica applicazione sviluppata.

LM35 (Sensore di temperatura)


L'integrato LM35 un sensore di temperatura in grado di riprodurre in maniera precisa e affidabile una tensione di uscita linearmente proporzionale alla temperatura cui esposto, misurata direttamente in gradi centigradi. Per applicazioni che, come quella discussa, richiedono l'utilizzo di questo tipo di scala, l'LM35 presenta allora un importante vantaggio su analoghi sensori calibrati per in gradi Kelvin, che costringono, per ottenere la temperatura in C, a sottrarre dalla loro uscita una tensione costante di valore piuttosto grande. Il sensore LM35 offre una accuratezza tipica di C su un range di temperature che si pu estendere da -55 C fino a 150C. Il basso costo associato al suo impiego assicurato dal fatto che il dispositivo non richiede la calibrazione dall'esterno. Inoltre, la sua bassa impedenza di uscita e la sua elevata linearit rendono l'interfacciamento verso il readout e la circuiteria di controllo particolarmente immediato. Nondimeno, il sensore consente indifferentemente l'impiego di una alimentazione singola o duale e produce un assorbimento di corrente bassissimo (circa 60 A): da questo segue un effetto di self-heating molto basso. Il package del dispositivo impiegato un TO92, quindi a 3 pin. Facciamo infine qualche considerazione sul modo in cui la tensione di uscita del sensore verr letta: la Fig.5 (riportata direttamente dal datasheet del dispositivo) mostra la semplice configurazione in cui il sensore verr impiegato. Assumiamo per le temperature misurate un range esteso tra 0 C e 128 C: in questo modo, per effetto della sensibilit di 10 mV/C si tratter di misurare all'uscita del sensore una tensione compresa tra 0 V e 1.28 7
Figura 5: Configurazione del sensore LM35.

V. L'ADC impiegato quantizza a 8 bit, il che vuol dire che sar possibile distinguere 28 = 256 valori

di tensione al suo ingresso. Suddividendo il fondo scala ipotizzato in maniera uniforme, saremo in grado di acquisire tutti i valori di temperatura compresi tra 0 C e 127.5 C a passo di 0.5 C: da queste considerazioni, come vedremo, seguir il calcolo del valore opportuno per la tensione di riferimento da fornire all'ADC. Come si evince dallo schematico rappresentato, ciascuno di questi sensori sar associato ad uno specifico indirizzo del multiplexer analogico, pilotato dal microcontrollore: quest'ultimo sar allora in grado di distinguere e conservare memoria di quale dei quattro sensori impiegati ha prodotto uno specifico valore di temperatura acquisito.

LM385 (Riferimento di tensione)


L'integrato LM385 un riferimento di tensione a band-gap, regolabile e a 3 terminali. Alimentato con una tensione singola compresa tra 1.24 V e 5.3 V, esso produce un riferimento di tensione fisso di valore tipico 1.24 V e assorbe una corrente compresa tra 10 A e 20 mA. I principali features del dispositivo sono un'impedenza di piccolo segnale eccezionalmente bassa e una buona stabilit termica, oltre che un'elevata precisione sul riferimento di tensione fornito. Altre caratteristiche interessanti risultano una bassa rumorosit e una elevata resistenza del dispositivo nel tempo. La modalit in cui il riferimento verr impiegato descritta dalla Fig.6, eccezion fatta unicamente per il valore della tensione di alimentazione
Figura 6: Configurazione per la generazione di una VREF a 1.24 V.

(nel nostro caso 5 V) e, di conseguenza, per il valore opportuno della resistenza di limitazione da impiegare. Subito a valle del riferimento a 1.24 V, la presenza di un trimmer di valore sufficientemente piccolo (nel nostro caso, 1 k) consente di produrre su un terzo nodo una tensione di riferimento del valore desiderato, ovvero 0.64 V (il motivo della necessit di questo riferimento chiarito nella sezione relativa all'ADC).

ADC0804 (Convertitore Analogico/Digitale)


L'integrato ADC0804 un convertitore analogico/digitale CMOS a 8 bit ad approssimazioni successive, che usa una scala differenziale potenziometrica. Esso viene visto dai microprocessori come una locazione di memoria o una porta di I/O, ovvero non richiede l'introduzione di ulteriore logica di interfaccia. L'ingresso analogico a tensione differenziale consente di ottenere una migliore reiezione di modo comune; inoltre riferimento dell'ADC pu essere regolato, agendo su un opportuno pin, in modo tale da codificare qualunque fondo scala, per quanto piccolo, utilizzando tutti gli 8 bit di risoluzione (compatibilmente, com' ovvio, alle specifiche di rumore del dispositivo). I limiti imposti per il valore della tensione di alimentazione (da 4.5 V a 6.3 V) sono 8

perfettamente compatibili con l'alimentazione a 5 V disponibile; l'assorbimento di corrente tipico dell'integrato pari a 2.5 mA.

Figura 7: Configurazione di utilizzo dell'ADC0804.

La configurazione con la quale il dispositivo viene impiegato dal sistema mostrata in Fig.7: sulla parte sinistra dell'integrato si distinguono gli I/O digitali, di controllo (i primi quattro) e di dati (gli ultimi otto); sulla parte destra sono stati rappresentati i restanti otto I/O analogici, corrispondenti ai pin di alimentazione (si osservi che, bench l'integrato offra la possibilit di distinguere massa analogica e digitale, nella nostra applicazione i due pin saranno posti sullo stesso nodo), i due pin per la generazione del segnale di clock, i due pin relativi all'ingresso differenziale e il pin destinato a leggere un eventuale riferimento di tensione esterno, mediante il quale possibile regolare il fondo scala dell'ADC. Prima di tutto, focalizziamo l'attenzione sulle problematiche relative agli ingressi analogici da fornire al dispositivo. Per la generazione del segnale di clock, si propongono due alternative: derivarlo direttamente da quello del micro oppure utilizzare la soluzione descritta in Fig.7 (ed effettivamente implementata), ottenuta mediante un gruppo RC esterno e un trigger di Schmitt integrato nel dispositivo. Dati i valori di R e C, si pu valutare la frequenza del segnale di clock ottenuto dalla formula fCLK = 1/(1.1*R*C): in corrispondenza dei valori scelti in Fig.7 si ottiene un valore di fCLK pari a 606.1 kHz; diversamente, per i valori R = 10 k e C = 100 pF (ovvero quelli scelti per la nostra applicazione, come desumibile dallo schematico di Fig.4) si ha fCLK = 909.1 kHz. Risulta importante osservare che tale comportamento ideale si ottiene a patto di non caricare il pin CLK R con una capacit parassita troppo grande (nel caso in cui si voglia utilizzare il segnale di clock cos generato anche per altri scopi): sono consentiti valori di capacit di carico fino a 50 pF, che corrisponderebbe a 7 convertitori A/D di questo tipo pilotati dal clock di uno solo. Discutiamo ora il riferimento di tensione da porre sul pin VREF/2. L'ADC stato progettato in modo tale che, se questo pin viene lasciato floating, il fondo scala si estende da 0 V a VCC. In pratica, la tensione di riferimento impiegata dall'integrato pu essere pari o a met della VCC oppure alla 9

tensione forzata dall'esterno sul pin VREF/2 (il guadagno interno in corrispondenza del pin di ingresso VREF/2 pari a 2, cos che la tensione di ingresso differenziale corrispondente al fondo scala risulta pari a due volte la tensione applicata al pin 9: si veda la Fig.8). Per maggiore chiarezza, facciamo un esempio numerico. Se i valori dell'ingresso analogico differenziale (VIN(+) - VIN(-)) cadono nel range compreso tra 0.5 V e 3.5 V, lo span che si vorrebbe ottenere pari a 3 V: si potrebbe allora porre un valore di tensione pari a 0.5 V sul pin VIN(-), in modo tale da assorbire l'offset, e uno pari a met di 3 V, ovvero 1.5 V, sul pin VREF/2. In questo modo, si effettivamente adattato il fondo scala dell'ADC alla dinamica del segnale di ingresso, producendo un ovvio miglioramento in termini di risoluzione ottenuta. Nel nostro sistema, il segnale da convertire sar di fatto l'uscita prodotta da ciascuno dei sensori di temperatura utilizzati: la dinamica di ingresso dell'ADC sar quindi estesa tra 0 V e, come anticipato, 1.28 V, nelle nostre ipotesi. Si dovr allora porre sul pin 9 dell'ADC0804 una VREF/2 pari a 0.64 V: nella sezione relativa al LM385 abbiamo gi discusso come ci stato ottenuto. Focalizziamo ora l'attenzione sui segnali di controllo che l'ADC dovr scambiare con il microcontrollore affinch il suo corretto funzionamento sia garantito: si tratta dei segnali CS (Chip Select), RD (Read Strobe), WR (Write Strobe) e INTR (Interrupt), ciascuno con una sua specifica funzione e tutti attivi bassi. La procedura di dialogo tra micro e ADC si compie in due fasi. La prima fase quella di avvio della conversione (il diagramma temporale riportato in Fig.9). Come
Figura 8: Circuito per la generazione del riferimento nell'ADC0804.

Figura 9: Diagramma temporale della fase di avvio conversione.

10

si evince dal diagramma, ci che di fatto avvia la conversione l'attivazione (transizione alto-basso) prima del CS, poi del WR, in modo tale che l'impulso di WR sia tutto contenuto all'interno dell'impulso sulla linea CS. In questa fase, il segnale INTR a livello logico alto. A questo punto, l'ADC legge il valore analogico di tensione differenziale ai suoi ingressi e produce in uscita la parola di conversione: a partire dal ritorno a livello logico alto di WR, si dovr aspettare un primo intervallo di tempo di preparazione della conversione stessa, che come evidenziato nel diagramma, si pu estendere tra 1 e 8 volte il periodo di clock dell'ADC, e un secondo intervallo di tempo associato alla conversione vera e propria e di durata TC (tempo di conversione). Per il valore di TC il datasheet dichiara un valore minimo di 66 e massimo di 73 cicli di clock: se il valore vero di TC fosse 70 cicli di clock, il tempo impiegato dal nostro ADC ad effettuare la conversione sarebbe, per quanto detto, pari a 70*(1/909.1 kHz) = 77 s. Al termine di questa fase, l'ADC stesso attiva (porta a livello logico basso) il segnale di INTR: tale segnale, allora, indica che la fase di lettura e conversione terminata. Si pu procedere quindi alla seconda fase, ovvero quella di lettura della parola di conversione prodotta (diagramma temporale in Fig.10). Si tratta di ripetere la sequenza di

Figura 10: Diagramma temporale della fase di lettura della parola di conversione.

commutazioni vista per i segnali CS e WR all'inizio della fase precedente semplicemente sostituendo a WR il segnale RD: si deve cio produrre un impulso di RD tutto contenuto all'interno di un impulso di CS. Come si evince dal diagramma, e in perfetta simmetria con quanto descritto nella fase precedente, gi dopo la commutazione alto-basso del RD (e un successivo tempo di attesa indicato con tACC e pari a 135 ns), i bit di dati iniziano a essere validi e la parola di conversione pu essere letta. Il ritorno a livello logico alto di RD indica all'ADC che la lettura della parola stata completata: di conseguenza, il dispositivo riporta i pin relativi alle bit dati di uscita ad alta impedenza. Risulta immediato allora intuire che il CS agisce come una specie di abilitazione della comunicazione per l'ADC, mentre l'INTR ha lo scopo di segnalare all'eventuale micro che user il 11

dispositivo che l'operazione di conversione si conclusa con successo: queste considerazioni conducono in maniera diretta alla soluzione che si scelto di implementare e che verr descritta in maniera dettagliata nel capitolo seguente, relativo al microcontrollore e alla sua programmazione.

L2014 (Display LCD)


Il modulo L2014 un display a cristalli liquidi (LCD) a matrice di punti a basso consumo di potenza, caratterizzato da un pannello LCD ampio e ad alto contrasto e un controllore CMOS integrato. Tutte le funzioni del display sono controllate da istruzioni, quindi il modulo particolarmente adatto ad essere interfacciato con un microprocessore (o, nel nostro caso, con un microcontrollore). Questo rende ovviamente il modulo utilizzabile in un'ampia gamma di applicazioni. Il display in grado di mostrare 20 caratteri, ciascuno ottenuto come una matrice di punti 5x7, su 4 linee. Esso include al suo interno una memoria ROM in grado di generare 192 tipi di caratteri (con la possibilit di generarne altri arbitrari mediante una apposita area RAM), una memoria dati di tipo RAM di dimensione 80 bytes, leggibile anche dal MPU, un oscillatore e un circuito di reset all'avvio integrati (di conseguenza non occorre fornire tali segnali dall'esterno). Risulta inoltre possibile interfacciare il display con MPU a 4 e 8 bit. Il dispositivo richiede una alimentazione singola a 5 V e tipicamente assorbe una corrente di 2.7 mA. Il modulo in grado di operare in un range di temperature che si estende da 0 C a 50 C. Il dispositivo presenta 14 pin in linea, il simbolo di ciascuno dei quali riportato nella tabella di Fig.11. VSS e VDD sono i pin di alimentazione (fissati rispettivamente a tensioni di 0 V e 5 V), mentre VLC un pin su cui si pu agire con un riferimento di tensione in modo da variare il contrasto dei cristalli liquidi (tale pin viene posto a massa, in modo da ottenere dal display il massimo contrasto; RS il pin associato al segnale di Register Select: quando alto, il display e il micro stanno scambiando dati, quando basso istruzioni o flag di controllo; R/W il pin associato al segnale di Read/Write: quando alto, il display in scrittura sul bus dati, quando basso in lettura (nello schematico, tale pin viene fissato a massa in quanto la nostra applicazione non richiede che venga scambiata alcuna informazione dal display verso il micro); E il pin associato al segnale di Enable: per effettuare una qualsiasi operazione, un impulso su questa linea necessario affinch essa venga avviata; i pin indicati con
Figura 11: Simboli associati ai pin del modulo L2014.

DBX sono quelli associati al bus dati: nella nostra specifica applicazione, solo la seconda met di questo bus viene utilizzato, e quindi parole di 8 bit verranno scambiate in due fasi successive (di 12

conseguenza, i primi 4 bit di questo bus restano, nello schematico mostrato, privi di connessioni elettriche). Il modulo L2014 si presenta piuttosto versatile: risulta d'altra parte opportuno, per ragioni di brevit, focalizzare l'attenzione unicamente su quelle funzionalit che vengono utilizzate nella nostra specifica applicazione. L'unica nostra esigenza quella di scrivere linee di testo predefinite sul display: si tratter allora essenzialmente di essere in grado di trasmettere dal micro verso il display istruzioni e dati. Abbiamo gi detto che il bit RS a indicare di volta in volta se una data parola trasmessa dal micro al display una parola di istruzioni o di dati (in questo secondo caso, nient'altro che un singolo carattere). Restano allora da chiarire due aspetti: quali segnali di controllo produrre per assicurare che il trasferimento della parola dal micro al display avvenga correttamente e, in secondo luogo, quale significato associa il display a un dato byte ricevuto (in maniera pi concreta, si dovr conoscere la lista delle istruzioni e la mappa dei caratteri). La prima questione risolta sinteticamente dalla Fig.12, che rappresenta il diagramma temporale dei segnali di controllo da attivare per effettuare una operazione di scrittura sul modulo L2014 (la linea "doppia", che indica un livello logico contemporaneamente alto e basso, corrisponde ad una condizione "don't care").

Figura 12: Diagramma temporale dei segnali di controllo per un'operazione di scrittura sul modulo L2014.

Come si evince dal diagramma, si tratter prima di tutto di porre il valore di RS al livello logico che ci interessa, sulla base di quanto detto relativamente sul suo significato; in secondo luogo, si dovr porre a livello logico basso il pin R/W: stiamo effettuando un'operazione di scrittura; successivamente, si porter il pin E, inizialmente basso, a livello alto. Poco tempo dopo, i bit 13

Figura 13: Lista delle istruzioni del modulo L2014.

presenti sul bus dati verranno letti: necessario che in questa fase essi rimangano stabili (per questo motivo, pi semplice fare in modo che essi vengano stabiliti prima ancora dell'inizio dell'operazione di scrittura e rimangano costanti per tutta la durata della stessa). Dopo un certo intervallo di tempo minimo PWEH, possibile concludere la procedura riportando il pin di E a livello basso: com' ovvio, tutti i valori dei tempi riportati sul diagramma sono commentati e quantificati nel datasheet, ma immediato osservare come tutti questi tempi, dell'ordine al pi di qualche centinaio di ns, risultino di fatto almeno di un ordine di grandezza inferiori ai tempi con cui 14

il micro esegue le sue istruzioni. La schematizzazione che segue allora molto semplice, soprattutto nell'ipotesi posta per la quale R/W fisso a livello basso: per effettuare un'operazione di scrittura sul modulo L2014 sar sufficiente scrivere il valore di RS, scrivere il valore dei bit sul bus, quindi produrre un impulso alto sul pin di E (tale impulso viene prodotto dal micro nel modo pi semplice che si possa immaginare, come si evince dal codice C riportato nel capitolo seguente). Nel caso che, come nella nostra applicazione, si utilizzino soli 4 bit del bus dati disponibile, la procedura resta identica, con l'unica differenza che si dovranno trasferire separatamente, in due operazioni successive di scrittura, la parte alta e la parte bassa della parola, dopo aver ovviamente settato il modulo per lavorare in questa modalit operativa ( questa infatti, come spiegato subito di seguito, una delle impostazioni che si pu richiedere al modulo mediante la scrittura di un'istruzione). Risolviamo a questo punto la seconda questione aperta, relativa a come il modulo interpreta le parole che il micro scrive su di esso. La tabella in Fig.13 fornisce la lista completa delle istruzioni disponibili del modulo L2014: ovviamente, non tutte queste istruzioni sono utili alla nostra specifica applicazione. Commentiamo allora unicamente le istruzioni che verranno impiegate. (1) Display clear: pulisce il display e riporta il cursore alla posizione di partenza. Di default, tale posizione coincide con l'indirizzo 0, in alto a sinistra, ma pu essere cambiata (per la mappa degli indirizzi associati a ciascuna posizione sul display, si veda la Fig. 14); (3) Entry Mode Set: imposta la direzione del movimento del cursore e se il display verr shiftato oppure no. L'istruzione prevede due parametri: il bit I/D determina se la posizione del cursore verr incrementata (I/D = 1) o decrementata (I/D = 0); il bit S determina se, in corrispondenza di un'operazione di scrittura di un carattere, si effettuer uno shift del display (S = 1) oppure no (S = 0). Nella nostra applicazione, si porr I/D = 1 e S = 0: il cursore verr incrementato e non verr effettuato alcuno shift del display; (4) Display ON/OFF control: determina lo stato di accensione del display complessivo e del cursore ed, eventualmente, avvia l'effetto di blinking sul cursore (per dettagli sull'effetto di blinking, nient'altro che l'oscuramento intermittente di tutti i punti di un dato carattere). L'istruzione prevede tre parametri: il bit D determina se il display vero e proprio nello stato ON (D = 1) oppure OFF (D = 0), il che non ha a che fare, ovviamente, con lo stato di accensione del modulo (anche con il display spento, se l'alimentazione viene mantenuta il modulo continua ad operare, ad esempio i dati scritti nella RAM del display vengono conservati); il bit C determina se il cursore mostrato sul display (C = 1) oppure no (C = 0); il bit B determina se l'effetto di blinking sul carattere nella posizione del cursore attivo (B = 1) oppure no (B = 0). Nella nostra applicazione, si porr D = 1, C = 1 e B = 0: il display sar sempre acceso, con il cursore mostrato e il blinking inattivo.

15

(6) Function Set: imposta alcuni parametri di funzionamento in base al valore assunto dai tre parametri che contiene: il bit DL (Data Length) determina la lunghezza del bus dati nell'interfacciamento verso il micro a 4 bit (DL = 0) o 8 bit (DL = 1); il bit N determina il duty ratio, fissandolo a 1/16 (N = 1) o a 1/8 (N = 0); il bit F determina il carattere tipografico, rendendolo una matrice di punti 5x10 (F = 1) o 5x7 (F = 0). Nella nostra applicazione, si porr DL = 0, N = 1 e F = 0: l'interfacciamento avverr sempre a 4 bit, il duty ratio sar imposto a 1/16 (scelta obbligatoria, come indicato dal datasheet, per lo specifico dispositivo L2014) e il carattere tipografico sar inquadrato in una matrice di punti 5x7 (in realt, per N = 0 il datasheet osserva che tale bit diventa per il modulo una condizione don't care e il carattere tipografico necessariamente quello indicato).

Figura 14: Mappa degli indirizzi della DD RAM sul display.

Resta da chiarire in che modo il modulo associa ad una data word ricevuta uno specifico carattere tipografico e in quale punto del display stampa questo carattere. Come anticipato, il display pu essere visto come una RAM di 80 byte, ovvero 20 caratteri per 4 linee: il nome con cui si fa riferimento a tale RAM sul datasheet appunto DD RAM, ovvero Display Data RAM. Ciascuna locazione di tale memoria ovviamente associata ad un indirizzo (esprimibile in due cifre esadecimali) e ad una specifica posizione sul display. In assenza di shift, la mappa delle locazioni della RAM sulle varie posizioni del display in Fig. 14. Semplicemente, gli indirizzi della DD RAM compresi tra 0x00 0x13 sono posti nella linea 1, quelli compresi tra 0x40 e 0x53 nella linea 2, quelli compresi tra 0x14 e 0x27 nella linea 3, infine quelli compresi tra 0x54 e 0x67 nella linea 4. Quindi, risultano consecutive le linee 1 e 3 e le linee 2 e 4. Nel momento in cui viene scritta una stringa di byte nella DD RAM, allora, si parte dalla posizione corrente del cursore (eventualmente, quella iniziale, o anche posizione "home") e si procede per indirizzi consecutivi. Sebbene esista la possibilit di spostare il cursore arbitrariamente sul display, la nostra applicazione non richiede necessariamente questo tipo di azione. La Fig.15 mostra infine il contenuto della ROM da 192 byte per la scrittura di uno dei caratteri "standard": l'operazione di scrittura di un carattere sul display, nella sua forma pi immediata, consister allora semplicemente nella selezione di uno di questi caratteri attraverso una parola scritta sul bus dati, secondo le corrispondenze indicate nella stessa Fig.15. 16

Figura 15: Mappa dei caratteri standard per il modulo L2014.

17

In ultimo, anticipiamo che la procedura di inizializzazione (dettagliatamente commentata nella sezione relativa alla descrizione del codice C sviluppato) si tradurr in una sequenza di istruzioni dei tipi visti e analizzati, da eseguire sul modulo prima del suo utilizzo vero e proprio.

CD74AC14E (Invertitore CMOS)


L'integrato CD74AC14 contiene sei invertitori indipendenti. Questo dispositivo realizza l'operazione booleana NOT mediante un trigger di Schmitt: gli invertitori hanno quindi differenti tensioni di ingresso di soglia per le commutazioni basso-alto (VT+) e alto-basso (VT-). Pi precisamente, l'isteresi, definita come la differenza VT+ - VT-, ha un valore tipico di 0.5 V. La tensione di alimentazione pu essere compresa tra 1.5 V e 5.5 V; fissata la VCC, l'ingresso pu collocarsi tra 0 V e la VCC stessa. La corrente erogata dal dispositivo sul pin di uscita , in entrambi gli stati, pari a 24 mA. La modalit, piuttosto tradizionale, in cui il dispositivo verr impiegato nel sistema non richiede l'indicazione di ulteriori dettagli. L'unica specifica che opportuno focalizzare il valore dei tempi di commutazione, dell'ordine di qualche nanosecondo: si tratta di tempi del tutto trascurabili rispetto a quelli con cui il micro svolge le sue operazioni (un clock a 11.0592 MHz equivale a un periodo di circa 90 ns, senza contare che una singola istruzione richiede svariati cicli di clock per essere completata: per ulteriori dettagli, si faccia riferimento al capitolo sul microcontrollore): potremo allora considerare l'operazione di inversione tra IN e OUT istantanea. Si osserva infine che il sistema utilizzer una sola delle porte logiche messe a disposizione dall'integrato: come evidenziato dallo schematico, gli altri ingressi e uscite verranno lasciate floating (in corrispondenza di questi e altri nodi stato utilizzato il simbolo Capture di "no connect").

HEF4051 (Multiplexer Analogico a 8 canali)


L'integrato HEF4051 un multiplexer analogico a 8 canali con tre ingressi di indirizzo, un ingresso di enable attivo basso, otto ingressi indipendenti e un'uscita comune. L'operazione di multiplexing ottenuta mediante un array di 8 switch analogici bidirezionali tra gli ingressi e l'uscita. Con il pin di enable basso, uno degli otto switch viene selezionato (stato di ON, bassa impedenza) in base ai valori logici presenti sui pin di indirizzo. Viceversa, con enable alto, tutti gli switch si pongono in alta impedenza, indipendentemente dal valore assunto dai bit di indirizzo. VDD e VSS sono i pin di alimentazione per gli ingressi di controllo digitali (quelli di indirizzo e l'enable). La differenza di potenziale tra i due pin deve essere compresa tra 3 V e 15 V. I segnali presenti sui pin analogici (gli ingressi e l'uscita comune) possono variare tra la stessa VDD (come limite superiore) e la tensione posta su un terzo pin, indicato con VEE. La differenza VDD-VEE non pu superare il valore di 15 V. Affinch il dispositivo funzioni come un multiplexer digitale, si deve cortocircuitare VEE a VSS (che tipicamente viene posto a massa). 18

Nel nostro specifico caso, solo 4 degli 8 ingressi analogici disponibili verranno utilizzati: si tratter allora di porre a massa 1 dei 3 pin di selezione. Ad esempio, come verr mostrato sia in fase di test su breadboard sia in fase di realizzazione del layout, si pu pensare di porre a massa il pin associato al bit di indirizzo di peso pi alto, quindi di utilizzare gli ingressi analogici indicizzati da 0 a 3. Un'ultima osservazione da rilevare riguarda i ritardi di propagazione tra la variazione dei bit di indirizzo e la variazione dell'uscita: tale ritardo corrisponde tipicamente a 150 ns. D'altra parte, a differenza di quanto detto a proposito dell'inverter, tale delay non va confrontato con la durata del clock, ma con il tempo impiegato dall'ADC a produrre la codifica (per ulteriori dettagli, si faccia riferimento alle sezioni relative al timing dell'ADC e al codice sviluppato).

HIN232 (Adattatore di livello RS-232)


L'integrato HIN232 un'interfaccia per la trans/ricezione verso una porta RS-232 particolarmente adatta a quelle applicazioni in cui la alimentazione a 12 V non disponibile. Esso richiede una alimentazione singola a 5 V e monta dei convertitori di tensione a pompa di carica che, partendo da quella, generano una ulteriore tensione di alimentazione a 10 V richiesta dal funzionamento del dispositivo stesso. Alcuni dei principali features del dispositivo sono: compatibilit CMOS e TTL all'ingresso, slew-rate limitato sui segnali di uscita, un rate che pu arrivare a 120 kbps, bassa dissipazione di potenza. In Fig.16 riportata la modalit in cui i condensatori esterni da 1 F o da 0.1F (come descritto in NOTE 1, sul datasheet) devono essere connessi al dispositivo. Il simbolo circuitale utilizzato indica esplicitamente, se ce ne fosse bisogno, che si debba trattare di condensatori elettrolitici. Il massimo valore consentito per la tensione di alimentazione 6 V, la corrente assorbita tipicamente pari a 5 mA; le tensioni di soglia superiore (per livello basso) e inferiore (per livello alto) all'ingresso sono rispettivamente 0.8 V e 2 V. La minima tensione sul pin di enable che abilita il relativo bit 2.4 V (nello schematico proposto, tale pin posto direttamente a massa). Il datasheet del dispositivo offre ulteriori dettagli strutturali e funzionali del dispositivo; d'altra parte, un ulteriore approfondimento di questi aspetti non risulta propedeutico alla comprensione del funzionamento del sistema realizzato, quindi verr tralasciato. 19
Figura 16: Pinout e connessione dei condensatori di supporto per l'integrato HIN232.

Il microcontrollore 80C32
Descrizione generale del dispositivo
L'integrato 80C32, prodotto dalla Philips, un microcontrollore ad alte prestazioni basato sull'architettura dell'8051 e fabbricato mediante il processo tecnologico CMOS ad alta densit di integrazione della Philips stessa. Il dispositivo, privo di ROM al suo interno, contiene una RAM da 256 byte, 32 linee di I/O parallelo (in pratica, 4 porte ciascuna da 8 bit), tre contatori (o timer) a 16 bit, una struttura di interrupt a 4 livelli di priorit, una porta di I/O seriale UART destinata a comunicazioni con altri microprocessori, un circuito di clock on-chip. Il dispositivo, pensato per lavorare con una tensione di alimentazione singola compresa tra 2.7 V e 5.5 V, ha anche un consumo di potenza statica particolarmente basso, oltre che due soluzioni software per la riduzione del consumo di potenza (poich tali modalit di utilizzo non verranno impiegate, esse non verranno discusse). Il pinout dell'integrato riportato in Fig.17. Ciascun simbolo associato a ciascun pin richiama mnemonicamente una funzione. In particolare: VSS: riferimento di massa; VCC: tensione di alimentazione singola (positiva); P0.0-7: la porta 0 una porta bidirezionale a 8 bit in configurazione open drain. I pin di tale porta che presentano un valore logico alto scritto su di essi vengono visti come floating, e possono quindi essere usati come ingressi ad alta impedenza. La porta 0 ha anche una funzione speciale, durante gli accessi alla ROM esterna: essa fornisce la parte bassa dell'indirizzo cui accedere e legge la parola prodotta dalla memoria stessa; P1.0-7: la porta 1 una porta bidirezionale a 8 bit, tirata a VCC da una rete di pull-up. Poich i pin di tale porta presentano un valore logico alto scritto su di essi dall'interno del micro, essi possono essere usati come ingressi nel momento in cui li si forzi a massa. Gli ultimi due pin della porta hanno anche una funzione alternativa, che per non verr utilizzata; P2.0-7: la porta 2 una porta bidirezionale a 8 bit, tirata a VCC da una rete di pull-up. Poich i pin di tale porta presentano un valore logico alto scritto su di essi dall'interno del micro, essi possono essere usati come ingressi nel momento in cui li si forzi a massa. La porta 2 ha anche una funzione speciale, durante gli accessi alla ROM esterna: essa fornisce 20
Figura 17: Pinout dell'integrato 80C32.

la parte alta dell'indirizzo cui accedere alla ROM, sia che si tratti del fetch di una istruzione sia che si tratti della lettura di un dato; P3.0-7: la porta 3 una porta bidirezionale a 8 bit, tirata a VCC da una rete di pull-up. Poich i pin di tale porta presentano un valore logico alto scritto su di essi dall'interno del micro, essi possono essere usati come ingressi nel momento in cui li si forzi a massa. La porta 3 assolve anche a numerose delle funzioni speciali associate ai features caratteristici dei microcontrollori basati sull'architettura dell'8051: RxD e TxD sono rispettivamente ingresso e uscita per la porta seriale; INT0 e INT1 sono gli ingressi associati all'interrupt esterno; T0 e T1 sono gli ingressi esterni destinati al controllo dei timer; WR e RD sono destinati rispettivamente ai segnali di write strobe e read strobe nelle fasi di accesso alla memoria esterna (per la lettura di un dato); RST: il pin di reset. Un valore alto su questo pin per due cicli macchina (naturalmente, mentre l'oscillatore in funzionamento) resetta il dispositivo. Un resistore integrato posto verso VSS consente un reset all'accensione utilizzando unicamente un condensatore esterno posto verso VCC ( la soluzione adottata nella nostra applicazione, si veda lo schematico); ALE: il pin di Address Latch Enable, destinato al segnale di controllo per il D-Latch che conserva memoria della parte bassa dell'indirizzo negli accessi alla memoria esterna. Durante il normale funzionamento, il segnale di ALE emesso a un rate costante di 1/6 della frequenza di oscillazione (esso viene saltato, invece, durante ogni istruzione di accesso alla memoria dati esterna). Tale segnale pu essere disabilitato settando un opportuno bit di un SFR (si veda di seguito per maggiore chiarezza): in questo caso, l'impulso di ALE viene prodotto unicamente durante un'istruzione di accesso alla memoria dati esterna (MOVX); PSEN: il pin di Program Store Enable, destinato al segnale di controllo per la memoria ROM esterna nelle fasi di fetch delle istruzioni da eseguire. Quando il microcontrollore sta eseguendo un codice dalla memoria di programma esterna, PSEN viene attivato due volte ad ogni ciclo di clock, eccetto quando l'istruzione in corso sia l'accesso alla memoria dati esterna (in tal caso, l'impulso viene "saltato"); EA/VPP: il pin di External Access Enable. Tale pin deve essere mantenuto a valore logico basso dall'esterno per abilitare il dispositivo a estrarre le istruzioni da eseguire dalla memoria programmi esterna; XTAL1: ingresso dell'amplificatore invertente integrato per ottenere l'oscillatore; XTAL2: uscita dell'amplificatore invertente integrato per ottenere l'oscillatore.

Lo schema a blocchi del dispositivo 80C32 riportato in Fig.18. Come gi anticipato, l'architettura di riferimento quella dell'8051, con variazioni poco significative: i dettagli relativi al

21

funzionamento del micro in uso saranno allora raccolti dall'ampia bibliografia dedicata ad esso (in particolare, si far riferimento al tutorial disponibile online al link http://www.8052.com/tut8051).

Figura 18: Schema a blocchi del microcontrollore 80C32.

Per la ragione appena esposta, si rimanda alla sezione successiva di questo capitolo la piena comprensione del principio di funzionamento che lo schema a blocchi in Fig.18 sottende.

L'architettura di riferimento: il microcontrollore 8051


Secondo un approccio gi utilizzato nelle sezioni precedenti, dell'architettura generale del micro verranno focalizzati unicamente quegli aspetti che risultano imprescindibili ai fini della piena comprensione del progetto realizzato. Verranno in particolare discusse caratteristiche quali: la memoria e i registri speciali, il tempo di esecuzione di un'istruzione, la porta seriale e i timer, gli interrupt.

22

La memoria e i registri speciali L'8051 prevede in generale l'utilizzo di tre tipi di memoria: memoria on-chip, memoria di programma esterna e memoria RAM esterna. La memoria di programma quella che contiene le istruzioni che il micro deve eseguire, limitata a 64 kB (da cui segue che la dimensione degli indirizzi di 16 bit) e pu essere sotto forma di EPROM interna o, come avviene nella maggior parte delle applicazioni, inclusa la nostra, esterna (si veda lo schematico): in particolare, solo in quest'ultimo caso essa pu raggiungere la dimensione massima indicata. In realt, anche se il progetto discusso non ne prevede, esistono espedienti hardware (che non verranno discussi) per fare in modo che l'8051 sia in grado di indirizzare una memoria programma pi estesa di 64 kB. L'8051 prevede anche l'utilizzo di una RAM esterna, anch'essa limitata a 64 kB, quindi pi ampia, ma ovviamente pi lenta, della memoria interna disponibile on-chip (tale soluzione non verr comunque implementata nel presente progetto). Come anticipato, l'8051 include una RAM interna montata on-chip, che si suddivide a sua volta in due aree (si faccia riferimento alla Fig.19): RAM interna e Area Registri Speciali (SFR appunto l'acronimo di Special Function Register). La RAM interna ha un'estensione di 128 byte: la memoria pi veloce e flessibile in lettura e scrittura (com' ovvio, si tratta tuttavia di una memoria volatile). Tale area costituita unicamente da:
Figura 19: Schema della memoria interna dell'8051.

4 banchi di registri "R", per ciascun banco numerati da 0 a 7, destinati alla memorizzazione di dati temporanei necessari ad una data operazione complessiva (ad esempio, un'addizione); tra questi banchi, di default (all'accensione) in uso il banco 0, ma si pu modificare questa impostazione agendo opportunamente sul valore di un SFR. Occorre osservare che ciascuna locazione di questa area di memoria pu essere indirizzata sia come un byte generico della RAM interna sia come un registro (tale considerazione sar chiara pi avanti);

una sezione di memoria indirizzabile a bit, compresa tra gli indirizzi da 0x20 a 0x2F. Tale sezione, oltre a poter essere indirizzata a byte come qualsiasi altra locazione della RAM interna, anche quella cui fanno riferimento alcune istruzioni che operano appunto "a bit" quali SETB e CLR, mediante gli indirizzi che richiedono come argomento (da 0x00 a 0x7F);

una sezione (i restanti 80 byte), compresa tra l'indirizzo 0x30 e 0x7F, che pu contenere variabili utente cui si richiede di accedere in maniera frequente e veloce (tale area viene utilizzata dal micro anche per le operazioni di stack, e risulta quindi spesso un po' esigua). 23

L'area SFR costituita da registri che controllano specifiche funzionalit dell'8051. Per esempio, 4 registri SFR consentono l'accesso alle 32 linee di I/O parallelo, un altro consente di leggere e scrivere sulla seriale, e cos via. I registri di quest'area appaiono ancora come locazioni della RAM interna, identiche alle altre ai fini dell'indirizzamento: bench non a tutte le locazioni di quest'area (indirizzi compresi tra 0x80 e 0xFF) corrisponda uno specifico SFR, altamente sconsigliato utilizzare tali locazioni di memoria apparentemente "libere" come byte di memoria general purpose. Di fatto, quindi, agire sui registri SFR vuol dire modificare in qualche modo la modalit operativa del micro. I registri SFR dell'8051 standard sono 21, e ovviamente a ciascuno di essi associato, oltre ad un indirizzo, anche un nome mnemonico, utilizzato ad esempio nella programmazione C dei microcontrollori (ciascun IDE dispone di librerie di file di intestazione che, fra le altre cose, contengono queste associazioni tra gli indirizzi di memoria interna e i relativi alias degli SFR); alcuni di questi, inoltre, sono accessibili a bit (i particolare, quelli associati a indirizzi multipli di 8). Forniamo a questo punto una rapida panoramica degli SFR utilizzati dalla nostra applicazione: P0: la porta P0 di I/O. Ciascun bit di questo registro corrisponde ad un pin del microcontrollore. Per esempio, il bit 0 il pin P0.0, il bit 7 il pin P0.7. Settare un bit di questo registro SFR equivale a forzare ad un livello alto il corrispondente pin di I/O, mentre resettarlo equivale a forzarlo ad un livello basso. Lo stesso discorso pu essere ripetuto in perfetta analogia per gli altri SFR, associati alle porte parallele (P1, P2 e P3); SP: lo stack pointer del microcontrollore, ovvero il registro che punta al prossimo valore dello stack nella RAM interna. Se ad un certo punto del flusso di programma tale registro ha valore x: la successiva istruzione di PUSH caricher il valore indirizzato dall'argomento nello stack, all'indirizzo x+1 (prima incrementa, poi carica); viceversa la successiva istruzione di POP caricher il valore dello stack all'indirizzo x in memoria all'indirizzo in argomento (prima carica, poi decrementa); DPL/DPH: sono i registri di Data Pointer (rispettivamente parte bassa e parte alta). Il registro corrispondente ai 16 bit complessivi (indicato con DPTR) un intero compreso tra 0 e 65535 con la funzione di puntare a uno specifico indirizzo di una memoria esterna da, al pi (come anticipato), 64 kB, che sia di codice o di dati; TCON: il registro di Timer Control, usato per configurare il modo di funzionamento dei timer dell'8051; alcuni dei suoi bit sono invece destinati a configurare o segnalere gli interrupt esterni (vedi sezioni sui timer e sugli interrupt); TMOD: il registro di Timer Mode, usato per configurare il modo di funzionamento di ciascuno dei due timer (vedi sezione sui timer); TL0/TH0: sono i registri che mantengono traccia dello stato corrente del timer 0, individuando un valore intero attraverso 16 bit; possono solo incrementare, e lo fanno a 24

seconda del valore assunto da TMOD. Lo stesso discorso pu essere ripetuto per TL1/TH1, con riferimento al timer 1; SCON: il registro di Serial Control, destinato alla configurazione e alla indicazione dello stato della porta seriale (vedi sezione sulla porta seriale); SBUF: il registro di Serial Buffer, usato per inviare e ricevere i dati dalla porta seriale dell'8051. Ogni valore scritto dal micro in questo registro verr inviato serialmente sul pin TxD, viceversa ogni valore letto dal pin RxD verr scritto su questo buffer e reso quindi disponibile al programma in esecuzione sul micro; IE: il registro di Interrupt Enable, usato per abilitare e disabilitare gli interrupt. I 7 bit meno significativi di IE sono usati per l'abilitazione di ciascun interrupt individualmente, mentre il bit pi significativo destinato all'abilitazione di tutti gli interrupt; IP: il registro di Interrupt Priority, usato per cambiare la priorit di ciascun interrupt (essa pu essere alta, se il bit corrispondente a quello specifico interrupt alto, o, viceversa, bassa). Un interrupt pu interrompere solo un altro interrupt a priorit pi bassa; PSW: il registro di Program Status Word, usato per memorizzare alcuni bit importanti che vengono aggiornati nell'esecuzione delle istruzioni (ad esempio, flag di carry e di overflow); ACC: l'accumulatore del micro, il registro pi usato, poich coinvolto in molte istruzioni; B: un registro di supporto utilizzato solo per effettuare operazioni di moltiplicazione e divisione. Pu anche, all'occorrenza, essere usato per memorizzare valori temporanei. Oltre ai gi descritti SFR, esistono altri registri detti "registri base", che includono i gi descritti registri ACC, B e SP, ma ancora il PC e i registri "R". Il PC (Program Counter) un indirizzo a 2 byte che punta alla locazione di memoria programma dove l'8051 andr a prendere la prossima istruzione da eseguire: di conseguenza, allo start-up varr PC = 0x0000, nel normale flusso di programma PC verr incrementato (a seconda della lunghezza della istruzione corrente), mentre durante l'esecuzione di istruzioni di controllo del flusso (e.g., istruzioni di salto incondizionato) potr essere fissato a qualsiasi valore intero a 16 bit. I registri "R" sono, come anticipato, un set di 8 registri denominati R0, R1 e cos via fino a R7, utilizzati come registri ausiliari all'accumulatore in molte operazioni (attraverso due bit del PSW possibile impostare il banco di registri in uso). Il tempo di esecuzione di un'istruzione L'8051 opera sulla base di un clock fornito da un quarzo esterno, con una frequenza di risonanza tipicamente (e nella nostra applicazione) pari a 11.059 MHz. Il micro opera allora sulla base dei cosiddetti "cicli macchina": un singolo ciclo macchina la minima quantit temporale che un'istruzione dell'8051 richiede per essere eseguita (minima nel senso che l'esecuzione di alcune istruzioni richiede pi cicli macchina). Da un punto di vista fisico, un ciclo macchina corrisponde a 12 impulsi dell'oscillatore al quarzo: possiamo allora calcolare in maniera semplice quanti cicli 25

macchina al secondo possono essere eseguiti dall'8051, ovvero 11.059.000/12 = 921.583. A questo punto, mediando approssimativamente sul numero medio di cicli macchina per istruzione richiesto dal set di istruzioni del micro, possiamo concludere con una stima di circa 600.000 istruzioni al secondo mediamente eseguite dal micro, ovvero un tempo medio di esecuzione per una singola istruzione di circa 1/600.000 sec-1 = 1.67 sec (tutte queste considerazioni hanno valore solo per l'8051 standard, in quanto in molti suoi derivati il timing delle istruzioni di fatto diverso). La porta seriale e i timer L'8051 classico dispone di due timer, che possono essere configurati, controllati e letti in maniera indipendente. Ciascuno di questi timer pu assolvere a tre funzioni generali: tenere traccia del tempo trascorso (ad esempio per misurare la distanza temporale tra due eventi), contare il numero di eventi e generare il baud-rate per la porta seriale. Di queste tre funzioni, la nostra applicazione impiega solo la terza: per questa ragione, discuteremo i timer unicamente da questo punto di vista. La porta seriale, o UART integrata, una delle funzionalit pi potenti dell'8051: essa rende possibile ottenere una comunicazione tra due sistemi a microcontrollore in maniera semplice e veloce. Una volta configurata la porta, sar sufficiente leggere o scrivere il registro SBUF gi presentato per ricevere od inviare dei dati in linea: sar il micro col suo hardware ad "avvertirci" (manipolando il valore di opportuni flag del registro SCON) quando un dato byte stato ricevuto (ed quindi pronto per la lettura da SBUF) oppure la trasmissione seriale di un dato byte (precedentemente scritto in SBUF) stata completata. L'inizializzazione della porta seriale richiede di agire essenzialmente sul registro SCON, gi presentato e indirizzabile a bit (l'indirizzo "a byte" di tale SFR 0x98, quindi i suoi bit avranno indirizzo compreso tra 0x98, bit meno significativo, e 0x9F, bit pi significativo). Vediamo nel dettaglio la funzione di ognuno dei suoi bit, in ordine decrescente di peso: SM0 (Serial Mode 0): bit 0 di modo; SM1 (Serial Mode 1): bit 1 di modo; SM2 (Serial Mode 2): abilitazione della comunicazione multiprocessore; REN (Receive Enable): abilitazione della ricezione; TB8 (Transmit Bit 8): nono bit da trasmettere nel modo 2 e 3; RB8 (Receive Bit 8): nono bit da ricevere nel modo 2 e 3; TI (Transmit Flag): flag che viene settato automaticamente quando il byte stato trasmesso; RI (Receive Flag): flag che viene settato automaticamente quando un byte stato ricevuto;

Innanzitutto, osserviamo che solo i 4 bit pi significativi del registro hanno una funzione in fase di configurazione (degli altri 4, utilizzeremo solo gli ultimi 2, ovvero i flag di trasmissione e ricezione: le modalit in cui verranno impiegati saranno chiare nella sezione relativa al codice C): di questi, verranno utilizzati effettivamente i bit SM0, SM1 e REN. L'ultimo semplicemente il bit che abilita 26

il micro a ricevere dati sulla porta seriale: ponendo REN = 0, si impedisce al micro di ricevere byte dalla porta seriale. A questo punto, dettagliamo meglio il significato dei bit di modo SM0 e SM1: la loro combinazione fornisce la modalit operativa della UART, associabile ad un intero compreso fra 0 e 3 (ottenuto ponendo SM0 a bit pi significativo). La modalit 0 consiste nella trasmissione di 8 bit e fissa il baud-rate della UART a un valore di fosc/12; la modalit 1 consiste nella trasmissione di 8 bit e deriva il baud-rate della UART dall'overflow del timer 1; la modalit 2 consiste nella trasmissione di 9 bit e fissa il baud-rate della UART a un valore di fosc/64; la modalit 3 consiste nella trasmissione di 9 bit e deriva il baud-rate della UART dall'overflow del timer 1. Innanzitutto, la porta seriale verr utilizzata unicamente a 8 bit, quindi si tratter di comprendere il funzionamento delle modalit 0 e 1 e di scegliere quella pi opportuna alle nostre esigenze. La differenza consiste, come anticipato, unicamente nel modo in cui viene calcolato il baud rate, ovvero la frequenza di simbolo della UART. Nel primo caso, esso risulta fissato unicamente dalla frequenza di clock: nella modalit 0, il baud rate sar inevitabilmente pari a 11.059.000/12 bps, ovvero circa 921.583 bps. Tale valore non si adatta alla nostra applicazione, quindi la scelta della modalit 1 risulta praticamente obbligata. Si tratta a questo punto di capire come impostare il timer 1 in modo tale che il baud rate assuma il valore da noi desiderato, per esigenze di compatibilit con l'applicativo sviluppato, assunto pari a 9600 bps: come anticipato, si dovr allora mandare il timer 1 in overflow proprio con questa frequenza. Analizziamo a questo punto il funzionamento dei timer, con l'obiettivo di comprendere come ottenere quanto richiesto. I due timer funzionano entrambi nella stessa maniera, e condividono i due registri TCON e TMOD, destinati al loro controllo; come anticipato, d'altra parte, ciascun timer dispone di due registri (valutabili come un unico registro da 16 bit) che mantengono traccia del loro stato: si tratta dei registri TL0 e TH0 per il timer 0, TL1 e TH1 per il timer 1. Ad esempio, parlando in decimali, quando il timer 0 ha valore 1000, TH0 conterr il valore 3, TL0 il valore 232: noto il valore scritto nei due registri, lo stato del timer 0 pu essere indicato da un intero calcolabile come TH0*256 + TL0. Ovviamente, lo stesso discorso pu essere ripetuto identico per il timer 1. Segue allora che il valore decimale pi grande che ciascun timer pu assumere 65535: quando uno dei due timer raggiunge tale valore, un ulteriore impulso di clock lo resetter o, per meglio dire, lo mander in overflow. Prima di avviare ciascuno dei timer, tuttavia, necessario stabilire la loro modalit operativa, in analogia a quanto detto a proposito della UART: ci viene ottenuto mediante il registro TMOD. Esso diviso in una parte alta destinata al timer 1 e una parte bassa destinata al timer 0. Ciascuna parte costituita da 4 bit, il cui valore stabilisce la modalit operativa di ciascuno dei due timer

27

(anche questo registro indirizzabile a bit). Ad esempio, con riferimento alla met pi significativa di TCON, si hanno in ordine decrescente di peso i bit: GATE1: quando questo bit settato, il timer 1 attivo solo quando il pin P3.3 (indicato anche con il simbolo INT1 nello schematico) nello stato alto. Se tale bit resettato, il timer 1 svincolato dallo stato del pin P3.3; C/T1: quando questo bit settato, il timer 1 conta il numero degli eventi sul pin P3.5 (indicato anche con il simbolo T1 nello schematico). Se tale bit resettato, il timer 1 viene incrementato ogni ciclo macchina; T1M1: bit 1 di modo del timer 1; T1M0: bit 0 di modo del timer 1.

La parte bassa del registro presenta bit di analoghe funzioni per il timer 0. I modi operativi definiti dai bit T1M1 e T1M0 possono essere associati, analogamente a quanto visto per i bit SM, a interi da 0 a 3. La modalit 0 detta "timer a 13 bit" ed presente nell'8051 unicamente per mantenere la compatibilit col suo predecessore, l'8048 (non quindi generalmente utilizzata nei nuovi sviluppi); la modalit 1 detta "timer a 16 bit", e prevede che TH1 venga incrementato ogni volta che TL1 va in overflow ( come se si disponesse per lo stato del timer di un unico registro a 16 bit); nella modalit 2, detta "timer a 8 bit con auto-reload", TH1 contiene il valore che deve essere caricato in TL1 quando quest'ultimo va in overflow: in pratica, quando TL1 ha valore 255 e viene ulteriormente incrementato, esso assume il valore caricato in TH1 invece di ripartire da 0; la modalit 3, infine, detta "timer in splitted mode", consente di splittare in due timer da 8 bit il timer 0, assegnando i bit di controllo di timer 1 al timer TH0: il timer 1, quindi, potr funzionare in una qualsiasi delle modalit 0, 1 o 2, ma potr essere solo incrementato a ogni ciclo macchina. Per la nostra applicazione si richiede di utilizzare uno dei due timer (che sar il timer 1) come generatore di baud rate ad una frequenza prefissata. Poich non si richiede l'utilizzo del timer 0 per altri scopi, la modalit che appare pi opportuna selezionare il modo 2: affinch l'evento di overflow sia generato dal timer 1 con la frequenza desiderata, entro la approssimazione migliore possibile, si tratter di calcolare il valore pi opportuno da scrivere nel registro TH1. Si imposta allora la seguente equazione (anch'essa riportata sulla guida, gi citata, a cui questa sezione fa riferimento): TH1 = 256 - ((Fq/384)/Baud) essendo Fq la frequenza del quarzo (come noto, 11.059 MHz) e Baud il baud-rate desiderato (pari a, come gi indicato, 9600 bps). Si ottiene in questo modo un valore di TH1 con ottima approssimazione pari a 253.

28

Gli interrupt L'ultimo aspetto dell'architettura dell'8051 standard e del suo utilizzo su cui il caso di soffermarsi la gestione degli interrupt. In termini generali, un interrupt un evento che interrompe la normale esecuzione di un programma. Il flusso di un programma sempre di tipo sequenziale e pu essere alterato da particolari istruzioni che svolgono appositamente questa funzione (le funzioni di salto); a differenze di queste, gli interrupt forniscono un meccanismo di "congelamento" del flusso di programma in corso: solo in corrispondenza di un evento prestabilito che, appunto, genera l'interrupt, viene eseguita una opportuna subroutine, denominata "interrupt handler", ovvero gestore di interrupt. L'evento generatore pu essere, ad esempio, il timer che va in overflow, oppure la ricezione di un byte dalla seriale, o ancora un evento (transizione logica) su un apposito pin esterno (come nel caso della nostra applicazione): l'8051 pu essere configurato per gestire ciascuno di questi eventi con un differente interrupt handler. Ovviamente, tale capacit di interrompere la normale esecuzione di un programma quando un particolare evento si verifica rende il programma stesso molto pi efficiente nel gestire processi asincroni, la qual cosa risulterebbe molto pi onerosa se si dovesse implementare mediante un codice che effettui, sufficientemente spesso, tutti i dovuti controlli: viceversa, la scrittura di un interrupt handler in linguaggio C, come sar possibile osservare, risulta particolarmente immediata. Anche nel caso degli interrupt, tuttavia, necessario configurare il microcontrollore in modo opportuno: il registro SFR su cui si deve in questo caso intervenire IE, che, come anticipato, destina un bit per l'abilitazione di ciascun tipo di interrupt, eccetto il pi significativo, destinato a abilitare/disabilitare il sistema complessivo degli interrupt. Nel nostro caso, si dovr abilitare l'interrupt relativo all'evento esterno sul pin INT 0 (o anche pin P3.2): questo vuol dire che dovranno essere portati a valore logico alto il bit pi significativo e il bit relativo a tale interrupt, ovvero il bit 0 del registro IE (per ulteriori dettagli, si consulti il riferimento citato). Poich non si richiede la gestione di pi di un interrupt, non avrebbe senso porsi il problema di variare il contenuto del gi introdotto registro IP, relativo alle priorit di ciascun interrupt.

Scelte di progetto preliminari: linguaggio e IDE


Il primo passo da compiere in questa fase , ovviamente, la scelta del linguaggio di programmazione tra le soluzioni alternative assembler e linguaggio C. Ad oggi, il dibattito su quale sia in generale la scelta migliore ancora aperto. Essenzialmente, l'assembler presenta il vantaggio di fornire il completo controllo del microcontrollore ed potenzialmente pi efficiente (tale potenzialit si concretizza, ovviamente, se il programmatore in grado di produrre un codice efficiente); il linguaggio C teoricamente pi portabile (il codice prodotto subisce meno variazioni cambiando il microcontrollore in uso), quindi in generale pi adatto a quei programmatori che non sono legati ad una specifica architettura (eventualmente, conosciuta in modo approfondito). D'altra 29

parte, i moderni compilatori C sono in grado di generare un codice altamente ottimizzato, che supera spesso in efficienza il codice assembler prodotto da un programmatore medio, specialmente se questo non ha una particolare qualificazione nell'utilizzo della specifica architettura; un'ultima considerazione pu riguardare il costo dei compilatori C, di frequente molto pi cari di quelli assembler. Considerati questi elementi, appare opportuno orientarsi verso la scelta del linguaggio C. Come anticipato, verr utilizzato il software Keil uVision, dotato di tutti gli strumenti necessari a una programmazione efficace. In fase di avvio del progetto, possibile scegliere la architettura su cui si sta sviluppando, nel nostro caso l'80C32 della NXP (fondata dalla Philips): questo ci consentir di avere a disposizione un header file (reg51.h) in cui ad ogni alias SFR associato l'indirizzo della memoria interna in cui collocato (questo ci permetter quindi di riferirci, nel corpo del codice, ad ogni byte o anche ad ogni bit "speciale" del nostro micro semplicemente richiamando il suo alias). Una volta scritto il codice su un text editor essenziale e intuitivo, sar possibile effettuarne una prima simulazione, con vista sulla memoria interna e su tutte le periferiche dell'8051. Il tool di debug consente addirittura, attraverso un opportuno prompt di comandi, di simulare segnali esterni con cui i pin del micro possono interagire. Accedendo alla finestra di opzioni per il target (si veda il project workspace), possibile anche impostare il valore della frequenza di clock del micro, in modo che la successiva simulazione possa anche fornire indicazioni sul tempo impiegato dal micro ad eseguire il codice sviluppato, step by step (questa possibilit risulta di una certa importanza per applicazioni in cui il timing un aspetto critico). Sempre nella stessa finestra, possibile specificare anche il tipo di output che il software dovr produrre al termine della fase di compilazione: nel nostro caso, si tratter di produrre il file eseguibile direttamente in codice macchina (formato HEX-80). Un'ultima funzionalit che appare importante evidenziare il disassembly: tale tool rende possibile osservare come ciascuna istruzione C del sorgente venga tradotta dal compilatore in assembler. In particolare, l'utilit di quest'ultima funzionalit sar evidente nella fase successiva alla simulazione del codice nell'IDE, ovvero quella di emulazione del codice compilato sull'architettura di riferimento e di test del sistema complessivo (per ulteriori dettagli su questa fase, si faccia riferimento al capitolo successivo).

Il codice C sviluppato
In questo paragrafo, il codice C sviluppato verr riportato e descritto sezione per sezione, secondo l'ordine del sorgente completo. Le sezioni di codice, comprensive delle linee di commento, verranno riportate sotto forma di una o pi screenshot acquisite direttamente dal text editor del Keil e a ciascuna sezione seguir la relativa descrizione e analisi. Il principio generale che ha ispirato la produzione del presente codice (lungo solo 234 linee) l'efficacia: non stato compiuto alcuno sforzo nel tentativo di rendere il codice riutilizzabile in altre 30

applicazioni (per questa ragione, non sono stati realizzati file di intestazione per l'area dichiarativa o file di libreria per le funzioni richiamate, ma tutto il codice prodotto riportato nell'unico file programma.c). Tale scelta, seppure poco elegante, ha consentito una certa semplicit in fase di progetto e non ha impedito di conservare un controllo completo sul codice, stante la sua moderata complessit concettuale. Sezione 1: direttive al preprocessore e dichiarazioni generali Le primissime due linee di codice rappresentano le direttive al preprocessore. Sono semplici istruzioni di inclusione: si richiede al preprocessore di ricercare eventuali variabili o funzioni richiamate nel codice, ma non ivi definite, nei due file di intestazione "reg51.h" e "stdio.h". Di questi due file, il primo quello che, come anticipato, contiene la definizione degli alias per gli SFR dell'80C32 (in pratica, quello che ci consente di riferirci ad esempio al SFR relativo alla porta parallela P1 senza dover indicare il suo indirizzo nella memoria interna). Il secondo file l'intestazione di un file di libreria ("stdio.c") che contiene, tre la altre, la funzione "sprintf", utilizzata nel seguito del programma.

Figura 20: Direttive al preprocessore e dichiarazioni generali.

Le primissime due linee di codice rappresentano le direttive al preprocessore. Sono semplici istruzioni di inclusione: si richiede al preprocessore di ricercare eventuali variabili o funzioni richiamate nel codice, ma non ivi definite, nei due file di intestazione "reg51.h" e "stdio.h". Di questi due file, il primo quello che, come anticipato, contiene la definizione degli alias per gli SFR 31

dell'80C32 (in pratica, quello che ci consente di riferirci ad esempio al SFR relativo alla porta parallela P1 senza dover indicare il suo indirizzo nella memoria interna). Il secondo file l'intestazione di un file di libreria ("stdio.c") che contiene, tre la altre, la funzione "sprintf", utilizzata nel seguito del programma. L'area dichiarativa che segue risulta di comprensione particolarmente immediata. Nella sua prima parte (linee 006-018) si stanno semplicemente ridefinendo con dei nomi mnemonici alcuni singoli bit a cui il programma dovr accedere. Il significato di ciascuno di questi bit nel seguito del codice illustrato nel seguente elenco puntato (ovviamente, ciascuna di queste ridefinizioni deve risultare coerente con le connessioni hardware stabilite tra i vari dispositivi a livello di schematico): D4-D7: sono i bit associati ai pin della porta parallela sui quali il micro comunicher con il display. Ogni comunicazione richieder l'invio di due semi-byte: questo tipo di approccio si preferito alla destinazione di una intera porta parallela di 8 bit per la stessa funzione poich si ha la necessit di utilizzare diversamente gli altri 4 bit; inoltre, come si vedr, la comunicazione attraverso un bus di 4 linee non costituisce una grave complicazione, ma presenta l'unica limitazione di risultare un po' pi lenta; EN: il bit corrispondente al pin di I/O parallelo destinato al segnale di Enable del display; RS: il bit corrispondente al pin di I/O parallelo destinato al segnale di Register Select del display; HIGH, LOW: sono i bit, normalmente alti, corrispondenti ai pin di I/O parallelo destinati all'accensione dei due LED rosso e blu (per ulteriori dettagli si veda la sezione relativa alla presentazione dello schematico); STATUS: il bit, normalmente alto, corrispondente al pin di I/O parallelo destinato all'accensione del LED verde di stato (ON/OFF); sel0, sel1: sono i bit corrispondenti ai pin di I/O parallelo destinati al controllo del multiplexer analogico che seleziona uno dei quattro sensori; La seconda parte (linee 019-036), invece, dichiara nuove variabili o costanti. In generale, la dichiarazione di una variabile o di una costante richiede che vengano specificati, nell'ordine: 1. il formato di dato, da cui dipender ovviamente il peso in byte del dato stesso. I formati utilizzati nel programma sono solo due, ovvero unsigned char (numeri interi compresi tra 0 e 255, occupano un solo byte di memoria) e float (numeri reali a virgola mobile, con segno, di modulo approssimativamente compreso tra 1.17E-38 e 3.40E+38, occupano ognuno 4 byte); 2. il tipo di memoria in cui il dato verr allocato. Nel programma, alcune variabili sono state allocate nella memoria RAM interna (idata, di dimensione 256 byte, come visto), altre nella memoria accessibile in maniera "diretta" (data, di dimensione 128 byte), altre ancora nella memoria di programma (code, di dimensione fino a 64 kbyte); infine, la sola variabile 32

ADC_out, stata allocata nella memoria dati esterna (xdata, di dimensione fino a 64 kbyte): il motivo di questa scelta sar chiaro pi avanti; 3. il nome e la dimensione (in termini vettoriali). Introduciamo a questo punto il significato di ciascuna delle variabili (o costanti) dichiarate: buffer[64]: il vettore che conterr i dati letti dal micro sulla porta di uscita dell'ADC, prima di qualsiasi altra operazione su di essi; la sua dimensione limitata a 64: poich i sensori sono 4, una volta che il vettore sia stato riempito, esso conterr 16 misure ottenute da ciascuno dei 4 sensori. Al termine di un ciclo di 64 acquisizioni, allora, prima di procedere alla sovrascrittura del vettore stesso con le nuove acquisizioni, sar possibile effettuare su tali misure le semplici elaborazioni richieste (operazioni di media e gradiente); temp: una variabile di appoggio ad uso generale, impiegata in diverse sezioni del codice; hex_val[2]: il vettore che conterr ciascuno dei due caratteri ASCII trasmessi, per ogni misura di temperatura ottenuta, sulla porta seriale (si veda la sezione 5 del codice); ADC_out: la variabile che conterr il valore letto dalla porta di uscita dell'ADC, dopo ogni singola acquisizione effettuata. Come si evince, l'unica variabile di tipo "xdata": si cio effettuata la mappatura in memoria dell'ADC. L'idea questa: poich si dispone di una memoria programma di 216 byte, dei quali verranno utilizzati sicuramente meno della met (data la ridotta complessit del codice), si scelto di fare in modo che il micro veda l'ADC come una locazione di memoria dati esterna, avente in particolare indirizzo 0xFFFF (a questo serve la forma sintattica "_at_ 0xFFFF"). A questo punto, ogni volta che il codice richieder l'accesso alla variabile ADC_out, il micro proceder ad una lettura da memoria dati esterna: tale procedura (implementata a livello hardware nel micro) consiste nell'invio dell'indirizzo in due parti, in maniera del tutto analoga a quanto descritto per l'accesso a memoria programma; in questo caso, tuttavia, l'impulso non verr inviato sulla linea PSEN (la memoria di programma mantiene quindi le sue uscite in alta impedenza), ma sulla linea WR (P3.6) in caso di scrittura o sulla linea RD (P3.7) in caso di lettura da memoria dati esterna. Quindi, il micro si aspetta di leggere il byte prodotto dalla memoria dati esterna sulla porta P0. Se si confronta questa sequenza di segnali di controllo con quella richiesta dall'ADC per la produzione della parola di conversione, esse risultano perfettamente compatibili una volta che si effettuino le assegnazioni riportate nello schematico (in particolare, si deve dedicare alla selezione dell'ADC il bit di indirizzo pi significativo), , nel senso che gli impulsi di WR e RD vengono inviati quando CS attivo. Questo vero a meno di una piccola differenza: il segnale di CS che l'ADC si aspetta di ricevere attivo basso, mentre quello prodotto dal micro non lo (in pratica, quando il micro vuole indirizzare ADC_out, alza tutti i bit dell'indirizzo, per la scelta operata, e il pi significativo 33

di questi, con riferimento allo schematico, proprio CS). La soluzione a questo problema ovviamente l'inversione logica operata dall'inverter CMOS impiegato e interposto fra l'uscita P2.7 del micro e l'ingresso CS dell'ADC; i: variabile intera utilizzata come indice per il vettore buffer; j: variabile intera utilizzata come generica variabile di ciclo; *in_text: si tratta di una costante scritta nella memoria di programma (e associata ad una stringa) e di cui in_text costituisce il puntatore; hex_offset: anch'esso una costante e rappresenta l'offset utilizzato per tradurre le misure da trasmettere (e rappresentativi di ciascuna misura) in una coppia di caratteri ASCII; ROW[20]: il buffer in cui verr temporaneamente memorizzata la stringa da stampare sul display, prima dell'invio dell'istruzione allo stesso; misure[4]: il vettore in cui verranno memorizzate le misure di temperatura acquisite (alla fine di una sequenza di 64 acquisizioni) mediate su ciascun sensore; svolge una funzione di supporto ai successivi calcoli di media e gradiente; mean, grad: variabili in cui verranno memorizzati rispettivamente il valor medio e il gradiente complessivi calcolati, in termini di codici binari (valore compreso tra 0 e 255); meanT, gradT: variabili in cui verranno memorizzati rispettivamente il valor medio e il gradiente complessivi calcolati, in termini di gradi centigradi C; temp_max, temp_min: valori costanti per le soglie superiore e inferiore, con le quali verr confrontata la temperatura media misurata (la scelta di tali valori arbitraria); Sezione 2: procedure relative all'utilizzo dell'ADC

Figura 21: Procedure relative all'utilizzo dell'ADC.

Come gi indicato, i segnali di controllo CS, WR e RD richiesti dall'ADC sono facilmente ottenuti attraverso la mappatura dell'ADC in memoria. Esiste per un quarto segnale di controllo, diretto questa volta dall'ADC al micro, che assolve nel programma una funzione fondamentale: il segnale di INTR (interrupt), che rende possibile la gestione asincrona della lettura delle parole prodotte dall'ADC. Il dispositivo, infatti, come desumibile dai diagrammi temporali gi analizzati, attiva 34

questo segnale nel momento in cui una conversione stata completata e lo conserva in questo stato fino alla ricezione dell'impulso sulla linea RD (che segnala la lettura della parola prodotta). Perch tutto ci possa avvenire occorre che il micro sia in grado di rispondere con la lettura della variabile ADC_out in seguito alla ricezione del segnale di INTR: l'idea allora porre questo segnale su uno dei due pin disponibili per gli interrupt esterni (in questo caso, il pin INT0), attivare il relativo interrupt e scrivere una adeguata procedura di interrupt handler. Proprio questa procedura quella riportata in questa sezione di codice: si tratta, in generale, della risposta del micro ad un livello logico basso sul pin INT0. La prima operazione che la procedura effettua appunto la lettura della parola prodotta dall'ADC e la scrittura di questo byte alla i-esima cella del vettore buffer. A questo punto, l'indice i viene incrementato, e, normalmente, viene avviata una nuova conversione mediante l'accesso in scrittura alla variabile esterna ADC_out: proprio questo accesso corrisponde infatti all'invio di un impulso di WR. L'avvio della nuova conversione deve per essere preceduto dalla selezione del sensore di temperatura successivo: le due operazioni booleane implementate corrispondono all'incremento unitario dell'indirizzo [sel1 sel0] con cui viene controllato il multiplexer. Procedendo in questo modo, il vettore buffer sar riempito in modo tale che le misure relative al sensore x (per x compreso tra 0 e 3) saranno collocate, nell'ordine, nelle celle corrispondenti ad indici i di modulo x in base 4. Facciamo un esempio. Il valore corrente di i 2, e si assegnano ai bit di selezione i valori sel1 = 1 e sel0 = 0 (l'ADC sta convertendo la misura prodotta dal sensore 2); l'ADC termina la conversione, quindi invia il segnale di INTR; il micro, ricevuto tale segnale, avvia l'interrupt handler, procedendo alla lettura della parola di conversione e all'incremento di i, che passa al valore 3, cio sel1 = 1 e sel0 = 1; l'impulso di WR avvia una nuova conversione, e tutta la procedura si ripete; quando i verr incrementato al valore 4, i bit di selezione verranno nuovamente incrementati e torneranno quindi ad assumere valore sel1 = 0 e sel0 = 0, ovvero il multiplexer torner a selezionare il sensore 0. Reiterando il discorso, questo vuol dire che sar possibile trovare tutte le misure successive acquisite dal sensore 0 nelle celle di buffer pari a 0, 4, 8 ecc., ovvero tutte le celle da 0 a 63 il cui modulo in base 4 pari a 0. Lo stesso ragionamento potr essere ripetuto per gli altri tre sensori. In generale, operando in questo modo sar sempre possibile associare ad ogni elemento del vettore buffer il sensore di temperatura da cui quella misura stata prodotta (specifica ovviamente fondamentale per ottenere successivamente misure derivate di gradiente). A questo punto, giustifichiamo la presenza della condizione if: la sequenza di istruzioni commentata dovr essere eseguita unicamente nel caso in cui il valore di i non sia 64. Come sar desumibile dal seguito del programma, in corrispondenza di tale valore di i, il micro sospender la lettura delle parole prodotte dall'ADC (quindi non consentir la sovrascrittura del vettore buffer) per dedicarsi al calcolo delle misure derivate di media e gradiente e all'aggiornamento del display (nonch, 35

eventualmente, alla trasmissione seriale, come sar chiaro in seguito). Solo dopo che queste altre operazioni siano state eseguite, il valore di i verr aggiornato a 0 e tutto si ripeter con la sovrascrittura di buffer con altre 64 misure. Sezione 3: procedure relative all'utilizzo del display In questo caso, per la gestione del display sono state scritte e utilizzate quattro procedure distinte. La prima e pi semplice la procedura delay, che assume come ingresso un intero positivo (a 16 bit, quindi compreso fra 0 e 65535). Tale procedura assolve unicamente alla funzione di fermare il flusso di programma principale per un ritardo di durata regolabile attraverso l'argomento time. Il suo principio di funzionamento si basa su un ciclo a decremento: la variabile time, dopo essere stata opportunamente normalizzata, viene decrementata fino al valore 0, in corrispondenza del quale la procedura termina e il flusso di programma principale riprende. Il valore di normalizzazione 8.95 stato calcolato in modo tale da consentire alla procedura di assumere in ingresso il tempo da aspettare espresso direttamente in s. Tale procedura risulta di importanza fondamentale per la gestione del display, che richiede spesso l'attesa di un certo intervallo di tempo tra due istruzioni consecutive: per questa ragione, le procedure che seguono utilizzano a loro volta tale procedura. La seconda procedura (lcd_init) quella di inizializzazione, da eseguire nel main unicamente all'avvio del programma, prima di eseguire qualsiasi altra operazione sul display. La prima istruzione che viene eseguita , appunto, l'attesa da parte del micro di 15 ms, tempo richiesto dal display tra l'istante di accensione del modulo e l'istante in cui pu ricevere la prima istruzione. Le successive istruzioni realizzano una sequenza di impulsi sul pin di Enable del display, richiesti dallo stesso in questa fase; in particolare, l'istruzione inviata al display nel segmento di codice (076-079) gli indica che si intende utilizzare la modalit operativa a quattro bit. La terza procedura (lcd_write) quella che verr utilizzata nel seguito del programma per inviare al display istruzioni e dati: proprio il primo argomento di ingresso (il bit what) serve a distinguere questi due casi (quando pari a 0, si tratta di un'istruzione, quando pari a 1, si tratta di un dato), mentre il secondo argomento di ingresso (il char word) rappresenta il dato stesso da inviare. Viene innanzitutto definita una variabile temporanea temp, che ovviamente, nello scope di questa procedura, sostituisce la variabile temp globale, quindi si pone sul pin RS del display il valore logico associato a what. A questo punto, pu cominciare la trasmissione del byte, diviso in due semiword. Viene trasmessa per prima la parte alta di word, e ci viene ottenuto mediante i seguenti passi: shift verso destra di 4 posizioni di word e scrittura del risultato in temp; mascheramento della parte alta di temp (in seguito a questa operazione, solo i 4 bit meno significativi di temp potranno essere non nulli, ovvero si forza a zero la parte alta di temp); mascheramento della parte bassa di P1 (si forza a zero la parte bassa di P1); operazione di OR logico bit a bit tra temp e P1; impulso di EN.

36

Figura 22(a-b): Procedure relative all'utilizzo del display

L'effetto complessivo ottenuto in pratica la alterazione della sola parte bassa di P1, che viene in particolare sovrascritta con la parte bassa di temp, ovvero quella che la parte alta di word. La sequenza di istruzioni nelle linee (094-099) ripete le stesse operazioni per la parte bassa di word, che viene quindi anch'essa trasmessa. Infine, il datasheet del display richiede di attendere 50 s prima dell'invio di un'altra parola: a questo serve l'istruzione delay della linea (101). Infine, la procedura lcd_config consente di effettuare la configurazione del display secondo le impostazioni desiderate: verr quindi richiamata nel main del programma prima dell'utilizzo del display e una sola volta, dato che l'applicazione non richiede di variare pi volte la modalit operativa del display stesso. Confrontando le linee di commento di ogni istruzione con la 37

descrizione dell'istruction-set del display gi riportata, possibile comprendere l'effetto di ogni linea di codice. Di fatto, si sta richiedendo complessivamente al display di operare secondo le impostazioni: comunicazione a 4 bit, duty ratio pari a 1/16, cursore a incremento, assenza di shift, display acceso, cursore nascosto ed effetto blinking disattivo. Sezione 4: procedura principale (fase di inizializzazione e avvio) Con questa sezione di codice ha inizio la procedura principale: si tratta della fase di inizializzazione dei vari dispositivi utilizzati. Tra questi dispositivi ovviamente incluso il micro stesso, all'inizializzazione del quale sono destinate le linee di programma (118-129) con particolare riferimento ai suoi registri SFR. Prima di tutto, vengono settati i valori iniziali da porre sulle porte di I/O parallelo: il bit STATUS viene portato a 0, cos da accendere il relativo LED, di colore verde; i bit sel0 e sel1 sono inizializzati entrambi a 0, cos che il multiplexer selezioni per primo il sensore di temperatura di indice 0; in questa fase, viene abilitato l'interrupt esterno 0 (agendo sul bit EX0), associato all'ADC, ma solo dopo aver disabilitato tutti gli interrupt agendo sul gi introdotto bit speciale EA (bit pi significativo del SFR IE) impedendo cos all'interrupt handler di assumere il controllo, per il momento; l'attivazione del bit speciale IT0 imposta il modo di trigger dell'interrupt esterno 0 (con la scelta operata, l'interrupt si attiva quando il micro vede sul pin INT0 una transizione di livello altobasso); le due istruzioni alla linea (124) impostano il gi descritto modo 1 per la porta seriale; analogamente, l'istruzione alla linea (125) imposta per il timer1 la modalit 2, mentre l'istruzione successiva imposta il valore di auto-reload del timer1 al valore calcolato in precedenza, pari a 253; quindi, le due istruzioni alle linee (127-128) abilitano il timer1 e la ricezione per la porta seriale.

Figura 23: Procedura principale (fase di inizializzazione e avvio).

38

Le linee (130-137) della presente sezione sono invece destinate al primo utilizzo del display, che sar ottenuto semplicemente richiamando le procedure gi preparate e descritte: si procede quindi alla inizializzazione, configurazione, puntamento alla prima locazione di memoria associata della DD-RAM del modulo (che corrisponde a porre il cursore in alto a sinistra sul display), quindi scrittura della stringa iniziale, puntata dalla variabile in_text. Quest'ultima funzione, in particolare, si esaurisce, come evidente, nel ciclo for alle linee (135-136). L'ultima parte della sezione corrisponde all'abilitazione del sistema di interrupt (che rester attivo da quel momento in poi fino allo spegnimento del sistema) e all'avvio della prima conversione A/D. Sezione 5: procedura principale (ciclo principale) Questa sezione rappresenta il ciclo perpetuo in cui si esplicher di fatto il funzionamento del circuito: il sistema sar in grado di effettuare tutte le operazioni richieste, ovvero acquisizione delle misure, calcolo di media e gradiente, aggiornamento del display ed eventuale trasmissione delle misure su porta seriale, per un tempo indefinito (in pratica, fino alla interruzione della alimentazione).

39

Figura 24(a-b-c): Procedura principale (ciclo principale).

Le istruzioni corrispondenti alle linee di codice (145-148) hanno una funzione solo in fase di debug, per questo sono state rimosse in fase di emulazione del sistema completo. In pratica, esse simulano il riempimento del buffer che nel sistema funzionante dovrebbe essere scritto dall'ADC, nelle modalit gi discusse. In questo modo, stato possibile anche in fase di simulazione sull'ambiente Keil verificare il funzionamento del programma rispetto alle fasi di calcolo di media e gradiente in gradi centigradi, pilotaggio del modulo LCD e trasmissione su porta seriale, senza dover utilizzare il complesso strumento, fornito dall'IDE, di simulazione dei segnali esterni. In queste linee di codice, allora, si pu scegliere di riempire il buffer con tutti valori unitari, o ancora un valore intero che sia pari a i o a 2*i, quindi proporzionale all'indice della rispettiva cella, e cos via, in modo da verificare volta per volta che i valori di media e gradiente calcolati risultino coerenti. Per descrivere il funzionamento del sistema completo, quindi, ragioniamo in assenza di questo blocco di istruzioni. Quello che si verifica in questa ipotesi, allora, possibile dedurlo osservando che subito dopo il 40

blocco condizionale if non presente alcuna istruzione: questo vuol dire che, se i diverso da 64, il ciclo verifica continuamente una condizione vera senza mai eseguire nessun'altra istruzione. In questa condizione il micro in attesa che il normale flusso di programma venga alterato. L'unico evento che pu generare questo tipo di deviazione appunto l'interrupt esterno 0, la cui procedura di gestione stata descritta precedentemente. Un effetto di tale procedura , come visto, l'incremento dell'indice i: quando un ulteriore interrupt incrementer la variabile i al valore 64, all'interno della procedura di interrupt handler non verr avviata alcuna nuova conversione, il controllo ritorner immediatamente al ciclo principale, che potr eseguire le istruzioni contenute nel blocco condizionale, compreso nelle linee (149-232). Proprio questo blocco condizionale quello che esegue tutte le operazioni richieste al micro (eccetto quella, gi realizzata, di acquisire le parole di conversione dall'ADC): la condizione di partenza quella di un vettore buffer di 64 acquisizioni completo e ordinato nel modo descritto nella sezione relativa all'interrupt handler. Evidentemente, la prima operazione da realizzare a questo punto il calcolo delle misure indirette (in gradi centigradi) di media e gradiente: appunto la fase di elaborazione, inclusa nelle linee (151-169). Le linee (152-153) inizializzano a zero tutte le celle del vettore misure, destinato a contenere, come anticipato, la media di 16 misure indipendenti ottenute da ciascuno dei 4 sensori; le linee (154-155) calcolano l'elemento di indice x del vettore misure come la cumulata delle 16 misure relative al sensore x, per x che va da 0 a 3 (viene scandito tutto il vettore buffer e ciascun suo elemento viene cumulato all'opportuno elemento di misure); le linee (156-157) dividono ciascun elemento di misure per 16, in modo da completare il calcolo delle 4 medie. Partendo dal vettore misure risultante, si pu procedere quindi al calcolo della media complessiva e del gradiente spaziale (ovviamente, si tratter di un gradiente gi mediato nel tempo, poich tale media temporale si effettuata, come descritto, sul vettore misure): le linee (158-161) effettuano il calcolo di mean nel modo pi ovvio; le linee (162-166) valutano il gradiente medio su una retta come la media delle tre differenze tra la misura di ogni sensore e quella del precedente (ovviamente, perch tale calcolo abbia senso, si richieder che i tre sensori vengano disposti sulla PCB finale ordinati in linea retta secondo l'indirizzo con cui ciascuno di essi viene visto dal multiplexer). A questo punto, le variabili reali mean e grad contengono i valori calcolati per media e gradiente. Si tratta tuttavia di valori numerici puri: non ancora stata effettuata l'operazione di conversione da numeri binari (prodotti dall'ADC) a valori di temperatura in gradi centigradi. Le linee (167-169) contengono quindi le istruzioni per effettuare questa conversione: avendo scelto per l'ADC (a 8 bit) un fondo scala di 128 C, il LSB assumer valore 128 C/28 = 0.5 C. Questo vuol dire che l'incremento unitario della parola di conversione dell'ADC corrisponde ad un incremento della temperatura misurata di 0.5 C: il coefficiente di proporzionalit per cui moltiplicare i valori binari mean e grad per ottenere le stesse grandezze in C (meanT e gradT) quindi proprio 0.5. 41

La prima operazione che viene effettuata sulle due nuove misure indirette calcolate il confronto di meanT con le due soglie (superiore e inferiore) predefinite, alle linee (170-180). I due test vengono effettuati in maniera indipendente: se, ad esempio, meanT dovesse risultare inferiore alla soglia minima prefissata, il micro pone a 0 il bit LOW, accendendo cos il LED blu che segnala quindi una temperatura media sotto soglia. Per il bit HIGH vale un discorso analogo. La parte successiva del ciclo, compresa nelle linee (181-198) controlla l'aggiornamento del display con entrambe le misure indirette calcolate. Per la preparazione della stringa da inviare al display (bufferizzata nella variabile stringa ROW) viene impiegata l'istruzione C sprintf, presente nella libreria standard del linguaggio C "stdio.c", richiamata attraverso il relativo file header. La stringa di formato impiegata per la rappresentazione dei due numeri reali meanT e gradT prevede l'utilizzo di due cifre per la parte intera e una per la parte decimale (separate tra loro da un punto); il carattere 'C', stampato sul display, indica quindi che si tratta di misure espresse in C. Il ciclo alle linee (185189) utilizza come condizione la fine della stringa: quando il carattere verificato coincide con il carattere di terminazione della stringa (in linguaggio C, il carattere '\0'), il ciclo si interrompe, poich la stringa ROW stata stampata interamente. Lo stesso discorso pu essere ripetuto in maniera pressoch identica per la stampa della seconda linea del display, con l'unica differenza che alla linea (191) viene ovviamente indirizzato il byte della DD-RAM relativo all'inizio della riga 2. Il segmento di codice alle linee (199-229) destinato all'eventuale svuotamento del vettore buffer sulla porta seriale. Come desumibile dalla linea (200), non detto che la trasmissione seriale delle misure acquisite debba verificarsi ogni volta che il programma accede al blocco condizionale associato alla condizione i == 64: la condizione che si deve verificare perch la trasmissione abbia luogo che essa venga esplicitamente richiesta dall'interlocutore attraverso l'invio di un qualche carattere. In particolare, si scelto di assumere un carattere specifico (nel caso mostrato, la lettera 'a') che, una volta verificato, possa avviare la trasmissione: questo ulteriore controllo, effettuato alla linea (204), consente di evitare il rischio che un qualsiasi disturbo, magari anche a connettore staccato, possa attivare questo segmento di codice. Una volta verificata questa condizione, si procede alla trasmissione delle misure vera e propria, compresa nelle linee (205-228): la prima operazione che viene effettuata la disabilitazione della ricezione seriale, quindi si procede in un ciclo for ad ogni iterazione del quale viene trasmesso un singolo elemento del vettore buffer. Poich il protocollo RS-232 progettato unicamente per la trasmissione di dati alfanumerici, non sarebbe possibile inviare le parole acquisite dall'ADC cos come sono (ovvero dati binari), ma necessario sottoporle ad una conversione che le renda adatte al protocollo di comunicazione. Inizialmente si era pensato di esprimere ognuno dei 256 valori possibili dell'i-esimo byte di buffer attraverso la sua rappresentazione esadecimale, da cui il nome del vettore hex_val: cos facendo, sarebbero state sufficienti, com' ovvio, due sole cifre alfanumeriche. Successivamente, si osservato come questo 42

tipo di approccio risultava in una procedura di conversione di inutile complessit; la scelta operata stata allora quella di collocarsi in un qualsiasi punto della tavola ASCII mediante un opportuno valore di offset (hex_offset, appunto) e inviare quindi i caratteri che si ottenevano sommando a questo offset il valore binario da trasmettere. Per l'offset stato scelto il valore 0x30 (48 in rappresentazione decimale), che corrisponde proprio al carattere '0': sulla tavola ASCII seguono i caratteri '1','2', e cos via fino a '9', quindi ':', ';', e altri, fino ad arrivare all'elemento alla riga hex_offset+15 (si intende questo nella linea di commento in cui si fa riferimento ad un codice esadecimale "non standard": rispetto all'esadecimale noto, in fondo, si sono solo cambiati i simboli relativi ai valori maggiori o uguali a 10). Quindi, si effettua separatamente la conversione in questo formato della parte alta e della parte bassa del byte buffer[i], e si procede infine alla trasmissione dei due caratteri ASCII dello stesso, alle linee (218-225): come la UART dell'8051 richiede di fare, si pone a 0 il flag di trasmissione TI, si scrive il valore da trasmettere serialmente nel registro SBUF, e si rinchiude il flusso di programma in un ciclo while che, per uscire, attende che il flag TI venga attivato ( il segnale che la trasmissione del byte conclusa); quindi, si ripete l'operazione per entrambi i caratteri. La linea (227) contiene l'istruzione che riabilita la ricezione seriale, per le iterazioni successive sul riempimento di buffer. Le ultime linee di codice (228-234), infine, oltre a chiudere cicli e blocchi condizionali rimasti aperti, svolgono la sola funzione, alla fine del blocco if (quindi ancora solo nel caso di i pari a 64) di azzerare la variabile i e avviare una nuova conversione: esse consentono quindi la produzione di un successivo riempimento del vettore buffer, che viene cos sovrascritto con le nuove misure acquisite.

43

Conclusioni
Le ultime fasi del flusso di progetto
La procedura di debug del codice C prodotto verr svolta mediante le seguenti modalit. Una volta che la simulazione del sorgente su ambiente Keil abbia dato esito positivo, il codice esadecimale prodotto verr passato all'emulatore e quindi testato direttamente su PCB. In questa fase, verr impiegato lo strumento di emulazione HITOP. Da un punto di vista hardware, possibile connettere l'emulatore alla PCB su cui sar montato il circuito da testare attraverso un opportuno pad, sul quale a sua volta sar montato il nostro micro 80C32. Sar in questo modo possibile gi a questo punto (prima quindi della realizzazione della PCB vera e propria) verificare la funzionalit del circuito in ogni sua parte, fatta eccezione unicamente per la ROM e per il D-Latch (e relative connessioni), sostituite nella funzione di fornire il codice da eseguire al micro dall'emulatore stesso. Procedendo in questo modo, sar possibile individuare quali segmenti del codice assembler producono errore e quindi, attraverso il disassembly del Keil, su quali istruzioni C del sorgente intervenire. Tale procedura di test potr essere ripetuta per ogni unit funzionale del circuito, prima di essere effettuata sul circuito complessivo; in particolare, verranno testati separatamente il funzionamento della unit di conversione A/D delle misure di temperatura, del display LCD e infine della comunicazione su porta seriale con il PC. Relativamente a questo punto, il primo test verr effettuato mediante l'utilizzo dell'Hyper Terminal di Windows; da questo test, quindi, sar possibile produrre le specifiche di progetto per l'applicativo Visual Basic da sviluppare. Anche questo applicativo verr quindi sottoposto a test direttamente nella comunicazione seriale col micro. Infine, sar possibile testare il sistema complessivo (con il codice C sviluppato ancora in fase di emulazione). Come ovvio, per ragioni di brevit, la descrizione del lungo processo di "trial & error" che ha portato alla produzione del codice completo e corretto interamente omessa. Nelle sezioni seguenti vengono piuttosto forniti alcuni dettagli relativi alla fase di test, le indicazioni principali sulla realizzazione del layout della PCB e, infine, poche considerazioni su eventuali possibili sviluppi futuri per un progetto di tipo analogo.

Test su breadboard
La breadboard di test realizzata rappresentata in Fig.25. Essa completa di tutte le connessioni, fatta eccezione per i tre LED (di ridotta criticit rispetto agli obiettivi del test stesso) e per la componentistica di supporto al micro (circuito di clock, D-LATCH, ROM) resa inutile dai segnali forniti dall'emulatore stesso. In basso a sinistra stata montata la sezione relativa al riferimento di tensione destinato all'ADC (sono riconoscibili il trimmer e l'integrato TO-92, l'LM385); in basso a 44

destra stata collocata la schiera degli LM35, disposti in modo arbitrario solo per la fase di test (come anticipato, nel disegno del layout finale della scheda si operer diversamente); il primo integrato sulla destra a partire dal basso il multiplexer analogico, la cui uscita diretta all'ADC (il terzo integrato a partire dal basso); il secondo integrato dal basso l'inverter, mentre quello pi in alto il convertitore di livello ( riconoscibile in questo caso la sezione di condensatori elettrolitici richiesti dal dispositivo nel suo normale funzionamento); sulla parte sinistra della breadboard stato montato il pad, su cui collocato a sua volta l'80C32 (anche se non visibile, il cavo che si interrompe sulla sinistra connesso al modulo HITOP); subito alla sinistra del pad riconoscibile il modulo L2014.

Figura 25: Breadboard montata per la fase di test.

Il display LCD rappresentato nel dettaglio di Fig.26: in questa figura possibile leggere (sebbene sfocate) le misure indirette stampate sul display stesso. Si misura un valor medio complessivo di +15.5 C e un gradiente medio pari a -0.1 C (come gi indicato, in questa fase di test tale misura non ha alcun significato, data la disposizione arbitraria dei sensori).

Figura 26: Dettaglio sul display LCD.

45

Ancora con riferimento alla Fig.25, possibile distinguere un cavo che si allontana dalla breadboard verso destra: si tratta del cavo seriale impiegato per realizzare la connessione verso il PC. In questa fase di test si sono potute sperimentare anche le funzionalit dell'applicativo su PC destinato alla raccolta, archiviazione, elaborazione e rappresentazione grafica delle misure acquisite (il sorgente di tale programma, riportato per completezza in appendice, stato prodotto in linguaggio Visual Basic). La screenshot in Fig.27 riproduce la schermata principale dell'applicativo.

Figura 27: Schermata principale dell'applicativo VB.

Innanzitutto, nel menu "File" sono accessibili unicamente le scelte "About" (che produce un messaggio con le informazioni sul programma) e "Exit" (che chiude il programma). Nel menu "Opzioni", sono invece accessibili le scelte relative a quale porta seriale (COM1/COM2) selezionare per la acquisizione delle misure. Come ulteriore alternativa (opzione "Test without COM"), possibile testare l'applicativo in assenza di acquisizioni via seriale: in questo caso, chiaramente, il programma simuler l'acquisizione di misure che saranno per valori pseudocasuali. Al momento della acquisizione della screenshot in Fig.27, era selezionata l'opzione COM2 (la porta COM1 del PC era stata destinata al modulo HITOP, quindi non era disponibile): le misure indirette di media e gradiente riportate sono allora associate a valori di temperatura realmente acquisiti. La prima casella di testo (in bianco) l'unico testo editabile e ammette unicamente numeri interi positivi: si tratta del numero di acquisizioni ripetute che si richiede al sistema di effettuare. La casella di testo affianco contiene invece il numero complessivo di acquisizioni effettuate a partire dall'ultimo reset fino a quel momento (naturalmente, le misure indirette riportate di seguito si riferiscono a tutte queste acquisizioni). Quindi, le successive caselle di testo contengono i dati descritti dalle rispettive label, tutti in unit C: la media per un sensore rappresenta il valore medio di temperatura calcolato su quel sensore; il gradiente per un sensore rappresenta il valore medio delle differenze tra ogni coppia di valori di temperatura (si considerano in questo caso coppie di valori acquisiti in maniera successiva nel tempo, il gradiente in questione quindi di natura temporale e non spaziale); la media complessiva rappresenta il valore medio di temperatura 46

calcolato su tutti i sensori; il gradiente complessivo rappresenta il valore medio delle differenze tra i valori medi di temperatura su coppie di sensori adiacenti (questo allora un gradiente spaziale). Chiariamo ora la funzione dei button: il pulsante "Acquire" avvia le procedure destinate ad effettuare il numero indicato di acquisizioni ripetute; il pulsante "Restart" effettua un reset del programma (le misure precedentemente acquisite verranno quindi, eventualmente, perse); il pulsante "Save" apre una finestra di dialogo per il salvataggio su file (in formato .txt) delle misure acquisite fino a quel momento dal micro. A titolo di esempio, si riporta di seguito la prima riga scritta dalla procedura di salvataggio su file in un file "misure.txt" prodotto durante il test finale: |016,0-016,0-016,0-015,0-015,5-015,0-015,5-016,0-017,0016,0|015,5-015,5-015,5-015,5-016,5-016,0-016,0-015,5-015,5015,5|015,0-015,5-015,5-016,0-016,0-015,5-016,0-016,0-016,0015,0|014,5-015,5-015,0-016,0-015,0-015,0-015,5-015,5-016,0015,5|23/09/2007-18.02.33-COM2 Le misure vengono salvate in formato testuale, espresse in gradi centigradi. Il carattere speciale '|' ha la funzione di terminazione: indica l'inizio e la fine della stringa di misure; il carattere speciale ',' ha la funzione di separare parte intera e parte decimale di ogni misura. Alla fine della stringa di 64 misure (ovvero di un buffer completo acquisito dal micro), vengono salvate anche ulteriori informazioni, separate dal carattere '-' e relative alla specifica acquisizione effettuata, quali data e ora dell'acquisizione da micro e porta seriale di comunicazione. Infine, i quattro button "View PDF", associato ciascuno ad un sensore, consentono di rappresentare un grafico della densit di probabilit dei valori acquisiti dal relativo sensore. In Fig.28 sono rappresentati due dei quattro grafici che era possibile ottenere, in particolare quelli relativi ai sensori 0 e 3 (come indicato nei titoli delle finestre).

47

Figura 28(a-b): Rappresentazione grafica della densit di probabilit dei valori acquisiti dai sensori 0 e 3.

Realizzazione layout della PCB


Come gi indicato, per lo sviluppo della documentazione relativa alla PCB stato utilizzato il pacchetto software Orcad, in particolare i tool Capture e Layout. Questo secondo tool proprio quello necessario a produrre i file gerber necessari alla realizzazione della PCB. Uno dei principali punti di forza del pacchetto software scelto probabilmente proprio l'immediatezza con cui possibile passare dalla rappresentazione del circuito sottoforma di schematico alla realizzazione del layout: molte delle operazioni da effettuare (essenzialmente placement dei componenti e routing delle piste) sono o possono essere rese automatiche, e ci funziona particolarmente bene se, come nel nostro caso, il circuito non presenta particolari criticit in termini di accoppiamento elettromagnetico tra i componenti o tra le piste stesse. Una volta realizzato lo schematico, Orcad Capture in grado di produrre autonomamente la netlist, ovvero un file di estensione .mnl che contiene la descrizione completa del circuito in formato testuale. All'avvio di Orcad Layout, possibile chiedere che il software carichi proprio tale netlist, associandola ad un opportuno file template, contenente nient'altro che informazioni generali di natura tecnologica sul tipo di scheda che si intende realizzare. L'unica operazione che si richiede all'utente di effettuare quella di associazione di un footprint adeguato ad ogni dispositivo (o pi in generale, ad ogni black-box) del circuito, fatta eccezione per i componenti gi a livello di schematico presi dalle librerie di Orcad. I possibili modi di procedere sono due: crearsi una libreria di footprint adatti ai dispositivi utilizzati e destinata quindi allo specifico progetto; viceversa, utilizzare i footprint gi messi a disposizione dalle librerie di Orcad ma rinominare i pin di ciascun dispositivo sullo schematico in modo tale che vi sia corrispondenza con il relativo footprint scelto (nel nostro caso, stata scelta la seconda opzione). Tutto ci che segue pu essere effettuato in 48

maniera automatica: nella nostra applicazione, si richiesta una particolare disposizione per i sensori di temperatura, che devono essere disposti in linea retta nell'ordine corretto (secondo quanto gi specificato) nel layout finale; ulteriori preferenze specifiche di collocazione della porta seriale, del display e dei LED hanno suggerito di effettuare in maniera manuale il piazzamento dei componenti della scheda. Viceversa, l'operazione di sbroglio delle connessioni stata eseguita in modo automatico: si richiesta in particolare la generazione di uno sbroglio per una scheda a due livelli (top e bottom).

Figura 29: Rappresentazione del layout complessivo (le piste relative ai due strati sono rappresentate con colori diversi).

Una volta corretti gli errori segnalati dal DRC (Design Rule Check) e ripetuto lo sbroglio, si prima di tutto ottenuta la rappresentazione del layout complessivo riportata in Fig.29, quindi le piste e le vie stampate in distintamente per i due strati in Fig.30 (come spesso accade, lo strato di bottom stato specchiato rispetto all'originale).

Figura 30(a-b): Rappresentazione di piste e vie per il top (a sinistra) e per il bottom (a destra) della PCB.

49

Considerazioni finali: possibili sviluppi


Gli aspetti del sistema progettato che potrebbe risultare opportuno ottimizzare sono numerosi. Innanzitutto, si potrebbe senza alcuno sforzo pensare di incrementare il numero di sensori di temperatura impiegati, per esempio arrivando a 16: questo implicherebbe ovviamente anche l'utilizzo di un multiplexer di dimensione doppia, il che a sua volta comporterebbe il problema di dover rinunciare magari a qualcuno dei LED di segnalazione per far posto al nuovo bit di selezione destinato al multiplexer. Tali 16 sensori potrebbero quindi essere disposti in maniera uniforme su una superficie quadrata: in tal modo, sarebbe possibile ottenere dalle misure acquisite informazioni sulla distribuzione superficiale di un campo di temperatura (risulterebbe tuttavia a questo punto necessario calcolare solo il gradiente lungo due direzioni ortogonali). Si potrebbe ancora pensare di migliorare l'aspetto relativo alla collocazione temporale delle misure acquisite, in modo tale da consentire misure derivate di variazione della temperatura nel tempo. Ancora nell'ipotesi di riuscire a fare posto a qualche segnale di controllo sulle porte parallele del micro, potrebbe risultare possibile ottenere la comunicazione del circuito con un PC remoto attraverso connessione wireless a corto raggio (ad esempio, in standard Bluetooth), il che renderebbe il sistema adatto ad effettuare misure in condizioni pi estreme (dove, generalmente, un operatore umano non pu arrivare). Infine, nulla esclude che il sistema progettato (inteso come interazione tra micro 8051-like, modulo LCD, ADC e connessione via seriale con PC) possa essere modificato per adattarsi all'acquisizione ed elaborazione elementare di dati prelevati da sensori diversi da quelli di temperatura.

50

Appendice: codice sorgente Visual Basic


Per ragioni di completezza, si riporta di seguito il codice sorgente VB dell'applicativo sviluppato per l'interazione del PC con il micro via seriale. Naturalmente, tale sorgente da ritenersi completo solo in associazione alla sua parte visuale, la descrizione della quale stata omessa nella presente relazione. In ogni caso, tutta la documentazione prodotta in ambiente VB durante lo sviluppo di tale applicativo, cos come quella prodotta in ambienti Orcad e Keil, riportata in formato digitale nel CD allegato alla presente relazione.
Option Explicit Dim rs Dim buffer Dim receive_req Dim porta Dim times Dim j

As As As As As As

Recordset String Boolean Integer Long Integer

Private Sub About_Click() MsgBox ("Misuratore di temperatura v1.0" & vbCr & "Authors: Columbo Lorenzo" & vbCr & " Columbo Gaetano") End Sub Private Sub bt_acquire_Click() ' ' Controlla che sia stata operata la scelta "PORTA COM" ' If COM1.Checked = False And COM2.Checked = False And Test_without_COM.Checked = False Then MsgBox ("Seleziona porta COM da Opzioni") Exit Sub End If If Test_without_COM.Checked = True Then porta = 0 If COM1.Checked = True Then porta = 1 If COM2.Checked = True Then porta = 2 If porta > 0 Then If apri_porta = False Then MsgBox ("Impossibile proseguire.") Exit Sub End If End If ' ' Controlla che frm_acquire sia integer ' If Not IsNumeric(frm_acquire) Then MsgBox ("Valore non ammesso.") frm_acquire.SetFocus Exit Sub End If If Not frm_acquire > 0 Then MsgBox ("Valore non ammesso.") frm_acquire.SetFocus Exit Sub End If times = 1 '... Sollecita invio e attende ricezione buffer BYTEWISE (se non test) Frame1.Enabled = False buffer = "" If porta > 0 Then MSComm1.Output = "a" Else For times = 1 To frm_acquire buffer = genera Call acquire Next

51

Call elaborate Frame1.Enabled = True End If End Sub Private Sub acquire() Dim ora As String ' ' Effettua una volta la scrittura nel rs delle 64 misure ' rs.AddNew '... Ricevi buffer seriale alla data e ora e da sorgente rs.Fields("data") = Date ora = Time rs.Fields("ora") = ora Select Case porta Case 0 rs.Fields("source") = "TEST" Case 1 rs.Fields("source") = "COM1" Case 2 rs.Fields("source") = "COM2" End Select '... Costruisci campi misure For j = 1 To 128 Step 8 rs.Fields("misure_0") = rs.Fields("misure_0") 0.5), "000.0") rs.Fields("misure_1") = rs.Fields("misure_1") 0.5), "000.0") rs.Fields("misure_2") = rs.Fields("misure_2") 0.5), "000.0") rs.Fields("misure_3") = rs.Fields("misure_3") 0.5), "000.0") Next bt_save.Visible = True ridimensiona (1) End Sub Private Sub elaborate() Dim i As Integer Dim w_media As Double Dim w_media_c As Double Dim w_grad As Double Dim w_grad_c As Double Dim w_num As Double ' '... Calcola e visualizza le medie ' w_media_c = 0 '... Calcola e visualizza la media di sensore 0 rs.MoveFirst w_media = 0 Do While Not rs.EOF For i = 1 To 80 Step 5 w_num = Mid(rs.Fields("misure_0"), i, 5) w_media = w_media + w_num Next rs.MoveNext Loop w_media_c = w_media_c + w_media w_media = w_media / (rs.RecordCount * 16) frm_media(0) = Format(Round(w_media, 2), "##0.00") '... Calcola e visualizza la media di sensore 1 rs.MoveFirst w_media = 0 Do While Not rs.EOF For i = 1 To 80 Step 5 w_num = Mid(rs.Fields("misure_1"), i, 5) w_media = w_media + w_num Next rs.MoveNext Loop

& Format((codice(Mid(buffer, j + 0, 2)) * & Format((codice(Mid(buffer, j + 2, 2)) * & Format((codice(Mid(buffer, j + 4, 2)) * & Format((codice(Mid(buffer, j + 6, 2)) *

52

w_media_c = w_media_c + w_media w_media = w_media / (rs.RecordCount * 16) frm_media(1) = Format(Round(w_media, 2), "##0.00") '... Calcola e visualizza la media di sensore 2 rs.MoveFirst w_media = 0 Do While Not rs.EOF For i = 1 To 80 Step 5 w_num = Mid(rs.Fields("misure_2"), i, 5) w_media = w_media + w_num Next rs.MoveNext Loop w_media_c = w_media_c + w_media w_media = w_media / (rs.RecordCount * 16) frm_media(2) = Format(Round(w_media, 2), "##0.00") '... Calcola e visualizza la media di sensore 3 rs.MoveFirst w_media = 0 Do While Not rs.EOF For i = 1 To 80 Step 5 w_num = Mid(rs.Fields("misure_3"), i, 5) w_media = w_media + w_num Next rs.MoveNext Loop w_media_c = w_media_c + w_media w_media = w_media / (rs.RecordCount * 16) frm_media(3) = Format(Round(w_media, 2), "##0.00") '... Calcola e visualizza la media complessiva w_media_c = w_media_c / (rs.RecordCount * 64) frm_media_c = Format(Round(w_media_c, 2), "##0.00") ' '... Calcola e visualizza i gradienti ' w_grad_c = 0 '... Calcola e visualizza il gradiente di sensore 0 rs.MoveFirst w_grad = 0 Do While Not rs.EOF For i = 1 To 71 Step 5 w_num = Mid(rs.Fields("misure_0"), i + 5, 5) - Mid(rs.Fields("misure_0"), i, 5) w_grad = w_grad + w_num Next rs.MoveNext Loop w_grad = w_grad / (rs.RecordCount * 16 - 1) frm_grad(0) = Format(Round(w_grad, 2), "##0.00") '... Calcola e visualizza il gradiente di sensore 1 rs.MoveFirst w_grad = 0 Do While Not rs.EOF For i = 1 To 71 Step 5 w_num = Mid(rs.Fields("misure_1"), i + 5, 5) - Mid(rs.Fields("misure_1"), i, 5) w_grad = w_grad + w_num Next rs.MoveNext Loop w_grad = w_grad / (rs.RecordCount * 16 - 1) frm_grad(1) = Format(Round(w_grad, 2), "##0.00") '... Calcola e visualizza il gradiente di sensore 2 rs.MoveFirst w_grad = 0 Do While Not rs.EOF For i = 1 To 71 Step 5 w_num = Mid(rs.Fields("misure_2"), i + 5, 5) - Mid(rs.Fields("misure_2"), i, 5) w_grad = w_grad + w_num Next rs.MoveNext Loop w_grad = w_grad / (rs.RecordCount * 16 - 1) frm_grad(2) = Format(Round(w_grad, 2), "##0.00") '... Calcola e visualizza il gradiente di sensore 3

53

rs.MoveFirst w_grad = 0 Do While Not rs.EOF For i = 1 To 71 Step 5 w_num = Mid(rs.Fields("misure_3"), i + 5, 5) - Mid(rs.Fields("misure_3"), i, 5) w_grad = w_grad + w_num Next rs.MoveNext Loop w_grad = w_grad / (rs.RecordCount * 16 - 1) frm_grad(3) = Format(Round(w_grad, 2), "##0.00") '... Calcola e w_grad_c = w_grad_c = frm_grad_c visualizza il gradiente complessivo Val(Replace(frm_media(3), ",", ".")) - Val(Replace(frm_media(0), ",", ".")) w_grad_c / 3 = Format(Round(w_grad_c, 2), "##0.00")

'... Visualizza numero totale acquisizioni al momento frm_acquired = rs.RecordCount End Sub Private Function apri_porta() As Boolean On Error GoTo errore With MSComm1 If .PortOpen Then .PortOpen = False .CommPort = porta .Settings = "9600,N,8,1" .RThreshold = 1 .SThreshold = 0 'default .PortOpen = True End With apri_porta = True Exit Function errore: MsgBox ("from apri_porta: " & Err.Description & ". Abort") apri_porta = False End Function Private Function genera() As String Dim i As Integer Dim valore As Integer Dim car As String Dim cifra As Integer For i = 1 To 128 valore = Int(16 * Rnd) + 48 car = car & Chr(valore) Next ' ' ' ' ' For i = 1 To 64 valore = 1 cifra = valore / 16 car = car & Trim(Str(cifra)) & Trim(Str(valore - cifra * 16)) Next genera = car

End Function Private Sub bt_save_Click() salva_rs End Sub Private Sub salva_rs() Dim i As Integer Dim riga As String Dim filename As String '... Verifica se rs esiste e non vuoto If rs Is Nothing Then Exit Sub If rs.RecordCount = 0 Then Exit Sub '... ottieni nome file filename = get_filename '... se "annulla", abbandona If Len(Trim(filename)) = 0 Then Exit Sub If SE_FILE_ESISTENTE(filename) Then If MsgBox("Il file esiste. Sovrascrivo?", vbYesNo) = 7 Then '... abbandona

54

Exit Sub End If End If '... Scrittura del file rs.MoveFirst Open CommonDialog1.filename For Output As #1 Do While Not rs.EOF riga = "|" '... Rappresentazione misure per sensore For i = 1 To 48 Step 5 riga = riga & Mid(rs.Fields("misure_0"), i, 5) & "-" Next riga = Mid(riga, 1, Len(riga) - 1) & "|" For i = 1 To 48 Step 5 riga = riga & Mid(rs.Fields("misure_1"), i, 5) & "-" Next riga = Mid(riga, 1, Len(riga) - 1) & "|" For i = 1 To 48 Step 5 riga = riga & Mid(rs.Fields("misure_2"), i, 5) & "-" Next riga = Mid(riga, 1, Len(riga) - 1) & "|" For i = 1 To 48 Step 5 riga = riga & Mid(rs.Fields("misure_3"), i, 5) & "-" Next riga = Mid(riga, 1, Len(riga) - 1) & "|" riga = riga & rs.Fields("data") & "-" riga = riga & rs.Fields("ora") & "-" riga = riga & rs.Fields("source") '... Scrittura su file Print #1, riga rs.MoveNext Loop Close #1 bt_save.Visible = False End Sub Private Function get_filename() As String CommonDialog1.filename = "" CommonDialog1.Filter = "File di testo (*.txt)|*.txt" CommonDialog1.FilterIndex = 1 CommonDialog1.ShowOpen If Len(CommonDialog1.filename) = 0 Then MsgBox ("Nome file mancante") Exit Function End If get_filename = CommonDialog1.filename End Function

Private Function SE_FILE_ESISTENTE(File As String) As Boolean Dim fs Set fs = CreateObject("Scripting.FileSystemObject") If fs.fileexists(Trim(File)) Then SE_FILE_ESISTENTE = True Else SE_FILE_ESISTENTE = False End If End Function Private Sub COM1_Click() COM1.Checked = Not COM1.Checked COM2.Checked = False Test_without_COM.Checked = False End Sub Private Sub COM2_Click() COM2.Checked = Not COM2.Checked COM1.Checked = False Test_without_COM.Checked = False End Sub Private Function codice(esa As String) As Integer

55

If Len(esa) <> 2 Then MsgBox ("From codice: Valore esa errato") codice = 0 Exit Function End If If Mid(esa, 1, 1) >= "0" And Mid(esa, 1, 1) <= "?" Then Else MsgBox ("From codice: Primo carattere esa errato della coppia: " & esa & " di indice: " & j) codice = 0 Exit Function End If If Mid(esa, 2, 1) >= "0" And Mid(esa, 2, 1) <= "?" Then Else MsgBox ("From codice: Secondo carattere esa errato della coppia: " & esa & " di indice: " & j) codice = 0 Exit Function End If codice = ((Asc(Mid(esa, 1, 1)) - 48) * 16) + ((Asc(Mid(esa, 2, 1)) - 48) * 1) End Function Private Sub Exit_Click() Unload MDIForm1 End Sub Private Sub PDF0_Click() Dim w_misure As String Dim w_valore As Double Dim i As Integer '... Costruzione del vettore delle occorrenze normalizzato per il sensore 0 For v_ind = 0 To 255 rs.MoveFirst v_occur(v_ind) = 0 Do While Not rs.EOF w_misure = rs.Fields("misure_0") For i = 0 To 15 w_valore = Mid(w_misure, (i * 5) + 1, 5) If w_valore = v_gradi(v_ind) Then v_occur(v_ind) = v_occur(v_ind) + 1 Next rs.MoveNext Loop v_occur(v_ind) = v_occur(v_ind) / (rs.RecordCount * 16) Next '... Apre il frm_chart0 frm_chr0.Show End Sub Private Sub PDF1_Click() Dim w_misure As String Dim w_valore As Double Dim i As Integer '... Costruzione del vettore delle occorrenze normalizzato per il sensore 1 For v_ind = 0 To 255 rs.MoveFirst v_occur(v_ind) = 0 Do While Not rs.EOF w_misure = rs.Fields("misure_1") For i = 0 To 15 w_valore = Mid(w_misure, (i * 5) + 1, 5) If w_valore = v_gradi(v_ind) Then v_occur(v_ind) = v_occur(v_ind) + 1 Next rs.MoveNext Loop v_occur(v_ind) = v_occur(v_ind) / (rs.RecordCount * 16) Next '... Apre il frm_chart1 frm_chr1.Show End Sub Private Sub PDF2_Click() Dim w_misure As String Dim w_valore As Double Dim i As Integer

56

'... Costruzione del vettore delle occorrenze normalizzato per il sensore 1 For v_ind = 0 To 255 rs.MoveFirst v_occur(v_ind) = 0 Do While Not rs.EOF w_misure = rs.Fields("misure_2") For i = 0 To 15 w_valore = Mid(w_misure, (i * 5) + 1, 5) If w_valore = v_gradi(v_ind) Then v_occur(v_ind) = v_occur(v_ind) + 1 Next rs.MoveNext Loop v_occur(v_ind) = v_occur(v_ind) / (rs.RecordCount * 16) Next '... Apre il frm_chart2 frm_chr2.Show End Sub Private Sub PDF3_Click() Dim w_misure As String Dim w_valore As Double Dim i As Integer '... Costruzione del vettore delle occorrenze normalizzato per il sensore 1 For v_ind = 0 To 255 rs.MoveFirst v_occur(v_ind) = 0 Do While Not rs.EOF w_misure = rs.Fields("misure_3") For i = 0 To 15 w_valore = Mid(w_misure, (i * 5) + 1, 5) If w_valore = v_gradi(v_ind) Then v_occur(v_ind) = v_occur(v_ind) + 1 Next rs.MoveNext Loop v_occur(v_ind) = v_occur(v_ind) / (rs.RecordCount * 16) Next '... Apre il frm_chart2 frm_chr3.Show End Sub Private Sub bt_restart_Click() '... Riavvia il programma: svuota gli array e ridimensiona main If Not rs Is Nothing Then If rs.RecordCount > 0 And bt_save.Visible = True Then If MsgBox("Salvo elaborazione precedente?", vbYesNo) = 6 Then salva_rs Else bt_save.Visible = False End If End If rs.Close frm_acquired = "" ridimensiona (0) frm_acquire = "" frm_acquire.SetFocus apri_rs End If End Sub Private Sub Form_Load() ' '... Inizializza ' set_v_gradi ridimensiona (0) apri_rs End Sub Private Sub set_v_gradi() For v_ind = 0 To 255 v_gradi(v_ind) = v_ind * 0.5 Next End Sub Private Sub ridimensiona(aspetto As Integer) Select Case aspetto Case 0

57

main.Height = 1300 main.ScaleHeight = 720 Case 1 main.Height = 4020 main.ScaleHeight = 3240 End Select End Sub Private Sub apri_rs() '... Verifica che il recordset non sia gi aperto If Not rs Is Nothing Then Set rs = Nothing End If Set rs = New ADODB.Recordset '... Definisce la struttura del record set rs.Fields.Append "misure_0", adVarChar, 80 rs.Fields.Append "misure_1", adVarChar, 80 rs.Fields.Append "misure_2", adVarChar, 80 rs.Fields.Append "misure_3", adVarChar, 80 rs.Fields.Append "data", adDate rs.Fields.Append "ora", adVarChar, 8 rs.Fields.Append "source", adVarChar, 4 rs.CursorLocation = adUseClient rs.Open , , adOpenStatic, adLockBatchOptimistic End Sub Private Sub Test_without_COM_Click() Test_without_COM.Checked = Not Test_without_COM.Checked COM1.Checked = False COM2.Checked = False End Sub Private Sub MSComm1_OnComm() Dim carattere If MSComm1.CommEvent = comEvReceive Then carattere = MSComm1.Input If carattere = vbCr Then Exit Sub buffer = buffer & carattere End If If Len(buffer) = 128 Then acquire If times < frm_acquire Then buffer = "" MSComm1.Output = "a" times = times + 1 Else Call elaborate Frame1.Enabled = True End If End If End Sub

58