Guida alla Programmazione in C del PIC, 6° Parte

Nella Sesta Parte si Parlerà dell’ADC e di come eseguire la conversione in modo efficiente, saranno inoltre presentati alcuni esempi…

 
 
 
 




 

ADC DEL PIC18F252

Il PIC18F252 dispone di un ADC a 5 canali che permette di convertire i valori analogici in un range che va da 0V a Vref con una risoluzione di 10bit. Avere una risoluzione di 10bit significa che il minimo valore che viene discriminato, ovvero il passo di quantizzazione, vale:

Q = Vref / 2^10

Se si utilizza un valore di Vref pari a VCC ovvero 5V si ha un passo di quantizzazione di 4,8828mV.

La struttura interna dell’ADC è:

struttura adc pic18f252Si può convertire solo un segnale alla volta e il segnale viene inviato all’ADC tramite un MUX analogico, inoltre vi è un selettore per selezionare se usare una tensione di riferimento esterna oppure la tensione di alimentazione come riferimento.

Per avere conversioni precise bisogna avere passi di quantizzazione che sono numeri interi per non perdere informazioni durante le elaborazioni del segnale convertito. Ad esempio usando una tensione di riferimento di 2.56V si ha un passo di quantizzazione di 2,5mV senza altre cifre dopo la virgola, così da non perdere informazioni nelle approssimazioni.

 

REGISTRI DELL’ADC

Per controllare e leggere i valori dell’ADC vi sono 4 registri:

  • ADRESH: msb del risultato della conversione
  • ADRESL: lsb del risultato della conversione
  • ADCON0 & ADCON1: registri di controllo

 

In particolare i registri ADCON0 e ADCON1 servono a settare la frequenza di funzionamento, da quale canale leggere il dato, se attivare o no l’ADC oppure che configurazione hanno gli I/O del PORTA. Il registro ADCON0 è così strutturato:

adcon0 register pic18f252I bit ADCS2 (che si trova in ADCON1), ADCS1 e ADCS0 servono a settare la frequenza di funzionamento dell’ADC. I Bit CHS0, CHS1 e CHS2 servono a selezionare quale canale leggere:

bit selezione canale adcon0 pic18f252Il Bit Go/Done indica se la conversione è in corso o è finito, se è 0 la conversione è finita, se è settato ad 1 il convertitore sta convertendo. Infine il Bit ADON serve per accendere o spegnere l’ADC, se settato ad 1 l’ADC è acceso, altrimenti spento.

Il registro ADCON1 è invece così strutturato:

adcon1 register pic18f252Il primo bit va a selezionare il formato dei dati, se settato ad 1 il registro ADRESL contiene 8 LSB del dato convertito mentre il registro ADRESH solo i 2 MSB del dato convertito. Se invece settato a 0 avrò 8 MSB del dato nel registro ADRESH mentre i due bit restanti sono i MSB del registro ADRESL.

La frequenza di funzionamento del ADC può essere così configurata:

clock adc pic18f252Il clock dell’ADC è una divisione da 2 a 64 del clock di funzionamento del PIC, oppure in modalità sleep si usa un oscillatore RC interno.

I Bit PCFG3, PCFG2, PCFG1 e PCFG0 invece selezionano il comportamento dei pin del PORTA, si ha:

pin configuration adc pic18f252Dove A e D indicano ingresso analogico o digitale, mentre Vref+ e Vref- sono i pin per il riferimento esterno. Ad esempio se setto PCFG=0000 avrò tutti ingressi analogici e la tensione di riferimento è la tensione di alimentazione, se PCFG=0001 allora ho tutti ingressi analogici tranne AN0 che riceve una tensione di riferimento esterna. L’altro riferimento è GND o VSS come indicato nella tabella oppure si può inviare tramite AN2.

Se si resetta il PIC l’ADC viene spento e ogni conversione abortita. L’ADC inoltre può generare un richiesta di interrupt quando finisce la conversione, in particolare il Flag è ADIF mentre l’abilitazione dell’interrupt si fa tramite il Bit ADIE.

Affinchè si abbia una conversione adeguata, il segnale in ingresso deve avere una impedenza di ingresso di 2,5KΩ. L’ADC non può lavorare con una frequenza superiore ai 600KHz circa, bisogna quindi opportunamente settare la divisione del clock principale. Questa frequenza fa si che il convertitore restituisca un dato convertito ogni 18µS quindi si ha una frequenza di campionamento di 55KHz solo se si usa un singolo canale.

Per quanto riguarda la sistemazione dei bit si ha:

left right adjustment adfm pic18f252Se ADFM = 1 avrò i Bit sistemati come si può vedere, ovvero tutti i LSB nel registro ADRESL, se invece ADFM = 0 avrò i MSB nel registro ADRESH.

 




 

LIBRERIA MIKROC PER L’ADC

MikroC dispone di una libreria che permette di inizializzare l’ADC e di convertire il segnale sul pin specificato. Le istruzioni sono:

 

ADC_Init(); // Inizializza l’ADC con valori di default

 

Questa istruzione inizializza l’ADC, ovvero lo accende, setta la sua frequenza e seleziona il riferimento di tensione come la tensione di alimentazione. Se si vuole avere il controllo del setting dell’ADC non bisogna usare questa stringa.

 

tmp = ADC_Read(2);   // Legge il valore analogico dal canale 2

 

Questa istruzione, ADC_Read(#) serve per leggere il valore del segnale analogico connesso al canale # dell’ADC. Questo valore viene inserito in una variabile tmp che deve essere in formato unsigned char se ho un ADC a 8Bit oppure unsigned int se ho ADC a 10 o 12Bit, come nel caso del PIC18F252. In questo caso non si può andare a selezionare se leggere solo dal registro ADRESL o ADRESH, utilizzando una variabile unsigned int si prelevano tutti i Bit possibili, se invece la variabile è un unsigned char prelevo solo 8 dei 10Bit e si prelevano i MSB.

 

Una istruzione simile è:

 

tmp = ADC_Get_Sample(2);   // Legge il valore analogico dal canale 2

 

Questa istruzione, rispetto alla precedente fa una lettura sola, dopo di che spegne l’ADC e lo riaccende quando viene rieseguita la stringa.

 

 

ESEMPIO DI CODICE USANDO LA LIBRERIA

Proviamo a scrivere un semplice programma che legge il valore da un canale dell’ADC e restituisce una variabile che rappresenta la tensione analogica in ingresso al canale dell’ADC.

I passi da seguire sono:

  1. Inizializzazione ADC
  2. Lettura dal canale dell’ADC
  3. Conversione numerica del valore letto dall’ADC

Una volta convertito il valore letto dall’ADC si può andare ad inserire in tutte le operazioni che possono servire, come ad esempio la visualizzazione su LCD. Il codice è il seguente:

unsigned int tmp;
float valore;

void main() {

ADC_Init();  // Inizializza l'ADC con valori di default

   while(1){

      tmp = ADC_Get_Sample(0);    // Legge il valore analogico dal canale 0

      valore = tmp * 0.004828;    //conversione in valore numerico

      // altre istruazioni utili all'utente

   }

}

Prima di tutto si inizializza un unsigned int per contenere il valore in uscita dall’ADC e poi si inizializza un numero floating per contenere il valore convertito.

Nel main si ha la stringa vista prima per inizializzar l’ADC e in un ciclo infinito si va a leggere il valore dal canale zero e poi moltiplicandolo per 0.004828 si ottiene il valore di tensione che si ha in ingresso.

Perché 0.004828? Si ha un ADC a 10Bit con una tensione di riferimento di 5V quindi si ha un passo di quantizzazione di 4.8828mV. Questo vuol dire che se ho un valore in uscita dall’ADC questo va moltiplicato per il corrispondente valore del passo di quantizzazione per avere la tensione vista dal canale dell’ADC.

Ad esempio se si hanno 0,5V l’ADC restituisce un valore pari a 0001100110 ovvero 102, questo perché il valore in ingresso diviso il passo di quantizzazione fa 102,4 (0.5/0.004828 =102,4) però con 10Bit non si possono avere valori con la virgola, quindi viene approssimato a 102.

Ora se vado a moltiplicare il valore di 102 per il passo di quantizzazione ottengo 0,498 che è quasi il valore in ingresso all’ADC. Per colpa di approssimazioni dell’ADC e della moltiplicazioni non si avrà mai il valore uguale.

Se provo a prelevare il valore dell’ADC con un valore inizializzato come char e provo a porre questo valore in uscita, si ottiene:

schema conversione ADC con libreria mikrocSi ottiene la parte meno significativa del valore convertito, perdo quindi due bit. Il codice è:

char valore;

void main() {

PORTB = 0;
ADC_Init();  // Inizializza l'ADC con valori di default

   while(1){

      valore = ADC_Get_Sample(0);    // Legge il valore analogico dal canale 0

     PORTB = valore;      
   }

}

 




 

 ESEMPIO DI CODICE SENZA LIBRERIA

Se si vuole avere un maggiore controllo sulle operazioni svolte dall’ADC non bisogna usare la libreria ma bisogna andare a programmare i registri “manualmente”. Inoltre si può scegliere di fare un algoritmo per la lettura invece di usare la stringa ADC_Read(#) oppure ADC_Get_Sample(#).

Iniziamo dall’inizializzazione, prima di tutto bisogna settare la frequenza, la sistemazione dei bit e quale canali utilizzare, per fare ciò bisogna agire sui valori dei registri ADCON0 e ADCON1.

Supponendo di lavorare con un clock di 4MHz e sapendo che l’ADC può lavorare al massimo con 650KHZ so che devo dividere il clock principale per un valore pari a 6,25 però questo valore non è convenzionale, quindi controllando la tabella che mostra i prescaler del clock per l’ADC vedo che il valore più vicino è 8, quindi se voglio far lavorare alla massima frequenza possibile l’ADC sceglierò questo valore.

Se ad esempio voglio solo due canali analogici e usare il riferimento interno, andrò a settare i Bit PCFG(3:0) =0100 che mi permette di avere AN0, AN1 e AN3 come ingressi analogici e il riferimento uguale alla tensione di alimentazione. La configurazione che cerchiamo non è presente ma questa è quella che si avvicina di più.

Inoltre vogliamo avere 8Bit nel registro ADRESH e i due Bit meno significativi della conversione nel registro ADRESL, quindi settiamo il Bit ADFM=0. Inoltre inizialmente l’ADC può essere acceso o spento in base al fatto se si vuole una conversione singola oppure se si vuole lasciare sempre acceso. In questo caso verrà effettuata una singola conversione e poi verrà spento, quindi ADON=0.

Quindi i valori da assegnare ai due registri sono:

ADCON0 = 01000000

ADCON1 = 00000100

Una volta fatta l’inizializzazione, per avere un algoritmo di lettura devo eseguire i seguenti passi:

  1. Accendo l’ADC
  2. Setto quale canale leggere
  3. Aspetto finchè la conversione non è finita

Per il primo punto basta settare ADON=1, per selezionare il canale invece uso i Bit CSH(2:0) mentre per sapere se la conversione è finita o no controllo il Bit GO/DONE.

Il codice è il seguente:

unsigned short int value1; //8Bit variable

void main() {

TRISB=0;                //PORTB out

ADCON0 = 0b01000000;    //adc off, channel 0, clock/16
ADCON1 = 0b01001110;    //left ADJ, clock/16, Vref+=VDD, Vref-=gnd, only channel0 analog

   while(1){
      ADCON0.ADON = 1;                 //Enable A/D module
      ADCON0.GO_NOT_DONE = 1;          //set flag
      while(ADCON0.GO_NOT_DONE);       //wait the end of conversion
      value1 = ADRESH;                 // read value
      ADCON0.ADON = 0;                 //disable A/D module

      PORTB = value1;                  //put value on output
      
      Delay_ms(500);
   }
}

 

Realizzando il circuito su Proteus si ottiene:

schema conversione ADC senza usare la libreria

Porto in uscita il valore convertito e spengo l’ADC dopo la conversione. La stringa while(ADCON0.GO_NOT_DONE); serve ad aspettare finché la conversione non finisce così da avere un valore corretto.

Facendo un right adjustment, ovvero inserire i bit più significativi tutti nel registro ADRESH eliminando i due bit meno significativi, implica una moltiplicazione per 4 del dato ovvero:

Se ho 500mV il convertitore lo convertirà in 0001100110 ovvero 102, che per via del right adjustment saranno così sistemati ADRESH=00011001 e ADRESL=10000000, senza considerare il valore contenuto in ADRESL ottengo in uscita 00011001 ovvero 25 che è la divisione senza resto di 102/4. Se invece faccio un left adjustment e non considero il valore di ADRESH, ponendo in uscita il valore del registro ADRESL ottengo 01100110 che equivale al numero 102.

Quindi si possono eseguire operazioni di divisione eliminando i LSB.

 

ESEMPIO UTILIZZANDO UN RIFERIMENTO ESTERNO

Nel caso precedente per passare dal valore convertito al valore numerico bisognava moltiplicare per 0.004828, operazione che richiede un numero molto grande di operazioni in assembli rallentando il programma, inoltre è un numero approssimato, quindi vi è un errore intrinseco.

Per ovviare a ciò si può scegliere di usare un passo di quantizzazione intero, ad esempio se uso una Vref di 2,56V il passo di quantizzazione sarà 2,56/1024 = 2,5mV. Se ho i soliti 500mV in ingresso avrò in uscita 0011001000 ovvero il numero decimale 200 se elimino i due LSB ottengo 50 che moltiplicato per 0.1 mi ritorna 0.5 che è proprio il valore in volt che ho in ingresso. Non serve a forza moltiplicare per 0.1 ma si possono estrarre centinaia, decine e unità del numero e ottenere quindi i volt, i decimi e i centesimi di volt.

Se invece utilizzo un LM35, il classico sensore di temperatura che fornisce in uscita 10mV/°C, e ho una temperatura di 20°C quindi in ingresso all’ADC 200mV e vado a convertirli con una Vref di 2,5mV ottengo 0001010000 ovvero 80 in decimale, se elimino i 2 LSB ottengo 00010100 ovvero visto che faccio la divisione per 4 eliminando 2 LSB avrò che questo valore binario corrisponde al valore decimale 20 che è proprio la temperatura. Quindi in questo caso evito di fare moltiplicazioni.

Proviamo a scrivere un programma per interfacciare in modo ottimo un LM35 con il microcontrollore, il programma sarà molto simile al precedente solo che questa volta si userà una tensione di riferimento esterna. Il codice è:

unsigned short int value; //variabile a 8 bit

void main() {

   TRISB=0;                //PORTB out
   ADCON0 = 0b01000000;    //adc off, channel 0, clock/16
   ADCON1 = 0b01000101;    //left ADJ, clock/16, Vref+=AN3, Vref-=gnd, only channel0 analog
   ADCON0.ADON = 1;                 //Enable A/D module
      
   while(1){
      ADCON0.GO_NOT_DONE = 1;          //set flag
      while(ADCON0.GO_NOT_DONE);       //wait the end of conversion
      value = ADRESH;                 // read value

      PORTB = value;                  //put value on output

      Delay_ms(500);
   }
}

Lo schema, con una tensione di riferimento esterna di 2,56V e una tensione in ingresso da convertire di 200mV è il seguente:

schema ADC con riferimento esterno

La tensione di riferimento può essere creata in diversi modi.

 

Tutti i codici compilati e le simulazioni qui presentate possono essere scaricate cliccando sul seguente LINK!!!





 

[Voti Totali: 1 Media Voti: 5]
Segui la Nostra Pagina Facebook: Facebook

2 pensieri su “Guida alla Programmazione in C del PIC, 6° Parte

  1. Ciao sto seguendo il tuo bel lavoro.
    Una cosa non ho capito:
    Riporto il tuo testo
    “Per ovviare a ciò si può scegliere di usare un passo di quantizzazione intero, ad esempio se uso una Vref di 2,56V il passo di quantizzazione sarà 2,56/1024 = 2,5mV. Se ho i soliti 500mV in ingresso avrò in uscita 0011001000 ovvero il numero decimale 200 se elimino i due LSB ottengo 25 che moltiplicato per 0.02 mi ritorna 0.5 che è proprio il valore in volt che ho in ingresso.”

    Dal numero binario 0011001000 se eliminando i due LSB ottengo 00110010. Giusto?
    Ma questo equivale a 50 decimale.
    Sbaglio?

    Grazie
    Ciao

    • Grazie mille Armando, una mia piccola svista, eliminando un LSB si divide per 2, Eliminando due si divide per 4, quindi 200/4=50, infatti poi hai 50 che ti rappresenta proprio i 500mV. Correggo subito, grazie per questa ed eventuali (si spera non ce ne sia bisogno) correzioni 🙂
      Cordiali Saluti,
      L.F.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *