Difficulty Rating: Intermediate (Requires understanding of attenuation and module pinouts)
Time to Build: 20 minutes
Target Board: ESP32-S3-DevKitC-1 (N8R8 Variant)
Core Version: ESP32 Arduino Core v3.x (2025/2026)
If you are migrating from the original ESP32 to the ESP32-S3, the analog-to-digital converter (ADC) landscape has changed. The ESP32-S3 features two 12-bit SAR ADCs with a total of 20 channels mapped to GPIO1 through GPIO20. However, the raw silicon pinout is a trap: on the most popular WROOM modules, internal SPI flash and PSRAM routing silently consume half of your ADC2 pins. Furthermore, the S3 ADC is notoriously non-linear at the voltage rails, meaning raw analogRead() values will yield inaccurate millivolt calculations without software calibration.
This guide cuts through the datasheet noise to tell you exactly which ESP32-S3 ADC pins are actually usable on your dev board, provides a decision matrix for pin selection, and delivers robust, calibrated code.
The ESP32-S3 ADC Pin Decision Matrix
Do not pick an ADC pin at random. Use this decision tree to lock in your hardware design before you wire the breadboard.
| Your Requirement | Decision Path | Concrete Pick |
|---|---|---|
| Need to read sensors while Wi-Fi is actively transmitting? | Original ESP32 blocked ADC2 during Wi-Fi. The S3 does not strictly block it, but ADC2 shares routing with RF calibration on some board layouts. Play it safe. | Use ADC1 (GPIO1-GPIO10) |
| Need high-accuracy analog readings (±1% tolerance)? | The internal S3 SAR ADC has up to ±5% non-linearity, even with curve-fitting calibration. | Use an external I2C ADC (ADS1115) |
| Need to read a standard 0-3.3V sensor (e.g., potentiometer, NTC thermistor)? | Internal ADC is fine, but you must use 11dB/12dB attenuation and software calibration. | GPIO4 (ADC1_CH3) |
| Need high-speed waveform capture (>10kHz)? | Standard analogRead() is too slow and blocks the CPU. The S3 supports I2S DMA for ADC. | Use I2S Continuous Mode on ADC1 |
Hardware Parts List & Usable Pin Mapping
The biggest point of failure for ESP32-S3 ADC projects is buying the wrong module variant and trying to use a pin that is internally hardwired to octal SPI PSRAM. Below is the parts list and the reality of the pinout.
Required Hardware
- MCU: ESP32-S3-DevKitC-1 (Specifically the N8R8 variant: 8MB Flash, 8MB PSRAM). Cost: ~$7.50
- Sensor: 10kΩ Potentiometer or any 0-3.3V analog output sensor.
- Wiring: 22 AWG solid core jumper wires.
- Measurement: Basic digital multimeter to verify VCC and GND references.
ESP32-S3 ADC Pin Mapping (N8R8 Reality Check)
The ESP32-S3 silicon has 20 ADC channels. But on the WROOM-1-N8R8 module, GPIO11 through GPIO17 are internally bonded to the SPI flash and PSRAM chips. If you try to read them, you will get garbage data or a flatline.
| GPIO | ADC Channel | ADC Unit | Usable on N8R8? | Notes / Gotchas |
|---|---|---|---|---|
| GPIO1 | CH0 | ADC1 | Yes | Safe for general use. |
| GPIO2 | CH1 | ADC1 | Yes | Safe for general use. |
| GPIO3 | CH2 | ADC1 | Yes | Safe for general use. |
| GPIO4 | CH3 | ADC1 | YES (Recommended) | Best default pin. No strapping conflicts. |
| GPIO5 | CH4 | ADC1 | Yes | Safe for general use. |
| GPIO6 | CH5 | ADC1 | Yes | Safe for general use. |
| GPIO7 | CH6 | ADC1 | Yes | Safe for general use. |
| GPIO8 | CH7 | ADC1 | Yes | Safe for general use. |
| GPIO9 | CH8 | ADC1 | Yes | Safe for general use. |
| GPIO10 | CH9 | ADC1 | Yes | Safe for general use. |
| GPIO11 | CH0 | ADC2 | NO | Internally wired to SPI Flash/PSRAM. |
| GPIO12 | CH1 | ADC2 | NO | Internally wired to SPI Flash/PSRAM. |
| GPIO13 | CH2 | ADC2 | NO | Internally wired to SPI Flash/PSRAM. |
| GPIO14 | CH3 | ADC2 | NO | Internally wired to SPI Flash/PSRAM. |
| GPIO15 | CH4 | ADC2 | NO | Internally wired to SPI Flash/PSRAM. |
| GPIO16 | CH5 | ADC2 | NO | Internally wired to SPI Flash/PSRAM. |
| GPIO17 | CH6 | ADC2 | NO | Internally wired to SPI Flash/PSRAM. |
| GPIO18 | CH7 | ADC2 | Yes | Usable, but check board strapping. |
| GPIO19 | CH8 | ADC2 | Yes | Usable, often used for USB D-. |
| GPIO20 | CH9 | ADC2 | Yes | Usable, often used for USB D+. |
Compilable Code: Calibrated ADC with Error Handling
The ESP32 Arduino Core v3.x introduced analogReadMilliVolts(), which leverages the underlying ESP-IDF esp_adc calibration libraries (curve fitting or line fitting depending on the chip batch). This abstracts away the non-linearity of the S3's SAR ADC.
The code below targets the ESP32-S3-DevKitC-1, reads GPIO4, applies software oversampling to filter high-frequency noise, and includes explicit error handling for invalid pin configurations.
#include <Arduino.h>
// --- PIN DEFINITIONS ---
// GPIO4 is ADC1_CH3. Safe on N8R8 modules.
const int ADC_PIN = 4;
// --- CONFIGURATION ---
const int OVERSAMPLE_COUNT = 16; // Number of samples to average for noise reduction
const int DELAY_MS = 500;
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to connect
Serial.println("ESP32-S3 Calibrated ADC Reader - Core v3.x");
// 1. Verify the pin is a valid ADC pin on this chip
if (digitalPinToAnalogChannel(ADC_PIN) == -1) {
Serial.printf("FATAL ERROR: GPIO %d is not a valid ADC pin on this ESP32 variant.\n", ADC_PIN);
Serial.println("Check if your module uses this pin for internal PSRAM/Flash.");
while (1) { delay(1000); } // Halt execution
}
// 2. Set Attenuation
// ADC_11db (or ADC_12db in newer cores) allows reading up to ~3.1V - 3.3V.
// Without this, the ADC clips at ~1.0V (0dB) or ~1.3V (2.5dB).
analogSetPinAttenuation(ADC_PIN, ADC_11db);
// 3. Set Resolution (S3 hardware is 12-bit natively)
analogReadResolution(12);
Serial.printf("ADC initialized on GPIO %d with 11dB attenuation.\n", ADC_PIN);
}
void loop() {
uint32_t raw_sum = 0;
uint32_t mv_sum = 0;
// Software oversampling to smooth out SAR ADC jitter
for (int i = 0; i < OVERSAMPLE_COUNT; i++) {
raw_sum += analogRead(ADC_PIN);
mv_sum += analogReadMilliVolts(ADC_PIN);
delayMicroseconds(200); // Small settling delay between reads
}
uint32_t avg_raw = raw_sum / OVERSAMPLE_COUNT;
uint32_t avg_mv = mv_sum / OVERSAMPLE_COUNT;
// Error Handling: Check for rail-clipping or disconnected pins
if (avg_mv == 0) {
Serial.println("WARNING: Reading 0mV. Check if sensor is tied to GND or pin is misconfigured.");
} else if (avg_mv >= 3100 && avg_raw >= 4050) {
Serial.println("WARNING: Reading near VCC rail. Signal may be clipping. Verify sensor voltage < 3.3V.");
}
Serial.printf("Raw Avg: %u | Calibrated Voltage: %u mV\n", avg_raw, avg_mv);
delay(DELAY_MS);
}
Debugging: Exact Errors and the First 3 Checks
When your S3 ADC project fails, it rarely fails silently. It either throws an IDF-level panic in the serial monitor, or it returns a stubborn, flatline value. Here is how to debug the most common failure modes.
Exact Error String: E (142) esp_adc_cal: adc_cali_create_scheme_curve_fitting: invalid argument
If you drop down to the ESP-IDF C-API or use an older library wrapper, you will see this error when the calibration handle fails to initialize.
- Cause 1 (Most Likely): You passed
ADC_ATTEN_DB_0orADC_ATTEN_DB_2_5to the curve-fitting function. On the ESP32-S3, curve fitting calibration is only supported at 11dB (or 12dB) attenuation. - Cause 2: You are targeting an ADC2 channel (GPIO11-17) that is physically bonded to the SPI flash on your N8R8 module, causing the driver to reject the channel mapping.
- Fix: Switch to
ADC_11dbattenuation and move your wire to an ADC1 pin (GPIO1-10).
Symptom: Flatline reading of 4095 or 0 on GPIO11-GPIO17
You wired a potentiometer to GPIO12, the code compiles, but the serial monitor only prints 4095 regardless of the knob position.
- Cause: You fell into the N8R8 PSRAM trap. GPIO12 is internally routed to the Octal SPI PSRAM chip. The external pin header is either dead or pulled high/low by the memory bus.
- Fix: Consult the pin mapping table above. Move the signal wire to GPIO4.
- Module Variant: Flip the board over. Does the metal shield say
N8R8orN8? If N8R8, ADC2 channels 0-6 are permanently dead. Use ADC1. - Attenuation Setting: Did you explicitly call
analogSetPinAttenuation(pin, ADC_11db)? If omitted, the default is often 0dB, which clips any signal above ~800mV. - Core Version Mismatch: Are you using
ADC_11dborADC_12db? ESP32 Arduino Core v2.x usedADC_11db. Core v3.x renamed it toADC_12dbto match the hardware reality. If your code fails to compile on the enum, update your core or change the constant.
Extending to Continuous Mode or Simplifying the Build
Depending on your project's end goal, you either need to strip this code down to its bare minimum, or scale it up for high-speed signal processing.
How to Simplify (The 'Button Threshold' Approach)
If you are just reading a voltage divider to detect if a battery is low, or reading an LDR to turn on a light, you do not need millivolt precision or software oversampling. The S3's non-linearity won't matter if you are just checking if a value crosses a threshold.
Action: Delete the oversampling loop. Delete analogReadMilliVolts(). Just use int val = analogRead(4); if (val < 1500) { // do something }. This saves CPU cycles and reduces code footprint.
How to Extend (I2S DMA Continuous Mode)
If you are building an audio sampler, an oscilloscope, or reading a high-frequency vibration sensor, analogRead() will bottleneck your CPU. The ESP32-S3 has a unique hardware feature: it can route the SAR ADC through the I2S peripheral, allowing DMA (Direct Memory Access) to stream ADC data into a buffer without CPU intervention.
Action: You must use the ESP-IDF esp_adc/adc_continuous.h driver. You cannot do this via standard Arduino functions. Configure the I2S DMA buffer, set the ADC1 channels, and start the continuous read. This allows sample rates up to 61kHz per channel on the S3. For a complete implementation, refer to the Espressif ADC Continuous Driver Documentation.
For authoritative details on the S3's internal routing and electrical characteristics, always cross-reference the ESP32-S3 Technical Reference Manual and the Arduino ESP32 ADC API docs.
Final Verdict: Stop guessing which pins work. Buy the ESP32-S3-DevKitC-1 N8R8, wire your analog sensors exclusively to ADC1 (GPIO1 through GPIO10), set your attenuation to 11dB/12dB, and rely on analogReadMilliVolts() to handle the silicon's non-linearity. If your application demands true 16-bit precision, abandon the internal ADC entirely and wire up an ADS1115 over I2C.






