OC
OceanRemote
Low-code IoT platform

๐Ÿ“Š ESP32 ADC Inaccurate Reading - Complete Fix Guide

โš ๏ธ Symptom: Your ESP32's analog readings are noisy, jumpy, inconsistent, or don't match the actual voltage. Values fluctuate even when the input is stable.

๐Ÿ” 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 TypeGPIO PinsWiFi 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
๐Ÿ’ก Critical: If you're using WiFi, ONLY use ADC1 pins (32-39) for analog readings! ADC2 pins will give random values when WiFi is active.

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!
โš ๏ธ Note: GPIO 34-39 are input-only pins. They cannot be used as digital outputs.

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);
}
AttenuationVoltage RangeUse Case
ADC_0db0-1.1VDefault, low voltage sensors
ADC_2_5db0-1.5VSlightly higher range
ADC_6db0-2.2VMedium range
ADC_11db0-3.6VFull 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
๐Ÿ”ง Wiring:
  • 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);
}
๐Ÿ’ก To measure actual Vref: Run the "ADC Calibration" example in Arduino IDE โ†’ File โ†’ Examples โ†’ ESP32 โ†’ AnalogRead โ†’ esp_adc_cal

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 VoltageR1R2Output at Max
5V2.2kฮฉ3.3kฮฉ3.0V
10V10kฮฉ3.3kฮฉ2.5V
12V10kฮฉ2.2kฮฉ2.2V
24V22kฮฉ3.3kฮฉ3.1V
30V33kฮฉ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 โ†’

๐Ÿš€ 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
Generate Firmware โ†’

โœ… Quick Checklist