Practical case: solar battery monitor, Arduino UNO

Practical case: solar battery monitor, Arduino UNO — hero

Objective and use case

What you’ll build: An Arduino-based solar battery monitor that reads 12 V battery voltage through a voltage divider, measures charge or load current with an ACS712 sensor, and displays both values on a 16×2 HD44780 LCD. The system gives near real-time local readings with a simple refresh around 2–5 updates/sec and typical display latency under 500 ms.

Why it matters / Use cases

  • Confirm a small solar panel is actually charging by checking for positive current, for example +0.3 A to +1.8 A in direct sun on a shed, camping box, or classroom demo setup.
  • Reduce battery damage by spotting low voltage early, such as a 12 V lead-acid battery dropping toward 11.8–12.0 V while powering LED lights, a router backup, or a small DC fan.
  • Measure real load current by placing the ACS712 in series with a lamp, pump, or USB converter and verifying expected draw, such as 0.15 A for LEDs or 0.8 A for a small pump.
  • Use it as a reusable bench monitor beside a charge controller or battery box for quick maintenance checks without needing a laptop, app, or cloud dashboard.
  • Learn practical embedded measurement, including analog sensing, calibration, ADC scaling, and sensor noise handling in a low-power Arduino project.

Expected outcome

  • A working LCD readout showing battery voltage and current continuously, for example “12.6 V” and “+0.42 A”.
  • Voltage readings scaled from the divider and current readings interpreted from the ACS712 with basic calibration for more stable values.
  • A compact monitor suitable for small 12 V solar experiments, with no meaningful GPU usage and no FPS dependency beyond the LCD refresh rate.
  • A practical baseline you can extend with alarms, data logging, state-of-charge estimates, or charge/discharge direction indicators.

Audience: Arduino beginners, students, makers, and hobbyists working with small solar or battery projects; Level: Beginner to intermediate

Architecture/flow: Arduino reads battery voltage from a resistor divider and current from the ACS712 analog output, converts both ADC values into engineering units, then refreshes the 16×2 HD44780 LCD every 200–500 ms with live voltage/current status.

Educational validation note

Before publication, this case passed the Prometeo automated validation gate with status PASS. The validator checked the code blocks, article structure, copy/paste-safe commands and consistency with the supported device catalog.

Published validation evidence

  • Automatic result: PASS.
  • Parsed structure: 3 sections, 4 tables and 2 code blocks detected before publication.
  • Checked code: 1 Arduino/arduino-cli compile, 1 Bash/copy-paste checks.
  • Supported catalog: the article text was checked against Prometeo’s validation-capable device profiles, and unsupported stacks block publication.
  • Report findings: no blocking findings.

This validation confirms syntax and tool compatibility for the published material, but it does not replace physical testing on your exact hardware, wiring and runtime environment.

Educational safety note

This is an educational low-voltage monitoring prototype, not a certified battery instrument.

Important limits and precautions:

  • Work only on low-voltage DC educational setups.
  • Do not connect the Arduino analog pin directly to a battery; always use the stated voltage divider.
  • Do not use this tutorial directly on large solar arrays, high-current battery banks, vehicle electrical systems, or mains-connected equipment.
  • The ACS712 module current path and terminal rating must match your actual current. Do not exceed the module rating.
  • Batteries can deliver high fault current even at low voltage:
  • avoid short circuits
  • fuse the battery line if possible
  • keep exposed conductors insulated
  • If your solar system includes a charge controller, understand the controller wiring before inserting the current sensor in series.
  • Do not rely on this prototype alone to protect a battery from overcharge, deep discharge, overheating, or wiring faults.
  • During startup calibration, ensure no current is flowing through the ACS712, otherwise the zero point will be wrong and the current reading will be biased.

Prerequisites

Before starting, you should have:

  • A computer with Arduino CLI installed
  • A USB cable for the Arduino UNO R3
  • Basic familiarity with:
  • uploading sketches
  • reading pin labels
  • using a multimeter
  • A small low-voltage solar-battery setup under test, such as:
  • a 12 V sealed lead-acid battery with a small solar charge controller
  • a 12 V LiFePO4 educational setup with proper protection
  • a bench DC source acting as a solar simulator for testing

For a first build, it is strongly recommended to test with a bench supply and a small DC load before connecting to a real outdoor solar system.

Materials

Use exactly this device model as the core of the project:

Arduino UNO R3 (ATmega328P) + voltage divider + ACS712 current sensor + 16×2 HD44780 LCD

Recommended parts list:

ItemSuggested specificationNotes
Arduino boardArduino UNO R3 (ATmega328P)Required
LCD16×2 HD44780 compatible, parallel interfaceRequired
Current sensorACS712 module, preferably 5 A version5 A version gives better sensitivity
Voltage divider resistor R147 kOhm, 1/4 WTop resistor from battery positive to analog pin
Voltage divider resistor R210 kOhm, 1/4 WBottom resistor from analog pin to GND
LCD contrast potentiometer10 kOhm trimmerRequired for visible text
Breadboard and jumper wiresStandardRequired
Small DC battery under testTypically 12 VKeep within divider input range
Small DC load for testing12 V lamp or resistor loadUseful for validation
MultimeterDigitalStrongly recommended

Why these resistor values?

With 47 kOhm and 10 kOhm, the analog input sees:

V_A0 = V_battery x (10 / (47 + 10)) = V_battery x 0.1754

That keeps the analog input under about 5 V up to roughly 28.5 V. This is comfortable for 12 V systems and gives useful headroom for charging voltages. Even so, this tutorial is intended for low-voltage educational battery systems, not large or high-energy installations.

Setup/Connection

Measurement concept

This monitor uses two analog channels:

  • A0 reads battery voltage through the resistor divider
  • A1 reads the ACS712 analog output

The LCD shows:

  • battery voltage
  • current
  • power
  • a simple state message such as CHARGING, DISCHARGE, or IDLE

Important current-path choice

Choose one current path to monitor with the ACS712:

  • Option A: charging current
  • Place ACS712 in series between the charge controller output and battery positive path, if your low-power educational setup allows it.
  • Option B: load current
  • Place ACS712 in series between battery positive and the DC load.

For a beginner build, Option B is usually easier and safer to understand:
– current near zero when load is off
– current increases when load is on

Arduino and LCD connections

Use the standard LiquidCrystal wiring below.

FunctionLCD pinConnect to
VSS1Arduino GND
VDD2Arduino 5V
VO3Middle pin of 10 kOhm potentiometer
RS4Arduino D7
RW5Arduino GND
E6Arduino D8
D411Arduino D9
D512Arduino D10
D613Arduino D11
D714Arduino D12
A (backlight +)155V through module resistor if required
K (backlight -)16GND

Potentiometer wiring:
– one outer pin -> 5V
– other outer pin -> GND
– middle pin -> LCD pin 3 (VO)

Analog measurement connections

Voltage divider

  • Battery positive -> 47 kOhm resistor -> node -> A0
  • Same node -> 10 kOhm resistor -> GND
  • Battery negative -> Arduino GND

This common ground is essential. Without it, the ADC reading will be meaningless.

ACS712

Most ACS712 modules have:
VCC
GND
OUT
– two screw terminals for the measured current path

Connect:
VCC -> Arduino 5V
GND -> Arduino GND
OUT -> Arduino A1

For the current path:
– connect the monitored positive wire so current flows through the ACS712 terminals
– note the module arrow or printed direction if present
– if the current sign looks reversed, swap the high-current terminals or invert the sign in software

Powering the Arduino during setup

For initial development:
– power the Arduino from USB
– keep the battery measurement and current sensor connected with common ground

After testing, you may later power the Arduino from a stable regulated 5 V source, but do not power the UNO directly from an unregulated solar panel.

Validated Code

Complete Arduino sketch: solar-battery-lcd-monitor.ino

Public preview of the validated file. The complete source is shown to members and in PDF/Print.

#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Analog pins
const uint8_t PIN_BATTERY = A0;
const uint8_t PIN_CURRENT = A1;

// ADC reference
const float ADC_REF_VOLTAGE = 5.0;
const int ADC_MAX = 1023;

// Voltage divider values
const float R1 = 47000.0; // ohms, top resistor
const float R2 = 10000.0; // ohms, bottom resistor

// ACS712 settings
// Set according to your module:
// 5A module  -> 185.0 mV/A
// 20A module -> 100.0 mV/A
// 30A module -> 66.0  mV/A
const float ACS712_MV_PER_AMP = 185.0;

// Calibration values
float currentZeroVoltage = 2.50;   // Will be refined during startup calibration
float voltageCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed
float currentCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed

// Sampling
const int NUM_SAMPLES_VOLTAGE = 20;
const int NUM_SAMPLES_CURRENT = 100;

// Timing
unsigned long lastDisplayUpdate = 0;
const unsigned long DISPLAY_INTERVAL_MS = 500;

// Optional status thresholds
const float LOW_BATTERY_VOLTAGE = 11.8;
const float IDLE_CURRENT_THRESHOLD = 0.08;

float readAverageVoltageAtPin(uint8_t pin, int samples) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(pin);
    delay(2);
  }
  float adc = sum / (float)samples;
  return (adc * ADC_REF_VOLTAGE) / ADC_MAX;
}

float readBatteryVoltage() {
  float dividedVoltage = readAverageVoltageAtPin(PIN_BATTERY, NUM_SAMPLES_VOLTAGE);
  float batteryVoltage = dividedVoltage * ((R1 + R2) / R2);
  batteryVoltage *= voltageCalibrationFactor;
  return batteryVoltage;
}

float readCurrent() {
  float sensorVoltage = readAverageVoltageAtPin(PIN_CURRENT, NUM_SAMPLES_CURRENT);
  float deltaVoltage = sensorVoltage - currentZeroVoltage; // volts
  float current = (deltaVoltage * 1000.0) / ACS712_MV_PER_AMP; // amps
  current *= currentCalibrationFactor;
  return current;
}

void calibrateCurrentZero() {
  // Assumes no current is flowing through ACS712 during startup.
  float total = 0.0;
  const int rounds = 10;
  for (int i = 0; i < rounds; i++) {
    total += readAverageVoltageAtPin(PIN_CURRENT, 50);
    delay(20);
  }
  currentZeroVoltage = total / rounds;
}
// ...

🔒 This content is premium. With the monthly subscription (7-day free trial) you can unlock the full didactic material and the print-ready PDF pack.🔓 Unlock it — 7-day free trial
#include <LiquidCrystal.h>

// LCD pins: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

// Analog pins
const uint8_t PIN_BATTERY = A0;
const uint8_t PIN_CURRENT = A1;

// ADC reference
const float ADC_REF_VOLTAGE = 5.0;
const int ADC_MAX = 1023;

// Voltage divider values
const float R1 = 47000.0; // ohms, top resistor
const float R2 = 10000.0; // ohms, bottom resistor

// ACS712 settings
// Set according to your module:
// 5A module  -> 185.0 mV/A
// 20A module -> 100.0 mV/A
// 30A module -> 66.0  mV/A
const float ACS712_MV_PER_AMP = 185.0;

// Calibration values
float currentZeroVoltage = 2.50;   // Will be refined during startup calibration
float voltageCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed
float currentCalibrationFactor = 1.000; // Adjust after comparing to multimeter if needed

// Sampling
const int NUM_SAMPLES_VOLTAGE = 20;
const int NUM_SAMPLES_CURRENT = 100;

// Timing
unsigned long lastDisplayUpdate = 0;
const unsigned long DISPLAY_INTERVAL_MS = 500;

// Optional status thresholds
const float LOW_BATTERY_VOLTAGE = 11.8;
const float IDLE_CURRENT_THRESHOLD = 0.08;

float readAverageVoltageAtPin(uint8_t pin, int samples) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(pin);
    delay(2);
  }
  float adc = sum / (float)samples;
  return (adc * ADC_REF_VOLTAGE) / ADC_MAX;
}

float readBatteryVoltage() {
  float dividedVoltage = readAverageVoltageAtPin(PIN_BATTERY, NUM_SAMPLES_VOLTAGE);
  float batteryVoltage = dividedVoltage * ((R1 + R2) / R2);
  batteryVoltage *= voltageCalibrationFactor;
  return batteryVoltage;
}

float readCurrent() {
  float sensorVoltage = readAverageVoltageAtPin(PIN_CURRENT, NUM_SAMPLES_CURRENT);
  float deltaVoltage = sensorVoltage - currentZeroVoltage; // volts
  float current = (deltaVoltage * 1000.0) / ACS712_MV_PER_AMP; // amps
  current *= currentCalibrationFactor;
  return current;
}

void calibrateCurrentZero() {
  // Assumes no current is flowing through ACS712 during startup.
  float total = 0.0;
  const int rounds = 10;
  for (int i = 0; i < rounds; i++) {
    total += readAverageVoltageAtPin(PIN_CURRENT, 50);
    delay(20);
  }
  currentZeroVoltage = total / rounds;
}

const char* stateText(float current, float batteryVoltage) {
  if (current > IDLE_CURRENT_THRESHOLD) {
    return "CHARGING";
  } else if (current < -IDLE_CURRENT_THRESHOLD) {
    return "DISCHARGE";
  } else if (batteryVoltage < LOW_BATTERY_VOLTAGE) {
    return "LOW BAT";
  } else {
    return "IDLE";
  }
}

void printPadded(String text, uint8_t width) {
  if (text.length() >= width) {
    lcd.print(text.substring(0, width));
  } else {
    lcd.print(text);
    for (uint8_t i = text.length(); i < width; i++) {
      lcd.print(' ');
    }
  }
}

void setup() {
  lcd.begin(16, 2);
  Serial.begin(9600);

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Solar Battery");
  lcd.setCursor(0, 1);
  lcd.print("Monitor Start");
  delay(1200);

  calibrateCurrentZero();

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Zero I V=");
  lcd.print(currentZeroVoltage, 3);
  delay(1500);

  Serial.println("solar-battery-lcd-monitor");
  Serial.print("Current zero voltage = ");
  Serial.println(currentZeroVoltage, 4);
}

void loop() {
  if (millis() - lastDisplayUpdate >= DISPLAY_INTERVAL_MS) {
    lastDisplayUpdate = millis();

    float batteryVoltage = readBatteryVoltage();
    float current = readCurrent();
    float power = batteryVoltage * current;
    const char* state = stateText(current, batteryVoltage);

    // LCD line 1: Voltage and current
    lcd.setCursor(0, 0);
    String line1 = "V:";
    line1 += String(batteryVoltage, 2);
    line1 += " I:";
    line1 += String(current, 2);
    printPadded(line1, 16);

    // LCD line 2: Power and state
    lcd.setCursor(0, 1);
    String line2 = "P:";
    line2 += String(power, 1);
    line2 += " ";
    line2 += state;
    printPadded(line2, 16);

    // Serial output for validation
    Serial.print("Battery_V=");
    Serial.print(batteryVoltage, 3);
    Serial.print(", Current_A=");
    Serial.print(current, 3);
    Serial.print(", Power_W=");
    Serial.print(power, 3);
    Serial.print(", State=");
    Serial.println(state);
  }
}

Notes on calibration values

You may need small adjustments after comparing with a multimeter:

  • voltageCalibrationFactor
  • Example: if LCD shows 12.40 V but multimeter shows 12.55 V, set factor near 12.55 / 12.40 = 1.012
  • currentCalibrationFactor
  • Example: if known current is 0.90 A but display shows 0.84 A, set factor near 0.90 / 0.84 = 1.071

For the first upload, leave both at 1.000.

Build/Flash/Run commands

Command table

StepCommand
Update board indexarduino-cli core update-index
Install AVR corearduino-cli core install arduino:avr
Compile sketcharduino-cli compile --fqbn arduino:avr:uno ~/arduino/solar-battery-lcd-monitor
Upload sketcharduino-cli upload --fqbn arduino:avr:uno --port <PORT> ~/arduino/solar-battery-lcd-monitor

Workflow

  1. Create a project folder:
  2. ~/arduino/solar-battery-lcd-monitor/
  3. Save the sketch as:
  4. ~/arduino/solar-battery-lcd-monitor/solar-battery-lcd-monitor.ino
  5. Run the commands below in order.
arduino-cli core update-index
arduino-cli core install arduino:avr
arduino-cli compile --fqbn arduino:avr:uno ~/arduino/solar-battery-lcd-monitor
arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ~/arduino/solar-battery-lcd-monitor

Replace <PORT> with your board port, for example:
– Linux: /dev/ttyACM0
– macOS: /dev/cu.usbmodem14101
– Windows: COM3

Run behavior

After upload:
1. The LCD should briefly show a startup message.
2. The sketch calibrates the ACS712 zero-current voltage.
3. The LCD begins updating every 0.5 seconds.
4. Open a serial monitor at 9600 baud if you want text logs for validation.

Step-by-step Validation

Use a multimeter and a small, low-power test setup. Perform the checks in this order.

1. LCD and startup check

Action
– Power the Arduino by USB.
– Leave the ACS712 current path open or with no active current.
– Adjust the LCD contrast potentiometer slowly.

Expected observation
– A readable startup message appears.
– After startup, the monitor shows voltage/current fields instead of random blocks.

Pass condition
– Text is visible and stable on both LCD lines.

2. Zero-current calibration check

Action
– Make sure no current is flowing through the ACS712 during reset or power-up.
– Observe the serial output or LCD after startup.

Expected observation
– Current should be close to 0.00 A, with small noise around zero.
– State should usually be IDLE, unless low battery voltage triggers LOW BAT.

Pass condition
– Idle current remains small and does not drift wildly, typically a few hundredths of an ampere around zero.

3. Battery voltage validation

Action
– Measure the battery voltage directly with a multimeter.
– Compare it to the LCD and serial output.

Expected observation
– The displayed battery voltage is close to the multimeter reading.
– Small differences are normal before calibration.

Pass condition
– The reading is logically correct and close enough that a small voltageCalibrationFactor can align it.
– If the multimeter reads 12.6 V and the display says 6 V or 20 V, the divider or grounding is wrong.

4. Current direction and magnitude check

Action
– Connect a small DC load through the ACS712.
– Turn the load on and off.
– If testing a charging path, expose the panel to light or use a bench source through a suitable controller.

Expected observation
– Current changes noticeably when the load or charging state changes.
– Sign should be consistent with your chosen wiring direction.
– Power should change in the same direction as current.

Pass condition
– Turning the load on produces a repeatable current reading.
– If sign is opposite from what you want, swap ACS712 current terminals or invert the sign convention.

5. State message check

Action
– Observe the second LCD line while changing operating conditions:
– no current
– active load
– active charge current
– low battery voltage if available

Expected observation
CHARGING for positive current
DISCHARGE for negative current
IDLE near zero current
LOW BAT when voltage is below the threshold and current is near zero

Pass condition
– The state label follows the measured electrical condition logically.

Troubleshooting

SymptomLikely causeFix
LCD backlight on but no textContrast not adjusted or wrong LCD wiringAdjust potentiometer; recheck RS, E, D4-D7 pins
Random characters on LCDWrong pin mapping in code or loose wiresMatch code pins to physical connections exactly
Battery voltage reads 0 VDivider node not connected to A0 or no common groundRecheck A0 node and battery negative to Arduino GND
Battery voltage far too high or lowWrong resistor values or divider formula mismatchConfirm 47 kOhm and 10 kOhm placement; verify code constants
Current always near zeroACS712 output not connected, no current path, or wrong test methodCheck A1, VCC, GND, and that load current actually passes through the sensor terminals
Current value unstableSensor noise, poor wiring, or switching load noiseShorten wires, improve grounding, average more samples
Current sign reversedSensor installed in opposite directionSwap current terminals or multiply current by -1 in code
Upload failsWrong port or missing AVR coreInstall arduino:avr core and verify <PORT>
Display resets when load changesPower supply instability or shared noisy supplyUse stable 5 V for Arduino and ensure common ground

Improvements

  • Better measurement quality
  • Add a small capacitor, such as 100 nF, from A0 to GND near the Arduino to reduce divider noise.
  • Increase current averaging if your load is steady.
  • Add a software calibration mode that stores factors in EEPROM.

  • More useful field behavior

  • Add a buzzer or LED warning for low battery.
  • Add a second page on the LCD that alternates between:
    • voltage/current/power
    • min/max voltage
    • accumulated amp-hour estimate over time
  • Add a pushbutton to toggle between charge-current monitoring and load-current monitoring labels.

  • Packaging and deployment

  • Move the circuit from breadboard to perfboard.
  • Install the monitor in a small plastic enclosure near a low-voltage battery box.
  • Use screw terminals for the divider input and ACS712 current path to make the prototype reusable.

Final Checklist

  • [ ] I used Arduino UNO R3 (ATmega328P).
  • [ ] I used a 47 kOhm / 10 kOhm voltage divider to battery voltage.
  • [ ] I connected battery negative, Arduino GND, LCD GND, and ACS712 GND together.
  • [ ] I connected the ACS712 OUT pin to A1.
  • [ ] I connected the voltage divider node to A0.
  • [ ] I wired the 16×2 HD44780 LCD to pins D7, D8, D9, D10, D11, D12 as used in the sketch.
  • [ ] I adjusted the LCD contrast potentiometer until text became visible.
  • [ ] I compiled with:
  • arduino-cli compile --fqbn arduino:avr:uno ~/arduino/solar-battery-lcd-monitor
  • [ ] I uploaded with:
  • arduino-cli upload --fqbn arduino:avr:uno --port <PORT> ~/arduino/solar-battery-lcd-monitor
  • [ ] I powered up with no current through the ACS712 for zero calibration.
  • [ ] I compared battery voltage with a multimeter.
  • [ ] I tested current response using a small DC load or a controlled charging condition.
  • [ ] I updated calibration factors only after measurement comparison.
  • [ ] I understand this is a basic educational monitor, not a certified protection device.

Find this product and/or books on this topic on Amazon

Go to Amazon

As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.

Quick Quiz

Question 1: What is the main device being built in the article?




Question 2: Which display is used to show the voltage and current readings?




Question 3: How is the 12 V battery voltage measured by the system?




Question 4: Which sensor is used to measure charge or load current?




Question 5: What refresh rate is mentioned for the monitor?




Question 6: What does a positive current reading indicate in the example use case?




Question 7: What battery voltage range can warn of a low 12 V lead-acid battery?




Question 8: How should the ACS712 be connected to measure real load current?




Question 9: What is the typical display latency mentioned for the system?




Question 10: What is one benefit of using this monitor as a bench tool?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Telecommunications Electronics Engineer and Computer Engineer (official degrees in Spain).

Follow me:
Scroll to Top