Error no lineal en las lecturas de ADC

Estoy midiendo voltajes de hasta 20 V con mi MCU ATmega2650 (ADC de 10 bits).

Estoy usando una referencia de voltaje de precisión de 5V (LT1021 - 0.05%).

Los divisores de voltaje se configuran con resistencias Panasonic al 1%.

Vcc->10kOhm->Medida->3.3kkOhm->GND.

Relación de división: 3.3/13.3=0.248

Lo que he notado son los siguientes errores crecientes al medir voltajes más grandes:

Vmeas   ADC Err(%)
3.05    152 -0.013382929
4.09    205 -0.0075968
5.02    253 -0.002075695
6.08    308 0.003057305
7.07    359 0.0054141
8.07    410 0.00595279
9.07    461 0.00637229
10.02   510 0.007764232
11.05   563 0.008777353
12.05   615 0.01046932
13.05   665 0.008925735
14.05   717 0.010366242
15.06   769 0.010955198
16.05   820 0.011495804
17.07   872 0.011368671
18.06   923 0.011826103
19.04   973 0.011739502
19.51   998 0.01271154
19.94   1020    0.012715509

¿Alguien puede explicar qué está causando tal no linealidad?

¿Algún consejo matemático para estimar esto (características de ADC en lugar de poliajuste)? Cualquier referencia a modelos matemáticos ayudaría.

EDITAR - errores en todo el rango de voltaje:
ingrese la descripción de la imagen aquí

La metodología de cálculo:

#define PSU_ANALOG_CHANNELS     3
#define PSU_ANALOG_MEASURES     5
#define PSU_ANALOG_MEASURE_DELAY 1

//apply vRef to each pin to measure post-divided ADC reading
const int psu_adc_corr[PSU_ANALOG_CHANNELS] =  {250,251,251};

int psu_volts_raw[PSU_ANALOG_CHANNELS];//stores ADC readings
float psu_volts[PSU_ANALOG_CHANNELS] = {0}; //stores final values
float mvAdc[PSU_ANALOG_CHANNELS]; //stores mV per each ADC-channel (to avoid division)

void calcMvADC(){
  for (int i=0; i<PSU_ANALOG_CHANNELS; i++) {
      mvAdc[i] = 5.0 / psu_adc_corr[i];
  }
}

//returns averaged reading for each ADC channel
int readAnalog(int ch) {
  int val = 0;
  for (int i=0; i<PSU_ANALOG_MEASURES; i++) {
    val += analogRead(ch);
    delay(PSU_ANALOG_MEASURE_DELAY);
  }
  return val/PSU_ANALOG_MEASURES;
}

void readADC() {
  for (int i=0; i<PSU_ANALOG_CHANNELS; i++) {
    psu_volts_raw[i]=readAnalog(i);
  }
}

/*
>6 <=7 : -1.1%
>7 <=9: -1.14%
>9 <=13: -1.25%
>13 -1.7%:
*/
float corrVoltage(float V) {
    if (V<6) return V;
    if (V>6 && V<=7) return V*0.989;
    if (V>7 && V<=9) return V*0.9886;
    if (V>9 && V<=13) return V*0.9875;
    if (V>13) return V*0.983;
    return V;
}

void calcVoltages() {
  for (int i=0; i<PSU_ANALOG_CHANNELS; i++) {
    psu_volts[i] = psu_volts_raw[i] * mvAdc[i];
    psu_volts[i] = corrVoltage(psu_volts[i]);
  }
}

void setup (){
  analogReference(EXTERNAL);
  calcMvADC();
  Serial.begin(115200);
}

void loop (){
  readADC();
  calcVoltages();
  for (int i=0; i<PSU_ANALOG_CHANNELS; i++) {
    Serial.println(psu_volts[i]);
  }
  delay(500);
}
¿Cómo se calcula el voltaje a partir del resultado del ADC, es /1023 o /1024?
Tal vez me lo esté perdiendo, pero ¿cómo está calculando todos estos valores verdaderos para el voltaje?
1023... o en este caso 250, 251 - la lectura que obtengo después de alimentar vRef al canal analógico apropiado
MathEE -> esta línea: psu_volts[i] = psu_volts_raw[i] * mvAdc[i]; 5,0/250 = 0,02 V; 344 (lectura ADC) * 0,02 = 3,3 V
psu_volts es el voltaje experimental calculado a partir de la lectura del ADC. ¿Cómo estás leyendo el voltaje real enviado al ADC?
Además, todos los ADC exhiben no linealidad en la función de transferencia. ¿La pregunta aquí es si esto está fuera de lo común o cómo modelar esta no linealidad?
MathEE: la lectura verdadera ocurre en readADC()->readAnalog()->analogRead(). El ADC lee valores precisos, pero solo aquellos que no superan los 5 V (aviso: todos los valores vienen a través del divisor de voltaje), por lo tanto, sospecho que el ADC no tiene nada que ver con esto, pero el divisor de voltaje sí.
Creo que respondiste mi pregunta en los comentarios a la respuesta de Sphero. Sus verdaderas medidas fueron obtenidas por voltímetro.

Respuestas (4)

Los ADC son naturalmente no lineales. Aproximadamente, la función de transferencia comienza en 0, luego aumenta más rápido que la función de transferencia lineal esperada hasta que alcanza los #bits/2 y luego se curva hacia donde debería estar. Puedo dibujar un diagrama si esta explicación no es clara.

El principal problema es que asume que el convertidor tiene la función de transferencia lineal con Voltaje = 5V/250*(valor de ADC). No lo hace y el error ni siquiera es lineal, como ya ha observado. Dada la forma de la función de transferencia real, los datos proporcionados por Andy y la forma en que está calculando los errores a voltajes más altos, el patrón que está viendo es el esperado.

No creo que lo hayas dicho, pero déjame hacer una conjetura basada en este análisis: tu ADC sobreestima constantemente los voltajes. Esto se debe a que la pendiente (5V/250) está cerca del máximo de la pendiente de la función de transferencia real.

Editado para agregar: tal vez quedó claro en su publicación que están siendo sobreestimados ya que Err% siempre es> 1. Está claro si Err% = (lectura de ATMega)/(valor real)

Segunda edición: en realidad, lo que puede hacer fácilmente y ver cómo cambian los resultados: coloque 20 V en el divisor de voltaje. Obtendrá un número, digamos $N$, luego defina psu_adc_corr=N y myAdc = 20/psu_adc_corr=20/N. Me interesaría ver lo que obtienes.

He actualizado las medidas. La pendiente parece disminuir. Para mi gran sorpresa, no pude alcanzar los 20 V (incluso en teoría, debería haber tenido 251 * 4 = 1004 ADC a 20 voltios)

esta es probablemente la mejor explicación que he encontrado del proveedor

Tiene una sección dedicada a la no linealidad con la siguiente conclusión: " La no linealidad no se puede compensar con cálculos simples. Para ese propósito, se pueden usar aproximaciones polinómicas o búsquedas en tablas " .

Así que hice una matriz int8_t de 14 valores de error ADC (que representan incrementos de 1 voltio). Apliqué esas correcciones en la lectura de ADC y ¡sí! - Ahora tengo las lecturas de voltaje con un error de 0 a 1mV.
Además, ahora puedo usar un valor único de mV_per_ADC_step (con respecto a mi versión anterior para la cual tenía un valor de mV dedicado para cada canal ADC).

Se especifica que el ADC en la MCU tiene una precisión absoluta de hasta 2,5 LSbs a una frecuencia de muestreo de 200 kHz. Esto se especifica en un voltaje Vref y Vcc de 4 V, pero supongo que será muy similar a 5 V. Si su referencia es 5V, 2.5 LSbs son aproximadamente 12mV. Esto puede manifestarse positiva o negativamente para un voltaje de entrada particular.

No estoy seguro de cómo se obtienen sus valores, pero a 0,248 * 7 V en una entrada, 12 mV puede ser un error de 0,69 %, que está en algún lugar del ámbito que está viendo.

ingrese la descripción de la imagen aquí

Pero si está utilizando un canal ADC diferencial, esto puede aumentar a aproximadamente 18 LSbs con una ganancia de unidad porque hay un amplificador interno que agrega un error.

No estoy tan familiarizado con el convertidor Atmel ADC, pero sugiero que tal vez el tiempo de adquisición para el S&H no sea lo suficientemente largo. Creo que debería ser 15usec o más.

La constante de tiempo máxima es 100K Ω 14 pF o 1.4useg, por lo que 10 constantes de tiempo son aproximadamente 15useg.

Alternativamente, ¿tiene algo como un diodo Zener de 5.1V en la entrada? Eso fácilmente podría causar errores en ese rango.

probablemente tendría un efecto en todas las lecturas, ¿no? Además, he intentado retrasar las lecturas de adc "intermedias" hasta 10 ms, sin impacto.
¿Sin zener? No, no afectaría a todas las lecturas por igual, de otros ADC con los que he trabajado, esperaría menos efecto en la escala media.
no, no zener. Y sí, hasta 5V las lecturas son tan precisas que podría usar mi configuración para calibrar voltímetros baratos.
¿Ha medido de forma independiente el voltaje de entrada con un voltímetro de alta impedancia directamente en el chip?
correcto, y así es como calculé el Err (%) en el gráfico, comparando las medidas de atmega con las medidas del voltímetro.
@FlegmatoidZoid De acuerdo, pero no creo que haya medido 16 V "directamente en el chip", entonces midió en la entrada del divisor y asumió que el voltaje del chip era 0.248 veces el voltaje medido o amplió una medida en el chip por 1/0.248?
a la derecha, la verdadera medición se realizó con un valor no dividido en la entrada con el voltímetro. La medición de atmega se realizó en el valor dividido y se amplió proporcionalmente (5.0/250*ADC_reading). Intenté configurar varios puntos de referencia (12.05/615, 19.94/1020), pero eso elimina el error solo para esa lectura en particular.