What is an ADC?
An ADC (Analog-to-Digital Converter) bridges the gap between the continuous analog world (temperatures, pressures, light levels) and the discrete digital world inside a microcontroller. It samples an input voltage at a moment in time and produces a binary number representing that voltage.
ESP32 has two built-in 12-bit SAR ADCs capable of reading up to 18 channels. They are fundamental to interfacing with potentiometers, temperature sensors, pH sensors, accelerometers with analog output, and many other transducers.
How SAR ADC Works (Simplified)
ESP32 uses a Successive Approximation Register (SAR) architecture:
- The ADC samples the input voltage and holds it steady (sample-and-hold)
- It starts with a guess of half the full-scale range (2048 for 12-bit)
- It compares the guess to the actual voltage: too high → go lower; too low → go higher
- Repeating 12 times, it narrows down to the exact 12-bit number
- The result (0–4095) is returned to your code via
analogRead()
Step 1: Guess 2048 (1.65V) — exact match for this example Result: 2048 out of 4095 Voltage resolution = 3.3V / 4095 = 0.000806V per step ≈ 0.8 mV/LSB In practice, with noise: expect ±2–5 LSB variation on stable inputs.
ADC1 vs ADC2 Channel Map
| GPIO | ADC | Channel | Wi-Fi Safe? |
|---|---|---|---|
| GPIO36 (VP) | ADC1 | CH0 | Yes ✓ |
| GPIO37 | ADC1 | CH1 | Yes ✓ |
| GPIO38 | ADC1 | CH2 | Yes ✓ |
| GPIO39 (VN) | ADC1 | CH3 | Yes ✓ |
| GPIO32 | ADC1 | CH4 | Yes ✓ |
| GPIO33 | ADC1 | CH5 | Yes ✓ |
| GPIO34 | ADC1 | CH6 | Yes ✓ |
| GPIO35 | ADC1 | CH7 | Yes ✓ |
| GPIO4 | ADC2 | CH0 | No ✗ |
| GPIO25 | ADC2 | CH8 | No ✗ |
| GPIO26 | ADC2 | CH9 | No ✗ |
Resolution and Voltage Mapping
void setup() {
Serial.begin(115200);
analogReadResolution(12); // 12-bit: 0–4095 (default)
// analogReadResolution(10); // 10-bit: 0–1023 (Arduino style)
// analogReadResolution(9); // 9-bit: 0–511
analogSetAttenuation(ADC_11db); // Input range: 0–3.3V
}
void loop() {
int raw12 = analogRead(34); // 12-bit raw
float volts = raw12 * (3.3f / 4095.0f); // Convert to voltage
Serial.printf("12-bit raw=%4d Voltage=%.4f Vn", raw12, volts);
delay(500);
}
Attenuation Deep Dive
void setup() {
Serial.begin(115200);
analogReadResolution(12);
// Set different attenuation on different pins:
analogSetPinAttenuation(34, ADC_0db); // GPIO34: 0–1.1V (highest precision)
analogSetPinAttenuation(35, ADC_6db); // GPIO35: 0–2.2V
analogSetPinAttenuation(36, ADC_11db); // GPIO36: 0–3.3V (full range)
}
void loop() {
Serial.printf("GPIO34(0dB)=%.3fV GPIO35(6dB)=%.3fV GPIO36(11dB)=%.3fVn",
analogRead(34) * (1.1f/4095.0f),
analogRead(35) * (2.2f/4095.0f),
analogRead(36) * (3.3f/4095.0f));
delay(500);
}
ADC Non-Linearity Problem
ESP32 ADC has a documented non-linearity, especially near 0V and 3.3V. The characteristic S-curve means readings in the 0–100 and 3900–4095 ranges are unreliable:
| ADC Raw Range | Accuracy | Notes |
|---|---|---|
| 0–100 | Poor (non-linear) | Avoid reading near 0V |
| 100–3900 | Good (±1–3%) | Safe operating range |
| 3900–4095 | Poor (saturates) | Avoid readings near 3.3V |
Workaround: Ensure your sensor signal stays between 0.1V and 3.1V (raw 100–3900) for the best accuracy.
ADC Calibration with esp_adc_cal
Espressif provides a calibration library that dramatically improves accuracy:
#include <esp_adc_cal.h>
#include <driver/adc.h>
esp_adc_cal_characteristics_t adcChars;
void setup() {
Serial.begin(115200);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11); // GPIO34
esp_adc_cal_characterize(
ADC_UNIT_1,
ADC_ATTEN_DB_11,
ADC_WIDTH_BIT_12,
1100, // Default Vref (mV) — use 1100 if no eFuse calibration
&adcChars
);
}
void loop() {
uint32_t raw = adc1_get_raw(ADC1_CHANNEL_6); // GPIO34
uint32_t voltage = esp_adc_cal_raw_to_voltage(raw, &adcChars); // mV
Serial.printf("Raw: %4d Calibrated: %4d mV (%.3f V)n",
raw, voltage, voltage / 1000.0f);
delay(500);
}
Oversampling for Better Effective Resolution
// Oversampling: 4^n samples → n extra bits of resolution
// 16 samples → 2 extra bits (12+2 = 14-bit effective)
uint16_t oversample(int pin, int samples = 16) {
uint32_t acc = 0;
for (int i = 0; i < samples; i++) {
acc += analogRead(pin);
delayMicroseconds(50);
}
return acc / samples; // Average (for 16 samples, decimation gives +2 bits)
}
void loop() {
Serial.printf("12-bit: %4d Oversampled: %5dn",
analogRead(34), oversample(34, 64));
delay(200);
}
External ADC: When ESP32 Built-in is Not Enough
| ADC | Resolution | Interface | Best For |
|---|---|---|---|
| ESP32 Built-in | 12-bit (~10 eff) | Native | General sensing |
| ADS1115 | 16-bit | I2C | Precision sensors |
| ADS1256 | 24-bit | SPI | Scientific/industrial |
| MCP3208 | 12-bit | SPI | Multiple channels |
Summary
ESP32's built-in 12-bit SAR ADC is capable and convenient. For best results: use ADC1 channels (GPIO32–GPIO39), set 11 dB attenuation for full 3.3V range, average multiple samples, and apply factory calibration when accuracy matters. For measurements needing better than ±1% accuracy, use an external I2C ADC like the ADS1115.