Phase 3: GPIO & Hardware 13 min read

Reading Analog Signals on ESP32: analogRead, Sensors, and ADC Tips

Learn to read analog signals on ESP32 — use analogRead(), map sensor values, interface potentiometers, LDR, NTC thermistors, and fix ADC non-linearity issues.

Updated June 19, 2026

Analog vs Digital Signals

Digital signals are binary: they are either HIGH or LOW. Analog signals vary continuously across a range of voltages — a temperature sensor might output 0.5V at 0°C and 2.5V at 100°C. ESP32 includes an Analog-to-Digital Converter (ADC) that samples this continuous voltage and converts it to a number your code can work with.

ESP32 ADC Pins

ADC GPIO Pins Wi-Fi Compatible? Recommended?
ADC1 GPIO32, GPIO33, GPIO34, GPIO35, GPIO36, GPIO39 Yes ✓ Always use ADC1
ADC2 GPIO0, GPIO2, GPIO4, GPIO12–GPIO15, GPIO25–GPIO27 No ✗ Avoid when Wi-Fi active

Best practice: Use GPIO34, GPIO35, GPIO36 (VP), GPIO39 (VN) for analog readings — these are input-only pins with no boot-strapping concerns and dedicated ADC channels.

Basic analogRead() Setup

Arduino (C++)
#define ADC_PIN 34   // ADC1_CH6 — input-only, no boot concern

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);                // 12-bit: 0–4095
  analogSetAttenuation(ADC_11db);          // Full range: 0–3.3V
  // OR per-pin: analogSetPinAttenuation(ADC_PIN, ADC_11db);
}

void loop() {
  int raw     = analogRead(ADC_PIN);
  float volts = raw * (3.3f / 4095.0f);
  Serial.printf("Raw: %4d  Voltage: %.3f Vn", raw, volts);
  delay(200);
}

Attenuation Settings

Attenuation Constant Input Range Use When
0 dB ADC_0db 0 – 1.1 V Low-voltage precision sensors
2.5 dB ADC_2_5db 0 – 1.5 V Audio input signals
6 dB ADC_6db 0 – 2.2 V Half-scale 3.3V sensors
11 dB ADC_11db 0 – 3.3 V Potentiometers, most sensors

Reading a Potentiometer

Potentiometer Pin Connect To
Left terminal ESP32 GND
Right terminal ESP32 3.3V
Wiper (middle) ESP32 GPIO34
Arduino (C++) — Potentiometer to LED brightness
#define POT_PIN 34
#define LED_PIN 16

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
  analogSetAttenuation(ADC_11db);
  ledcSetup(0, 5000, 8);
  ledcAttachPin(LED_PIN, 0);
}

void loop() {
  int raw       = analogRead(POT_PIN);          // 0–4095
  int brightness = map(raw, 0, 4095, 0, 255);   // Scale to 8-bit PWM
  ledcWrite(0, brightness);
  Serial.printf("Pot: %4d  Brightness: %3dn", raw, brightness);
  delay(50);
}

Averaging to Reduce Noise

Arduino (C++) — 16-sample average
int readADCAverage(int pin, int samples = 16) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(pin);
    delayMicroseconds(100);   // Small gap between samples
  }
  return sum / samples;
}

void loop() {
  int smooth = readADCAverage(34, 16);
  float v    = smooth * (3.3f / 4095.0f);
  Serial.printf("Averaged: %4d  (%.3f V)n", smooth, v);
  delay(100);
}

Reading an LDR (Light Sensor)

An LDR (Light Dependent Resistor) changes resistance with light level. Wire it as a voltage divider:

Component Connection
LDR leg 1 ESP32 3.3V
LDR leg 2 GPIO35 AND 10kΩ resistor to GND
Arduino (C++) — LDR light level
#define LDR_PIN 35

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
  analogSetPinAttenuation(LDR_PIN, ADC_11db);
}

void loop() {
  int raw   = analogRead(LDR_PIN);
  int light = map(raw, 0, 4095, 0, 100);   // 0 = dark, 100 = bright
  Serial.printf("LDR raw: %4d  Light: %3d%%n", raw, light);

  if (light < 20) Serial.println("  ← Dark — turn on lights");
  else            Serial.println("  ← Bright enough");
  delay(500);
}

Reading an NTC Thermistor

Arduino (C++) — Temperature from NTC
#define NTC_PIN    34
#define NOMINAL_R  10000.0f   // 10kΩ at 25°C
#define NOMINAL_T  25.0f      // Reference temperature
#define BETA       3950.0f    // Beta coefficient (from datasheet)
#define SERIES_R   10000.0f   // Fixed 10kΩ resistor in divider

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
  analogSetPinAttenuation(NTC_PIN, ADC_11db);
}

void loop() {
  int raw = analogRead(NTC_PIN);

  // Calculate NTC resistance from voltage divider
  float voltage = raw * (3.3f / 4095.0f);
  float ntcR    = SERIES_R * voltage / (3.3f - voltage);

  // Steinhart-Hart simplified (Beta equation)
  float steinhart = ntcR / NOMINAL_R;
  steinhart = log(steinhart);
  steinhart /= BETA;
  steinhart += 1.0f / (NOMINAL_T + 273.15f);
  float tempC = (1.0f / steinhart) - 273.15f;

  Serial.printf("Temperature: %.1f °Cn", tempC);
  delay(1000);
}

ADC Non-Linearity Warning

ESP32 ADC has a known non-linearity issue: readings near 0 and near 4095 are less accurate. The ADC is most linear between approximately 100–3900 (out of 4095). For precision measurements, use Espressif's calibration API or an external 16-bit ADC (ADS1115 over I2C) which provides far better accuracy.

Summary

Use ADC1 pins (GPIO32–GPIO39) for reliable analog readings, especially in Wi-Fi projects. Set 11 dB attenuation for full 0–3.3V range. Average multiple readings to reduce noise. Be aware of the non-linearity in the 0–100 and 3980–4095 ranges. For sensors requiring higher accuracy (medical, precision control), use an external I2C ADC.

Frequently Asked Questions

ESP32 has two ADCs: ADC1 (GPIO32–GPIO39) and ADC2 (GPIO0, GPIO2, GPIO4, GPIO12–GPIO15, GPIO25–GPIO27). ADC1 is preferred because ADC2 is shared with Wi-Fi and returns errors when Wi-Fi is active. Use GPIO32, GPIO33, GPIO34, GPIO35, GPIO36, GPIO39 for reliable analog readings.
By default, ESP32 ADC reads 0–1.1V (with default 0dB attenuation) and maps it to 0–4095 (12-bit). For a 0–3.3V range, set 11dB attenuation: analogSetAttenuation(ADC_11db). Most sensor projects need 11dB attenuation.
voltage = analogRead(pin) * (3.3 / 4095.0) — for 12-bit ADC with 11dB attenuation. Due to ADC non-linearity, this is only approximate at the extremes. For accurate voltage readings, use the ESP32 ADC calibration API from the ESP-IDF.
ADC2 shares its hardware with the Wi-Fi RF subsystem. When Wi-Fi is active (which includes any ESP32 sketch that calls WiFi.begin()), ADC2 analogRead() returns -1 or incorrect values. Always use ADC1 pins (GPIO32–GPIO39) for analog readings in Wi-Fi projects.
Take multiple readings and average them: int avg = 0; for (int i=0;i<16;i++) avg += analogRead(pin); avg /= 16; This reduces random noise significantly. Also add a 100nF decoupling capacitor from the sensor output to GND near the GPIO pin.
Connect the potentiometer outer terminals to 3.3V and GND, and the wiper (middle pin) to an ADC1 GPIO (e.g. GPIO34). Call analogSetAttenuation(ADC_11db) and analogRead(34) to get 0–4095. Map to percentage: int pct = map(analogRead(34), 0, 4095, 0, 100).
No. ESP32 GPIO pins (including ADC pins) have a 3.6V maximum input voltage. A 5V sensor output must be attenuated through a voltage divider (10kΩ + 20kΩ to give 3.33V from 5V) before connecting to an ADC pin. Without protection, 5V will permanently damage the chip.
ESP32 ADC supports 9-bit (0–511), 10-bit (0–1023), 11-bit (0–2047), and 12-bit (0–4095) resolution. 12-bit (default) gives the highest resolution. Set it with analogReadResolution(12) in setup(). Note that due to noise and non-linearity, effective resolution is closer to 10–11 bits.
Use a voltage divider with the NTC and a 10kΩ fixed resistor. Connect 3.3V → NTC → GPIO34 → 10kΩ → GND. analogRead(34) gives raw value. Convert to resistance, then apply the Steinhart-Hart equation (or simplified Beta equation) to get temperature in degrees Celsius.
Attenuation sets the full-scale input voltage of the ADC. 0dB = 0–1.1V, 2.5dB = 0–1.5V, 6dB = 0–2.2V, 11dB = 0–3.3V. Use 11dB (ADC_11db) for most sensor work. Set per-pin with analogSetPinAttenuation(pin, ADC_11db) or globally with analogSetAttenuation(ADC_11db).

Projects to Build

Put this knowledge to work — try one of these hands-on projects.