Reading the ADC
analogRead gives you a number between 0 and 4095, and turning that into volts is where everyone goes wrong. The range is not 0 to 3.3 V, the top is flat, the bottom is dead, and on the classic ESP32 half the pins stop working when Wi-Fi starts.
The three surprises, in one line
raw / 4095 × 3.9, not × 3.3. Better still, call analogReadMilliVolts(), which uses the calibration burnt into your particular chip at the factory.ADC1 or ADC2
This one matters and it is invisible until it breaks.
- ADC1 — GPIO 32–39 on the classic ESP32. Works whether the radio is on or off. Use these.
- ADC2 — GPIO 0, 2, 4, 12–15, 25–27. Shares hardware with Wi-Fi. Once
WiFi.begin()runs, reads from these return errors or zeros.
The S3, C3, C6 and P4 do not have this restriction, but the habit of putting analog sensors on ADC1 costs nothing and survives a change of chip.
When 12 bits is not enough
The ESP32's ADC is fine for a light sensor, a potentiometer or a battery divider. It is not fine for a load cell, a thermocouple, or anything where you care about the third digit. For those, an external ADC on I2C or SPI — an ADS1115 or an HX711 — is a two-pound part that solves the whole problem.
The code
Two things make readings usable - averaging away the noise, and using the factory calibration instead of dividing by 4095 yourself.
const int PIN = 34; // an ADC1 pin - works with Wi-Fi on
void setup() {
Serial.begin(115200);
analogSetPinAttenuation(PIN, ADC_11db); // 0 to about 3.1 V
}
int readAveraged(int pin, int n = 16) {
long sum = 0;
for (int i = 0; i < n; i++) sum += analogReadMilliVolts(pin);
return sum / n;
}
void loop() {
Serial.printf("%d mV raw=%d\n", readAveraged(PIN), analogRead(PIN));
delay(500);
}analogReadMilliVolts applies the calibration data burnt into your particular chip at the factory. It is more accurate than any formula you can write, and it is one function call.
Same idea. read_uv returns microvolts using the same calibration, which saves you the attenuation arithmetic entirely.
from machine import ADC, Pin
import time
adc = ADC(Pin(34))
adc.atten(ADC.ATTN_11DB) # 0 to about 3.1 V
def read_mv(n=16):
return sum(adc.read_uv() for _ in range(n)) // n // 1000
while True:
print(read_mv(), 'mV')
time.sleep(0.5)ADC.ATTN_11DB is the widest range and the one most examples use. Narrower attenuation is more precise over a smaller span, which is worth it for a sensor that only swings a little.
When it does not work
You are on an ADC2 pin. On the classic ESP32 the second ADC block shares hardware with the radio, and the radio wins. Move the sensor to an ADC1 pin - GPIO 32 to 39. Later chips do not have this problem.
The reading is clipping. Above the attenuation's full scale the line goes flat, so every voltage returns the same number. Use a divider that puts your maximum near the middle of the range.
Normal. The ADC is noisy. Average 16 or 64 samples, and put a 100 nF capacitor from the pin to ground for anything with a long wire on it.
Below about 100 mV the ADC is not linear at all. That part of the range is unusable, so shift your signal up rather than trying to correct it.
That was reading a voltage. The classic ESP32 can also produce a real one, from two pins only.
DAC output →Edit this page — content/esp32/reading-the-adc.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.