ESP32 ADC Explained: Non-Linearity, Attenuation, and Calibrating Analog Readings for Real Accuracy
Plenty of ESP32 projects on this site read an analog sensor with a plain analogRead() call and move on — and for a lot of applications, that's fine. But if you've ever compared an ESP32's analog reading against a multimeter and found it off by 5-10%, or noticed the error changes depending on where in the voltage range you're measuring, you've run into a well-known quirk of the ESP32's ADC: it's genuinely less linear than the ADC in a classic AVR-based Arduino, and getting accurate readings out of it means understanding attenuation settings and using calibration rather than trusting the raw value.
Why the ESP32 ADC is different from an Uno's
The ATmega328P in an Arduino Uno uses a successive-approximation ADC referenced against a clean, well-characterized voltage, and its error is small and predictable across its full range. The ESP32's ADC is a capable peripheral, but the silicon itself has more measurement non-linearity, especially near the very top and bottom of whatever voltage range you've configured — deviations of 50-100+ mV aren't unusual on an uncalibrated reading, which is a lot when you're trying to resolve a sensor signal to any real precision. This isn't a defect specific to one board; it's a known characteristic Espressif documents and provides tooling to correct for.
Attenuation: the setting most people skip
The ESP32 ADC input pins are only safely linear across a fairly narrow input range by default, so Espressif exposes an attenuation setting per pin that scales what full-scale range the ADC is reading against. Get this wrong and you either clip a signal that exceeds the range or waste resolution reading a small signal against a range that's far too wide.
AttenuationUsable input range (approx.)When to use it 0 dB~0 – 950 mVSmall-swing sensor outputs, reference voltages under 1V 2.5 dB~0 – 1250 mVRarely used; narrow middle ground 6 dB~0 – 1750 mVSignals conditioned to roughly half of 3.3V 11 dB~0 – 2450–3100 mV (varies by chip revision)Most 0–3.3V sensor outputs, battery voltage dividersMost beginner tutorials leave attenuation at its default (11 dB on many cores, covering close to the full 3.3V range) and never revisit it — which works, but throws away accuracy if your actual signal only swings across a fraction of that range. If a sensor's output only ever moves between 0.2V and 1.0V, reading it at 0 dB attenuation gives you far better effective resolution than reading the same signal at 11 dB, where that narrow swing occupies a small slice of the ADC's total range.
Using the built-in calibration API instead of raw counts
Espressif ships a calibration scheme in ESP-IDF (and exposed through the Arduino core) that reads factory-programmed calibration values burned into each chip's eFuses during manufacturing and uses them to convert raw ADC counts into millivolts far more accurately than a naive linear formula. In ESP-IDF this is the esp_adc/adc_cali component; in the Arduino core, recent versions of analogRead() and the newer ADC.h/analogReadMilliVolts() API apply this calibration for you automatically when it's available on the chip. The practical takeaway: prefer analogReadMilliVolts() over raw analogRead() when you need calibrated millivolt values, and in ESP-IDF code call adc_cali_create_scheme_curve_fitting() (or the line-fitting scheme on older chip revisions) rather than hand-rolling your own raw * 3300 / 4095 conversion, which ignores exactly the non-linearity you're trying to correct for.
A practical calibration workflow when you need better than factory accuracy
- Set the attenuation that matches your actual signal range rather than defaulting to the widest setting.
- Use the built-in calibration API to get millivolt-accurate readings rather than raw counts.
- If your application needs precision the factory calibration still doesn't deliver (weighing scales, precision battery gauges), take multiple known reference voltages — a calibrated bench supply or a few precision resistor-divider points — and fit your own two-point or multi-point linear correction on top of the calibrated reading, storing the offset and gain constants in NVS per device rather than assuming every board behaves identically.
- Oversample and average. ADC noise is real; taking 16-64 samples and averaging (or using the ESP32's hardware oversampling where available) meaningfully reduces jitter in the final value.
- Keep analog source impedance low. A high-impedance sensor or a long, unbuffered wire feeding the ADC pin will read inaccurately regardless of calibration — the ESP32 ADC's sample-and-hold capacitor needs a reasonably low-impedance source to charge accurately within the sample window; add a simple op-amp buffer (see this site's op-amps guide) if your sensor's output impedance is high.
Where this actually matters
For a soil moisture sensor or a rough light-level reading, none of this is worth the effort — the sensor's own variability dwarfs ADC error. It matters when you're building something like a battery fuel gauge from a resistor divider (where a 50 mV error translates directly into a meaningfully wrong percentage), a load cell amplifier output, or any project where this site's current-sensing or thermocouple guides already push you toward dedicated ADC ICs like the ADS1115 or a HX711 — which sidestep this problem entirely by moving the ADC off the ESP32 and onto a chip built for precision measurement in the first place. Knowing when the internal ADC is good enough, and when to reach for external precision, is the real skill here.
Related Guides
- I2C vs SPI vs UART: How to Choose and Use Serial Communication Protocols
- How to Use Sensors with Arduino and ESP32: Temperature, Distance, Load, Current, and Hall Effect
- Build a Wired ESP32 IoT Sensor Node with W5500 Ethernet for Reliable Uptime
- Build a DIY ESP32 DMX512 Lighting Controller
- How to Program Addressable LED Strips: WS2812B Patterns, Effects, and Power Design
- How to Control Motors with Arduino and ESP32: Stepper, DC, and Servo Drivers
- ESP32: Setting Up for Arduino IDE
- Arduino vs ESP32: Which Should You Use? A Practical Comparison