To understand how an analog to digital converter works, treat it as a strict unit conversion from continuous Volts to discrete Digital Counts. For a direct answer: converting a 1.65V input signal using a 12-bit ADC with a 3.3V reference yields exactly 2048 counts. The governing formula is Code = (Vin / Vref) × 2^n. Substituting our values: (1.65 / 3.3) × 2^12 = 0.5 × 4096 = 2048. This mathematical bridge is the fundamental mechanism translating real-world physics into microcontroller logic.
The Core Conversion: Volts to Digital Counts
An ADC does not 'read' voltage the way a human reads a dial; it quantizes a continuous analog waveform into discrete digital steps. The size of each step is called the Least Significant Bit (LSB). In our 12-bit, 3.3V example, the LSB is 3.3V / 4096 = 0.805mV. Every time the input voltage crosses another 0.805mV threshold, the digital code increments by one.
Here is how the converted code shifts across a ±20% range around our 1.65V nominal input, assuming a perfect 12-bit converter with a 3.3V reference:
| Input Voltage (Vin) | Variance | Calculation | Digital Code (Decimal) |
|---|---|---|---|
| 1.320V | -20% | (1.32 / 3.3) × 4096 | 1638 |
| 1.485V | -10% | (1.485 / 3.3) × 4096 | 1843 |
| 1.650V | Nominal | (1.65 / 3.3) × 4096 | 2048 |
| 1.815V | +10% | (1.815 / 3.3) × 4096 | 2253 |
| 1.980V | +20% | (1.98 / 3.3) × 4096 | 2458 |
What assumption fixes the answer? In AC power math, the fixed assumptions are power factor and phase angle. In ADC conversion, the absolute fixed assumptions are Reference Voltage ($V_{ref}$) stability and Bit Resolution ($n$). If your 3.3V LDO regulator is actually outputting 3.25V due to a brownout or thermal sag, your 1.65V input now reads (1.65 / 3.25) × 4096 = 2079. That introduces a 1.5% systemic error before the ADC's internal noise floor is even considered. According to Texas Instruments' ADC design guidelines, $V_{ref}$ noise and drift are often the primary limiting factors in high-resolution measurement systems.
How the Answer Shifts: Reference Voltages and Resolutions
Just as calculating AC power shifts fundamentally when moving from a 120V single-phase to a 230V or 3-phase system, ADC conversion math shifts drastically when you change the reference voltage or the bit resolution. A 5V reference on an Arduino Uno creates a completely different step size than a 3.3V reference on a Raspberry Pi Pico, or an external 16-bit module. You cannot apply a single conversion multiplier universally across different hardware.
Below is a data-dense specification table comparing how the exact same 1.65V physical signal is converted by five common microcontrollers and ADC ICs used in modern 2026 workbench projects:
| Microcontroller / IC | Resolution | $V_{ref}$ (Nominal) | Total Steps | LSB Size | 1.65V Input Code |
|---|---|---|---|---|---|
| Arduino Uno (ATmega328P) | 10-bit | 5.0V (VCC) | 1024 | 4.88 mV | 338 |
| ESP32-S3 (Internal ADC) | 12-bit | 3.3V (Internal) | 4096 | 0.81 mV | ~2150 (Non-linear)* |
| Raspberry Pi Pico (RP2040) | 12-bit | 3.3V (Internal) | 4096 | 0.81 mV | 2048 |
| TI ADS1115 (External I2C) | 16-bit | 4.096V (Internal) | 65536 | 0.0625 mV | 26400 |
| STM32F407 (12-bit mode) | 12-bit | 3.3V (VDDA) | 4096 | 0.81 mV | 2048 |
*Note on the ESP32-S3: While older ESP32 chips had notoriously poor ADC linearity, the ESP32-S3 improves this, but you still must use the Espressif ADC calibration API to map raw SAR readings to accurate millivolts. Raw counts on internal ESP ADCs should never be trusted without software calibration.
When the Conversion Becomes Meaningless
There are three specific bench scenarios where applying the Code = (Vin / Vref) × 2^n formula yields garbage data, rendering the conversion mathematically meaningless:
- Saturation ($V_{in} > V_{ref}$): If you feed 4.0V into an ADC with a 3.3V reference, the converter saturates and simply outputs the maximum code (e.g., 4095 for 12-bit). It cannot distinguish between 3.3V, 4.0V, or 5.0V. It is the exact equivalent of trying to measure 240V on a multimeter set to the 120V range.
- Noise Floor > 1 LSB: If your breadboard circuit has 5mV of switching noise from a nearby buck converter, and your ADC's LSB is 0.81mV (12-bit), the lowest 3 bits of your conversion will just be random noise toggling between 0 and 1. The 'precision' of those lower bits is an illusion. You must either filter the analog signal with an RC low-pass filter or drop to a 10-bit resolution in software.
- Source Impedance Mismatch: Successive Approximation Register (SAR) ADCs, like those inside the RP2040 and ATmega328P, work by charging an internal sampling capacitor (typically 10pF to 14pF) during the acquisition window. If your voltage divider uses 1MΩ resistors, the capacitor cannot charge fully before the conversion starts. The ADC will consistently read artificially low, and no amount of software averaging will fix the physics of the RC time constant. Keep your source impedance under 10kΩ.
Frequently Asked Questions
Why does my Arduino Uno ADC read 1023 when I apply 4.8V?
The ATmega328P's ADC is 10-bit, meaning it has 1024 steps (0 to 1023). With a 5.0V reference, the maximum measurable voltage is actually 5.0V - 1 LSB (4.88mV) = 4.995V. However, if your USB VCC is sagging to 4.7V, and you are using VCC as your reference, applying 4.8V exceeds the reference. The ADC saturates and clips at 1023. Always measure your actual VCC pin with a multimeter and update your $V_{ref}$ variable in code.
Can I just use the microcontroller's 3.3V VCC pin as the ADC Vref?
You can, but it degrades accuracy. VCC rails are noisy, carrying digital switching spikes from the MCU's internal clock and GPIO toggling. For precision sensor work (like load cells or thermistors), use a dedicated, low-noise voltage reference IC (like the TI REF3033) or route a clean analog LDO output directly to the MCU's dedicated $V_{ref}$ or VDDA pin.
How do I convert the digital code back to voltage in my code?
Simply invert the formula: Vin = (Code / 2^n) × Vref. For a 12-bit system with a 3.3V reference reading a code of 2048, the math is (2048 / 4096) × 3.3 = 1.65V. Ensure you cast your integer variables to floats in C/C++ before dividing, or integer division will truncate your decimals to zero.






