Electronics, Software

Quick and Dirty ATTiny85 LiPO Battery Gauge

Portable projects require some kind of battery, but getting information on the voltage level or how much battery life you have left is a whole other problem. This can be solved by monitoring the voltage on the battery using an analog to digital converter, and then some firmware to read that digital value and produce a corresponding battery level.

it would be nice if we could just monitor the voltage as it goes from like 5v to 0v but it’s not that easy. This is because almost all electronics won’t work at close to 0v as the voltage regulators that supply current will cut out. Batteries will cut out even sooner, for example, lipo batteries can become unstable, die, and become a safety hazard if they discharge too low.

So what we can do is map the charged voltage and the battery cutoff voltage of our battery to a 100% to 0% range.

ATTiny85

I decided to use this Arduino-compatible microcontroller board to monitor the voltage for several reasons, it’s small, cheap, has several Analog and digital IO, and it runs Arduino so others could easily create their own.

Specs

  • Atmel ATtiny85 processor running at 16.5MH
  • Micro USB connector for easy programming
  • 8KB Flash memory, 6K program space
  • 6 Digital I/O
  • 3 PWM shared with the digital I/O
  • 4 Analog inputs shared with digital I/O
  • I2C and SPI interface shared with digital I/O
  • External interrupt capability
  • 3-5.5V or 7-12V Operation

Lipo Batteries

Lithium polymer batteries are almost the defacto battery that goes into portable devices. This is due to their superior capacity can current supply, but this comes with some disadvantages. One is that they need to be monitored continuously since they can easily become unstable if they are discharged too low or stored at an improper voltage.

What we need to do to determine the battery life is to characterize the output of the battery over time. This includes its maximum voltage at full charge, its cutoff voltage, and the safe minimum voltage, along with how long the battery stays at different levels during operation.

A good starting point is these tables of capacity to the voltage provided at the links below but I will also do my own testing as well.

Using the charged voltage of 4.2v and a minimum voltage of 2.84v in the following calculations.

Voltage Divider

I needed a way to remap the high voltage into the range of the ATTiny85. This is because I am running the ATTiny85 at 3.3v so that its IO is compatible with the Raspberry Pi IO. So to do this I used a voltage divider which uses a pair of resistor values to create a voltage value at a corresponding ratio. In my case a voltage source of 4.2v and the desired output of 3.3v.

I used these online calculators as references to quickly calculate the resistor values for the divider.

I then combined several common resistor values in series to create the correct R1 to R2 ratio for the divider.

I then soldered up a small protoboard with the resistor values and connected it up with some jumper leads.

Arduino Code and Programming

To program the ATTiny85 with the Arduino IDE you need to do some setup installing a library and board package. But I was only able to get it to work successfully on a Windows machine. Not sure why that is but if you follow the instructions in the first link below you should be able to program the device over USB.

The second link has some code examples of how to interface to the Digital and Analog pins. Here is an example of what your Arduino IDE should show when you have successfully programmed the ATTiny85. It’s a bit tricky as you need to start the upload and then plug in the ATTiny85 when the console tells you to.

A brief explanation of my battery monitor code is that it reads the analog voltage and converts that to a binary number output on 3 digital pins from 0 to 7. it is configurable by setting the MAX_V which should be the maximum value of your ADC which is a 10bit value for this board, the set the STEP_V which will determine when you transition from one value to the next. Setting this requires knowing the minimum safe battery voltage and dividing by 8. I intentionally made this step smaller to add a margin to keep the battery within a safe range.

Voltage per ADC division
Low voltage after divider mapped to ADC range

We can then write the code to step through these like a state machine based on the step voltage that we defined above.

#define MAX_V 1023
#define STEP_V 38
int voltage = 0;
void setup() {
pinMode(0, OUTPUT); //P0
pinMode(1, OUTPUT); //P1
pinMode(3, OUTPUT); //P3
}
void loop() {
voltage = 0;
for (int i=0; i < 10; i++) {
voltage = voltage + analogRead(1);
delay(20);
}
voltage = voltage/10;
//voltage = analogRead(1); //Read P2
if (voltage >= MAX_V-STEP_V) {
digitalWrite(0, HIGH);
digitalWrite(1, HIGH);
digitalWrite(3, HIGH);
}
else if ((voltage < MAX_V-STEP_V) && (voltage >= MAX_V-(2*STEP_V))) {
digitalWrite(0, LOW);
digitalWrite(1, HIGH);
digitalWrite(3, HIGH);
}
else if ((voltage < MAX_V-(2*STEP_V)) && (voltage >= MAX_V-(3*STEP_V))) {
digitalWrite(0, HIGH);
digitalWrite(1, LOW);
digitalWrite(3, HIGH);
}
else if ((voltage < MAX_V-(3*STEP_V)) && (voltage >= MAX_V-(4*STEP_V))) {
digitalWrite(0, LOW);
digitalWrite(1, LOW);
digitalWrite(3, HIGH);
}
else if ((voltage < MAX_V-(4*STEP_V)) && (voltage >= MAX_V-(5*STEP_V))) {
digitalWrite(0, HIGH);
digitalWrite(1, HIGH);
digitalWrite(3, LOW);
}
else if ((voltage < MAX_V-(5*STEP_V)) && (voltage >= MAX_V-(6*STEP_V))) {
digitalWrite(0, LOW);
digitalWrite(1, HIGH);
digitalWrite(3, LOW);
}
else if ((voltage < MAX_V-(6*STEP_V)) && (voltage >= MAX_V-(7*STEP_V))) {
digitalWrite(0, HIGH);
digitalWrite(1, LOW);
digitalWrite(3, LOW);
}
else {
digitalWrite(0, LOW);
digitalWrite(1, LOW);
digitalWrite(3, LOW);
}
delay(1000);
}

The analog read is averaged over 10 reads, and a delay of 1000 is used to prevent the code from updating too often.

Raspberry Pi Interface

With this simple 3-pin digital interface, I can quickly set up the Raspberry Pi GPIO to be simple inputs in a python script to read the pins as a binary value.

GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

Installed in Pieca Camera

This device was designed to be installed in my Raspberry Pi camera project that uses a PiSugar battery system to easily monitor the battery life and shut down gracefully when the battery was close to cutting out.

Testing

After everything was connected up I was curious about the battery life, so I wrote a small python script to write the time and the battery level to a CSV file that I could plot in excel or LibreOffice calc.

import RPi.GPIO as GPIO
import time
from datetime import datetime

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(24, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
GPIO.setup(3, GPIO.IN)
GPIO.setup(26, GPIO.IN, pull_up_down=GPIO.PUD_UP)

while True:
    print(GPIO.input(24), GPIO.input(23), GPIO.input(22))
    print()
    print(GPIO.input(3))
    print()
    print(GPIO.input(26))
    print()

    file1 = open("test.csv", "a")
    now = datetime.now()
    current = now.strftime("%H:%M:%S")
    file1.write(current+",")
    file1.write(str(int(str(GPIO.input(24))+str(GPIO.input(23))+str(GPIO.input(22)),2))+ "\n")
    file1.close()

    time.sleep(1)

This shows that we are getting about 5 hours of battery life before the power cuts out, this is with a 5-inch touch screen attached so it would likely be much longer with a bare Pi and PiSugar battery system.

From here you can now implement battery monitoring in your application to perform functions like turning off the device automatically or displaying this info to a user interface.

GitHub

If you would like to implement your own ATTiny85 battery monitor for your project all the code is located on my GitHub repo.

Tschucker: ATTiny85-battery-monitor