If you read an ADC integer of 512 on a standard 5V Arduino Uno (10-bit resolution), the converted analog voltage is exactly 2.50V. The universal formula to convert any raw ADC reading to voltage is Voltage = (ADC_Value × V_ref) / (2^Resolution - 1). Substituting the Uno's baseline values, the math looks like this: (512 × 5.0) / 1023 = 2.502V. This conversion assumes a stable 5.0V reference and a 10-bit analog-to-digital converter (ADC); change the board or the reference pin, and the multiplier shifts entirely.
The Core Conversion Table (5V / 10-Bit Baseline)
When debugging sensor circuits on an ATmega328P-based board (Uno R3, Nano, Pro Mini), you rarely need to calculate every single step. The ADC step size (least significant bit, or LSB) is 4.88 mV per integer. Below is a quick-reference table covering a ±20% range around the midpoint (512), which is the most common operating window for biased AC signals and midpoint-referenced sensors.
| ADC Integer (10-bit) | Calculated Voltage (5V Ref) | Typical Sensor State |
|---|---|---|
| 410 | 2.00V | Lower threshold / Discharging |
| 461 | 2.25V | Below midpoint bias |
| 512 | 2.50V | Exact midpoint (Zero-crossing bias) |
| 563 | 2.75V | Above midpoint bias |
| 614 | 3.00V | Upper threshold / Charging |
How Microcontroller Architecture Shifts the Math
The single-voltage answer above is only universal if you never upgrade your hardware. The two assumptions that fix your conversion math are Reference Voltage (V_ref) and Bit Resolution. When you move from a classic Uno to an ESP32 or a modern Uno R4, the denominator and multiplier change drastically.
float before multiplying in C++. Writing (adc * 5) / 1023 using integers will truncate the decimal to zero before the division occurs. Always write (adc * 5.0) / 1023.0.
| Microcontroller Board | ADC Resolution | Max Integer | Default V_ref | Step Size (LSB) |
|---|---|---|---|---|
| Arduino Uno R3 (ATmega328P) | 10-bit | 1023 | 5.0V | 4.88 mV |
| Arduino Uno R4 Minima (RA4M1) | 14-bit | 16383 | 5.0V | 0.30 mV |
| ESP32-WROOM-32 | 12-bit | 4095 | 3.3V (Nominal) | ~0.80 mV (Non-linear) |
| Raspberry Pi Pico (RP2040) | 12-bit | 4095 | 3.3V | 0.80 mV |
According to the official Arduino analogRead() documentation, the classic 10-bit ADC takes about 100 microseconds to read, yielding a maximum sampling rate of roughly 9,600 samples per second. If you are using the Uno R4, you must explicitly configure the resolution in setup() using analogReadResolution(14), otherwise it defaults to 10-bit for backward compatibility.
When Analog-to-Digital Conversion is Meaningless
Math only works if the physics feeding the ADC are sound. Your converted voltage readout is entirely meaningless under three specific conditions:
- Floating Inputs: If an analog pin is not connected to a driven circuit or a pull-down resistor, the sample-and-hold capacitor inside the MCU will pick up ambient electromagnetic noise. You will see random integers between 0 and 1023. A 10kΩ pull-down resistor to GND fixes this.
- ESP32 ADC Saturation and Non-Linearity: The ESP32's internal ADC is notoriously non-linear at the extremes. According to Espressif's ADC calibration guidelines, readings below 0.1V (approx. 100) and above 2.5V (approx. 3100) are highly inaccurate. If your ESP32 reads
4095, the actual voltage could be 2.6V or 3.3V; the conversion math breaks down here. - Missing Common Ground: Voltage is a potential difference. If your sensor's ground is not physically bonded to the Arduino's GND pin, the ADC is measuring the potential difference against a floating reference, resulting in garbage data or erratic spikes.
FAQ: ADC Edge Cases and Code Implementation
How do I handle the ESP32 ADC non-linearity in code?
Stop using raw analogRead() for precision voltage math on the ESP32. Instead, use the Arduino-ESP32 core's built-in millivolt conversion function, which applies factory-stored eFuse calibration data automatically:
// Returns calibrated voltage in millivolts (e.g., 2500 for 2.5V)
int mV = analogReadMilliVolts(A0);
float trueVoltage = mV / 1000.0;
Why does my Arduino Uno read 1023 when I apply exactly 5V?
Because 5V is the ceiling of the reference. The ADC maps 0V to 0, and V_ref (5.0V) to 1023. However, if your USB bus is sagging to 4.7V, your V_ref is actually 4.7V. An integer reading of 1023 now means 4.7V, not 5.0V. For precision work, bypass the USB V_ref and use the EXTERNAL reference pin with a dedicated 4.096V precision voltage reference IC.
Does the map() function work for voltage conversion?
It works, but it uses integer math, which destroys precision. map(val, 0, 1023, 0, 500) will give you centivolts (e.g., 250 for 2.50V), but it truncates fractional steps. For scientific logging or PID control loops, always use floating-point multiplication (val * 0.004887) rather than the map() function.






