Problema ingressi analogici

La saga del ADC continua :slight_smile:
Dato che spesso vedo scritte cose che stridono non poco con quanto scritto sul data sheet del 328, p.e. che tocca fare più letture a perdere quando si cambia il canale, stamattina mi sono divertito a fare alcune prove pratiche.
Il circuito di test è costituito da un semplicissimo partitore con tre uscite realizzato mettendo in serie queste resistenze: "150 ohm, 220 ohm, 380 ohm, 820 ohm", GND è collegato alla r da 150 e il +3.3V è collegato alla r da 820 ohm e ad Aref, ho misurato con un multimetro le reali tensioni in modo da avere un riscontro preciso sulla misura e sono: "0.311V, 0.771V, 1.58V" mentre l'esatto valore del 3.3V è 3.302V.
Le tensioni sono collegate agli ingressi A0, A1, A3, gli ingressi A2, A4, A5 sono liberi.

Lo sketch di test è questo:

float ADC_l, ADC_2, ADC_3, ADC_4, ADC_5, ADC_6;
byte i;

void setup()
{
  analogReference(EXTERNAL);
  
  pinMode(A0,INPUT);  
  pinMode(A1,INPUT); 
  pinMode(A2,INPUT); 
  pinMode(A4,INPUT); 
  pinMode(A5,INPUT); 

  pinMode(13, OUTPUT);

  digitalWrite(13, HIGH);  
  Serial.begin(19200);
}

void loop() 
{
  for(i=0;i<20;i++) 
  {  
    ADC_l = analogRead(A0);    
    ADC_2 = analogRead(A1); 
    ADC_3 = analogRead(A2); 
    ADC_4 = analogRead(A3);
    ADC_5 = analogRead(A4); 
    ADC_6 = analogRead(A5); 

    Serial.print(ADC_l*0.00322);
    Serial.print(",");
    Serial.print(ADC_2*0.00322);
    Serial.print(",");
    Serial.print(ADC_3*0.00322);
    Serial.print(",");
    Serial.print(ADC_4*0.00322);
    Serial.print(",");
    Serial.print(ADC_5*0.00322);
    Serial.print(",");
    Serial.println(ADC_6*0.00322);
  }
  while(1);
}

La costante 0.00322 è data 3.302 / 1024, in pratica leggo tutti gli ingressi ADC, stampandone il valore, per 25 volte consecutive.

Il risultato è il seguente:

0.306,0.763,1.121,1.578,1.558,1.826
0.306,0.763,0.886,1.578,1.507,1.616
0.306,0.763,0.808,1.578,1.526,1.565
0.306,0.763,0.789,1.578,1.533,1.546
0.306,0.763,0.786,1.578,1.546,1.542
0.306,0.763,0.782,1.578,1.555,1.546
0.306,0.763,0.782,1.578,1.558,1.549
0.306,0.763,0.782,1.578,1.562,1.552
0.306,0.763,0.782,1.578,1.565,1.552
0.306,0.763,0.779,1.578,1.568,1.558
0.306,0.763,0.782,1.578,1.568,1.558
0.306,0.763,0.782,1.578,1.571,1.565
0.306,0.763,0.782,1.578,1.568,1.562
0.306,0.763,0.779,1.575,1.565,1.562
0.306,0.763,0.779,1.578,1.565,1.562
0.306,0.763,0.776,1.578,1.562,1.558
0.306,0.763,0.776,1.578,1.558,1.558
0.306,0.763,0.773,1.578,1.555,1.555
0.306,0.763,0.770,1.578,1.552,1.552
0.306,0.763,0.766,1.578,1.552,1.552

Come visibile non solo i valori di A0, A1 e A3 sono coerenti con le reali tensioni, nel limite della precisione del ADC, si nota pure come gli ingressi lasciati liberi siano fortemente influenzati dalla misura dell'ultimo ingresso realmente collegato ad una tensione.

Come mai la misura in sequenza dei cinque ingressi, senza buttare una lettura, è corretta ?

La risposta è semplicissima, su Ardino l'ADC viene fatto lavorare in single run e non in free run, infatti guardando il relativo codice troviamo quanto segue:

#if defined(ADCSRA) && defined(ADCL)
	// start the conversion
	sbi(ADCSRA, ADSC);

	// ADSC is cleared when the conversion finishes
	while (bit_is_set(ADCSRA, ADSC));

	// we have to read ADCL first; doing so locks both ADCL
	// and ADCH until ADCH is read.  reading ADCL second would
	// cause the results of each conversion to be discarded,
	// as ADCL and ADCH would be locked when it completed.
	low  = ADCL;
	high = ADCH;

Il bit ADCC (registro ADCSRA) viene settato ogni volte per avviare la conversione, si autoresetta alla fine, e non viene settato il bit ADATE (registro ADCSRA) applicando come sorgente ADIF, il che consentirebbe l'auto trigger del ADC ogni volta che termina una conversione (free run).
Presumibilmente nelle vecchie versioni di Arduino la gestione del ADC era in free run, altrimenti non sarebbe stato necessario il delay(1), ora commentato, all'interno della funzione, sicuramente poi è stato scelto il modo single run per non dover inserire un delay forzato o obbligare gli utenti a gettare la prima lettura dopo il cambio canale, in modo single run il problema non esiste perché la commutazione dell'ingresso avviene con l'ADC fermo.
Ho controllato il sorgente "wiring_analog.c" della versione 0023, anche questa lavora in single run, praticamente il sorgente è lo stesso dell'attuale 1.0.4 salvo le differenze su i controlli del tipo processore per gestire la Leonardo.

Adesso scatta la domanda da 1.000.000 di Euro, come mai a volte è comunque necessario buttare via delle letture, quando si cambia canale, per ottenere dei valori corretti ?
Anche in questo caso la risposta è semplicissima, il problema è legato all'impedenza d'uscita del generatore di tensione che andate a misurare, se questa è maggiore di 10 kohm il sistema di campionamento del ADC non riesce a recepire la nuova tensione con sufficiente rapidità pertanto sono necessari più cicli di lettura per permettergli di adattarsi.
In questi casi la vera soluzione è aumentare il tempo di sampling del ADC, però su Arduino è fisso a ~10ksps e per modificarlo tocca agire direttamente su i registri del ADC, se non si è capaci di farlo si fa prima ad eseguire 2-3-...n letture consecutive in modo da ottenere lo stesso il corretto valore.

Dal data sheet del 328 sezione ADC.

The ADC is optimized for analog signals with an output impedance of approximately 10 k? or less.
If such a source is used, the sampling time will be negligible. If a source with higher imped-ance is used, 
the sampling timewill depend on how long time the source needs to charge the
S/H capacitor, with can vary widely. The user is recommended to only use low impedance
sources with slowly varying signals, since this minimizes the required charge transfer to the S/H
capacitor.