The fundamental ADC calculation to convert a raw digital microcontroller reading back into a real-world analog voltage is V_in = ADC_raw × (V_ref / 2^n). While many hobbyist tutorials use 2^n - 1 (e.g., 1023 for a 10-bit ADC) as the denominator, precision datasheets from manufacturers like Microchip and Espressif define the Least Significant Bit (LSB) step size as V_ref / 2^n. Using the exact step size prevents a systematic scaling error that compounds in high-precision sensor applications.
The Core ADC Calculation Formula & Symbol Definitions
Before writing firmware, you must map the physical electrical limits of your microcontroller to the mathematical variables in the conversion equation. The standard Successive Approximation Register (SAR) ADC formula is:
Vin = ADCraw × (Vref / 2n)
| Symbol | Unit | Description & Bench Notes |
|---|---|---|
| Vin | Volts (V) | The actual analog voltage present at the microcontroller's GPIO pin. This is post-voltage-divider if you are measuring higher voltages. |
| ADCraw | Counts (unitless) | The integer value returned by the analogRead() function. Ranges from 0 to 2n - 1. |
| Vref | Volts (V) | The ADC reference voltage. Often tied to VCC (5V or 3.3V), but can be an internal bandgap reference (e.g., 1.1V on ATmega328P). |
| n | Bits (unitless) | The resolution of the ADC hardware. Common values are 10, 12, or 16 bits. |
| LSB | Volts (V) | Calculated as V_ref / 2^n. Represents the voltage change required to increment the raw count by 1. |
Microcontroller ADC Specifications Reference
You cannot calculate accurate voltages without knowing the exact hardware limits of your specific silicon. Below is a data-dense reference table for the most common microcontrollers used in embedded projects. Note that theoretical resolution often differs from usable resolution due to internal noise and non-linearity.
| Microcontroller | Resolution (n) | Default Vref | Theoretical LSB | Max Safe Input | Bench Reality / Gotchas |
|---|---|---|---|---|---|
| ATmega328P (Arduino Uno) | 10-bit | 5.0V (VCC) | 4.88 mV | 5.0V | Readings fluctuate ±2 counts without a decoupling capacitor on AREF. |
| ESP32-WROOM-32 | 12-bit | ~3.1V (Internal) | 0.76 mV | 3.3V (with 11dB atten) | Notoriously non-linear. Use analogReadMilliVolts() in modern cores to apply eFuse calibration. |
| RP2040 (Raspberry Pi Pico) | 12-bit | 3.3V | 0.81 mV | 3.3V | Excellent linearity, but shares ground return with digital IO; keep high-current digital traces away from ADC ground. |
| STM32F103C8T6 (Blue Pill) | 12-bit | 3.3V | 0.81 mV | 3.3V | Requires explicit ADC calibration routine in HAL/LLVM before first read to clear offset errors. |
Rearranged Forms of the ADC Equation
On the bench, you rarely just solve for Vin. You often need to size a voltage divider to hit a specific raw count, or reverse-engineer an unknown Vref on an uncalibrated board. Here are the algebraic rearrangements of the core formula:
- Solving for Raw Count (Predicting firmware output):
ADC_raw = V_in / (V_ref / 2^n) - Solving for Reference Voltage (Calibrating an unknown board):
V_ref = (V_in × 2^n) / ADC_raw
Use this by feeding a known precision voltage (e.g., 2.048V from an LM4040) into the pin and reading the raw count to find the actual internal V_ref. - Solving for Required Resolution (Sizing the ADC for a project):
n = log2(V_ref / (V_in / ADC_raw))
Use this to determine if a 10-bit ADC is sufficient or if you need to upgrade to a 16-bit external I2C ADC like the ADS1115.
Worked Examples with Unit Tracking
Abstract formulas lead to firmware bugs. Here are two real-world scenarios with explicit unit tracking to demonstrate how the math flows from the physical circuit into the C++ code.
Problem 1: Arduino Uno Reading a 12V Battery via Voltage Divider
Scenario: You are monitoring a 12V lead-acid battery using an Arduino Uno (ATmega328P). Because the Uno's max input is 5V, you use a voltage divider with R1 = 10,000Ω and R2 = 3,300Ω. The battery is currently at 12.40V. What raw ADC count will the firmware return?
- Calculate the voltage at the analog pin (Vin):
V_in = V_batt × [R2 / (R1 + R2)]
V_in = 12.40 [V] × [3300 [Ω] / (10000 [Ω] + 3300 [Ω])]
V_in = 12.40 [V] × 0.2481 = 3.076 [V] - Calculate the LSB step size for the ATmega328P:
LSB = V_ref / 2^n = 5.0 [V] / 2^10 = 5.0 [V] / 1024 = 0.0048828 [V/count] - Calculate the expected raw ADC count:
ADC_raw = V_in / LSB = 3.076 [V] / 0.0048828 [V/count] = 629.96 [counts]
Result: The analogRead() function will return 630. In your code, you reverse this by multiplying 630 by the LSB (0.0048828) to get 3.076V, then divide by the divider ratio (0.2481) to display 12.40V on your LCD.
Problem 2: ESP32 Reading a 30A Current Sensor (ACS712)
Scenario: An ESP32-WROOM-32 is reading an ACS712-30A bidirectional current sensor. The ACS712 outputs a 1.65V offset at 0A, with a sensitivity of 66 mV/A. The ESP32's internal Vref is measured at 3.10V (typical for the ESP32 due to internal diode drops). The analogRead() returns a raw count of 2650. What is the actual current?
- Convert raw count to pin voltage (Vin):
V_in = ADC_raw × (V_ref / 2^n)
V_in = 2650 [counts] × (3.10 [V] / 4096)
V_in = 2650 × 0.0007568 [V/count] = 2.005 [V] - Subtract the sensor's zero-current offset:
V_sense = V_in - V_offset = 2.005 [V] - 1.650 [V] = 0.355 [V] - Convert sensed voltage to Current (I):
I = V_sense / Sensitivity = 0.355 [V] / 0.066 [V/A] = 5.378 [A]
Result: The load is drawing 5.38 Amps. Note that if you had incorrectly assumed the ESP32 Vref was exactly 3.3V, your calculated Vin would have been 2.136V, resulting in a false current reading of 7.36A—a massive 37% error caused purely by ignoring the silicon's actual reference voltage.
Assumptions, Edge Cases, and Unit Mistakes
The formula V_in = ADC_raw × (V_ref / 2^n) is mathematically pristine, but physical hardware is not. Understanding when this formula breaks down is what separates a working prototype from a reliable product.
When the Formula Applies (and When it Doesn't)
This formula assumes a linear Successive Approximation Register (SAR) ADC. It applies perfectly to the ATmega328P, RP2040, and external I2C chips like the ADS1115. It does not apply directly to Sigma-Delta ADCs (often used in high-resolution audio or load cell HX711 modules) without accounting for the decimation filter and programmable gain amplifier (PGA) settings. Furthermore, as noted in Table 2, the ESP32's SAR ADC suffers from severe non-linearity near the 0V and 3.3V rails. For the ESP32, the raw formula is only accurate in the middle 60% of the range; outside of that, you must use lookup tables or the manufacturer's calibrated analogReadMilliVolts() API.
Unit Mistakes That Break the Math
- The 1023 vs 1024 Trap: A 10-bit ADC has 1024 discrete steps (from 0 to 1023). The voltage step size (LSB) is Vref / 1024. If you divide by 1023 in your code, you introduce a 0.1% gain error. At 5V, this means your 5.00V reading will map to 5.004V. While negligible for a battery monitor, this fails calibration on precision lab equipment.
- Mixing Millivolts and Volts: If your Vref is defined as 5000 (mV) in your code to avoid floating-point math, your Vin result will also be in mV. If you then feed that result into a thermistor Steinhart-Hart equation that expects Volts, your temperature calculation will read absolute zero or throw a NaN (Not a Number) error.
- Ignoring Impedance Matching: The formula assumes the analog pin sees a stiff, low-impedance voltage source. The ATmega328P datasheet specifies a maximum source impedance of 10kΩ. If your voltage divider uses 100kΩ and 100kΩ resistors to save battery life, the internal sample-and-hold capacitor won't have time to charge during the ADC acquisition window (~1.5 clock cycles). Your raw counts will read artificially low, and no mathematical formula can fix a hardware charging deficit.
What a Realistic Answer Magnitude Looks Like
Develop a mental sanity check for your LSB. On a 5V system with a 10-bit ADC, one count is roughly 5mV. On a 3.3V system with a 12-bit ADC, one count is roughly 0.8mV. If your firmware calculates an LSB of 0.5V or 0.0001V, you have dropped a decimal or messed up a bit-shift operator. Always print your calculated LSB to the serial monitor during initial bring-up to verify the magnitude before trusting the sensor data.






