Guida alla Sesta Esercitazione curata da Ilaria Casale Obiettivi Dopo aver svolto questa esercitazione si: conosceranno le caratteristiche generali di un ADC; sarà appreso come utilizzare l'ADC, integrato nel micro, come voltmetro per misurare la tensione al suo ingresso. Generalità sugli ADC (Analog to Digital Converter) e sui DAC(Digita to Analog Converter) DAC: dispositivo che consente di ricevere un numero digitale, scritto in binario, e fornisce in uscita una grandezza elletrica proporzionale a questo (esiste una relazione lineare tra ingresso e uscita). Generalmente i DAC hanno l’uscita in corrente. ADC: dispositivo che consente di ricevere una tensione di ingresso e fornire un numero digitale in uscita (l’inverso del DAC). Lo scopo è, osservando il numero in uscita e conoscendo la dinamica d’ingresso del convertitore, ricavare proprio il valore di tensione di ingresso dell’ADC. Viene quindi utilizzato come strumento di misura. La dinamica dei convertitori può riferirsi all'uscita oppure all'ingresso. Se si parla della dinamica in termini di Volt, il fondo scala (F.S.) potrebbe essere unipolare, quindi andare da 0 a F.S., oppure bipolare e variare tra –F.S. e +F.S. Per l'ADC integrato nell'LPC1769 della scheda LPCXpresso la dinamica è 0 – 3.3 V. N.B. 3.3 V non è la tensione di alimentazione del dispositivo, ma la sua tensione di riferimento. La tensione di ingresso di un ADC è un segnale continuo, può assumere infiniti valori, la cui dinamica è stabilita dal fondo scala. Al contrario l’uscita è discreta trattandosi di numeri digitali. Ciò che ne consegue è una caratteristica a gradini. I punti intermedi di questi, nel caso ideale, sono tutti allineati su una retta passante o per l’origine del grafico o per –F.S. Qui sotto è riportato il caso di una dinamica bipolare (da –F.S. a +F.S.). Quando l’ingresso è –F.S. il codice dell’uscita è 000. Quando si è a metà dinamica, essendo in questo caso bipolare e a 3 bit, l’uscita corrispondente è il numero 4 (011). A +FS corrisponde il numero 7 (111), il massimo. 1 Caratteristica ingresso-uscita di un ADC Viceversa, un DAC in ingresso riceve dei numeri digitali, quindi si hanno solo dei valori interi sull’asse delle ascisse. In corrispondenza si hanno sempre dei valori interi della tensione d’uscita. Da ciò ne consegue che la caratteristica è formata da punti. Anche in questo caso la dinamica può essere bipolare o unipolare, ma stavolta ci si riferisce all’uscita. Caratteristica ingresso-uscita di un DAC : 2 Sempre in riferimento alla dinamica, i bit che fornisce l’ADC (ma il discorso vale anche per il DAC) possono avere 2 significati: Numeri interi: I pesi vanno da 20 a 2n-1 Numeri frazionari: I pesi vanno da 2-n a 2n-1 Al dispositivo (ADC) arriva una tensione di riferimento di 3.3, nel caso unipolare, quindi FS=3.3. La caratteristica per ragioni di simmetria inizia con un mezzo gradino. Quando in uscita si ha il codice 0000 si ha cosi, in corrispondenza, una funzione intorno a un certo intervallo dopo lo zero. Se si avesse infatti il gradino iniziale proprio sullo zero tale intervallo andrebbe a finire su valori di tensione negativi (ciò non rispetterebbe la dinamica d’ingresso unipolare). Ciò che si avrebbe teoricamente in uscita, da un ADC a n bit, se arrivasse una tensione di ingresso esattamente pari a F.S., nel caso di 4 bit sarebbe 10000. Al massimo della dinamica corrisponde in uscita, trovandosi ancora sull’ultimo gradino, un numero che l’ADC stesso non è in grado di rappresentare (essendo a 4 bit). Nella pratica in corrispondenza a F.S. l'ADC satura al valore 2n-1. Per conoscere la risoluzione in riferimento all’ingresso, ovvero sapere l’intervallo in Volt che l’ADC può distinguere, si ha il rapporto LSB (Least Significant Bit, valore indicato dal datasheet): 3 In cui 1/2n rappresenta la risoluzione di un ADC a n bit (avendo questo 2n codici possibili). Il dispositivo utilizzato in questo corso ha un F.S. di 3.3 V ed è a 12 bit, perciò la sua risoluzione è inferiore a 1 mV. L’ADC per ovvie ragioni sfrutta il processo di campionamento. Campiona il segnale tempo continuo che si ha in ingresso con un determinato periodo e quindi una determinata frequenza che deve essere almeno due volte maggiore alla massima frequenza proprio del segnale di ingresso. Tali campioni saranno convertiti in formato numerico. Per evitare il fenomeno di Aliasing si filtra il segnale d’ingresso nella banda di interesse. In generale, prima di un ADC (non nel nostro caso in quanto si misurano grandezze che variano molto lentamente) è necessario un filtro passabasso per escludere tutte le frequenze oltre la banda di Nyquist. Inoltre un ADC necessita anche della presenza di un circuito Sample (campionamento) and Hold (mantenimento): Buffer di ingresso, interruttore, condensatore, buffer d’uscita. Quando l’interruttore è chiuso (campionamento) il buffer di ingresso fa si che la tensione ai capi del condensatore sia pari a quella di ingresso. Nel momento in cui l’interruttore viene aperto (mantenimento), il condensatore, con impedenze molto elevate, mantiene la memoria dell’ultimo valore assunto (della tensione ai suoi capi). Idealmente quindi il condensatore non si scarica dando quindi la possibilità all’ADC in serie a tale circuito di effettuare la conversione di una tensione costante. Un'altra informazione necessaria, indicata nel datasheet di un ADC è la rumorosità, in particolare riferita all’ingresso. Esempio: Noise = 33 µVrms (rms indica il valore efficace). Tale valore non varia al variare della risoluzione. Si intuisce che qualunque segnale al di sotto di 33 µV viene mascherato dal rumore interno del circuito analogico di ingresso. Quindi l'LSB non potrà essere inferiore a 33 µV. Se si divide il F.S. per 33µV e se ne calcola il logaritmo in base 2. In questo caso si ottiene il valore di 16 bit che rappresenta la massima risoluzione dell'ADC che sia priva di rumore (ed infatti viene indicata come Noise-Free Resolution, che una delle caratteristiche indicate nel datasheet). 4 Esercitazione con l'ADC Schema elettrico che indica i blocchi collegati, il numero dei pin utilizzati con i rispettivi nomi: Si osserva che, per utilizzare il codice Lab6a_testADC, che si analizzerà in seguito, il pin 20 della strip è utilizzato per far giungere al canale 5 (ADC.05) dell’ADC una tensione analogica. L’ADC montato nel LPC1769 è a 8 canali, con un multiplexer a 8 ingressi. Per simulare un segnale analogico da fornire in ingresso si fa una partizione della tensione 3.3 V attraverso un trimmer da 50 kΩ (ma va bene anche anche un qualunque trimmer di valore dell'ordine del kΩ o decine di kΩ. Con il trimmer collegato come indicato nello schema di figura si riesce a sfruttare l’intera dinamica di ingresso dell’ADC che va da 0 a 3.3 V. In questo modo sarà possibile visualizzare, in corrispondenza, tutti i numeri che vanno da 0 a 4095 (uscita dell'ADC). La figura qui sotto mostra un possibile schema di montaggio. Si noti la presenza di un condensatore da 100 nF ai capi della tensione di alimentazione dell'LCD. Questo è necessario per limitare il rumore indotto dalle commutazioni durante la comunicazione con l’LCD. Molti circuiti integrati presentano in prossimità condensatori di circa 100 nF, per cortocircuitare a massa le variazioni rapide sulla tensione di alimentazione. 5 Esercizio 1: Importare il progetto “Lab6a_testADC” per verificare come utilizzare l'ADC: lanciare il programma e ruotare il cursore del trimmer da 50 kΩ in modo da visualizzare un valore intorno a 2000 (circa a metà corsa); si dovrebbe osservare che la cifra meno significativa non è stabile; inserire un breakpoint nel punto indicato nel codice e, lanciando più volte il programma (con Resume), osservare di quanto varia il numero visualizzato sull'LCD; accedendo alla scheda “Variables”, modificare il valore della variabile n per aumentare il numero di acquisizione su cui fare le medie e poter avere sull'LCD un numero visualizzato più stabile; compilare una tabella del tipo riportato qui sotto. Quant'è l'aumento della “risoluzione” col numero di acquisizioni di cui si fa la media? In questo progetto sono presenti due nuovi file, ADC.h e ADC.c, in cui sono inserite le funzioni di gestione proprio dell’ADC. Nel file ADC.c si osserva inizialmente la sua funzione di inizializzazione: Nel main.c a viene passato a questa il parametro 5, perché come già riferito in tale progetto si utilizza ADC0.5, o meglio il quinto canale dell’ADC. Un'altra funzione utilizzata nel main.c è: Permette di acquisire il numero convertito dall’ADC che varia quindi tra 0 e 4095. 6 Nel codice tale numero è assegnato a una costante a 32 bit (CHANNEL) : . Si osserva quindi il ciclo for all’interno di quello infinito: Per i che va da 0 a 2n (i shiftato a sinistra di n), si accumulano in adcData 2n acquisizioni consecutive. Si utilizza quindi la variabile adcData, inizialmente posta a 0, come accumulatore, precisamente a ogni ciclo for si accumula in questa il numero che viene dall’ADC, addizionando di volta in volta i nuovi campioni. Volendo fare la media si deve dividere il valore ottenuto per il numero di campioni, 2n. Tutto ciò è contenuto nel ciclo infinito, che in ultimo fa sì che il risultato venga visualizzato sul Display, a 4 cifre: Effettuando il Debug di tale codice, con n=0 (si tratta quindi del risultato delle acquisizioni successive ad ogni ciclo while che l’ADC restituisce) si nota che la cifra meno significativa non è stabile, questo perché non lo è la tensione di riferimento. In questo caso per aumentare il rapporto segnale rumore si effettuano delle medie. Infatti cambiando il valore di n, si effettua una media di quel numero che mediamente è proprio la tensione di uscita dal trimmer, con sovrapposto un rumore additivo casuale, che mediamente è zero. Facendo più medie si riesce a filtrare tale rumore. Eseguito il Debug e inserito un breakpoint alla fine del codice (indicato anche dai commenti) il programma si arresta ad ogni conversione. Ciò che si visualizza è un numero stabile. Invece ogni volta che si preme il tasto Resume, si visualizza sul Display un numero che varia continuamente (a causa del rumore). Osservando che quando n=0 il valore varia di circa 3 o 4 unità, per evitare di arrestare il Debug, cambiare il suo valore e riprogrammare, è possibile tramite la scheda Variables, modificare direttamente il valore di n. Il Debugger di conseguenza inserisce quest’ultimo nella corrispondente locazione RAM, senza cambiare il codice. 7 Esercizio 2: Copiare e incollare Lab6a_testADC e rinominare la copia in “Lab6b_Voltmetro”; Modificare il progetto in modo da visualizzare il valore di tensione in ingresso a ADC0.5 sapendo che la tensione massima è pari a 3.3 V e quella minima 0 V. La tensione deve essere visualizzata nel formato: Vin: #.### V Nota: la funzione Write_ndigitsval inserisce degli spazi al posto degli zeri non significativi. Duplicare la funzione e rinominare la copia Write_ndigitsval0 in modo che con essa si possa visualizzare un numero decimale a più cifre in cui siano comunque visibili anche gli zeri non significativi. La modifica da effettuare al codice precedente, oltre a imporre n=5 per avere quindi 32 acquisizioni, è l’aggiunta delle seguenti funzioni alla fine del ciclo infinito: La terza istruzione è per trasformare il numero adcData in millivolt. Per questo il valore del fondo scala è 3300 (mV). Si deve fare attenzione a non andare oltre, con il risultato dell’operazione, alla risoluzione del microprocessore. Essendo adcData al massimo 4095, moltiplicato per 3300 assicura questa condizione (non andare oltre la rappresentazione a 32 bit, cioè poco oltre 4 miliardi). Se si facesse la divisione per 4095 prima della moltiplicazione si perderebbe risoluzione, anzi il risultato sarebbe 0. Il valore ottenuto dalla prima moltiplicazione viene quindi shiftato di 12 bit, ovvero diviso per 4095. Si noti che l’operazione: corrisponde a LSB*adcData. Infine, essendo il risultato in millivolt, si divide per 1000 per ottenere i Volt. Nella quarta istruzione si vuole ottenere e visualizzare la parte decimale della divisione per 1000, la parte frazionaria. Si noti che la funzione qui usata non è Write_ndigitsval, che avrebbe fatto visualizzare ad esempio “2. 5 V” e non “2.005 V”. 8 Si è infatti copiata e incollata tale funzione nel file HD44780.c, per crearne una nuova denominata Per poter effettuare la modifica, si deve analizzare la definizione già esistente: Questa funzione riceve il numero che si vuole visualizzare (intero) sul display a n cifre, assegnato alla variabile locale dummyVal. La variabile ten_base, anch’essa locale, viene inizializzata a 1. A ndigits viene sempre assegnato, con la chiamata della funzione un valore. Perciò la condizione di predecremento nel primo ciclo while non fa altro che prendere il valore iniziale, sottrarre 1 e poi valutare se il risultato è vero o falso. Per ndigits volte, ten_base viene moltiplicato per 10. Il risultato finale non è altro che 10ndigits. Questo perché il processore non è in grado di eseguire le potenze, si dovrebbero importare delle librerie matematiche, non trattandosi di una potenza di 2 non si potrebbe infatti eseguire semplicemente uno shift. Si vuole visualizzare un numero a ndigits cifre, la cui più significativa è ottenuta dal valore di dummyVal diviso 10ndigits (ad esempio se si dividesse per mille si otterrebbe il valore delle migliaia). Nel secondo ciclo while, essendo le cifre sul display rappresentate dalla più alla meno significativa, il numero iniziale (in dummyVal) lo si divide per ten_base che se ad esempio fosse 107 fornirebbe le decine di milioni (essendo la divisione intera). Il risultato viene assegnato alla variabile digit. Inoltre, con le ultime due istruzioni, si prende il resto della divisione per 10 milioni, per escludere la cifra più significativa. Dovendo comunicare quanto fatto ten_base deve essere divisa per 10. Ora infatti è proprio 2^(ndigits-1). Si procede il tal modo fino a che ten_base non arriva a zero. 9 Il compito del ciclo if è valutare che digit sia pari a 0, quando ciò non è verificato, la cifra va sicuramente scritta, significativa o non significativa che sia. Non appena si trova una cifra diversa da 0, vuol dire che gli zeri non significativi sono terminati. La variabile leading_zeroes_flag, anch’essa inizialmente posta a 1, rimane a tale valore (flag alzata) fino a che la condizione dell’if non risulta vera. Se leading_zeroes_flag sta a 1 (quindi ci si trova nella parte non significativa) e si sta oltre le unità (in quanto se il numero fosse zero, almeno l’unità anche se pari a 0 andrebbe visualizzata) allora va inserito uno spazio. In riferimento alla terzultima istruzione: NB. In C ciò che è scritto prima del simbolo “?” rappresenta la condizione di un if, ciò che si trova tra “?” e “:” è l’istruzione da eseguire qualora la condizione fosse verificata. Infine la parte di istruzione che segue il simbolo “:” (else) è ciò che va eseguito qualora la condizione non fosse verificata. Per ottenere la nuova funzione Write_ndigitsval0, va cancellato il ciclo if e proprio la quartultima istruzione, sostituendola semplicemente con l’operazione relativa all’else (“:”), ovvero: 10