Fix non-linear ADC
Posted: Sun Mar 07, 2021 10:08 am
The ADC of the ESP32 has a – fully justified – bad reputation.
- The advertised 12 bits are practically 9 bit +3 bit noise.
- The V/ADC relation is not linear
- The first 0.21V of the input (by 11dB attenuation) are ignored
I am proposing here a solution to get an acceptable result despite of all that drawbacks.
1) usefull settings:
11 bit or 10 bit is much more realistic
It is the attenuator that is non-linear, the best is to switch it off and to use a resistor divider.
2) oversampling and filtering
3) input circuitry:
-Use a resistive voltage divider and a decoupling capacitor c2 to stabilize the input to the ADC.
as well as
-Feed a minimal voltage supplement of ~0.07V into the ADC to get rid if the ADC offset:
In my circuit I am sourcing the feed from 5V, (which has for me the best stability) eliminating potential ripple through R4,C1 and injecting a small residual current into the ADC through R3
Shouldn't you have a stable 5V, you might use 3,3V as well, in that case the value of R3 is 390K instead of 560K.
Without any input you should measure ~ 0.07V at the input of the ADC, just enough to pull the input slightly above the offset.
4) final adjustment:
Whereas "ADC_0V" is the value of the ADC without any input and VOLT_MAX the voltage when the ADC shows 2047-
Using all these methods I could get a precision of 2% over the full range of measurements.
Enjoy!
- The advertised 12 bits are practically 9 bit +3 bit noise.
- The V/ADC relation is not linear
- The first 0.21V of the input (by 11dB attenuation) are ignored
I am proposing here a solution to get an acceptable result despite of all that drawbacks.
1) usefull settings:
Code: Select all
// Settings for ADC
analogSetWidth(11); // 11Bit resolution
analogSetAttenuation(ADC_0db);
It is the attenuator that is non-linear, the best is to switch it off and to use a resistor divider.
2) oversampling and filtering
Code: Select all
A0Raw = A3Raw = 0;
for (byte n = 0; n < 5; n++)
{
A0Raw += analogRead(A0);
delay (2);
}
A0Raw = A0Raw / 5;
A0Raw = constrain( A0Raw,lastA0-3,lastA0+3); // eliminate spikes
lastA0 = A0Raw;
dashboard.Vout += (adc2volt(A0Raw) - dashboard.Vout) / 8 ; // 1rst order low pass filter
-Use a resistive voltage divider and a decoupling capacitor c2 to stabilize the input to the ADC.
as well as
-Feed a minimal voltage supplement of ~0.07V into the ADC to get rid if the ADC offset:
In my circuit I am sourcing the feed from 5V, (which has for me the best stability) eliminating potential ripple through R4,C1 and injecting a small residual current into the ADC through R3
Shouldn't you have a stable 5V, you might use 3,3V as well, in that case the value of R3 is 390K instead of 560K.
Without any input you should measure ~ 0.07V at the input of the ADC, just enough to pull the input slightly above the offset.
4) final adjustment:
Code: Select all
float adc2volt(unsigned int adc)
{
float volt = map(adc, ADC_0V, 2047, 0, VOLT_MAX);
volt = volt / 1000;
return volt;
}
Using all these methods I could get a precision of 2% over the full range of measurements.
Enjoy!