๐ ESP32 ADC Inaccurate Reading - Complete Fix Guide
๐ Common Causes of ADC Inaccuracy
- ๐ Using ADC2 with WiFi โ ADC2 pins are unreliable when WiFi is active
- ๐ No Attenuation Configuration โ Default range is only 0-1.1V, not 0-3.3V
- ๐ Electrical Noise โ No filtering capacitor on analog input
- ๐ Missing Calibration โ ESP32 ADC is non-linear and needs calibration
- ๐ High Source Impedance โ Sensor output impedance is too high
- ๐ Voltage Divider Issues โ Incorrect resistor values for measuring higher voltages
๐ ESP32 ADC Pin Reference
| ADC Type | GPIO Pins | WiFi Compatibility |
|---|---|---|
| ADC1 (Recommended) | 32, 33, 34, 35, 36, 37, 38, 39 | โ Works with WiFi |
| ADC2 (Avoid with WiFi) | 0, 2, 4, 12, 13, 14, 15, 25, 26, 27 | โ Unreliable when WiFi is on |
1๏ธโฃ Use ADC1 Pins When WiFi is Active
โ WRONG - Using ADC2 with WiFi:
// GPIO 4 is ADC2 - WILL FAIL when WiFi is on!
pinMode(4, INPUT);
int value = analogRead(4); // Random values!
โ CORRECT - Use ADC1 pins:
// GPIO 34 is ADC1 - Works perfectly with WiFi
pinMode(34, INPUT);
int value = analogRead(34); // Stable readings!
2๏ธโฃ Configure ADC Attenuation Correctly
By default, ESP32 ADC measures only 0-1.1V. You need attenuation to measure up to 3.3V:
#include <esp_adc_cal.h>
void setup() {
Serial.begin(115200);
// Configure ADC attenuation for 0-3.3V range
analogReadResolution(12); // 12-bit (0-4095)
analogSetAttenuation(ADC_11db); // 0-3.6V range
// For specific pins:
analogSetPinAttenuation(34, ADC_11db);
}
void loop() {
int raw = analogRead(34);
float voltage = raw * (3.3 / 4095.0);
Serial.print("Raw: ");
Serial.print(raw);
Serial.print(" | Voltage: ");
Serial.println(voltage);
delay(100);
}
| Attenuation | Voltage Range | Use Case |
|---|---|---|
| ADC_0db | 0-1.1V | Default, low voltage sensors |
| ADC_2_5db | 0-1.5V | Slightly higher range |
| ADC_6db | 0-2.2V | Medium range |
| ADC_11db | 0-3.6V | Full range (most common) |
3๏ธโฃ Add a Filtering Capacitor
Electrical noise causes readings to jump. Add a capacitor to smooth the signal:
// Connect a 0.1ยตF to 1ยตF capacitor between:
// - ADC pin and GND (as close to the pin as possible)
// This filters out high-frequency noise
- Sensor Output โ ADC Pin (e.g., GPIO 34)
- 0.1ยตF capacitor โ between ADC Pin and GND
- 10kฮฉ resistor (optional) โ between ADC Pin and GND for pull-down
4๏ธโฃ Implement Software Filtering
Average multiple readings to reduce noise:
// Method 1: Simple averaging
int readADC(int pin, int samples = 10) {
long total = 0;
for (int i = 0; i < samples; i++) {
total += analogRead(pin);
delay(1); // Small delay between samples
}
return total / samples;
}
// Method 2: Exponential moving average (lighter on memory)
float filteredValue = 0;
float alpha = 0.1; // Smoothing factor (0-1)
float readADC_Filtered(int pin) {
float newValue = analogRead(pin);
filteredValue = (alpha * newValue) + ((1 - alpha) * filteredValue);
return filteredValue;
}
// Method 3: Remove outliers (median filter)
int readADC_Median(int pin, int samples = 10) {
int values[samples];
for (int i = 0; i < samples; i++) {
values[i] = analogRead(pin);
delay(1);
}
// Sort array
for (int i = 0; i < samples - 1; i++) {
for (int j = i + 1; j < samples; j++) {
if (values[i] > values[j]) {
int temp = values[i];
values[i] = values[j];
values[j] = temp;
}
}
}
// Return median (middle value)
return values[samples / 2];
}
5๏ธโฃ Calibrate Your ADC
ESP32 ADC is non-linear. Use esp_adc_cal library for better accuracy:
#include <esp_adc_cal.h>
esp_adc_cal_characteristics_t adc_chars;
void setup() {
Serial.begin(115200);
// Configure ADC
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
// Calibrate ADC
esp_adc_cal_characterize(
ADC_UNIT_1, // ADC unit
ADC_ATTEN_DB_11, // Attenuation
ADC_WIDTH_BIT_12,// Bit width
1100, // Default Vref (can measure actual)
&adc_chars
);
}
void loop() {
int raw = analogRead(34);
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(raw, &adc_chars);
float voltage = voltage_mv / 1000.0;
Serial.print("Raw: ");
Serial.print(raw);
Serial.print(" | Calibrated Voltage: ");
Serial.print(voltage);
Serial.println("V");
delay(100);
}
6๏ธโฃ Measure Higher Voltages with a Voltage Divider
To measure voltages above 3.3V, use a voltage divider:
// Measure up to 10V with 10kฮฉ and 3.3kฮฉ resistors
//
// Vin ----/\/\/\----+----/\/\/\---- GND
// R1 | R2
// |
// ADC Pin
//
// Vout = Vin * (R2 / (R1 + R2))
// For Vin max 10V: 10 * (3.3 / (10 + 3.3)) = 2.48V (safe for ESP32)
float measureVoltage(int pin, float r1, float r2) {
int raw = analogRead(pin);
float vout = raw * (3.3 / 4095.0);
float vin = vout * (r1 + r2) / r2;
return vin;
}
// Example: Measure 12V battery
float batteryVoltage = measureVoltage(34, 10000, 3300); // R1=10k, R2=3.3k
| Max Voltage | R1 | R2 | Output at Max |
|---|---|---|---|
| 5V | 2.2kฮฉ | 3.3kฮฉ | 3.0V |
| 10V | 10kฮฉ | 3.3kฮฉ | 2.5V |
| 12V | 10kฮฉ | 2.2kฮฉ | 2.2V |
| 24V | 22kฮฉ | 3.3kฮฉ | 3.1V |
| 30V | 33kฮฉ | 3.3kฮฉ | 2.7V |
๐ง Complete Working Example
#include <esp_adc_cal.h>
#define ADC_PIN 34 // Use ADC1 pin!
esp_adc_cal_characteristics_t adc_chars;
void setup() {
Serial.begin(115200);
// Configure ADC
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
// Calibrate
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12, 1100, &adc_chars);
Serial.println("ADC Ready!");
}
int readADC_Filtered(int pin, int samples = 10) {
long total = 0;
for (int i = 0; i < samples; i++) {
total += analogRead(pin);
delay(5);
}
return total / samples;
}
void loop() {
int raw = readADC_Filtered(ADC_PIN);
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(raw, &adc_chars);
float voltage = voltage_mv / 1000.0;
Serial.print("Raw: ");
Serial.print(raw);
Serial.print(" (0-4095) | Voltage: ");
Serial.print(voltage, 3);
Serial.println("V");
delay(500);
}
โ Best Practices for Accurate ADC Readings
- Always use ADC1 pins (32-39) when WiFi is active
- Set attenuation to ADC_11db for 0-3.3V range
- Add a 0.1ยตF capacitor between ADC pin and GND
- Average 10-50 samples to reduce noise
- Use esp_adc_cal library for calibration
- Keep wires short to minimize noise pickup
- Avoid running analog wires near high-current or high-frequency signals
โ Frequently Asked Questions
Q: Why do my analog readings change when I turn on WiFi?
A: You're using ADC2 pins (GPIO 0,2,4,12-15,25-27). These conflict with WiFi. Switch to ADC1 pins (GPIO 32-39) for stable readings.
Q: What's the maximum voltage I can measure?
A: Directly: 3.3V. With a voltage divider: up to 30V (or higher with appropriate resistors).
Q: Why are my readings non-linear?
A: ESP32 ADC has inherent non-linearity, especially at the extremes. Use the esp_adc_cal library for correction.
Q: What's the difference between ADC1 and ADC2?
A: ADC1 works perfectly with WiFi. ADC2 conflicts with WiFi and gives random values when WiFi is active. Always use ADC1 for WiFi projects.
Q: Does OceanRemote support analog sensors?
A: Yes! OceanRemote supports NTC thermistors, potentiometers, and other analog sensors with built-in filtering and calibration. Generate your firmware โ
๐ Related Troubleshooting Guides
๐ Need a Simpler Solution?
OceanRemote supports analog sensors with:
- โ Built-in filtering and averaging
- โ NTC thermistor support with Steinhart-Hart
- โ Automatic calibration
- โ No coding required