This project demonstrates how to monitor the voltage of 12V and 24V lead-acid battery systems using an Arduino Uno. A resistor divider scales the battery voltage (up to 32V) into the 0–5V range accepted by the Arduino’s ADC.
The nominal high and low voltages are set for a discharged and fully charged lead-acid battery at 12V and 24V systems. The Arduino’s ADC reads the battery voltage as an integer value from 0–1023 (10-bit resolution). This ADC value is converted to actual voltage using the resistor divider formula.
The measured voltage is compared against the thresholds for 12V or 24V systems, and the logic sets internal parameters accordingly. LEDs are updated based on whether the voltage is below, within, or above the nominal range.
The voltage range between the lower and upper thresholds defines 0% and 100% charge. Voltages below the lower threshold are considered 0% charged, above the upper threshold are 100% charged, and values in between are linearly mapped to a percentage of charge.
The calculated charge percentage also controls the pulse width of the yellow LED to provide a simple visual indication of battery state.
This system could be enhanced with additional thresholds, for example for 6V batteries for vintage motorbikes, or extended with an SPI or I²C display to provide voltage readings and status directly.
A simple two-resistor divider is used to reduce the battery voltage before measurement. The basic formula is:
Vout = Vin × (R2 / (R1 + R2))
For this project, the resistor values are:
Rearranged to compute Vin from a 0–5V ADC reading:
Vin = Vout × ((R1 + R2) / R2)
Thus, with a 5V ADC limit:
This allows safe measurement of 12V and 24V lead-acid battery systems.
Battery charge status is shown using three LEDs:
/*
* Arduino Uno: Lead-Acid Battery Monitor with LEDs and Resistor Divider
* Voltage divider setup: 56kΩ (R1) and 10kΩ (R2) to measure up to 32V
* © 2024 Copyright Peter I. Dunne, all rights reserved
* Released under the Mozilla Public License
*/
const int analogPin = A0;
const int greenLED = 9;
const int yellowLED = 10;
const int redLED = 11;
const float R1 = 56000.0;
const float R2 = 10000.0;
const float lowThreshold12V = 11.4;
const float highThreshold12V = 14.4;
const float lowThreshold24V = 22.8;
const float highThreshold24V = 28.8;
unsigned long previousMillis = 0;
unsigned long ledInterval = 500;
const float maxVoltage = 32.0;
void setup() {
pinMode(greenLED, OUTPUT);
pinMode(yellowLED, OUTPUT);
pinMode(redLED, OUTPUT);
Serial.begin(115200);
Serial.println("Lead Acid Battery Monitor - ©2024 Peter Ivan Dunne");
Serial.println("Released under the Mozilla Public License");
}
void loop() {
int sensorValue = analogRead(analogPin);
float voltage = sensorValue * (5.0 / 1023.0) * ((R1 + R2) / R2);
bool is24V = voltage >= 16;
float chargeLevel = calculateChargeLevel(voltage, is24V);
if (chargeLevel < 0) chargeLevel = 0;
if (chargeLevel > 100) chargeLevel = 100;
handleLEDs(voltage, is24V);
Serial.print(is24V ? "24V system" : "12V system");
Serial.print(" Battery Voltage: ");
Serial.print(voltage);
Serial.print(" V, Charge Level: ");
Serial.print(chargeLevel);
Serial.println("%");
delay(1000);
}
float calculateChargeLevel(float voltage, bool is24V) {
float maxV = is24V ? highThreshold24V : highThreshold12V;
float minV = is24V ? lowThreshold24V : lowThreshold12V;
return ((voltage - minV) / (maxV - minV)) * 100.0;
}
void handleLEDs(float voltage, bool is24V) {
float lowV = is24V ? lowThreshold24V : lowThreshold12V;
float highV = is24V ? highThreshold24V : highThreshold12V;
unsigned long currentMillis = millis();
if (voltage >= highV) {
digitalWrite(greenLED, HIGH);
digitalWrite(yellowLED, LOW);
digitalWrite(redLED, LOW);
}
else if (voltage > lowV && voltage < highV) {
digitalWrite(greenLED, LOW);
if (currentMillis - previousMillis >= ledInterval) {
previousMillis = currentMillis;
digitalWrite(yellowLED, !digitalRead(yellowLED));
}
ledInterval = 1000 - (calculateChargeLevel(voltage, is24V) * 10);
digitalWrite(redLED, LOW);
}
else {
digitalWrite(greenLED, LOW);
digitalWrite(yellowLED, LOW);
if (currentMillis - previousMillis >= ledInterval) {
previousMillis = currentMillis;
digitalWrite(redLED, !digitalRead(redLED));
}
ledInterval = (voltage < lowV) ? 1000 : 333;
}
}