Ottimizzare le letture dell'ADC

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/ è:

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

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.

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

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).

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.

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 :astonished:

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

ADC = (Vin * 1024) / Vref

avendo poi solo fino a 0x03FF ... non ha altra possibilità ... :roll_eyes:

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

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

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 :wink:

Guglielmo

gpb01:
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?

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 :grin:

Guglielmo

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

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

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

Altrimenti, purtroppo, qualsiasi prova tu faccia ... non ha alcun valore ... :roll_eyes:

Guglielmo

gpb01:
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.