Arduino Battery Voltage on OLED — A Meter You Can Build Today
Animations are fun. A voltage readout on a tiny OLED is useful. I use this pattern on robot packs and portable lab boxes so students can see the battery sag under load instead of guessing from a “full” LED that lies.
Warning up front: never put a raw Li-ion pack above 5V straight into an Arduino analog pin.
You need a voltage divider (and preferably a fuse / proper battery protection). This guide assumes a
simple two-resistor divider into A0.
Parts
- Arduino Uno or Nano
- 0.96" I2C SSD1306 OLED
- Two resistors for the divider — e.g. 100k + 100k (÷2) for up to ~10V packs on a 5V Arduino
- Battery pack with a safe measurement point (protected pack recommended)
Divider math (keep it honest)
For resistors R1 (from battery+ to A0) and R2 (from A0 to GND):
V_pin = V_battery × R2 / (R1 + R2)
V_battery = V_pin × (R1 + R2) / R2
Example: R1 = R2 = 100k → divide by 2. A 8.4V 2S pack becomes 4.2V at A0 — safe for a 5V Uno. A 12V lead-acid needs a bigger ratio (for example 100k / 47k) so the pin stays under 5V.
Resistor tolerance is ±1% or ±5%. Your reading will be wrong until you calibrate with a multimeter. That is normal — build calibration into the sketch.
Wiring
- OLED: VCC→5V, GND→GND, SDA→A4, SCL→A5 (Uno)
- Battery+ → R1 → node → R2 → GND
- Node → Arduino A0
- Battery− → Arduino GND (common ground required)
Double-check polarity before connecting a pack. Reverse polarity into the divider can still hurt the pin if you also short something — take a breath, wire once.
Sketch: OLED voltmeter with calibration
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
// Divider: R1 (top) and R2 (bottom). Change to match your resistors.
const float R1 = 100000.0;
const float R2 = 100000.0;
const float DIV = (R1 + R2) / R2; // 2.0 for equal resistors
// Measure Arduino 5V rail with a meter if you can; USB is often ~4.9–5.1V
const float VREF = 5.0;
// Tweak after comparing OLED vs multimeter (e.g. 1.02 if you read 2% low)
const float CAL = 1.00;
const int PIN = A0;
float readBatteryVolts() {
// Average a few samples to reduce noise
long sum = 0;
for (int i = 0; i < 16; i++) {
sum += analogRead(PIN);
delay(2);
}
float adc = sum / 16.0;
float vPin = adc * (VREF / 1023.0);
return vPin * DIV * CAL;
}
void setup() {
analogReference(DEFAULT);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for (;;);
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
}
void loop() {
float v = readBatteryVolts();
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.println(F("Pack voltage"));
display.setTextSize(3);
display.setCursor(0, 22);
display.print(v, 2);
display.setTextSize(2);
display.print(F(" V"));
// Simple bar (0–12V scale — change maxV for your pack)
const float maxV = 9.0;
int bar = (int)constrain(v / maxV * 120, 0, 120);
display.drawRect(0, 54, 124, 8, SSD1306_WHITE);
display.fillRect(2, 56, bar, 4, SSD1306_WHITE);
display.display();
delay(300);
}
Calibrate in five minutes
- Measure the pack with a decent multimeter.
- Note what the OLED shows.
- Set
CAL = multimeter / oled(example: 8.20 / 8.00 = 1.025). - Re-upload. Check again under a small load — some packs droop; that is real, not a bug.
Also measure the Uno’s 5V pin while powered from USB. If it is 4.85V, put VREF = 4.85.
USB power is rarely a perfect 5.000V.
What this project teaches (beyond the number)
- Analog input limits and why dividers exist
- Noise averaging (
analogReadis jittery on USB-powered boards) - Updating an OLED without flashing the whole UI every microsecond
- Why “percentage battery” from voltage alone is rough — chemistry curves are not linear
Safety notes I tell students
- Use protected Li-ion packs. Bare cells + hobby wiring is how benches get scars.
- Do not power motors from the same weak USB port you use for measurement and expect stable readings.
- If you monitor a car battery, remember load dumps and spikes — add protection or use a proper module.
Ideas to extend
- Add a low-voltage warning icon (flash a bitmap when V < threshold).
- Log min/max since boot.
- Show mV and a bigger bar for classroom demos.
For icons and bars without hand-packing bitmaps, draw shapes in the shape exporter or drop a small warning glyph from the animation maker.
Related
Add a battery icon animation later
Design a charging/empty glyph, export drawBitmap code, drop it into this meter.
Open the free tool →