If you feed a 2.50V DC signal into a standard 10-bit microcontroller ADC referenced to 3.3V, the converted digital output is exactly 775. The underlying formula for how an analog to digital converter (ADC) works is Digital Code = (V_in ÷ V_ref) × (2^n - 1). Substituting our exact values: (2.50 ÷ 3.3) × 1023 = 775. This quantization process is the fundamental bridge between physical analog signals and your microcontroller's digital logic. However, that output code is entirely dependent on your reference voltage and bit resolution; change either, and the resulting number shifts dramatically.

Below is a quick-reference table showing how the digital output shifts across a ±20% voltage range (2.0V to 3.0V) around our 2.5V baseline, comparing a standard 10-bit Arduino Uno ADC against a 12-bit ESP32 ADC, both assuming a perfect 3.3V reference.

Table 1: ADC Digital Code Output for ±20% Voltage Range (3.3V V_ref)
Analog Input (V_in) 10-Bit Code (Arduino) 12-Bit Code (ESP32) Variance from Baseline
2.00V (-20%)6202481-155 / -620
2.25V (-10%)6972791-78 / -310
2.50V (Baseline)77531010
2.75V (+10%)8523411+77 / +310
3.00V (+20%)9303721+155 / +620

The Core Assumptions That Fix Your ADC Conversion

The mathematical conversion above assumes three fixed variables: the input voltage (V_in), the bit resolution (n), and most critically, the reference voltage (V_ref). The reference voltage is the ceiling against which the ADC compares the input. In a Successive Approximation Register (SAR) ADC—the type found in most hobbyist microcontrollers—the chip uses a digital-to-analog converter (DAC) internally to guess the input voltage, comparing it against V_ref until it finds the closest match.

If you assume your Arduino Uno's V_ref is exactly 5.0V, but the USB rail is actually sagging to 4.7V under load, your conversion formula breaks. A true 2.50V input read against a 4.7V reference yields a code of 542, not 512. When precision matters, you must either measure the actual VCC rail with a multimeter and hardcode that value into your sketch, or use a dedicated external voltage reference IC.

According to All About Circuits' guide on ADC basics, the resolution dictates your Least Significant Bit (LSB) step size. Here is how common embedded platforms compare in real-world step sizes:

Table 2: Microcontroller ADC Specifications and Step Sizes
MCU / IC Resolution (n) Max V_ref LSB Step Size (at Max V_ref) Architecture
ATmega328P (Arduino Uno) 10-bit 5.0V 4.88 mV SAR
ESP32-WROOM-32 12-bit 3.3V 0.80 mV SAR
STM32F407 12-bit 3.3V 0.80 mV SAR
TI ADS1115 (External) 16-bit 6.144V (FSR) 0.1875 mV Sigma-Delta

How the Conversion Shifts: 120V AC vs 230V AC vs 3-Phase

Microcontroller ADC pins will instantly destroy themselves if exposed to mains voltage. To measure AC mains, you must step the voltage down using a voltage transformer (like the ZMPT101B module) or a high-impedance resistor divider, then bias the AC signal into the ADC's readable DC window (e.g., 0V to 3.3V).

Here is how the conversion math shifts depending on the mains standard:

  • 120V AC (North America): A 120V RMS sine wave has a peak voltage of roughly 170V. If your ZMPT101B module scales 170V peak down to 1.65V peak, and you bias it at 1.65V, the ADC sees a wave swinging from 0V to 3.3V. Your code must sample rapidly, find the peak-to-peak ADC codes, convert them back to voltage, and multiply by your transformer's specific scaling ratio.
  • 230V AC (EU/UK/AU): A 230V RMS wave peaks at ~325V. If you use the exact same ZMPT101B module without adjusting the onboard potentiometer, the 325V peak will exceed the ADC's 3.3V limit, causing clipping. You must increase the voltage divider ratio so the 325V peak maps to 3.3V, which consequently reduces your resolution (increases the mV per ADC step) for the lower-voltage portions of the wave.
  • 3-Phase Systems: Measuring 3-phase power requires three synchronized ADC channels. Because the internal SAR ADCs on chips like the ESP32 use a multiplexer to switch between pins, reading Phase A, then Phase B, then Phase C introduces a microsecond delay between samples. For accurate power factor (PF) and phase-angle calculations, this multiplexing delay causes conversion errors. You must use three separate external ADCs (like three ADS1115 modules) or an MCU with simultaneous sampling capabilities.

When the ADC Conversion Becomes Meaningless

An ADC will always output a number, but that number is mathematically meaningless under three specific conditions:

  1. Clipping (V_in > V_ref): If your reference is 3.3V and you feed in 4.0V, a 12-bit ADC will simply output 4095. It cannot distinguish between 3.3V, 4.0V, or 5.0V. The conversion is saturated.
  2. Aliasing (Violating Nyquist): If you are sampling a 60Hz AC waveform but your analogRead() loop only executes 50 times a second, you will capture a "beat frequency" that looks like a slow-moving DC drift. To accurately reconstruct a 60Hz signal, your ADC must sample at a minimum of 120Hz (though practically, you want 1kHz+ for RMS calculations).
  3. Noise Floor Exceeds LSB: If you are trying to measure a 1mV voltage drop across a shunt resistor using an Arduino Uno (5V ref, 10-bit), your LSB step size is 4.88mV. A 1mV change will not trigger a single step in the digital code. The conversion is blind to your signal. You must either amplify the signal with an op-amp or switch to a 16-bit external ADC like the ADS1115.

Frequently Asked Questions

Why does my ESP32 ADC read non-linear values above 2.5V?

The internal 12-bit ADC on the original ESP32-WROOM-32 is notorious for non-linearity near the 3.3V rail. Above ~2.5V, the internal amplifier saturates, and the step size compresses. If you need accurate readings up to 3.3V, use the analogReadMilliVolts() function introduced in ESP-IDF v4.4+, which applies factory-calibrated lookup tables from the eFuse to correct the curve, or bypass the internal ADC entirely and use an I2C ADS1115.

How do I convert the ADC reading back to voltage in code?

Reverse the initial formula. For a 10-bit Arduino reading on a 5.0V reference:
float voltage = (adc_reading * 5.0) / 1023.0;
Always use floating-point math for the final calculation to avoid integer truncation errors.