If you are reading a 1.65V analog signal using a 12-bit ADC with a 3.3V reference voltage (the standard internal setup for an ESP32-WROOM-32), your converted digital value is 2048. The foundational formula for the application of ADC converter math is ADC_Count = (V_in / V_ref) * (2^n - 1). Substituting our exact query values: (1.65 / 3.3) * (2^12 - 1) becomes 0.5 * 4095, which equals 2047.5 (rounded to 2048 in integer math). This direct conversion assumes a perfectly stable 3.3V reference and an ideal linear response, which we will adjust for real-world silicon behavior below.
The Core Conversion Formula and Neighboring Values
To apply an ADC converter effectively in firmware, you must map the physical voltage to the microcontroller's digital register. The resolution (n) dictates the total number of discrete steps. A 12-bit ADC yields 4,096 steps (0 to 4095). The voltage weight of a single step—the Least Significant Bit (LSB)—is calculated as V_ref / 2^n. For a 3.3V system at 12 bits, 1 LSB equals roughly 0.805 mV.
When designing sensor thresholds or scaling analog inputs, it is critical to know how the digital count shifts across a tolerance band. Below is the conversion table for a ±20% variance around our 1.65V nominal target, assuming a 12-bit / 3.3V architecture.
| Analog Input (V) | Variance from 1.65V | 12-Bit ADC Count (3.3V VREF) | Physical Meaning |
|---|---|---|---|
| 1.32V | -20% | 1638 | Lower threshold boundary |
| 1.485V | -10% | 1843 | Minor signal sag |
| 1.65V | Nominal | 2048 | Target midpoint |
| 1.815V | +10% | 2252 | Minor signal swell |
| 1.98V | +20% | 2457 | Upper threshold boundary |
How Resolution and Reference Voltage Shift the Answer
A common mistake in embedded systems is treating a single voltage-to-count mapping as universal. The application of ADC converter logic shifts drastically depending on the microcontroller's native resolution and its active reference voltage. If you port code from an Arduino Uno to an ESP32 without adjusting the math, your physical readings will be entirely wrong.
Here is how that exact same 1.65V physical signal translates across three common bench setups:
- Arduino Uno (ATmega328P): Uses a 10-bit ADC and a default 5.0V VREF. The formula is
(1.65 / 5.0) * 1023. The resulting count is 337. (1 LSB = 4.88 mV). - ESP32-WROOM-32: Uses a 12-bit ADC and a 3.3V VREF. As calculated, the count is 2048. (1 LSB = 0.805 mV).
- External ADS1115 Breakout: A 16-bit I2C ADC. If configured with the internal Programmable Gain Amplifier (PGA) set to a ±4.096V Full Scale Range (FSR), the formula is
(1.65 / 4.096) * 32767. The resulting count is 13183. (1 LSB = 0.125 mV). Read the TI ADS1115 Datasheet for exact PGA scaling tables.
If you are measuring a 120V AC mains waveform via a ZMPT101B voltage transformer module, the AC signal is biased to a DC midpoint (usually 1.65V or 2.5V). Your ADC conversion must subtract this DC bias in firmware before calculating the RMS voltage, otherwise, the math will yield a massive false-positive DC offset.
When ADC Conversion Becomes Meaningless
Theoretical math falls apart when real-world physics interfere with the silicon. Your calculated ADC count is entirely meaningless under the following conditions:
The Floating VREF Trap: If your microcontroller uses the raw VCC line as its analog reference (common on cheap Arduino clones powered via USB), a 5V USB hub that actually outputs 4.7V will silently skew every reading by 6%. The math assumes 5.0V, but the hardware is comparing against 4.7V. Always use a dedicated internal bandgap reference (like the ATmega's 1.1V internal VREF) for precision measurements, or measure your VCC pin with a multimeter and hardcode that exact value into your V_ref variable.
1. Source Impedance is Too High
Inside every microcontroller ADC is a tiny sample-and-hold capacitor (often around 10-14 pF). When the internal multiplexer connects to your pin, this capacitor must charge to the input voltage before the conversion completes. If your signal comes through a high-impedance voltage divider (e.g., two 1MΩ resistors), the RC time constant is too slow. The capacitor won't charge fully, and the ADC will consistently read lower than the actual voltage. The Arduino analogRead() documentation explicitly recommends keeping source impedance under 10 kΩ for accurate 10-bit conversions.
2. ESP32 Native ADC Non-Linearity
The ESP32’s internal 12-bit ADC is notorious for non-linearity at the extremes of its range. Voltages below ~0.15V and above ~3.15V do not scale linearly; they flatten out, meaning multiple different voltages will return the exact same 0 or 4095 count. Furthermore, the internal 3.3V reference has a factory gain error of up to ±3%. If your application demands strict accuracy (like a precision digital multimeter or a laboratory power supply), you must either use the ESP32's two-point factory calibration values stored in eFuse, or bypass the internal ADC entirely and use an external I2C chip like the ADS1115.
Frequently Asked Questions
How does the application of ADC converter math change for AC signals?
You cannot feed raw AC (which swings negative) into a unipolar microcontroller ADC; it will clip the negative half-wave and potentially damage the silicon. You must bias the AC signal to a DC midpoint (e.g., shifting a ±1V AC wave to swing between 0.65V and 2.65V on a 3.3V system). In firmware, your conversion must first subtract the DC bias count (e.g., 2048) from the raw reading to get the instantaneous AC count, square those values over one full waveform cycle, average them, and take the square root to find the true RMS digital count before converting back to physical voltage.
Why is my ESP32 application of ADC converter reading erratic values at 3.2V?
This is the ESP32 ADC saturation zone. The internal amplifier struggles to resolve voltages within roughly 150mV of the 3.3V VREF rail. If your sensor outputs 3.2V, the ADC might read anywhere from 3800 to 4095, jittering wildly. To fix this, either add a precision op-amp buffer to scale the 0-3.3V signal down to 0-2.5V, use a voltage divider to drop the maximum expected voltage to 2.8V, or switch to an external ADC module that supports rail-to-rail input.
Can I use the internal 1.1V reference on an Arduino for better precision?
Yes, but it restricts your input range. By calling analogReference(INTERNAL) on an ATmega328P, you set VREF to 1.1V. Your 10-bit resolution now maps 0-1.1V to 0-1023, giving you an LSB weight of just 1.07 mV (compared to 4.88 mV at 5V). This is excellent for reading low-voltage shunt resistors for current sensing or thermistors with small voltage swings. However, if you apply 1.65V to the pin while the 1.1V reference is active, the ADC will simply max out at 1023 and you risk damaging the internal multiplexer if the voltage exceeds the chip's absolute maximum ratings.






