The Ultimate Single Cell Lithium UPS for any Raspberry Pi

Public Chat
Similar projects worth following
This is an Uninterruptible Power Supply, employing a single cell lithium battery, for all raspberry pi computers. It will function with both Li-Ion and LiFePO4 battery chemistries. The UPS communicates over an I2C interface and provides charger status and UPS status. Maximum output current is 2.5A.


This is what this UPS must do:

  1. Communicate to the Pi the battery chemistry used and the charging status (Charging or Fault). This is a must for this application, where multiple chemistries are allowed.
  2. Provide power to the output ( the Pi power input) when external power is lost by drawing from the battery source.
  3. Let the Pi know that this has occurred, using a 2-pin I2C interface, so that it can do a proper shutdown at an appropriate time before the battery is exhausted.
  4. Allow the Raspberry Pi to monitor the state of the battery to decide when to shutdown its power. As a last resort the UPS should notify the Raspberry Pi that the battery is exhausted and that it is shutting off power.
  5. Accept a shutdown command from the Pi and handshake to confirm the shutdown command was received, and after shutdown is complete, switch the Pi off by disconnecting the power.
  6. Reconnect the power to the Pi, which causes the Pi to boot, when the external power returns.
  7. Maintain the battery in good condition near its maximum capacity.

New Stuff:

I was researching battery management ICs when I ran across the LTC4040. It is billed as a "2.5A Battery Backup Power Manager". The similarities between the LTC4040 and the LTC4041 (which I was using for a supercap UPS circuit -- see my other projects) were too much to ignore. I already had several working UPSs using a lithium battery -- why another one?

After spending a bit of time reading and understanding the data sheet I thought that the LTC4040 could add a lot of capability that was missing in the previous design:

  1. Multi-chemistry charger: capable of charging/maintaining both LiIon and LiFe batteries.
  2. Programmable charge termination voltage: four programmable termination voltages for each battery chemistry to maximize battery longevity.
  3. Synchronous buck charger for increased efficiency.
  4. A safety timer for the charger.
  5. A temperature sensor to prevent charging outside safe limits.
  6. A fault indicator for problem batteries.
  7. Automatic switchover from AC input to battery backup when AC power fails.
  8. Input current limiting to prevent overloading the AC adapter when charging.
  9. Potentially lower current drain from the battery in both normal mode and sleep mode.
  10. Lower component count -- the jury is out on this one. The LTC4040 replaces a $0.60 stand-alone Li battery charger and the Ti boost converter ($2.50). It also simplifies the input switch.
  11. Lower cost -- OK it's not. The PCB is the same size, and the LTC4040 is about $6, which is more than the cost of the two ICs that it replaces, but you're getting a lot more functionality. I don't know if it's worth it yet. [Edit 2019-03-03:'s worth it. The ability to monitor charging and faults via the I2C interface is probably worth a couple extra dollars.]

The Current Schematics:

The schematic is not very pretty. That's because the connections of the PIC micro controller were decided by the efficiencies of the layout rather than niceties of the schematic page. There are now two versions -- a "Hat-like" version using a 14500 battery, and a standalone version using an 18650 battery.

Originally, I was going to use a 3 position DIP switch to program the battery chemistry and the charge termination voltage without the Raspberry Pi being aware of those parameters, since there were not enough pins on the PIC to monitor all of it. The problem with that approach is that the Raspberry Pi, and the PIC, can't know which battery chemistry is being used, and therefore it can't determine when to pull the plug and shutdown the system in order to prevent damage to the battery. I then considered, briefly, allowing the PIC to set the battery chemistry and charging parameters via software. The problem with using software to set the charger parameters is that it is potentially susceptible to being hacked and...

Read more »

Python program to monitor the ups and shutdown the Raspberry Pi when the power has failed and the battery is near exhaustion.

x-python-script - 10.11 kB - 03/04/2019 at 20:34


Gerber files to make a PCB for the 18650 version.

Zip Archive - 68.57 kB - 03/04/2019 at 20:34


Gerber files to make a PCB for the Hat version.

Zip Archive - 59.33 kB - 03/04/2019 at 20:33


This is the Microchip MPLAB project for the PIC16F18323. It includes the assembler code and hex files for programming the PIC. The 18650 UPS version uses the same code.

Zip Archive - 86.16 kB - 02/19/2019 at 16:39


  • This Is The End (I Hope).

    Bud Bennett03/05/2019 at 21:45 0 comments

    I assembled two Hat versions and two 18650 versions. All PCBs are the latest revisions. All of the boards are functional, but I haven't bothered to take extensive parametrics. One of the 18650 boards is in service to my seismometer. The other 18650 board will be a spare. One of the Hat versions will find use in support of the various Raspberry Pi boards performing various functions around the house. The other has been shipped to a friend in The Netherlands that has been an invaluable consultant and sounding board for these kinds of things.

    Discovered A Design Flaw:

    I confronted one of my design assumptions today when swapping out an older UPS version for this version to service my seismometer. When I upgraded my seismometer I did not connect the UPS back into the system properly. Consequently, the backup battery drained to unsafe levels. When I transferred the battery, which had now drained to a voltage around 1V, into the U1LiUPSRPi-18650 version the UPS failed to supply power to the Raspberry Pi. 

    The problem was that I assumed that the battery would almost always have a potential greater than the POR threshold of the PIC16F18323. In this particular case it did not, so the PIC kept the LTC4040 shutdown. Without the LTC4040 activated the output voltage of the UPS did not increase to the point where the PIC could operate. I removed the battery and performed a recovery operation on it (10% charge current rating until the battery voltage exceeded 2.5V), then reinserting the battery when the voltage exceeded 3.2V. At this point the UPS worked properly again and proceeded to charge the battery to full.

    If this was a commercial product I would be iterating the design at least once more. The UPS should function whenever a valid supply voltage is available at its inputs. But since it is primarily for my own's good enough, as long as I'm aware of its shortcomings. BTW: I think the flaw could be corrected with another low current Schottky diode from the input to the PIC VDD pin. 

    LiFePO4 Disappointment:

    I purchased four PKCELL 14500 LiFePO4 batteries from Amazon to see how well they perform compared to the 14500 Li-Ion cells. Just comparing W-hr the LiFePO4 are inferior:

    W-hr = 600mAh x 3.2V = 1.9 W-hr for LiFePO4

    W-hr = 650mAh x 3.7V = 2.4 W-hr for Li-Ion.

    It also appears that the LiFe cells have a relatively high internal resistance and high leakage current. I measured the current that the UPS drains from the battery terminal as 45µA, so it should be weeks between recharge events. But the log is reporting that the battery need recharging every day:

    2019-03-06 08:07:50,872 INFO     *** UPS Monitor is active ***
    Battery Type is LiFePO4
    Charge Termination Voltage = 3.6
    2019-03-06 14:25:14,430 INFO     Battery is charging. VBAT = 3.46
    2019-03-06 14:25:34,453 INFO     Battery charging terminated. VBAT = 3.63
    2019-03-07 14:31:23,550 INFO     Battery is charging. VBAT = 3.63
    2019-03-07 14:31:33,563 INFO     Battery charging terminated. VBAT = 3.63
    2019-03-08 14:34:55,227 INFO     Battery is charging. VBAT = 3.60
    2019-03-08 14:35:05,277 INFO     Battery charging terminated. VBAT = 3.63
    2019-03-09 14:35:14,757 INFO     Battery is charging. VBAT = 3.47
    2019-03-09 14:35:34,780 INFO     Battery charging terminated. VBAT = 3.63
    2019-03-10 15:33:21,720 INFO     Battery is charging. VBAT = 3.47
    2019-03-10 15:33:31,732 INFO     Battery charging terminated. VBAT = 3.63

    The recharge event is usually less than 1 minute. I had to change the update time for the UPS monitor program to 10 seconds in order to catch the event. I don't know what effect that will have on battery longevity. I do know that the LiIon 14500 cell did not require a recharge for at least three weeks.

    If I find anything else that's noteworthy I'll post it here.

  • U1LiUPSRPi Python Code

    Bud Bennett03/04/2019 at 19:17 3 comments

    I assembled the latest PCBs for the Hat and 18650 yesterday. Both are performing as designed. Time to finalize the code. The logging information contains the battery chemistry and the charger termination target voltage when the ups monitor program begins. The log also records power failures, power recovery, fault events, charging events, and shutdowns. 

    # Name:
    # Purpose:
    # Author:      Bud Bennett
    # Created:     31-01-2019
    # Copyright:   (c) Bud Bennett 2017
    # Licence:     <your licence>
    This program monitors the status of the UPS via the i2c interface and
    manages the Raspberry Pi shutdown when the battery is exhausted.
    Normally: This programs powers off the Raspberry Pi when UPS indicates that
    PWRGOOD = 0 (power has failed) and the battery voltage is less than 3.0V (for LiIon) or
    2.75V (for LiFePO4).
    Otherwise: The UPS will assert BATLOW = 1, wait 20 seconds and then disconnect power when
    it determines that the battery has dropped below 2.75V (for LiIon) or 2.5V (for LiFePO4).
    If the UPS battery voltage (VBAT) is greater than 3.3V, the status is checked
    every minute. If VBAT < 3.3V then the checking interval is shortened to 1 second.
    Normally: If the Pi sets SHUTDN = 1 then the UPS asserts BATLOW = 1 as confirmation and
    immediately begins a 20 second timer to terminate the power.
    Otherwise: When the UPS asserts BATLOW = 1, the UPS will wait 5 seconds for the
    Raspberry Pi to acknowledge by setting the SHUTDN bit. If the Pi has
    not set the SHUTDN bit within the 5 second period then the UPS begins the 20 second timer irregardless.
    So the Pi must initiate it's shutdown sequence immediately after receiving the BATLOW
    signal from the UPS and sending/confirming the SHUTDN acknowledgement.
        from smbus import SMBus
    except ImportError as e:
        print("Python module smbus not found, install it first")
        # Install the i2c interface using the following instructions:
        # sudo apt-get install i2c-tools
        # sudo apt-get install python-smbus
        # sudo raspi-config , goto Interfacing Options, enable i2c
        # sudo reboot # to make the i2c interface active
        # now check to see if the USB i2c device is present at address 36
        # sudo i2cdetect -y 1
    import time, os
    from datetime import datetime
    import logging
    import logging.handlers
    import traceback
    import sys
    # instantiate smbus interface
    i2c = SMBus(1)  # set interface to i2c-1 (newer RPi's)
    #--logger definitions
    # save daily logs for 7 days
    LOG_FILENAME = "/var/log/bud_logs/ups_mon.log"
    LOG_LEVEL = logging.INFO  # Could be e.g. "TRACE", "ERROR", "" or "WARNING"
    logger = logging.getLogger(__name__)
    handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=10000, backupCount=4) # save 4 logs
    formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
    class MyLogger():
        A class that can be used to capture stdout and sterr to put it in the log
        def __init__(self, level, logger):
                '''Needs a logger and a logger level.'''
                self.logger = logger
                self.level = level
        def write(self, message):
            # Only log if there is a message (not just a new line)
            if message.rstrip() != "":
                    self.logger.log(self.level, message.rstrip())
        def flush(self):
            pass  # do nothing -- just to handle the attribute for now
    # Global flag to print more information
    DEBUG = False
    # If this script is installed as a Daemon by systemd, set this flag to True:
    DAEMON = True # when run as daemon, pipe all console information to the log file
    # --Replace stdout and stderr with logging to file so we can run it as a daemon
    # and still see what is going on
    if DAEMON :
        sys.stdout = MyLogger(logging.INFO, logger)
        sys.stderr = MyLogger(logging.ERROR, logger)
    def read_ups_data(address=0x36, cmd=0x00):
    Read more »

  • The 18650 Version.

    Bud Bennett02/19/2019 at 16:37 0 comments

    The latest schematic for this version is shown in the details section of this project.

    Now that the Hat version is pretty much performing to specifications, I'm ready to expand the UPS to an 18650 battery form factor. The only schematic differences between the two versions is the increased charging current to 1A and the removal of the LED indicators. Typical 18650 Li-Ion batteries have a capacity around 1.5-2.5Ah, so the UPS should charge the battery at about 1/2 C rate. More than this would overload a typical AC adapter rated for 2.5A @5V. 

    Almost all of the components are mounted on the top side of the PCB, which is the same width as the battery holder. The reset push button, 10K NTC thermistor, JST connector for the I2C interface, and the output voltage terminal block are also mounted on the bottom side of the PCB. I expect this unit to be mounted with the battery holder facing up, and servicing a headless Raspberry Pi in a closet somewhere, so the LED indicators were removed.

    New PCBs for both versions were ordered from JLCPCB in 1 oz copper. I expect to receive them in a bit more than one week.

  • First Pass Preliminary Test Results

    Bud Bennett02/01/2019 at 05:24 0 comments

    These PCBs are technically the second pass, but I never assembled the first pass PCBs because they did not have the proper cutout to fit the case for the Raspberry Pi. 

    Executive Summary:

    The performance, as far as tested, meets the design objectives. There is one functional failure -- the RESET function doesn't put the part to sleep when power is not applied -- the fix is easy but will require another PCB turn.

    Functional Testing:

    • When the battery was inserted, or the RESET button was pressed, the circuit immediately went into backup mode by providing output power via the battery. I was expecting the part to enter sleep mode waiting for power to be applied. Pressing the RESET button caused the circuit the enter backup mode until it was released, then the output voltage would fall for a second but then recover back to backup mode. The cause of this phenomenon was that R15 was connected to GND, so when RESET was pressed and the PIC outputs all went to high impedance the LTC4040 was enabled instead of disabled. The fix is to connect R15 to either BAT or the PIC's VDD supply. I cut R15's trace to GND and rewired it to BAT and this problem went away. This fix will require another PCB pass.
    • The primary ability of automatic switchover to battery power appears to work well. I've been testing this function on a Raspberry Pi Zero W for a couple of days without a glitch.
    • All of the PIC code appears to work well. The I2C interface is transmitting all of the new status bits and the Raspberry Pi can easily decode them without error. The PIC properly controls the on/off state of the LTC4040 and responds when the ACPR line is pulled low by the LTC4040. It also properly detects ACPR when in sleep mode. The PIC code doesn't appear to require any changes for functionality.
    • The battery charger is functioning properly. At first the charger current was very low because the PROG pin was not connected. A bit of re-soldering fixed it. The DIP switches alter the charge termination voltage to correct levels. The Charge LED and Fault (cold) LED work as designed and the status is passed to the RPi via I2C.

    Parametric Testing (room temp, Vin = 5.2V):

    • Battery current when shutdown: 17.8µA @ 4.1V, 10µA @ 3.5V.
    • Battery current when AC is present but not charging: 47.5µA.
    • Output voltage: 4.79V with 4Ω load. No change with lighter load.
    • Battery Charge Current: 640-670mA (target = 600mA)
    • Battery termination and recharge voltages: 

    (This chart shows the wider spread reflected in the data sheet for when F2=0 and F1 = 1)

    • ADC voltage error: +40-50mV (about 1.25%, which is acceptable)
    • Booster efficiency with 4Ω load: 90% for VBAT = 4.1V, 77% for VBAT = 3.0V
    • Input current limit on AC adapter: TBD.
    • Fault thresholds on NTC pin: Cold = 75%, Hot = 34.6% (slightly low, but in spec).

    Testing to date shows performance meeting specs, except for battery charging current, which is too high. The low currents from the battery indicate that it will be a long time between recharge events so expect long battery life.

  • Coding for the PIC

    Bud Bennett12/11/2018 at 02:11 0 comments

    I updated the assembler code for the PIC.  Here's the latest flow chart:

    The watchdog timer is disabled. There isn't really anywhere for the program to hang (famous last words.) The only interrupt driven routine is the I2C interface -- and if it hangs then all is lost anyway.
    The ACPR input is used to both wake up the PIC from sleep (ACPR going low to high) and changing the behavior when the AC power fails (ACPR low). Most of the other code differences revolve around the increased pin count and having to store the pin data in the I2C_STAT register to be transmitted over the I2C bus.

    The flowchart also shows how the F0-F2, CHRG_ and FAULT_ pins are driven to zero when the PIC is running off the battery voltage but change to inputs with weak pull-ups when running off the 5V UPS output rail.


    Here's the flow chart for the subroutine to check the battery voltage:

    FVR is the Fixed Voltage Reference (set to 4.096V). The FVR and the ADC are normally shutdown when not needed to reduce power. The normal duty cycle is one sample per second. The rolling average is 8 samples.

    After a bit of real-time debugging I will post the latest code to the files section of this project. I will also keep this log updated with the latest info regarding the PIC code.

  • Design Considerations

    Bud Bennett12/07/2018 at 10:33 0 comments

    This is a distillation of the thought process involved with putting this UPS together. There are always compromises to be made. I will go through the trade-offs involved with this design -- section by section. It's a bit long-winded, but I found it is best to put it into words before you spend money on parts/services. There was a saying in my engineering design circles, "If you can't explain how it works, you don't know."

    Basic LTC4040 application

    I haven't deviated much from what Analog Devices recommends for the LTC4040. Refer to the LTC4040 data sheet for particulars. I have included a couple of band-aids to help alleviate potential problems base upon my experience with the LTC4041 -- a supercap back up manager. 

    There are multiple ceramic capacitors at the load and also at the battery. This is normal SMPS procedure to place a smaller ceramic capacitor along with a larger ceramic capacitor at the input/output of the switching regulator. The smaller cap has a higher resonance frequency and tends to quell the HF stuff. The larger cap provides a lower Z but has a lower resonance frequency. The combination of the two capacitors covers a larger range and tends to prevent problems with ringing in response to switch transients. There is also a large tantalum capacitor, C4, which is a tank capacitor to hold up the output when the input power fails. 

    The input switch

    The LTC4040 data sheet block diagram shows two input switches required to disconnect the input from the load. The basic application of the LTC4040 shows an NMOS FET connected with its source at the input and the drain connected to the load. This doesn't work for this application because the body diode of the FET will keep power applied to the load even if its gate is grounded. This application can get away with a single FET switch to disconnect the input from the load because the input source nearly always (always) becomes a high impedance when the AC power fails. In this case, M1 holds the input voltage at a diode drop below the output voltage because the FET drain-source is reversed. When IGATE is pulled to GND the FET turns off and allows the load voltage to drop to zero.

    Note: if the input power source fails by shorting its output to GND then this approach would not work. In my experience all 5V AC adapters become high Z when the AC power fails.

    The TSM038 is a logic level FET switch with 4mΩ Rdson, 30V BVDSS and can handle ± 20V from gate to source, which is necessary because the IGATE voltage can exceed 8V from gate to source. It costs about $0.60 in low quantities.

    Safety First

    I had originally intended that the battery charger parameters be programmed by a 3 position dip-switch. I got rid of the dip-switch when I upgraded the PIC controller to 14 pins, thinking that all the charger parameters could be programmed by the Raspberry Pi via the I2C interface. But this idea quickly fell by the wayside simply because it would expose the charger parameters to being hacked, which is a safety issue. So the dip-switches came back (albeit in SMD form) and the PIC is simply informing the Raspberry Pi about the switch settings and has no control over them. The user must set the dip switch positions when he/she installs the battery into the UPS.

    I also implemented the thermistor sensor to prevent charging outside a safe temperature range. The sensor must be attached to the battery, not to the PCB, in order to be effective. I figure you can just tape it to the battery. Having the thermistor attached to the battery will prevent dangerous situations that could cause the battery to overheat and catch fire. At cold temperatures the battery electrolyte freezes so it is not a good idea to charge it under those conditions. The thermistor...

    Read more »

View all 6 project logs

Enjoy this project?



Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates