The 0–25 V Voltage Sensor: a 12 V Battery Monitor on an ESP32
The "25 V" module is just two resistors, 30 kΩ and 7.5 kΩ. We see why on an ESP32 it measures only 16.5 V rather than 25, how to calibrate the reading against a multimeter, and how to turn a lead-acid battery voltage into a charge percentage.
About the project
To measure a voltage above 3.3 V you have to scale it down with a divider. The "Voltage Sensor 0–25 V" module is a ready-made 30 kΩ / 7.5 kΩ divider with a screw terminal and header pins. It divides by 5: 25 V in gives 5 V out — exactly the Arduino Uno range. With it you can easily watch the battery of an alarm system, a solar setup or a car.
What you need
- ESP32 DevKit (or an Arduino Uno)
- A 0–25 V voltage sensor module
- A 12 V source: a battery or a power supply
- A multimeter for calibration
Wiring
| Module | Connect to |
|---|---|
| VCC terminal | battery + |
| GND terminal | battery − |
| S | GPIO34 (ADC1) |
| + | nothing: on most modules this pin is not connected at all |
| − | ESP32 GND |
The battery minus and the board GND must be common, otherwise there is nothing to measure against.
The main trap: 3.3 V
The module is designed for 5 V Arduinos. An ESP32 input takes up to 3.3 V, so with a 5:1 divider the maximum is 16.5 V. That is enough for a 12 V battery (up to 14.4 V while charging), but not for a 24 V system: 25 V would put 5 V on the pin and damage it. For higher voltages make your own divider, for example 100 kΩ / 10 kΩ (11:1).
On top of that the ESP32 ADC is non-linear near the top, so we read analogReadMilliVolts() with its factory calibration and average 32 readings.
Code
// 12 V battery monitor: 0–25 V voltage sensor (30k / 7.5k divider)
const int SENSE_PIN = 34;
const float DIVIDER = (30000.0 + 7500.0) / 7500.0; // = 5
const float CALIBRATION = 1.00; // adjust by comparing with a multimeter
float batteryVolts() {
uint32_t mv = 0;
for (int i = 0; i < 32; i++) mv += analogReadMilliVolts(SENSE_PIN);
return mv / 32.0 / 1000.0 * DIVIDER * CALIBRATION;
}
int percentLeadAcid(float v) { // 12 V lead-acid at rest: 11.8 V ≈ 0 %, 12.7 V ≈ 100 %
return constrain((int)((v - 11.8) / (12.7 - 11.8) * 100), 0, 100);
}
void setup() {
Serial.begin(115200);
}
void loop() {
float v = batteryVolts();
Serial.printf("Battery %.2f V ~%d %%%s\n", v, percentLeadAcid(v), v < 11.9 ? " LOW!" : "");
delay(1000);
}
In the simulator change the power supply voltage. Set 20 V and the simulator reminds you that this is too much for an ESP32.
Calibration
The resistors have a 1–5 % tolerance and the ADC reference drifts too, so an error of 0.1–0.3 V is normal. Measure the voltage with a multimeter, compare it with the Serial Monitor, and set CALIBRATION = multimeter / reading. For example, the multimeter says 12.46 V and the sketch 12.31 V: CALIBRATION = 1.012.
Charge percentage
A lead-acid battery voltage shows the charge accurately only at rest, an hour after charging or a load. Under load it sags, and while charging it reads high. Lithium batteries need a different table: a 1S Li-ion goes from 3.0 V (0 %) to 4.2 V (100 %), and the curve is far from linear.
What next
- A Telegram message when the voltage drops below 11.9 V.
- A 24-hour chart on an ESP32 web page: you can see the solar panel charge the battery during the day.
- Current and power — add an INA219, which measures both.