Go Down

Topic: Ottimizzare le letture dell'ADC (Read 3330 times) previous topic - next topic

PaoloP

Mar 11, 2014, 12:33 pm Last Edit: Mar 11, 2014, 12:45 pm by PaoloP Reason: 1
Uno dei principali problemi legati alle letture effettuate con l'ADC di Arduino è il valore del riferimento di tensione usato per confrontare le letture.
In base al modello di Arduino è possibile usare il riferimento a 5V, a 3V3, a 2.56V e a 1.1V.
Il valore del riferimento a 5V è preso direttamente dall'alimentazione Vcc del microcontrollore (ad esempio nella UNO). E' possibile con una semplice procedura leggere il valore di Vcc in modo da poterlo usare nelle formule di conversione dell'ADC.

A pag. 252 del datasheet del 328P (http://www.atmel.com/Images/Atmel-8271-8-bit-AVR-Microcontroller-ATmega48A-48PA-88A-88PA-168A-168PA-328-328P_datasheet.pdf) c'è la formula per le letture effettuate dall'ADC.
Ovvero:
                  ADC = (Vin * 1024) / Vref
Noi infatti per ottenere il valore di lettura usiamo
                  volt = ADC * Vcc / 1024.0
Per ottenere i millivolt, spesso più utili
                  milliVolt = ADC * Vcc * 1000.0 / 1024.0
Se noi andassimo a leggere modificando i registri interni il valore del riferimento di tensione di 1.1V con l'ADC avremo che la tensione è proporzionale a Vcc. Quindi ribaltando la formula è possibile risalire a Vcc
                  Vcc = (Vref * 1024) / ADC
sostituento il valore di Vref e passando ai milliVolt la formula diventa
                   Vcc = (1.1V * 1000 * 1024) / ADC
ovvero
                   Vcc = 1126400L / ADC

Il codice che si occupa di fare ciò (prelevato e modificato da qui --> http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/ è:
Code: [Select]
long readVcc() {
 // Read 1.1V reference against AVcc
 // set the reference to Vcc and the measurement to the internal 1.1V reference
 #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
   ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
   ADMUX = _BV(MUX5) | _BV(MUX0);
 #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
   ADMUX = _BV(MUX3) | _BV(MUX2);
 #else
   ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 #endif  

 delay(2); // Wait for Vref to settle
 ADCSRA |= _BV(ADSC); // Start conversion
 while (bit_is_set(ADCSRA,ADSC)); // measuring

 uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
 uint8_t high = ADCH; // unlocks both

 long result = (high<<8) | low;

 result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
 return result; // Vcc in millivolts
}


Per ottimizzare le letture esistono altri accorgimenti tra cui la possibilità di disabilitare il collegamento dei pin digitali dai pin analogici
Code: [Select]
DIDR0 = B00111111; // Disable unused digital buffers.
e altri accorgimenti di natura hardware che lascio ad utenti più esperti da questo punto di vista.

p.s. il codice della funzione prevede la moltiplicazione con valore massimo dell'ADC ovvero 1023.0.
Devo studiare bene i passaggi e fare delle verifiche strumentali.

                                           

gpb01

Paolo, secondo me c'è un errore ...

Come ti dicevo nell'altro thread, vero che abbiamo 1024 possibilità, ma sono da 0 a 1023, ovvero l'ADC a 10 bit ritorna un numero che è compreso tra 0 (0x0000) e 1023 (0x03FF).

Ora, quando ritorna il massimo 0x03FF indica 5V quindi significa che se vuoi ogni singolo step devi dividere per 1023 e non per 1024 ...

5 / 1023 = 0.0048876 il che significa che quando ti ritorna il massimo (1023 appunto) tu hai : 1023 * 0.0048876 = 5.00001

Se tu dividessi per 1024 avresti :

5 / 1024 = 0.0048828 il che significherebbe che, quando ti ritorna il massimo avresti : 1023 * 0.0048828 = 4.99510 che non esatto.

Ti torna il ragionamento ?

Guglielmo
Search is Your friend ... or I am Your enemy !

PaoloP

Il problema è in quello scritto nel datasheet. (pag. 252 di quello linkato)
Li la costante è 1024, anche se parla di valore massimo pari a 0x3FF (Vref - 1 LSB, che quindi non è il massino).
Quote
ADC Conversion Result
After the conversion is complete (ADIF is high), the conversion result can be found in the ADC Result Registers
(ADCL, ADCH).
For single ended conversion, the result is


where VIN is the voltage on the selected input pin and VREF the selected voltage reference (see Table 24-3 on page
254 and Table 24-4 on page 255). 0x000 represents analog ground, and 0x3FF represents the selected reference
voltage minus one LSB.


Anche l'autore originale usa 1023.
Bisogna fare delle verifiche strumentali con voltmetri che abbiano un accuratezza e precisione maggiore dell'ADC di Arduino.

gpb01

Messa come la mettono sul datasheet sembrerebbe che l'ADC darà il valore massimo di 0x03FF NON quando Vin == Vref, ma già quando è uno step sotto  :smiley-eek:

Del resto, se è vero che all'interno applica la formula :

ADC = (Vin * 1024) / Vref

avendo poi solo fino a 0x03FF ... non ha altra possibilità ...  :smiley-roll:

La cosa è corroborata dall'ultima frase : "0x000 represents analog ground, and 0x3FF represents the selected reference voltage minus one LSB." in cui dice espressamente che 0x0000 è il valore 0 (GND), ma che il valore massimo di 0x03FF (1023) NON è Vref, ma è il valore 1 step sotto ...

Quindi, se tutto questo è vero ... il range ch si può leggere con l'ADC del ATmega328P NON è da 0 a Vref, ma da 0 a (Vref - (Vref/1024)) !  Ed in tal caso allora mi torna la divisione per 1024 per calcolare ogni singolo passo ...

Guglielmo
Search is Your friend ... or I am Your enemy !

leo72

E se leggo 512, che sto leggendo?
(Vref/1024)*512 oppure (Vref/1024)*512-(Vref/1024)*(1024/512)

gpb01

No, se leggi 512 stai leggendo (Vref /1024) * 512 ...

Per questo NON potrai mai avere Vref ... perché (Vref/1024) * 1023 ... ti da appunto 1 step in meno ;)

Guglielmo
Search is Your friend ... or I am Your enemy !

PaoloP


Se tu dividessi per 1024 avresti :

5 / 1024 = 0.0048828 il che significherebbe che, quando ti ritorna il massimo avresti : 1023 * 0.0048828 = 4.99510 che non esatto.


Ogni step è di 0.00488 V.
Quindi se io fornisco una tensione pari a GND ad un pin analogico in lettura ottengo 0.
E se gli fornisco 0.003 o 0.004 (3 o 4 millivolt) dovrei leggere 1 oppure 0?
E tra 4.995 e 5.000 leggo 1023?

Chi ha un generatore di tensione cosi sensibile da poter verificare questi casi?

gpb01

#7
Mar 11, 2014, 02:35 pm Last Edit: Mar 11, 2014, 02:36 pm by gpb01 Reason: 1
Salvo smentite pratiche (... di cui dubito perché significherebbe che il datasheet è sbagliato), la teoria corroborata da quanto c'è scritto sul datasheet è molto chiara :

Vref / 1024 è lo step

quindi ogni valore restituito dal ADC è pari a (Vref / 1024) x valore_fornito_ADC !

Non mi sembra molto complicato  XD

La sola cosa da tenere presente è ben specificata nell'ultima frase : "0x3FF represents the selected reference voltage minus one LSB." ...

Ovvero, 1023 (valore massimo che può fornire con 10 bit) rappresenta Vref meno uno step (== meno 1 bit meno significativo) ... cosa, del resto, piuttosto ovvia ... se dividi per 1024 e moltiplichi per 1023  :smiley-mr-green:

Guglielmo
Search is Your friend ... or I am Your enemy !

gpb01

Oh ... intanto una piccola precisazione ...
... stiamo facendo tante belle chiacchiere sulla carta, poi ... o disponete di un generatore di tensione di precisione (... per la Vref) o ... 1020, 1023, 1024 ... non significa nulla  XD XD XD

Sto misurando adesso la tensione che Arduino fornisce sui 5V e, indipendentemente dal fatto se siano più o meno ... è che sono stabili solo alla seconda cifra decimale, nel mio caso 4.98V dalla terza in poi ... balla che è una bellezza quindi ... sai che precisione di misura  XD XD XD

Guglielmo
Search is Your friend ... or I am Your enemy !

PaoloP

Ho implementato un piccolo codice per verificare il discorso dell'ADC e forse ho trovato un bug in AVRdude.  :smiley-eek:
Stanotte fornirò maggiori informazioni.

gpb01

Paolo, possiedi un generatore di tensione professionale che ti garantisca la precisione dei millivolt ?

Altrimenti, purtroppo, qualsiasi prova tu faccia ... non ha alcun valore ...  :smiley-roll:

Guglielmo
Search is Your friend ... or I am Your enemy !

PaoloP


Paolo, possiedi un generatore di tensione professionale che ti garantisca la precisione dei millivolt ?


No. Infatti non ci sono arrivato.
Durante il caricamento AVRdude mi da errore di verifica. Mentre se carico un blink sullo stesso micro no.
Inoltre se cambio leggermente codice compila e carica, con il primo codice che ho scritto compila e da errore.
Adesso non ho con me tutta l'attrezzatura per fare verifiche.
Ne riparliamo dopo.

Go Up