Warning - Low Battery

Customisable Battery Voltage Display

Johann Wyss

Issue 29, December 2019

This article includes additional downloadable resources.
Please log in to access.

Log in

Build this simple ATtiny-based battery voltage indicator to help avoid your high power batteries from becoming flat or damaged.

BUILD TIME: 1.5 Hours + 3D Printing Time

High capacity and deep-cycle rechargeable batteries are all around us. We use them to power our projects that don’t have access to mains power, in back-up systems for UPS, NBN and alarm systems, and in the cars we drive.

If your rechargeable battery is not directly connected to a charger, you need to ensure it doesn’t self-discharge to a level where it can become damaged and never fully recover.

Perhaps you have a weather station project remotely powered by a 12V SLA battery, or you have a camper trailer, boat, ride-on mower or similar vehicle sitting idle in your backyard or shed with its battery slowly discharging. If you don’t regularly check the battery voltage, you’ll be left without power when you need it, or you could be up for a battery replacement if it goes too flat.

Checking the voltage on the battery usually means the inconvenience of getting the multimeter out each time you want to measure the battery voltage. That is where this project can come in handy.


This compact battery voltage indicator can be connected permanently to your battery, and at the touch of a button, you get to see the battery status on an RGB LED. Red for very low, blue for low and green for ok.

Usually, when requiring a battery level indicator, the circuit that comes to mind is an Op-amp working as a comparator with the inverting input. This would be set to the desired reference state using a Zener diode and the non-inverting input connected to the battery. If the voltage of the battery drops to the reference voltage, the Op-amp pulls its output high to trigger an LED.

This method would be a simple way to get a low voltage indicator on your project but it can only provide feedback if the battery level is lower than the reference voltage. This value is also set with hardware. If say, you wanted it to trigger at a different voltage level, you would need to modify the circuit so the reference voltage would be at that new level.

As makers and hobbyists, our projects often use a wide range of input voltages dependent on what battery technology we have available and what voltages we require for the project. As such, there is no ‘cookie-cutter’ way to approach this task. This means that our battery level indicator would need to be adjustable to work with a whole range of applications.


Naturally, this need for an adjustable circuit led us to a programmable solution. Effectively, we can use a microcontroller to read the voltage of the battery and then compare that voltage against a user defined variable. We then simply have the microcontroller activate an LED accordingly. Not only does this make it very simple for the user to customise the threshold of the indicator, it also enables us to program different ways to display the results.

We have opted to use a single RGB LED to indicate the voltage using colours. This configuration allows the user to set multiple thresholds and the colour depending on the measured voltage.

For simplicity, we will set three voltage thresholds, specifically designed for use with a 12V lead acid battery. This project, however, can be programmed to work with many different battery voltages and chemistries, as long as the voltages fall within the design limitations.

This circuit has a minimum input voltage of 6V and a maximum of 15V, so it’s ideal for deep-cycle batteries. If your project uses a voltage that is lower or higher than this, we will explain the circuit and give you ideas on how you can easily adapt the hardware to suit your application.


We require a voltage divider circuit to convert the battery voltage being measured down to a safe voltage for the microcontroller analog pin to read, i.e. 5V.

This circuit allows us to put a higher voltage on the VCC side and step it down to a lower voltage, which is proportional to the ratio of resistors R1 and R2. This is calculated using the following formula:

Vout = Vin x (R2 / (R1 + R2))

Since we know that our 12V lead acid battery has a maximum charging voltage of around 14V, our Vin needs to be around 14V. Since our Vout can’t exceed 5V, we know that figure. To make the math simple, we can make Vin 15V as it’s a multiple of our output.

We can arbitrarily pick a value for R2, keeping in mind that we don’t want to create a significant load on the battery. As such, we picked a 100kΩ resistor for R2.

Therefore, to calculate R1, the formula is:

R1 = R2/Vout x Vin - R2

R1 = 100000/5 x 15 - 100000

R1 = 200KΩ

This combination of resistors will ensure that the input voltage does not exceed the 5V maximum for the microcontroller general purpose input and output (GPIO) pins. To keep the math nice and simple, we now simply multiply the voltage that we read on the GPIO by 3 to get the actual voltage.

If you, for example, want to read up to 20V. You can use the same formula and replace Vin with 20V, which will mean R1 needs to be 300KΩ. You would need to multiply the voltage read by the microcontroller by four to convert it back into the actual voltage.

Note: In our prototype, we used three 100KΩ resistors for our device as we didn’t have a 200kΩ.

For our final build, we also add a 100nF capacitor in parallel with R2. This capacitor, combined with R1, will produce a low pass filter. This will mean that any voltage present across R2 is the DC voltage we want to read and not any coupled noise or transients from the electrical system we are testing. If you’re using this device in a purely DC situation this capacitor can be considered optional.

We can calculate the roll-off frequency of this filter using the formula:

Fc = 1 / (2 x π x RC)

Where:Fc is the roll-off frequency in Hertz

R is the value of R1. i.e. 200000Ω

C is the value of the bypass capacitor. i.e. 100nF or 100 x 10−9F

Fc = 1 / (2 x π x 200000 x (100 x 10−9)) = 8Hz (approx.)


We have configured our project to work with a 12V system, which is a common voltage for many hobbyist projects and other applications around the home or workshop, including 12V electrical systems in a car, motorbike, caravan, boat, etc.

These automotive-type electrical systems, however, tend to be very noisy and often produce very large transient voltage spikes. These transients can exceed the 15V maximum input by a considerable amount. Albeit the transient itself tends to be very short-lived, in the area of a few hundred nanoseconds.

As such, we have included a 15V Zener diode and 100nF ceramic capacitor in parallel with the input to the project. The 100nF capacitor will give any short pulse transients a path to ground, while the 15V Zener will help to clip the longer duration pulses.

Note that this is just a very crude and inexpensive way to help protect your electrical circuits from common automotive transients. If you’re intending to use this device in a less hostile environment, the Zener diode is indeed optional but still recommended as it will protect the microcontroller GPIO from overvoltage.


We have opted to use the 78L05 5V low drop out “LDO” linear voltage regulator. This regulator will provide the 5V regulated DC voltage for the microcontroller. This device in the small TO-92 package has a maximum current delivering capacity of 100mA. This means, at maximum capacity with the maximum input voltage of 15V, the device will need to dissipate 1.5W. i.e. P = V x I = 15 x 0.1 = 1.5W.

This 1.5W is quite significant for such a small package. As such, we need to make sure that we are drawing as minimal current as possible. The prototype circuit was built using an Arduino Nano with the 5V from the LDO connected directly to the Arduino Nano’s 5V pin. Note this is not advised by Arduino, as it bypasses the input protection and could hypothetically damage the 5V regulator, as the output would be at a higher potential than the input.

However, in this configuration we have a total current draw of around 50mA with two LEDs illuminated and up to 80mA with all three colours lit up.

Nano = 20mA

LED = 20mA (each)

Voltage divider = 50μA

In this configuration, the 78L05 did get warm to touch but was still operating inside of the specifications. However, if you’re intending to run the device using the Nano, we highly recommend you use the LM7805 in the TO-220 package as it is much better suited for the higher power dissipation (i.e. 1A vs. 100mA).

The circuit is the usual linear regulator configuration with a 100nF bypass capacitor in parallel with the regulator’s output for noise reduction, and a larger 100uF electrolytic for ripple smoothing. Together, these capacitors provide a smooth regulated voltage for the microcontroller.


The final part of the circuit is the LED display. For our project, we used a single common cathode RGB LED. This one component can create different colours to signify various voltage levels.

Prototype Fritzing Diagram.

We calculated the required resistors by first measuring the forward voltage of each of the LEDs and using the following formula:

R = (Vcc - Vf)/If = (5 - Vf)/0.02


Vf = forward voltage

If = forward current

Vcc = 5V

ColourForward VoltageResistorNearest E24 Value

We wanted to get these resistor values as close as possible to ensure an even level of illumination/brightness for each colour, and will, therefore, allow the user to program very specific colours.

Note that the forward voltage for your LED may differ from the one we used, so we recommend you use a multimeter so you can calculate the ideal resistor for your LED.

The Prototype:

The prototype was built using a breadboard and the Arduino compatible Nano microcontroller development board. This allowed us to easily make changes to the program and test them without needing to use a second programmer.

In this configuration, the device had an average current draw of around 30mA with 12V on the input and only one of the LEDs illuminated at full brightness. This test was done using an LM7805 and confirmed that we could use the more inexpensive 78L05 variant, as the current draw was sufficiently low enough. However, if you wish to have all three LEDs illuminated to make various specific colours, we recommend using the LM7805 as the current draw will jump to an average of around 70mA. This regulator is not a low drop-out version, which will mean the minimum input voltage will be 7-8V. Keep this in mind if you’re wanting to use the device with lower voltages.

Note: A recommended safety measure would be to add a 500mA (or less) fuse in series with the power input. This will help protect and avoid the Zener diode catching fire, in case of a high current transient from an automotive system or similar.

Parts Required:JaycarAltronicsCore Electronics
1 x Arduino Compatible NanoXC4414Z6372A000005
1 x 78L05 Low Dropout Voltage RegulatorZV1539Z0466-
1 x 5mm Common Cathode RGB LEDZD0270-COM-00105
1 x 100µF Electrolytic CapacitorRE6310R4825CE05258
2 x 100nF CapacitorsRC5360R2865CE05188
1 x 15V 1W Zener Diode*ZR1415Z0636CE05126
1 x 2-way Screw Terminal BlockHM3130P2034APOLOLU-2440
1 x 110Ω Resistor*RR0549R7535-
1 x 120Ω Resistor*RR0550R7035-
1 x 160Ω Resistor*RR0553R7539-
3 x 100KΩ Resistors*RR0620R7070COM-10969

Parts Required:

*Quantity required, may only be supplied in packs. Breadboard and prototyping hardware also required.

Note we did not use a switch on the prototype, as we were originally intending on having an inline switch. A pushbutton switch was later added to the final design when soldering the circuit to perfboard.


The code is a fairly straightforward, and can be downloaded from the resources section of our website.

Essentially, all we need to do is read the analogue voltage across the voltage divider, convert that value into a voltage, and then multiply it by the multiplication/division factor, which in our case is 3.

However, there are a few user definable values that you can use to customise the project to suit your specific application. These are as follows.

const int SAMPLES = 10;
// used for averaging
const int NOM = 12;
// Battery nominal voltage
const int DISCHARGE = 11; 
//battery discharged voltage
const float FACTOR = 3; 
// division factor for multiplication

SAMPLES is the number of times we take a reading and is used to get an average result.

NOM is the nominal voltage of the battery you are using. For our application, a lead acid battery has a nominal voltage of 12V. This is essentially, in our case, the voltage level indicating half charge level.

DISCHARGE is the point the battery is considered flat or discharged.

FACTOR is the multiplication factor related to the voltage divider. You can find it by dividing the maximum input voltage Vin by the maximum output voltage Vout. In our case, 15V/5V = 3.

In the code shown here, we use a For loop to take x number of samples that we defined previously using the SAMPLES constant. This is used for averaging purposes, as we simply take the reading this many times each time, adding it to a variable to form a running total/summation. Once done, we simply divide that total by the number of samples to get our average.

voltage = (analogRead(A0) * (vRef / 1023.0));
//converts to voltage

This line takes a reading of the analog 0 pin and then multiplies that by Vref, which is 5 divided by 1024.

This 5 represents the 5V maximum of the ATmega328 ADC, and 1024 is the resolution the 10bit ADC can read. This means the minimum resolution the ADC can read is 4.8mV.

voltage = ((voltageSum / SAMPLES) * FACTOR);
// used for averaging

This takes the average of the readings and then multiplies it by the division/multiplication factor, which in our case, is 3. This removes the voltage divider effect in software.

for (int i = 0; i < SAMPLES; i++) {
voltage = (analogRead(A0) * (vRef / 1023.0));
//converts to voltage
voltageSum = (voltageSum + voltage);
voltage = ((voltageSum / SAMPLES) * FACTOR);
// used for averaging

The SETUP and LOOP code is too long to show here. We have made comments in the code to guide you on how it works.


Testing the prototype is pretty simple. With the circuit connected correctly and the Arduino Nano programmed with the prototype code, use a bench power supply as a battery substitute. You can adjust the voltage to the voltage divider input, being sure to not exceed 15V. At the same time, use the Arduino serial monitor to check the voltage reading. Verify that the voltage reading correlates with the input voltage. If your reading has a discrepancy check the resistor values used for the voltage divider.

The Main Build:

With the prototype working as expected, we can start work on the final design. We wanted the project to be as small and inexpensive as practical, so we knew we needed to replace our Arduino compatible Nano microcontroller development board with a smaller microcontroller.

We needed a microcontroller with at very least one analog input and 3 digital outputs, which is a fairly common trait among the ATtiny variants. As such, we designed the project to work with the ATtiny85 or even the slightly cheaper ATtiny13.

We added a button in the final design so that the device was not draining the battery it was there to test. The momentary push button only applies power to the project when held down, therefore, there is absolutely no load on the circuit until you want it to display the charge state.

ADDITIONAL Parts Required:JaycarAltronicsCore Electronics
1 x ATtiny85 or ATtiny13ZZ8721Z5105COM-09378
1 x 8 Pin DIP SocketPI6500P0550PRT-07937
1 x PerfboardHP9550H0701ADA2670
1 × Tactile SwitchSP0600S1120FIT0179

ADDITIONAL Parts Required:

OPTIONAL JaycarAltronicsCore Electronics
ATDEV Programmer-K9815-



Follow the circuit diagram and images shown here to build your main build. Note: See above for wiring diagram.


We have designed a 3D printable enclosure to house the project in. The case was designed using Fusion360, and you can grab the working files from the resources section of our website so you can modify the design as you see fit. Of course, we will also provide the .STL files so you can print your own enclosure if you wish.The enclosure consists of three individual pieces, which will comfortably print without support material. They are designed to press-fit together without the need for screws, however, you will still need some small screws to secure the PCB to the standoffs.

Note all of the components were sliced using Simplify 3D version 4.1.2


The button is a very small and quick print. It took about 10 minutes at 100-micron layer height using our Flashforge Creator Pro along with Flashforge PLA.


The enclosure was printed in transparent red Flashforge PLA at 100-micron layer height on our Flashforge Creator Pro. It’s designed to print flat on the bed, and takes around 2 hours to print.


The lid was also printed on our Flashforge Creator Pro using Flashforge PLA. It was printed flat on the build surface with no support, and took about one hour to print at these settings.


There are various methods to program an ATtiny. We recommend using the ATDEV programming shield from DIYODE Issue 14 with and an Arduino UNO, which forms an in-circuit serial programmer “ISP”. This process is fairly straightforward, however, if it is your first time programming an ATtiny, follow the instructions carefully to avoid damaging your microcontroller if you get it wrong. You can find the procedure for using this programmer here:


It’s important to check each of the connections are correct before first powering on the device. You want to pay special attention to the ground and VCC power connections. We recommend using your multimeter in continuity mode to physically check each of the microcontroller pins are connected as intended.

It is good practice to download the microcontroller’s datasheet and use the pin configurations section to double check each connection. You can find the datasheet for the ATtiny85 here:

Using your multimeter in continuity buzzer mode, attach one probe to pin 4 of the 8-pin dual inline package “DIP” IC socket and the other to the ground input of the screw terminal. If your multimeter beeps, you know that connection is good. Leave the probe on pin 4 of the DIP socket and place the other probe onto pin 8 to also ensure that there is not a short circuit to ground anywhere. If you don’t get a beep here that is great. Continue this process until all of the DIP socket connections have been verified.

Verify that:

  • Pin 2 goes to the output of the voltage divider
  • Pin 3 goes to the 120Ω resistor
  • Pin 4 goes to GND
  • Pin 5 goes to the 110Ω resistor
  • Pin 6 goes to the 160Ω resistor
  • Pin 8 goes to the output of the 78L05 regulator

After confirming the connections for the ATtiny are correct, it’s time to test that the voltage regulator and voltage divider are working as expected. To do these tests, you will need access to an adjustable power supply that provides up to 15VDC.

Note: The circuit only gets input voltage when the tactile button is depressed. You will not see any connection unless this button is actuated.

With the ATtiny still removed from the circuit, apply 15VDC to the input. Using your multimeter in DC voltage mode, put the cathode probe (black) onto the negative/GND input into the circuit. Put the anode probe (red) onto pin 8 of the DIP socket. You should see a reading of 5V. If so, you know that the voltage regulator is working as expected. If not, double check the solder joints for the voltage regulator portion of the circuit.

Next, with the cathode probe still placed on the gnd input, attach the anode probe to pin 2 of the DIP socket. With 15V on the input, you should see around 5V on this pin. If you adjust power supply to limit the circuit input to 10V, the voltage measurement on this pin should drop to around 3.3V. If this is not the case, double check the resistor values you have used and ensure the connections are correct.

If everything is working correctly, remove power from the circuit and insert the ATtiny85 into the DIP socket, making sure the IC is inserted correctly and around the right way. You can now apply a voltage between 7 – 15V on the input and the LED should illuminate. The colour will depend on the voltage applied and pre-defined levels. Using the code we provided, the LED should turn red for voltages below 11V, blue for voltages between 11-12V, and green for voltages above 12V.

Note: Avoid exceeding 15V when doing the tests to avoid possibly damaging the circuit.


This circuit is a simple building block circuit that you can easily modify to suit your needs. For example, you could easily attach a set of X60 connectors to the device that would allow you to attach it in line with a lithium battery pack and mount it to your remote-controlled vehicle. This would give you the battery status at the push of a button. All you need to do is define the voltage levels discussed earlier to customise the device to suit your specific use case.

Johann Wyss

Johann Wyss

Staff technical writer