Realizziamo un termometro digitale – Corso programmazione microcontrollori PIC® in C (aggiornamento MPLAB X) – Il Modulo ADCC – Parte 2/2
Questo articolo rappresenta la seconda parte della lezione sul modulo ADCC dei nuovi microcontrollori PIC® ad 8bit. Per proseguire nella lettura è quindi assolutamente necessario aver letto la prima parte:
Nonchè l’articolo relativo ai sensori di temperatura della Microchip:
Dal momento che andremo ad utilizzare proprio uno di questi 2 sensori. In particolare l’MCP9700A.
In aggiunta ricordo sempre che questi articoli, che si trovano nella categoria “PIC® MCU – nuovo corso” sono un aggiornamento del vecchio corso di programmazione microcontrollori PIC in C. In particolare l’ho ripreso per poter utilizzare il nuovo MPLABX IDE, i compilatori XC e i nuovi PIC® ad 8bit. In particolare nel vecchio corso usavamo il PIC16F877A, che è stato sostituito prima dal PIC16F887 e oggi dal PIC16F18877.
Le 2 vecchie lezioni a cui faccio riferimento sono le seguenti:
- Il convertitore Analogico/Digitale – Principi di funzionamento
- Il convertitore Analogico/Digitale – Esempi pratici: Termostato con isteresi con LM35
Indice dei contenuti
Introduzione
Nella prima parte di questa lezione abbiamo visto cosa è il modulo ADCC: la seconda C, che sta per “Computation”, sta ad indicare un modulo ADC ricco di nuove caratteristiche, che ci permettono di ridurre il codice e il carico di lavoro della CPU dal momento che sono state introdotte tante nuove funzionalità come il calcolo automatico della media su una serie di conversioni, il calcolo dell’errore del valore misurato rispetto ad un setpoint, il confronto dell’errore con soglie di intervento che ci permettono di avere un ulteriore interrupt, ADTIF, che possiamo utilizzare per realizzare controlli.
In questa parte finale relativa al modulo ADCC andremo a realizzare un’applicazione pratica: un termometro digitale.
Il codice sorgente e lo schema elettrico permettono di realizzare due diversi tipi di visualizzazione: mediante un display LCD alfanumerico 16×2 con controller HD44780 oppure mediante un display a led a 7 segmenti ad anodo comune.
Dal momento che gran parte della circuiteria è ripresa pari pari dagli esempi fatti appunto coi due tipi di display citati, vi invito, se non l’avete già fatto, a leggere gli articoli relativi, sia per capirci qualcosa di più, sia per consentirvi di fare modifiche se i componenti che avete voi a disposizione sono differenti da quelli che ho utilizzato io e richiedono quindi modifiche circuitali o nel codice sorgente:
- Pilotare display alfanumerici compatibili Hitachi HD44780
- Realizziamo un cronometro al centesimo di secondo con i display led a 7 segmenti
Schema elettrico
Andremo ad utilizzare il PIC16F18877, che possiede un modulo ADCC. Utilizzeremo l’oscillatore interno, quindi niente quarzo esterno. A seconda del tipo di visualizzazione che volete scegliere andrete a collegare il display LCD oppure il display a 7 segmenti ad anodo comune con 4 cifre più puntino decimale. Io, per semplicità, vi consiglio di realizzare quello con display LCD, anche se quello con i display a 7 segmenti è sicuramente più ricco di fascino, anche se più complesso sia concettualmente, sia a livello hardware dato che ci vogliono 8 resistenze per i segmenti e 4 transistor con la loro resistenza.
Per tutto quello riguarda la discussione sul display a 7 segmenti utilizzato e tutto il resto, vi invito a leggere l’articolo precedente, già citato più in alto, dove ne ho già parlato in abbondanza. Stessa cosa vale per la discussione sul display LCD.
Il sensore di temperatura che ho utilizzato è l’MCP9700A, che abbiamo già avuto modo di conoscere. Come abbiamo visto, questo sensore ha la caratteristica di avere un offset di 500mV a 0°C per cui è possibile leggere temperature negative senza barcamenarci troppo con un riferimento di tensione sotto gli 0V come facevamo con il vecchio LM35DZ.
Impostazioni modulo ADCC e FVR
Delle nuove feature del modulo ADCC andremo ad utilizzare unicamente la possibilità di eseguire in automatico la media su un tot di valori letti con trigger automatico, ovvero useremo il modulo ADCC in modalità Burst Average. Non utilizzerò quindi le funzionalità introdotte dai registri Setpoint/Errore/Soglie : confido nei lettori più bravi del mio blog che sono sicuramente in grado di sfruttare queste possibilità per realizzare un sistema di intervento sulla temperatura (i seguaci di vecchia data ricordano sicuramente che 8 anni fa ho presentato proprio un esempio simile: un termostato con il buon vecchio PIC16F877A che non aveva tutte queste caratteristiche avanzate).
Tutti i settaggi delle periferiche sono contenuti, per entrambe le versioni del circuito, nel file settings.c in cui ho diviso le impostazioni di ogni periferica in funzioni separate, un po’ come fa anche il code configurator (che vi invito ad utilizzare se vi ci trovate a vostro agio).
Imposto il modulo ADCC per lavorare in modalità Burst Average, ovvero saranno eseguite una serie di misurazioni e conversioni una dopo l’altra, sommando i risultati tra loro nel registro ADACC e infine verrà eseguita una media che troveremo nel registro ADFLTR:
ADCON2bits.ADMD=0b011; // burst average mode |
Imposto quindi i registri per avere una media su 32 campioni. Ricordo che in questa modalità, oltre ad settare il registro del numero di ripetizioni (ADRPT), bisogna anche settare il valore dei bit ADCRS in ADCON2 che eseguono lo shift a destra del numero di posizioni specificato. Quindi se il registro delle ripetizioni è a 32, i bit ADCRS vanno impostati a 5 (2^5=32):
ADRPT=32; ADCON2bits.ADCRS=5; |
la sorgente del clock di conversione la imposto su FOSC, e quindi sul valore impostato dal registro ADCLK:
ADCON0bits.ADCS=1; // sorgente clock di conversione (1=ADLCK, 0=FRC) |
Nelle lezioni vecchie utilizzavo sempre l’Frc, che consente di avere il tempo per singola acquisizione, Tad, sempre corretto dato che è indipendente dal clock di sistema.
Queste cose le ho spiegate nel vecchio corso, nell’articolo citato in alto.
Con lo studio ho potuto capire che la scelta di Frc non è sempre quella migliore. L’Frc è un oscillatore interno dedicato esclusivamente al modulo ADC e con una frequenza fissa a circa 500KHz. L’opzione Frc consente al modulo ADC di operare anche se il dispositivo si trova in modalità Sleep, modalità durante la quale tutte le altre sorgenti di clock sono spente. Se l’applicazione non prevede una modalità sleep (oppure se la prevede, ma in sleep non è necessario leggere gli ingressi analogici) è sempre meglio utilizzare il clock derivato dal sistema dato che, in questo modo, se il rumore è più elevato è comunque correlato con la sequenza di operazioni interne del modulo ADC e i risultati sono più ripetibili e stabili. Dopo aver scelto come sorgente di clock il clock di sistema, bisogna impostare il valore di divisione di FOSC nel registro ADCLK cercando di rispettare il tempo Tad che deve essere compreso sempre tra 1 e 6µS. Per questo possiamo fare riferimento alle tabelle nel datasheet:
Nella tabella vedete che sono da evitare i valori che portano ad un Tad evidenziato in grigio. Quando andiamo ad inserire il valore in ADCLK dobbiamo far finta che in testa (ovvero nella posizione più a destra) al valore binario da noi inserito, ci sia un altro “1”, per cui se immetto il valore 0x3F (0b00111111) verrà interpretato come 0x7F (0b01111111) ovvero 128, per cui la sorgente di Clock del modulo ADC varrà FOSC/128. Nel caso in cui stessimo utilizzando il clock interno a 32MHz, Tad varrà quindi 128/32=4µS che rientra nel range:
ADCLK=0x3F; // clock di conversione pari a FOSC/128 (tad=4uS) |
Come dicevo all’inizio, ho realizzato il sorgente per due versioni del termometro: una con il display a 7 segmenti e una per il display LCD. Non sono riuscito a far funzionare la libreria LCD ad una frequenza di Clock superiore ai 20MHz, per cui, per la versione con display LCD, senza perdere tempo, ho impostato il clock interno a 16MHz piuttosto che a 32, per cui per la versione LCD, ho impostato ADCLK ad un valore diverso dato che anche il clock è diverso:
ADCLK=0x30; // clock di conversione pari a FOSC/97 (Tad=6uS) |
Tralasciando tutti gli altri settaggi che dovrebbero essere chiari, ricordo infine la cosa più importante: il riferimento di tensione. Nell’articolo precedente ho detto che questi nuovi PIC hanno un modulo FVR (Fixed Voltage Reference) e che conviene utilizzarlo. Ho quindi impostato il modulo ADCC appositamente:
ADREFbits.ADNREF=0; // riferimento negativo: Vss ADREFbits.ADPREF=0b11; // riferimento positivo: valore da modulo FVR |
Ricordo, come sempre, che riferimento “negativo” va inteso come riferimento inferiore e non negativo nel senso stretto del termine: non potete fornire una tensione inferiore a 0V al microcontrollore!
Il modulo FVR va inizializzato:
FVRCONbits.FVREN=1; // modulo FVR abilitato FVRCONbits.TSEN=0; // modulo Temperature Indicator disattivato FVRCONbits.CDAFVR=0; // modulo FVR non attivo per comparatore (inutile qui) FVRCONbits.ADFVR=0b01; // modulo FVR attivo per modulo AD con guadagno 1x (1024mV) // aspetto la stabilizzazione del modulo while (!FVRCONbits.FVRRDY); |
L’ultima istruzione, sul PIC16F18877 è in realtà inutile dato che sul datasheet è spiegato che il bit FVRRDY vale sempre 1, ma ho lasciato l’istruzione perchè magari su qualche altro PIC questo non è vero.
Lettura dei valori analogici
Avendo impostato come riferimento il modulo FVR settato a 1024mV vuol dire che il nostro PIC sarà in grado di leggere tensioni da 0 a 1024mV nonostante sia alimentato a 5V. Possiamo fornire in ingresso una tensione superiore a 1024mV perchè si tratta comunque di un PIC a 5V, ma i valori superiori a 1024mV verranno sempre letti come 1024mV.
Avendo il PIC16F18877 un modulo AD a 10bit, vuol dire che con un valore in ingresso pari a 1024mV otterremo una lettura del modulo AD pari a 2^10=1024: notate che in questo caso abbiamo una risoluzione di 1mV per ogni unità letta dal modulo AD.
Stiamo utilizzando il sensore MCP9700A: dall’articolo precedente sappiamo già che avendo impostato il riferimento positivo a 1024mV saremo in grado di leggere una temperatura fino a 52.4°C, che a 500mV (letti come 500 dal modulo AD) avremo 0°C e che per valori inferiori a 500 abbiamo una temperatura negativa fino ad un minimo di -40°C che sono rappresentati dal valore 100. Quindi capite che l’intepretazione del valore restituito dal modulo AD è molto semplice:
sign=false; temp -= 500U; if (temp<0) { sign=true; temp *= (-1); } |
Non faccio altro che riportare la temperatura a 0 sottraendo 500 dalla lettura del modulo A/D (valore che corrisponde a zero). Se dal modulo, ad esempio, leggiamo 605, otteniamo 105 che rappresenta 10.5°C : notate che abbiamo già il valore di temperatura moltiplicato 10. Nelle routine ottengo semplicemente la parte intera (il 10 del mio esempio) dividendo per 10 (la divisione in C restituisce sempre e solo la parte intera) e quindi ottengo il decimale utilizzando l’operatore MOD (il simbolo %) che, invece, restituisce il solo resto di una divisione: più semplice di così si muore.
Riguardo alla questione del segno ho preferito utilizzare una variabile a parte per contenere il solo segno nel caso in cui la temperatura sia negativa (e che riporto, quindi, ad un valore positivo moltiplicando per -1), questo perchè mi semplifica di molto le cose quando vado a lavorare con il display a 7 segmenti dato che sul quarto display (che io chiamo il display delle migliaia) andrò ad accendere il solo segmento G.
Per quanto riguarda la lettura vera e propria dal modulo AD, dato che la media su 32 letture, data dal burst, non mi fornisce ancora un valore stabile sul display, eseguo in sequenza 16 burst, per ogni burst mi recupero i valori di media dal registro ADFLTR, li sommo tra loro e alla fine eseguo uno shift a destra di 4 posizioni (divisione per 16):
int32_t temp=0; // valore temporaneo di temperatura //int32_t ADaccumulator=0; // valore nel registro ADACC uint8_t i; // counter ADPCH=channel; // eseguo 16 burst per avere una lettura a display più stabile for (i=0; i<16; i++) { // avvio la conversione. Da impostazioni del modulo ADCC // ogni trigger eseguirà 32 misurazioni ADCON0bits.ADGO=1; // apetto che termina il burst while (ADCON0bits.ADGO) {continue;} // ADaccumulator=(ADACCH<<8)+ADACCL; // valore del registro ADACC // temp += (ADaccumulator>>5); // media fatta a mano // in ADFLTR è contenuta la media delle 32 acquisizioni in modalità burst temp += ((int32_t)(ADFLTRH<<8)+ADFLTRL); // temp+=(uint16_t)((ADRESH<<8)+ADRESL); // lettura da usare solo in modalità legacy __delay_ms(10); } // ciclo ripetuto 16 volte temp >>= 4; // shift destro di 4 posizioni = divisione per 16 |
Ho lasciato commentate alcune istruzioni giusto per farvi capire: in ADaccumulator c’è il valore dei registri ADACCH e ADACCL che è la somma delle 32 letture eseguite in un burst. Se facciamo la divisione per 32 del valore di ADACC viene fuori lo stesso valore contenuto in ADFLTR, quindi è inutile. In aggiunta ho commentato l’istruzione prima dell’ultimo delay dato che in questa modalità non si usano i registri ADRES, ma vorreste poterli utilizzare in modalità Legacy.
Video
Giusto per divertimento, ho fatto un video in cui mostro la lettura di una temperatura negativa utilizzando il display a 7 segmenti e una bomboletta di butano per ricaricare gli accendini:
il vecchio video (watch?v=YM_pmiH1shM) è stato rimosso da Youtube per “violazione dei termini di servizio”. Non riesco a capire cosa ci sia che violi i termini di servizio. L’ho ricaricato e potete vederlo qui sopra. Se il video scompare nuovamente…sapete già cos’è successo.
Dato che il sensore non legge temperature sotto i -40°C il display delle migliaia non viene utilizzato per le temperature negative se non per mostrare il segno. Nel caso di temperature positive, invece, può essere utilizzato per leggere temperature superiori ai 99.9°C, ma in quel caso sapete che non potete, dato che stiamo utilizzando l’FVR a 1024mV.
Notate infine che utilizzo la rappresentazione a virgola fissa: il display delle decine (il secondo da destra, che mostra l’unità del valore di temperatura), ha sempre il puntino decimale acceso. Anche il puntino è gestito dal multiplex dato che ogni digit ha il suo. Per cui se state utilizzando lo stesso circuito della lezione sul cronometro digitale, dovete fare una modifica scollegando i due puntini di separazione e collegando invece il segmento DP (decimal point), in comune così come i segmenti di ogni digit.
Buon divertimento, vi lascio con i sorgenti.
Downloads
- Schema termometro digitale con MCP9700A (422 download)
- Termometro con PIC16F18877 e MCP9700A - Variante con LCD (471 download)
- Termometro con PIC16F18877 e MCP9700A - Variante con display 7 segmenti (444 download)