An Analog-to-Digital Converter (ADC) is a hardware peripheral that translates continuous real-world voltage signals into discrete binary numbers a microcontroller can process. In a physical circuit, the ADC changes an infinite range of analog possibilities—like a smoothly varying 1.65V from a potentiometer wiper—into a finite, stepped integer (like 2048) that your C++ or MicroPython code can evaluate in an if-statement. Without an ADC, a microcontroller is blind to the physical world, restricted to seeing only binary HIGH (3.3V/5V) or LOW (0V) digital states.
Think of a water tank with a measuring stick that only has painted marks every 1 inch. If the water level rests at 10.4 inches, the stick can only report '10 inches'. The ADC is that stepped measuring stick for voltage, quantizing a smooth electrical signal into the nearest available digital rung.
The Core Definition of ADC in Embedded Systems
At the silicon level, an ADC inside a microcontroller (like the ATmega328P on an Arduino Uno or the Xtensa LX6 on an ESP32) uses a network of capacitors and comparators to sample an input voltage. It compares that voltage against an internal reference voltage (Vref) and outputs a binary code representing the ratio between the two.
What people commonly confuse the ADC with is its inverse, the DAC (Digital-to-Analog Converter), which takes a binary number and outputs a smooth voltage. Another massive point of confusion is conflating resolution with accuracy. A microcontroller might advertise a 12-bit ADC (resolution), meaning it can chop the voltage range into 4,096 discrete steps. However, due to internal noise, thermal drift, and non-linear silicon behavior, the Effective Number of Bits (ENOB) might only be 9 or 10 bits. Just because your code returns a 12-bit integer doesn't mean the last two or three bits actually reflect real-world voltage changes; they are often just digital noise jittering between 0 and 1.
Crunching the Numbers: Resolution, Vref, and Step Size
To use an ADC effectively, you must calculate the Least Significant Bit (LSB), or 'step size'. This tells you exactly how many millivolts each increment in your code represents. The formula is straightforward:
Step Size = Vref / (2^Resolution)
Let's look at a worked numeric example comparing two of the most common hobbyist microcontrollers:
| Microcontroller | ADC Resolution | Default Vref | Total Steps | Step Size (LSB) |
|---|---|---|---|---|
| Arduino Uno (ATmega328P) | 10-bit | 5.0V | 1,024 | 4.88 mV |
| ESP32 (ESP32-WROOM-32) | 12-bit | 3.3V | 4,096 | 0.80 mV |
| Raspberry Pi Pico (RP2040) | 12-bit | 3.3V | 4,096 | 0.80 mV |
If you are reading a sensor on an Arduino Uno and the analogRead() function returns 512, the math is: 512 * 4.88mV = 2.498V. If you switch that exact same sensor circuit to an ESP32, the voltage is still 2.498V, but the ESP32's code will return 2498mV / 0.80mV = 3122. Understanding this math is the difference between writing code that works on the bench and code that fails when you swap microcontroller families.
Where You Meet ADCs in Practice
You will encounter ADCs anytime a sensor outputs a variable voltage rather than a digital protocol (like I2C or SPI). Common bench encounters include:
- Resistive Sensors: NTC thermistors, photoresistors (LDRs), and flex sensors. These change resistance, so you must wire them into a voltage divider to convert the resistance change into a voltage change the ADC can read.
- Potentiometers: Used for manual dials, joysticks, and volume controls. The wiper pin outputs a direct voltage ratio.
- Current Sensing: Shunt resistors paired with op-amps, or Hall-effect current sensors (like the ACS712), which output a voltage proportional to AC or DC current flow.
- Battery Monitoring: Reading the voltage of a LiFePO4 or 18650 cell through a high-impedance voltage divider to calculate State of Charge (SoC).
For deeper architectural details on how these internal peripherals sample signals, the Arduino analogRead() documentation provides an excellent baseline for understanding sampling windows and prescalers.
Bench Walkthrough: Reading an NTC Thermistor on an ESP32
Theory is clean; the workbench is messy. Here is a real-world scenario that traps almost every embedded developer transitioning from 8-bit Arduino to 32-bit ESP32.
The Setup:
You want to log room temperature using a 100kΩ NTC thermistor. To save battery current in a solar-powered IoT node, you pair it with a 100kΩ precision pull-down resistor, creating a voltage divider powered by the ESP32's 3.3V rail. The midpoint is wired to GPIO 34 (an input-only ADC pin). At exactly 25°C, the NTC's resistance is 100kΩ.
The Numbers:
Because the resistors are equal (100kΩ and 100kΩ), the voltage at the midpoint should be exactly half of 3.3V, which is 1.65V.
Using our ESP32 step size (0.805mV per step), the expected ADC reading is:
1.65V / 0.000805V = 2049
The Outcome:
You flash the firmware, open the serial monitor, and the ESP32 reports an ADC value of 1710. You calculate the voltage backwards (1710 * 0.000805 = 1.37V), plug it into the Steinhart-Hart equation, and your code claims the room is 38°C. The room is actually 25°C.
What Went Wrong:
Two distinct silicon realities collided here. First, the ESP32's internal ADC is notoriously non-linear, particularly in the 1.5V to 2.5V range, meaning the step size isn't actually a perfect 0.805mV across the whole curve. Second, and more critically, is source impedance loading.
An ADC doesn't just 'look' at the voltage; it briefly connects an internal sample-and-hold capacitor to the pin to take a snapshot. The Espressif ESP32 Technical Reference Manual notes that the ADC input impedance is relatively low. Your 100kΩ/100kΩ voltage divider has a Thevenin equivalent source impedance of 50kΩ. This 50kΩ resistance forms a low-pass filter with the ADC's internal sampling capacitor. Because the resistance is too high, the capacitor cannot fully charge to 1.65V before the conversion window closes, resulting in an artificially low reading. Furthermore, Texas Instruments application notes on ADC input impedance universally recommend keeping source impedance below 10kΩ for accurate 12-bit sampling.
The Fix:
- Lower the Impedance: Swap the 100kΩ resistors for 10kΩ or 4.7kΩ resistors. This drops the source impedance below 5kΩ, allowing the internal capacitor to charge fully. (Trade-off: quiescent current draw increases from 16µA to 350µA, which may require a larger solar panel).
- Use an External ADC: If you absolutely must keep the 100kΩ resistors for ultra-low power, bypass the internal ESP32 ADC entirely. Use an I2C external ADC like the ADS1115, which features a high-impedance buffered input and true 16-bit resolution.
- Software Calibration: If you are stuck with the internal ADC and 10kΩ resistors, implement a multi-point lookup table in your code to correct the ESP32's native non-linearity curve, rather than relying on raw linear math.
Common ADC Debugging Questions
Why do my ADC readings jump around by 5-10 bits even when the sensor is untouched?
This is noise floor jitter. USB power from a laptop or cheap wall brick is notoriously 'dirty', carrying high-frequency switching noise directly into the microcontroller's Vref. To fix this, add a 100nF ceramic capacitor and a 10µF electrolytic capacitor in parallel between the ADC input pin and GND. This creates a hardware low-pass filter that smooths out high-frequency noise before the ADC samples it.
Can I read a 4-20mA industrial sensor directly with a microcontroller ADC?
No. A 4-20mA signal is a current loop, not a voltage. You must pass the current through a precision shunt resistor to convert it to a voltage. For a 4-20mA loop, a 150Ω shunt resistor will yield 0.6V at 4mA and 3.0V at 20mA, which fits perfectly inside the 3.3V ADC range of an ESP32 or Raspberry Pi Pico without clipping.
What is the difference between ADC resolution and sampling rate?
Resolution (e.g., 12-bit) dictates how finely you can slice the voltage range (accuracy of the Y-axis). Sampling rate (e.g., 100 kSPS - kilo-samples per second) dictates how many times per second the ADC takes a reading (accuracy of the X-axis). If you are reading a slow-moving temperature sensor, you want high resolution and low sampling rate. If you are reading an audio waveform or AC current, you need a high sampling rate to satisfy the Nyquist theorem, even if you sacrifice a bit of resolution.






