The Core ADC Formula and Symbol Definitions
The fundamental Analog-to-Digital Converter (ADC) formula translates a continuous physical voltage into a discrete digital integer that a microcontroller can process. Whether you are reading a thermistor on an ATmega328P or sampling a battery voltage on an ESP32-WROOM-32, the underlying math remains identical. The direct answer for converting an analog input to a digital reading is:
ADC_Value = (V_in / V_ref) × (2n - 1)
To use this formula correctly on the bench or in your firmware, you must understand exactly what each symbol represents. Below is the definitive specification table for the standard Successive Approximation Register (SAR) ADC architecture used in most hobbyist and industrial microcontrollers.
| Symbol | Unit | Definition & Hardware Context |
|---|---|---|
| ADC_Value | Dimensionless (Count) | The discrete digital output integer. Ranges from 0 to (2n - 1). |
| V_in | Volts (V) | The analog input voltage present at the microcontroller's GPIO pin. Must be ≤ V_ref. |
| V_ref | Volts (V) | The reference voltage. The maximum voltage the ADC can measure. Often tied to VCC (3.3V or 5V) or an external precision reference. |
| n | Bits | The resolution of the ADC. Common values are 8, 10, 12, or 16 bits. |
| LSB | Volts (V) | Least Significant Bit. The smallest voltage change the ADC can detect. Calculated as V_ref / (2n - 1). |
Rearranged Forms: Solving for Any Variable
In embedded systems, you rarely use the formula in just one direction. When displaying sensor data on an OLED screen, you solve for V_in. When debugging a saturated signal, you might solve for V_ref. Here are the algebraically rearranged forms for every variable in the equation:
- Solving for Analog Voltage (Most Common in Code):
V_in = ADC_Value × [ V_ref / (2n - 1) ] - Solving for Reference Voltage (Hardware Debugging):
V_ref = [ V_in × (2n - 1) ] / ADC_Value - Solving for Digital Count (Simulation/Testing):
ADC_Value = (V_in / V_ref) × (2n - 1) - Solving for Resolution / Effective Number of Bits (ENOB):
n = log2( [ (ADC_Value × V_ref) / V_in ] + 1 )
Note: In practice, 'n' is fixed by hardware, but this rearrangement is used by test engineers to calculate the Effective Number of Bits (ENOB) when noise degrades actual performance.
Worked Examples with Unit Tracking
Abstract formulas cause bugs. Let us walk through two concrete, real-world scenarios with strict unit tracking to ensure your firmware scales correctly.
Problem 1: Calculating the Digital Reading (Arduino Uno / ATmega328P)
Scenario: You have a voltage divider monitoring a 12V lead-acid battery. The divider outputs 1.85 V to analog pin A0 on an Arduino Uno. The Uno is powered via USB, so V_ref is 5.0 V. The ATmega328P has a 10-bit ADC.
Goal: Find the expected ADC_Value returned by analogRead(A0).
- Identify Knowns: V_in = 1.85 V, V_ref = 5.0 V, n = 10.
- Select Formula:
ADC_Value = (V_in / V_ref) × (2n - 1) - Substitute Values:
ADC_Value = (1.85 V / 5.0 V) × (210 - 1) - Calculate Exponent:
210 = 1024, so(1024 - 1) = 1023. - Compute Ratio:
1.85 V / 5.0 V = 0.37(Volts cancel out, leaving a dimensionless ratio). - Final Multiplication:
0.37 × 1023 = 378.51 - Truncate to Integer: The ADC hardware truncates (floors) the decimal. ADC_Value = 378.
Problem 2: Calculating the Analog Voltage (ESP32-WROOM-32)
Scenario: You are reading a soil moisture sensor on an ESP32 DevKit v1. The serial monitor prints an ADC_Value of 1450. The ESP32 is running on a 3.3V rail (V_ref = 3.3 V) and is configured for its native 12-bit resolution.
Goal: Find the actual analog voltage (V_in) at the GPIO pin.
- Identify Knowns: ADC_Value = 1450, V_ref = 3.3 V, n = 12.
- Select Formula:
V_in = ADC_Value × [ V_ref / (2n - 1) ] - Substitute Values:
V_in = 1450 × [ 3.3 V / (212 - 1) ] - Calculate Denominator:
212 = 4096, so(4096 - 1) = 4095. - Compute LSB (Voltage per step):
3.3 V / 4095 = 0.00080586 V/count(or ~0.806 mV/count). - Final Multiplication:
1450 counts × 0.00080586 V/count = 1.1685 V. - Result: V_in ≈ 1.169 V.
Assumptions, Unit Mistakes, and Realistic Magnitudes
The formula above describes an ideal ADC. When your bench measurements do not match your math, it is usually because one of the underlying assumptions has been violated or a unit error has crept into your code.
When the Formula Applies (and Its Assumptions)
This formula assumes a perfect Successive Approximation Register (SAR) ADC with zero offset error (reads exactly 0 at 0V) and zero gain error (reads exactly max-count at V_ref). It assumes the reference voltage is perfectly stable during the conversion cycle. In reality, internal microcontroller ADCs suffer from integral non-linearity (INL) and differential non-linearity (DNL). For example, the ESP32's internal ADC is notoriously non-linear below 100mV and above 3.1V, meaning the formula will yield mathematically correct but physically inaccurate voltages at the rails.
Unit Mistakes That Break the Math
- The Off-By-One Divisor Error: The most common firmware bug is dividing by
2n(e.g., 1024) instead of2n - 1(e.g., 1023). A 10-bit ADC has 1024 states, but the maximum count is 1023. Dividing by 1024 introduces a systematic scaling error that worsens as voltage increases. - Mixed Voltage Units: Passing
V_inin millivolts (e.g., 1850) while leavingV_refin volts (e.g., 5.0). The ratioV_in / V_refmust be dimensionless; both must be in the same unit before dividing. - Integer Division Truncation in C++: Writing
(1850 / 5000) * 1023in C++ results in0 * 1023 = 0because integer division truncates the decimal. Always cast to float first:(1850.0 / 5000.0) * 1023.
Realistic Answer Magnitudes
If your calculated ADC_Value falls outside these bounds, your math is wrong or your hardware is saturated:
- 8-bit ADC: 0 to 255 (e.g., ATtiny85 default)
- 10-bit ADC: 0 to 1023 (e.g., ATmega328P / Arduino Uno)
- 12-bit ADC: 0 to 4095 (e.g., ESP32, STM32, Raspberry Pi Pico)
- 16-bit ADC: 0 to 65535 (e.g., External ADS1115)
Decision Path: Picking the Right ADC Resolution and Reference
Do not guess your ADC requirements. Use this decision tree to select the correct hardware architecture for your sensor, terminating in a concrete component recommendation.
| Application Scenario | Required Resolution (n) | Reference Voltage (V_ref) | Hardware Pick |
|---|---|---|---|
| Audio / AC Waveforms (High speed, moderate precision) |
10 to 12 bits | VCC (3.3V) | Internal MCU ADC (ESP32 / STM32) |
| User Inputs (Potentiometers, joysticks) |
8 to 10 bits | VCC (5V or 3.3V) | Internal MCU ADC (ATmega328P) |
| Slow DC / Precision (Thermistors, load cells, battery monitoring) |
16 bits+ | External Precision (e.g., 2.048V) | External I2C/SPI ADC IC |
The Concrete Recommendation
If your project involves measuring slow-changing DC signals (like a thermistor or a shunt resistor for current sensing) and you require an accuracy better than ±10mV on a 3.3V rail, the internal ADCs of the ESP32 and ATmega328P will fail you due to thermal noise and non-linearity.
Default Pick: Use the Texas Instruments ADS1115 (widely available as the Adafruit ADS1115 Breakout, Product ID: 1085). It provides true 16-bit resolution, an internal precision reference, a programmable gain amplifier (PGA), and communicates via I2C. It costs roughly $10 and eliminates the need for complex analog filtering and software oversampling.
Hardware Implementation: ESP32 vs ATmega328P ADC Quirks
Understanding the fundamentals of data converters is only half the battle; you must also navigate the specific silicon quirks of your chosen microcontroller.
ATmega328P (Arduino Uno) Quirks
The ATmega328P defaults to using the 5V USB rail as its V_ref. Because USB voltage can sag from 5.0V down to 4.7V under load, your V_ref is a moving target, destroying measurement accuracy. The Fix: Use the EXTERNAL reference mode in code (analogReference(EXTERNAL)) and wire a precision shunt reference like the LM4040 (3.3V or 4.096V) to the AREF pin. This locks your denominator to a known, stable value.
ESP32-WROOM-32 Quirks
The ESP32 features a 12-bit SAR ADC, but Espressif's own Technical Reference Manual notes significant non-linearity. Furthermore, the analogRead() function in the Arduino core defaults to returning a 12-bit value, but the underlying ESP-IDF allows you to configure attenuation. If you do not set the attenuation correctly, the ADC will saturate at ~1.1V instead of 3.3V. The Fix: Always explicitly call analogSetAttenuation(ADC_11db) in your setup routine to ensure the full 0V–3.1V range is mapped to the 0–4095 count range.
Bench Tip: When debugging ADC math, never trust the serial monitor alone. Always measure the actual voltage at the GPIO pin with a calibrated digital multimeter (DMM) while the circuit is powered. If your DMM reads 1.65V but your formula outputs 1.40V, your V_ref is likely sagging, or your sensor's output impedance is too high (>10kΩ) for the microcontroller's internal sample-and-hold capacitor to charge fully during the conversion window.






