Corso programmazione microcontrollori PIC® in C (aggiornamento MPLAB X) – Il Modulo ADCC – Parte 1/2

Questa lezione è un aggiornamento per MPLAB X IDE e compilatore XC delle 2 vecchie lezioni sugl modulo di conversione Analogico/Digitale (ADC = Analog to Digital Converter). Per tutta la parte preliminare di teoria potete fare riferimento alle lezioni del vecchio corso di programmazione microcontrollori PIC in C in cui parlo del convertitore Analogico/Digitale.

Le 2 vecchie lezioni a cui faccio riferimento sono le seguenti:

Non tratterò, quindi, tutta la parte già scritta in quei due articoli che, se non l’avete già fatto, dovete leggere per capire concetti essenziali come il circuito Sample and Hold (S/H), il tempo di acquisizione singolo bit TAD, i registri che contengono il risultato della conversione (ADRESH e ADRESL) ecc, che sono necessari per proseguire con la lettura di questo articolo.

La scelta della funzione Analogica: i registri ANSEL

Sui vecchi microcontrollori PIC®, come il PIC16F877A, per impostare le porte che dovevano avere la funzione analogica, si ricorreva al registro ADCON1, mediante il gruppo di bit PCFG, che, tra le altre cose, permetteva più che altro di settare delle combinazioni di porte analogiche e digitali. Con l’introduzione dei registri ANSEL la situazione è migliorata notevolmente perchè ci è permesso di selezionare singolarmente le porte da utilizzare come analogiche. Dal momento che anche i registri ANSEL hanno subìto un’evoluzione, passeremo un attimo per il PIC16F887 per arrivare al PIC16F18877.

Selezione porte analogiche sul PIC16F887 : ANSEL e ANSELH

Sul PIC16F887 (ormai obsoleto, sostituì in un primo momento il PIC16F877A e oggi è stato sostituito dal PIC16F18877) la costruzione del modulo ADC non è cambiata: il modulo è praticamente identico al vecchio PIC16F877A. L’unica differenza l’abbiamo nel registro ADCON1: non ci sono i gruppi di bit PCFG e ora la selezione delle porte analogiche viene eseguita dai registri ANSEL.

Ricordo sempre che, dopo un reset, la funzione analogica è quella di default su tutti pin, su tutti i microcontrollori PIC.

In particolare, il PIC16F887 ha 14 ingressi analogici distribuiti tra la porta A (AN0 – AN4), la porta E (AN5 – AN7) e la porta B (AN8 – AN13). Su questo PIC sono presenti due registri ANSEL:

  • ANSEL (per settare gli ingressi analogici da AN0 ad AN7)
  • ANSELH (da AN8 ad AN13).

L’unica differenza rispetto a prima, come dicevo, è la selezione degli ingressi analogici, ora se abbiamo bisogno di utilizzare, ad esempio, l’ingresso analogico AN4 (che è posizionato su RA5), andremo a scrivere:

ANSELbits.ANS4=1;

Su questo PIC bisogna sempre andare a vedere sul datasheet dove sono posizionati gli ingressi analogici, perchè non sempre hanno la numerazione corrispondente con quello della porta digitale a cui sono associati e, in aggiunta, all’avvio, se vogliamo disattivare le funzionalità analogiche su tutti i pin, andremo quindi a scrivere:

ANSEL=0;
ANSELH=0;
ADCON0bits.ADON=0; // questo spegne il modulo ADC

Stop. Per il resto potete fare riferimento agli articoli precedenti citati ad inizio post. Se invece volete rimanere aggiornati continuate pure nella lettura.

Selezione porte analogiche sul PIC16F18877 : ANSELx

Come dicevo prima: anche il PIC16F887 è ormai obsoleto ed è stato soppiantato dal PIC16F18877. Su questo PIC, e su tutti i nuovi, la funzionalità analogica è presente su tutti i pin e quindi per ogni gruppo di porte (A,B,C,D,E) abbiamo il corrispondente registro ANSEL (ANSELA, ANSELB, ANSELC, ANSELD, ANSELE) che permette di attivare/disattivare la funzione analogica su qualsiasi pin, e per di più, l’ingresso analogico ora ha un nome allineato alla corrispondente porta digitale, per cui se vogliamo utilizzare l’ingresso analogico posto sulla porta digitale RB0, scriveremo:

ANSELBbits.ANSB0=1;

Anche sul PIC16F18877 ricordo che la funzione analogica è attiva di default su tutti i pin. Le cose quindi sono diventate più logiche. Ma… ci sono anche tante altre novità oltre a questa!

Il modulo ADC2

Sul PIC16F18877 la vera differenza la fa il modulo ADC2 (scritto anche come ADCC o ADC2), che sta per “Analog to Digital Converter with Computation”. Quella “computazione” nella sigla aggiunge delle caratteristiche molto interessanti che prima eravamo costretti a realizzare manualmente via software (ad esempio la media di una serie di letture analogiche per ottenere un valore buono e stabile oppure ancora metodi di confronto del valore letto con un setpoint per realizzare sistemi di controllo).

Tutto quello che verrà detto da ora in poi vale solo per il PIC16F18877 e chiaramente per tutti i PIC con modulo ADCC, per cui non potete applicarlo al PIC16F877 per il quale, a parte i registri ANSEL di cui abbiamo parlato poc’anzi, vi rimando alle vecchie lezioni indicate in cima all’articolo.

A causa/Grazie alla presenza della seconda C nel modulo, vi sono tantissimi altri registri associati al modulo A/D che dobbiamo imparare a configurare, ma non abbiate paura: non è così complicato come sembra e, ad ogni modo, il modulo ADCC può anche operare in modalità Legacy, ovvero come il vecchio modulo ADC (ma ci perdiamo gran parte del divertimento!). In ogni caso in MPLABX c’è sempre il Code Configurator che aiuta, ma comprendere bene il significato e il funzionamento dei registri sicuramente aiuta di più.

Cominciamo col vedere lo schema di base del convertitore ADCC:

In rosso ho cerchiato le principali novità rispetto al vecchio modulo. Procedendo dall’alto notiamo:

Modulo FVR

L’FVR (Fixed Voltage Reference) è un riferimento di tensione fisso, interno al PIC che è possibile utilizzare come Vref per il modulo A/D senza appoggiarsi alla tensione di alimentazione come facevamo la maggior parte delle volte (a meno che non usavamo un riferimento esterno sui pin Vref).

L’utilizzo dell’FVR è sempre consigliato dal momento che non risente delle fluttuazioni della tensione di alimentazione e consente quindi una misura più stabile. Il modulo FVR non è un modulo strettamente legato al convertitore ADCC ma è a se stante e utilizzabile anche dal comparatore o dal modulo DAC o anche come canale di ingresso al modulo ADCC come vedremo dopo.

L’FVR ha una tensione fissa di 1024mV e può essere selezionato un guadagno di 1x (1024mV), 2x (2048mV) o 4x (4096mV). Capite bene che, nel caso in cui andiamo ad utilizzare l’FVR, i calcoli/le conversioni ecc andranno fatte tenendo conto di questo. Se utilizziamo come riferimento l’FVR settato a 1024mV e il nostro modulo AD ha una risoluzione di 10bit, vuol dire che quando avremo il valore 1024, questo corrisponderà a 1024mV. Valori superiori alla tensione di riferimento saranno sempre misurati come 1024.

Se anche la tensione più alta di 4096mV non è sufficiente ai nostri scopi, in questo caso si potrà utilizzare come riferimento la tensione di alimentazione o una tensione esterna, stabile, sul pin Vref+.

Il modulo FVR ha due buffer indipendenti: il buffer1, utilizzato dal modulo ADCC e il buffer 2, utilizzato dai comparatori e dal DAC.

La struttura del registro è molto semplice:

  • FVREN: abilita (1) il modulo
  • FVRRDY: flag che indica (1) che il modulo è pronto. In realtà sul PIC16F18877 vale sempre 1
  • TSEN: flag che indica (1) che il modulo indicatore temperatura è pronto. E’ un termometro interno al microcontrollore PIC
  • TSRNG: imposta il range del sensore di temperatura. Non affronteremo questa feature
  • CDAFVR: impostano il guadagno del buffer 2 (quello usato da comparatore e DAC)
  • ADFVR: impostano il guadagno del buffer 1 (quello usato dal modulo ADCC)

i bit CDAFVR e ADFVR possono assumere questi valori:

  • 11 : 4096mV
  • 10: 2048mV
  • 01: 1024mV
  • 00: spento

Canali interni

Il registro ADPCH consente di selezionare da quale canale analogico leggere. Sui PIC vecchi avevamo a disposizione unicamente i canali esterni (ovvero le porte analogiche). Qui ci sono anche canali interni al PIC che nella fattispecie sono: la tensione negativa di alimentazione (Vss), l’uscita del modulo FVR, l’uscita del modulo indicatore di temperatura (un sensore di temperatura interno al PIC), l’uscita del modulo DAC.

Selezione del Trigger – il registro ADACT

Sui vecchi PIC avviavamo la conversione settando il bit GO/DONE (ADGO). Qui, oltre a questo sistema, possiamo anche utilizzare un altro evento esterno o interno utilizzando il registro ADACT (AD Auto Conversion Trigger). La conversione può quindi essere gestita dai segnali PWM, dalle condizioni di un Timer, dal cambio di stato di un pin ecc. Ci sono molte configurazioni e per questo vi invito a leggere la pagina 334 del datasheet del PIC16F18877 perchè la lista è lunga ed è inutile metterla qui.

L’utilizzo del trigger esterno, però, non assicura che vengano rispettate le tempistiche necessarie per la conversione (la questione del Tad che spiegavo nei vecchi articoli). Il Trigger esterno, in aggiunta, può far funzionare il modulo A/D anche in modalità sleep purchè sia utilizzato Frc come oscillatore.

Se non state capendo di cosa sto parlando è perchè non avete letto gli articoli vecchi!

Un’altra modalità di triggering automatica è eseguita settando il bit ADCONT (AD CONtinuous Triggering) del registro ADCON0. Questa funzionalità permette il triggering continuo alla fine di ogni conversione fino a che non viene settato un particolare flag di interrupt, ADTIF (ammesso che sia settato il bit ADCON3.ADSOI – AD Stop On Interrupt), che vedremo dopo nel dettaglio, oppure fino a che non resettiamo il bit ADGO in ogni caso.

Funzioni avanzate

Credevate che le funzioni avanzate fossero queste appena esposte? Ebbene no. Lo schema del modulo A/D difatti continua. Sappiamo bene, dai vecchi articoli, che il risultato della conversione viene salvato nei registri ADRES in ogni caso (sia sui vecchi PIC che su quelli nuovi). Le funzioni avanzate consentono di esporre il risultato della conversione, contenuto nei registri ADRES, ad altri registri che ci permettono di realizzare tutta una serie di automatismi interessanti:

Approfittiamo di questo schema per introdurre i nuovi registri e il loro significato.

Nella trattazione mi riferirò ai registri con il loro nome “intero”, ad esempio ADRES, ma in realtà dato che i PIC che stiamo trattando hanno i registri ad 8 bit e questi registri sono da 16 o più bit, bisogna tener conto che, in realtà, i registri di cui parliamo sono fisicamente divisi in 2 o più registri da 8. Nella stragrande maggioranza dei casi si ha un registro che contiene gli 8 bit alti, che avrà il nome che finisce con H (High) e il registro che contiene gli 8 bit bassi, che avrà il nome che termina con L (Low). Ad esempio il registro ADRES che è a 16bit, è diviso fisicamente in ADRESH e ADRESL. 

Registro accumulatore ADACC

AD ACCumulator. Nello schema precedente non è, ahimè, rappresentato. E’ un registro in cui si ha la possibilità di sommare tra di loro i risultati della conversione. I PIC che hanno il modulo ADC a 10 bit, come il PIC16F18877, hanno ADACC a 16 bit (quindi è diviso in due registri ADACCH e ADACCL). I PIC che hanno invece il modulo a 12 bit, come il PIC16F18446, hanno ADACC a 18 bit e quindi il registro accumulatore è diviso in 3 registri: ADACCU, ADACCH e ADACCL. Chiaramente in ADACCU soltanto i 2 bit più bassi vengono utilizzati.

Registro filtro ADFLTR

AD FiLTeR. Qui viene memorizzato il risultato di una serie di operazioni che possono essere la media di un certo numero di valori o un’operazione di filtro passa-basso. E’ erroneamente indicato nello schema come ADFILT. E’ un registro a 16 bit (quindi abbiamo ADFLTRH e ADFLTRL). Vedremo dopo nel dettaglio come funziona.

Registro risultato precedente ADPREV

AD PREVious. Qui viene memorizzato il valore della conversione precedente a quella attuale (se ADCON2.ADPSIS=0) oppure viene memorizzato il valore precedente di ADFLTR (ADCON2.ADPSIS=1). E’ a 16 bit (ADPREVH e ADPREVL).

Registro Setpoint ADSTPT

AD SeT PoinT. E’ un valore che può essere utilizzato nel calcolo dell’errore. Si può utilizzare, ad esempio, nei controlli. Immaginiamo di voler eseguire una regolazione di temperatura, nel setpoint mettiamo il valore che il nostro sistema deve mantenere costante.

Registro calcolo errore ADERR

AD ERRor. Contiene il valore di errore, a 16 bit signed, che viene calcolato come una differenza tra due valori, in base al valore impostato dai bit ADCON3.ADCALC:

Vediamo, ad esempio, che resettando ADCON1.ADDSEN (che vedremo dopo) e impostando ADCALC al valore 0b101, il registro ADERR conterrà il valore ADFLTR (che può, ad esempio, contenere la media delle nostre misurazioni) MENO il valore di setpoint ADSTPT. Questo è utile per vedere se il valore medio che stiamo misurando si sta allontanando dal setpoint.

Registri di soglia alta e bassa ADUTH e ADLTH

Dopo aver ottenuto il valore di errore, possiamo confrontarlo con una soglia alta (ADUTH – AD Upper THreshold) e una bassa (ADLTH – AD Lower THreshold) e far quindi scattare un interrupt di soglia ADTIF, intercettarlo, e fare le regolazioni sul nostro sistema. Il modo in cui deve scattare l’interrupt, ovvero che tipo di confronto eseguire tra ADERR e le soglie, viene definito dai bit ADCON3.ADTMD (AD Threshold MoDe):

I valori di soglia sono a 16 bit signed.

Spunti di utilizzo dei registri di errore e di soglia

Supponiamo di dover realizzare un sistema che mantiene costante una temperatura. Abbiamo il nostro sensore di temperatura su un canale analogico. Nel registro di setpoint ADSTPT impostiamo il valore corrispondente alla temperatura che intendiamo mantenere costante. Impostiamo ADCON3.ADCALC al valore 0b101 in modo che nel registro ADERR venga trasferito il valore di media-setpoint. Impostiamo quindi le due soglie minime a massime che deve avere l’errore (possiamo ad esempio mettere il valore corrispondente a +1°C nella soglia alta e -1° nella soglia bassa) e quindi impostiamo ADCON3.ADTMD al valore 0b100 in modo che scatti l’interrupt ADTIF se il valore misurato si trova al di fuori delle soglie.

Quando la temperatura misurata aumenta o diminuisce di più di un grado rispetto al setpoint, viene generato l’interrupt ADTIF. Nella routine di interrupt andremo ad azionare, ad esempio, una ventola se la temperatura è troppo alta o un sistema di riscaldamento se la temperatura è troppo bassa (possiamo fare un IF sul valore di ADERR o controllare il registro ADMATH che vedremo dopo). 

Il flag di interrupt ADTIF è contenuto nel registro PIR1 come il flag ADIF e, se vogliamo intercettarlo, va abilitato nel registro PIE1 con il bit ADTIE e in aggiunta vanno abilitati gli interrupt di periferica (bit PEIE di INTCON) e quindi gli interrupt generali (INTCON.GIE).

Modalità di funzionamento del modulo ADCC

Cominciamo col dire che il modulo ADCC può operare in diverse modalità, che vengono impostate nel registro ADCON2 con i bit ADMD (AD MoDe):

  • 000: Basic (legacy) mode
  • 001: Accumulate
  • 010: Average
  • 011: Burst Average
  • 100: Low-Pass Filter

 

000 – Modalità Basic

Questa è la modalità da utilizzare quando si vuole rimanere compatibili con i vecchi PIC. Non si sfruttano molte delle caratteristiche avanzate (tutte quelle legate all’utilizzo dell’accumulatore) ma è possibile sfruttarne solo alcune. La conversione A/D avviene normalmente su un singolo campione come facevamo prima: non cambia assolutamente nulla.

In questa modalità possiamo però sfruttare qualche altra cosuccia in più:

Settando il bit (ADCON1.ADDSEN – AD Double Sample ENable) si ha la possibilità di eseguire la conversione su due campioni successivi. In questo caso bisogna dare due trigger separati per eseguire le due conversioni e il bit classico di interrupt (ADIF) viene settato solo dopo la seconda conversione. Nei registri ADRES troveremo il risultato della seconda conversione e nel registro ADPREV quello della prima.

Questo è vero se ADCON2.ADPSIS=0. Il bit ADPSIS (AD Previous Sample Input Select) permette di selezionare cosa trasferire nel registro ADPREV: se 0, viene trasferito ADRES all’inizio della conversione successiva. Se 1, viene trasferito ADFLTR, ma in questa modalità ADFLTR, in realtà, non viene utilizzato ma è possibile utilizzare questa funzione per altre combinazioni con altre modalità.

001 – Modalità Accumulate

In questa modalità ogni campione viene sommato al precedente all’interno del registro accumulatore ADACC e ad ogni trigger viene incrementato il registro ADCNT, che funge da contatore.

Bisogna stare attenti perchè è facile saturare l’accumulatore: a furia di sommare tanti valori si può superare il valore massimo contenibile da ADACC: in questo caso viene settato un bit che segnala l’overflow: ADAOV nel registro ADSTAT. Il registro counter arriva fino a 255 e non torna a zero, bisogna resettarlo a mano.

Il bit ADCON2.ADACLR permette di resettare a mano (settandolo) sia ADACC che ADCNT.

Abbiamo la possibilità di eseguire una divisione eseguendo uno shift automatico verso destra di un numero di posizioni da 1 a 6 (divisione per 2 fino a 64) impostando i bit ADCRS di ADCON2. Se ad esempio vogliamo dividere per 2 impostiamo ADCON2.ADCRS=1 (shift a destra di 1 posizione = divisione per 2). Il risultato di questa divisione viene trasferito nel registro ADFLTR (a 16 bit, quindi diviso in ADFLTRH e ADFLTRL). La divisione avviene sempre in automatico ad ogni trigger. Per farvi capire meglio, in questa tabella riporto un esempio di quello che avviene prendendo ad esempio la misurazione di un canale analogico che restituisce sempre il valore 514 :

ADCNTADCRSADRESADACCADFLTR
1351451464
235141028128
335141542192
435142056257
535142570321
635143084385
735143598449
835144112514

Dalla tabella vediamo che sono stati dati 8 trigger (sono state eseguite 8 conversioni). ADCRS è impostato a 3 per cui ad ogni trigger viene eseguito uno shift a destra dell’accumulatore di 3 posizioni (che equivale a dividere per 8). Il risultato di ogni singola conversione è contenuto come sempre in ADRES, in ADACC vengono sommate le singole conversioni e in ADFLTR c’è il risultato dello shift a destra di 3 posizioni eseguito su ADACC.

Vedete chiaramente che dopo un numero di conversioni pari a 8, in pratica in ADFLTR abbiamo la media!

Il flag di interrupt ADIF viene settato alla fine di ogni conversione e la comparazione del valore di errore con le soglie viene eseguito ad ogni conversione e quindi si può avere un flag di interrupt ADTIF ad ogni trigger.

010 – Modalità Average

Questa modalità in pratica è uguale alla precedente ma con una sottile differenza: si imposta il registro ADRPT per dire quanti campioni bisogna misurare per poter fare la media e bisogna impostare anche ADCRS sullo stesso valore. Quando il valore del counter ADCNT=ADRPT allora in ADFLTR c’è la media. Il trigger deve essere fornito per ogni conversione e le uniche differenze con la modalità precedente sono che quando si verifica la condizione ADCNT=ADRPT i registri ADCNT e ADACC vengono resettati all’inizio del ciclo successivo e in aggiunta la comparazione di soglia viene eseguita solo alla fine di tutto il ciclo piuttosto che ad ogni conversione e quindi la comparazione avviene con il valore medio e così anche il flag ADTIF può essere settato alla fine. Anche in questa modalità ogni trigger va eseguito manualmente.

011 – Modalità Burst Average

E’ perfettamente uguale alla modalità Average, con la sola differenza che il trigger viene dato in automatico alla fine di ogni convesione indipendentemente dal flag di Continuos Sampling (ADCON0.ADCONT).

100 – Modalità Low-pass filter

Uguale alla modalità Average, ma invece di un’operazione di media, esegue un’operazione di filtro passa-basso su tutti i campioni riducendo l’effetto di rumore ad alta frequenza sulla media. In questa modalità i bit ADCRS determinano la frequenza di taglio del filtro. Per ulteriori informazioni su questa modalità vi rimando alla lettura del datasheet. Se c’è qualcuno più esperto di me che se la sente di spiegare con parole semplici il funzionamento di questa modalità fornendo un esempio pratico nella vita reale, mi contatti.

Altri registri

Indico qui gli altri registri associati al modulo ADCC di cui non ho parlato ancora.

Stato del modulo ADCC – ADSTAT

Contiene alcuni bit che possono essere controllati per verificare casi particolari. Ad esempio:

  • ADAOV – come detto più in alto, vale 1 se l’accumulatore è andato in overflow
  • ADUTHR – vale 1 se ADERR > ADUTH (errore più grande della soglia superiore)
  • ADLTHR – vale 1 se ADERR < ADLTH (errore più piccolo della soglia inferiore)
  • ADMATH – vale 1 se c’è stato un aggiornamento dei registri ADACC, ADFLTR e altri dall’ultimo reset di questo bit
  • ADSTAT – gruppo di 3 bit che fornisce informazioni sullo stato di acquisizione

 

Funzionalità CVD – Capacitive Voltage Divider

E’ una funzionalità avanzata che permette di realizzare controlli touch. Ne avevo fatto cenno tempo fa riguardo alla PIC16F19197 Low-Power Touch Enabled LCD Demonstration. Non è una modalità semplice da utilizzare e per questo la Microchip ha realizzato la libreria mTouch™ che può essere caricata dal Code Configurator. Questa modalità fa uso di 3 registri speciali dei quali riassumo qui brevemente le funzioni:

Condensatore aggiuntivo – ADCAP

Serve ad aggiungere una ulteriore capacità al circuito S/H. E’ possibile impostare questo registro per aggiungere capacità da 1pF a 31 pF.

Controllo dell’acquisizione – ADPRE e ADACQ

La precarica è un periodo di tempo aggiuntivo che serve a portare il canale esterno e il condensatore interno del circuito S/H allo stesso livello di tensione. Durante il periodo di precarica, il condensatore interno viene disconnesso dall’esterno e viene collegato a Vss o Vdd (in base al valore di ADCON1.ADDPOL). Nello stesso momento viene preso il controllo del pin al quale è collegato il sensore e viene portato a livello logico alto o basso (stessa selezione eseguita da ADDPOL). Il registro ADPRE permette di settare il tempo per cui questa situazione deve durare. E’ una funzione chiaramente utilizzata unicamente per sensori touch: non devono esserci collegati sensori attivi, che producono cioè una tensione sul pin! Il registro ADACQ serve per stabilire quando deve cominciare l’acquisizione se durante il periodo di precarica, se dopo, se a metà ecc

Conclusioni

Nel prossimo articolo illustrerò un progetto molto semplice che fa uso del modulo ADCC sul PIC16F18877. Realizzeremo un semplicissimo termometro digitale con il sensore MCP9700. Per la visualizzazione della temperatura potremo utilizzare sia un display LCD Hitachi HD44780 che un display a led  7 segmenti  (vi anticipo qui il video).

Bibliografia:

Lezione successiva

Se questo articolo ti è piaciuto, condividilo su un social:
Se l'articolo ti è piaciuto o ti è stato utile, potresti dedicare un minuto a leggere questa pagina, dove ho elencato alcune cose che potrebbero farmi contento? Grazie :)