Learn

Reading a potentiometer with an ESP32

Sign up freeor see every lesson
  • 192parts on the bench
  • 25boards running now
  • 1.00xreal time, on every board
ESP32 DevKit: knoblive0.000 s 0.00x
Click to open it in the editor
A 10 k pot on GPIO 32 (ADC1 channel 4) sets the brightness of an LED on GPIO 4.

analogRead on an ESP32 looks like the Arduino call you already know, but three things about it catch almost everyone: the numbers go to 4095 instead of 1023, the top of the range is not your supply voltage, and half the analog pins stop working the moment WiFi starts. This page walks through all three with a potentiometer and an LED.

The circuit above is a classic ESP32 DevKit with a 10 k potentiometer between 3.3 V and ground, its wiper on GPIO 32, and an LED on GPIO 4. The sketch reads the pot five times a second, maps the reading to a brightness from 0 to 255, writes it to the LED and prints both. With the knob in the middle the serial monitor reads "knob 1734 -> duty 107". Drag the knob and watch both numbers move.

Twelve bits, and why 3.3 V is not 4095

The ESP32’s converter is 12-bit, so readings run from 0 to 4095 against an Uno’s 0 to 1023. But the value 4095 does not mean 3.3 V. The input goes through an attenuator first, and at the default setting, 11 dB, the full scale is about 3.9 V. A voltage of 3.3 V therefore reads about 4095 x 3.3 / 3.9, which is roughly 3466, and the pot can never reach the top of the range.

Check it against the reading above. The wiper in the middle sits at 1.65 V, and 4095 x 1.65 / 3.9 is 1733. The simulator printed 1734. That is not a coincidence: Mokxi models the converter as that straight line with the datasheet’s nominal full-scale values.

There is a knock-on effect in the example sketch that is worth spotting. It maps 0 to 4095 onto 0 to 255, so with the pot turned all the way up the duty only reaches about 215 and the LED never gets to full brightness. The fix is to map from the range you actually get, map(raw, 0, 3466, 0, 255), or on a real board to use analogReadMilliVolts, which applies the chip’s own calibration.

firmware/esp32/examples/knob (the program running above, comments trimmed)
const int kKnob = 32;   // ADC1 channel 4
const int kLed = 4;

void setup() {
  Serial.begin(115200);
  pinMode(kLed, OUTPUT);
}

void loop() {
  int raw = analogRead(kKnob);
  int duty = map(raw, 0, 4095, 0, 255);
  analogWrite(kLed, duty);
  Serial.print("knob ");
  Serial.print(raw);
  Serial.print(" -> duty ");
  Serial.println(duty);
  delay(200);
}

Attenuation settings

analogSetAttenuation (or analogSetPinAttenuation for one pin) trades range for resolution. At 0 dB the full scale is about 1.1 V, at 2.5 dB about 1.5 V, at 6 dB about 2.2 V and at 11 dB about 3.9 V. If your sensor never goes above one volt, 0 dB gives you four times as many steps across its range.

On a real ESP32 those are nominal figures and the ends of the range are not trustworthy: at 11 dB the reading bends noticeably below about 150 mV and above about 3.1 V, and every chip differs slightly. Mokxi’s converter is the ideal straight line, which is right for learning the arithmetic and optimistic about the ends. On hardware, keep the signal inside the middle of the range, and average several readings, because a real ESP32 reading jumps by a few counts even with the pot standing still.

ADC1, ADC2 and WiFi

The classic ESP32 has two converters. ADC1 is on GPIO 32 to 39. ADC2 is on a set of other pins, and on a real board the WiFi driver uses ADC2 itself, so analogRead on an ADC2 pin fails while WiFi is running. That is why this example uses GPIO 32: it keeps working in a project that later adds a web page. Mokxi does not model ADC2 at all, so use ADC1 pins here and your sketch will carry over.

GPIO 34 to 39 are input only, which makes them good analog inputs and useless as outputs. And never put more than 3.3 V on any ESP32 pin. The attenuator’s 3.9 V full scale is a measuring range, not a safe input voltage; a 5 V sensor output needs a divider before it reaches the pin.

The LED side: analogWrite on an ESP32

The ESP32 has no timers wired to fixed PWM pins the way an Uno does. analogWrite here hands the pin to LEDC, the chip’s LED PWM controller, which can drive almost any GPIO. The runtime uses 8 bits at 1 kHz, so the 0 to 255 values behave the same as on an Uno. When the frequency or resolution matters, for a servo or a motor driver, ledcAttach(pin, frequency, bits) and ledcWrite set them explicitly.

The LED on GPIO 4 runs at 3.3 V, so the resistor math changes from an Uno: with about 2 V across a red LED, (3.3 - 2) / 150 ohms is roughly 9 mA. The same 220 ohm resistor you would use on 5 V gives about 6 mA, a little dimmer and perfectly safe.

Questions

Why does my ESP32 analogRead never reach 4095?

Because the default full scale is about 3.9 V and the pin can only see 3.3 V, so the most it reads is roughly 3466. Map from the range you actually get, or use analogReadMilliVolts on a real board.

Which ESP32 pins can I use for analogRead with WiFi on?

The ADC1 pins, GPIO 32 to 39. ADC2 pins stop working for analogRead on a real ESP32 while WiFi is active.

Is the ESP32 ADC accurate?

Adequate for knobs and light sensors, not for precise measurement. It is noisy and non-linear near the ends of each range. For accurate voltages, use an external ADC such as an ADS1115 over I2C.

Does the ESP32-C3 behave the same way?

Mostly. The C3 has a 12-bit ADC on GPIO 0 to 4 and the same attenuation idea, but its top range differs from the classic ESP32. Check the board page for the pins and range Mokxi models.

Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.