Use a divider that keeps the pin voltage under its maximum at a full battery, then calibrate against a meter.
What the message means
The pin reads a fraction of the battery voltage. Resistor tolerance and the ADC reference make the raw result a little off, so a one-time calibration fixes it.
Fix it in this order
- Choose resistors so the divided voltage at a full battery is below the pin limit, for example 3.3 V on an ESP32.
- Use values in the tens of kilohms to keep the drain low, and add a small capacitor at the pin.
- Measure the real battery voltage with a multimeter and compute a correction factor.
- Average several readings to reduce noise.
- On the ESP32 use the calibrated reading function if your core provides it.
- Check the divider is not left connected to a pin during sleep if you need low drain.
Why it happens
A microcontroller ADC cannot read a battery voltage above its supply, so you scale the voltage down with a resistor divider. Readings go wrong when the divider output exceeds the ADC range, when the resistor values are too large, or when the reference is not accurate.
For a 3S LiPo at 12.6 V, a 30 kΩ and 10 kΩ divider gives about 3.15 V. Put a small capacitor from the ADC pin to ground, average several samples, and calibrate against a multimeter.
If you see this, do this
| You see | Do this |
|---|---|
| Reading a constant percentage off | Apply a correction factor. |
| Reading jumps | Add a capacitor and average. |
Related
noisy ADC readings, voltage divider basics.
Last reviewed: November 2026. These are the usual causes and the order that finds them fastest. Test on your own board; clones and cables differ.
Why this happens
A resistor divider gives Vout = Vin x R2 / (R1 + R2), where R2 is the resistor to ground. The ADC then reports a number proportional to Vout compared with its reference. Three things spoil the result: the ratio used in code is not the real ratio of your resistors, the reference voltage is not what the code assumes, and the divider is too high in resistance for the ADC to sample it properly.
Many ADCs have a small sampling capacitor that must charge through the source resistance. The ATmega328P datasheet suggests a source of about 10 kohm or less. For a 30 kohm and 10 kohm divider, the ADC sees both in parallel, which is 7.5 kohm, so it is within that guidance, but higher values start to cause low or unsteady readings. A capacitor at the pin gives the ADC a local source of charge.
How to confirm the cause
- Measure the battery with a multimeter and write down the value. This is the truth you calibrate against.
- Measure the voltage at the divider mid-point, with the pin connected. Multiply it by (R1 + R2) / R2. If the result matches the battery, the divider is fine and the code or reference is wrong.
- Measure each resistor out of circuit. A 5 percent resistor can be off enough to move the result by a few percent.
- Print the raw ADC counts and the millivolts. If counts rise steadily with battery voltage but the scale is off, a correction factor fixes it. If counts jump around, add a capacitor and average.
- Confirm the full-charge voltage stays under the pin limit, 3.3 V on an ESP32 and the supply voltage on an Uno.
Worked example
A 3S LiPo (12.6 V full) through a 30 kohm and 10 kohm divider on an ESP32. Ratio is (30 + 10) / 10 = 4. At 12.6 V the pin sees 12.6 / 4 = 3.15 V. The divider draws 12.6 / 40 000 = 0.315 mA. If a meter shows 12.40 V while the code shows 12.55 V, the correction is 12.40 / 12.55 = 0.988.
const float RATIO = 4.0; // (30k + 10k) / 10k
const float CAL = 0.988; // meter / code, measured once
float readBattery() {
long sum = 0;
for (int i = 0; i < 32; i++) sum += analogReadMilliVolts(34);
return (sum / 32.0) / 1000.0 * RATIO * CAL;
}Common mistakes
- Using a 4S pack (16.8 V full) with the same divider. The pin would see 4.2 V, above the ESP32 limit.
- Swapping R1 and R2 in the formula, which inverts the ratio.
- Using the nominal pack voltage instead of the full-charge voltage when checking the pin limit.
- Using an ESP32 pin on ADC2 while Wi-Fi is on, where analogRead() does not work reliably.
- Calibrating with a half-flat battery and expecting the factor to hold across the whole range, since ADC response is not perfectly linear.
If this did not fix it
- Check the ground. If the battery negative and the board ground are not joined, the reading is meaningless.
- Check the attenuation setting on the ESP32. A setting for a lower range clips the top of your signal.
- Check for noise from motors on the same supply. Read the ADC with the motors off to see if the noise comes from them.