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:
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);
}
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.
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.
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 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.
alexan_e
algunosEE
Flegmatoide Zoide
Flegmatoide Zoide
algunosEE
algunosEE
Flegmatoide Zoide
algunosEE