paulv
Posts: 564
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

A safe and precise Pi power supply with UPS & Shutdown

Mon Aug 25, 2014 7:59 am

As several (too many) of you have experienced, a sudden loss of power can corrupt the SD card for the Pi. This happened one time too many for me, so I set out to fix this once and for all. I was also a getting a little tired of WiFi outings due to a 5V supply that really wasn’t supplying the Pi with 5V.
So, there were a few objectives that I wanted to cover in my solution.
  • 1. A stable and precise 5V, measured between TP1 and TP2 on the Pi itself.
    There are a lot of issues with wall-warts not supplying the right voltage, and using a USB cable to feed the Pi drops precious volts due to the very tiny leads in those cables. You could easily drop a few 100 mV through a short cable.
    2. The current requirements for the Pi are critical if you add peripherals like a WiFi-dongle, let alone stuff fed through the P1 connector. A stable supply that delivers about 1 Amp will take care of most problems. Typical wall-wart chargers claim they do, but imagine 1 Amp going through these tiny, tiny wires in the USB cable and you should be concerned if that ever reaches the Pi.
    3. A safe and controlled shutdown (halt) process that will preserve whatever program is running on the Pi. I especially wanted a better test-bed for my own hardware and software developments, which are inherently unstable.
    A good shutdown sequence is especially critical for programs that run as a daemon, run out of RAM (when you use tmpfs), or that need to preserve run-time information.
    4. A button to wake the Pi up when it has been halted.
    5. Finally, I wanted to use standard parts as much as possible and they should not be fancy or expensive. There is no need to order PCB’s, there are no SMD components, and everything can be built on vero-board. Everybody with some basic knowledge of electronics and can handle a soldering iron should have no issues with the design.
What this will NOT provide is a power supply for a 24x7 embedded application, because you will need a lot more than just providing stable power and a back-up supply. In those cases, you also need to provide for a clean start-up when the power returns. This seems easy to do, but when you look into it, it’s not so easy, believe me. However, if this is what you’re after, look at another post where I describe a solution. It’s long because it covers the fact finding process, but it should be a good read if that’s what you’re after.

Here’s that post:
http://www.raspberrypi.org/forums/viewt ... 37&t=50470

So, starting with our set of requirements, these are the main building blocks:
  • 1. A stable 12V DC wall-wart that can deliver upwards of 1A. Typically, you can find these feeding your router or modem. The leads are typically ending in a barrel connector, and the cables are really fit to supply an Amp or more.
    2. A simple DC-DC step-down convertor to go from the 12V DC to an adjustable 5V. They can be purchased for less than $10 of eBay. Delivery times can be long, because they typically come from China. You can also check Amazon. Google for “dc step-down converter module”
    3. A battery holder for 4 AA or AAA cells. And 4 NiCad or NiMH rechargeable cells. The capacities of the cells depend on how long you want to use the UPS function. I use 2600mAh cells and they will provide power for several hours. They cost more, and if you only need to provide power for the shutdown sequence, you need power for only a minute or so. In that case, almost anything goes. The charging/trickling is simple and not as dangerous or tricky as with LiPo cells.
    4. A DC-DC step-up convertor to go from the battery supply to an adjustable 5V. They also can be purchased for less than $10 of eBay. Delivery times can be long, because they typically come from China. You can also check Amazon. Google for “dc-dc step-up converter module”
    5. Some electronic glue to trickle-charge the cells, to detect the main power-loss, to switch between the two supplies, and to issue a shutdown sequence when the batteries are getting empty. Plus two push buttons for shutdown and restart. Most of you will have the electronic parts in their stash, nothing is really critical.
Here is the schematic of my solution that I will explain function by function:
Pi Supply Schematic.gif
Pi Supply Schematic.gif (54.03 KiB) Viewed 5604 times
The 12V DC enters from the left. If you don’t use different kind of 12V DC supplies and don’t use the barrel connectors, you probably don’t need D1. It is there to avoid damage by +/- reversal connections. D1 can also be a “normal” diode like the 1N4001.

R4, R5, C1 and C2 will create a 3V3 max signal when the mains is on, and that will drop to 0 volt when off. C1 and C2 are there to filter out spikes, and to make the transition smooth. This signal goes to one of the GPIO pins as input.

The circuit around D3, R1, D2, T1, R2//R3 will take care of the trickle charge for the batteries. T1 with R1 and D2 will drop the 12 V DC with the voltage of D2 (5V1) and the resistors R2 and R3 set the charging current. I selected a current of about 100mA, so R2 and R3 need to be 100 Ohms and of a minimum of ½ watt each. The 2N2219A can handle 800mA but will get pretty warm, so I recommend a cooling fin cap.

The 12 V DC goes through a PTC fuse, and goes to the DC-DC step-down convertor. The output goes through the diode “mixer” with D4 and D5, and passes through yet another PTC fuse and out to either P1-1 and P1-3 of the Pi, or through a USB cable to the micro USB connector if that is what you prefer. If you feed the Pi through P1, note that I don’t have a TVS diode over the 5V output.

The voltage from the batteries goes through another PTC fuse, F2 and then to the DC-DC step-up convertor, and from there to the “mixer” diode D5 and out to the Pi.

With this, you already have a functional system.

Warning:
Both DC-DC converters can produce lethal voltages for your Pi. Be careful to set the voltages! I used the following procedure.
Do not connect the Pi yet! First adjust the voltage of the step-down convertor to measure 5V1 at the anode of D4. Then adjust the step-up convertor to deliver 5V0 at the anode of D5. Only then is it safe to connect your Pi! I suggest you do that the very first time without the SD card present, but with the rest of your hardware (Wi-Fi etc.) connected.
You can then proceed to fine-tune the voltage of the step-down convertor again by measuring 5.0 V at TP1 and TP2 on the Pi. Then move the test leads to the anode of D4 and ground and measure that voltage. Then move the test leads to the anode of D5 and ground, and adjust the step-up convertor output to be 100mV less than that of the measurement on the anode of D4. This will ensure that the mains driven step-down convertor is always supplying the Pi, and when that falls away, the batteries will take over. The Pi will not care about the 100 mV difference when the “mixer” switches between the supplies.
After you have done this, it's time to let the Pi power-up normally with the SD card (it consumes more power now) and to redo the fine tuning again.

The circuit around IC1, which is a comparator around an LM311N, is used to measure the discharge level of the batteries. At a certain level, the comparator switches and this signal can be used to start a shutdown sequence to halt the Pi. The threshold can be set by R7 so you can either drain the batteries until the very end (say 0.9 V per cell), or shutdown much quicker. You can even do that in software so you keep the comparator for emergencies only.

Because the trickle charge is relatively small, it can take more than 26 hours to fully charge my cells again. If you expect many main outages, and your application does not need to run very long on the UPS, you can adjust the level such that the UPS time is short.
The comparator gets the voltage level from the batteries through R6. C6 and C5 are there to filter this signal. The reference for the comparator is set by R7 and R8, and this is stabilized further by C3. R10 is there to create a hysteresis, so there will be no jitter during the cross-over period. The LM311N is an open collector device, so we need R11. The output will go to the 5V rail, which is too high for the GPIO input pins. R12 and R13 drop that to a max of 3.3 V.

If you want, you can use 3V3 Zener diodes instead of R5 and R13 to further protect the GPIO pins. (a Zener needs about 5mA, so you need to change the values of R4 and R12)

If you’re not experienced, I suggest you get a 5V1 zener diode with a minimum of 1W, better 3W or even 5W, and connect that at the output to the Pi, after F3, and keep track of the temperature of this zener. This zener will bleed-off any access voltage to the Pi, but will dissipate that in heat. As a minimum, keep that zener there during your testing, but keep in mind that the tuning of the step-down regulator to get 5V0 across TP1 and TP2 may not work well, if the cable to the Pi drops the voltage too much. Again, keep track of the temperature of the 5V1 zener, or keep the voltage below 5.1V!

Switch S1 and R9 provide a manual shutdown method. The software makes sure that you need to press it for several seconds, to avoid accidental shutdowns.

Switch S2 will either reset the Pi, or kiss it awake after it has been put into a halt state by the shutdown sequence. Note that the CPU continues to run in single user mode and the Pi will still draw some current (about 70mA). This is one reason not to deplete the batteries completely. However, should the voltage of the batteries drop to about 2.7V, the DC-DC step-up convertor will shut off and the Pi will become powerless. If that happens and the main returns, the Pi will boot normally. The 2.7 volt is set by the DC-DC convertor design or chip used, so read the specs.

Note however, that after the shutdown and halt of the Pi, and before the shutting-off of the step-up convertor output, the Pi will not awake by itself when the mains returns. You can only manually bring it back to life with S2. As I mentioned above, if this is not good enough for you, look at my other posting.

So this is it for the hardware. Now we need to design the software such that the Pi can handle all this and we have a closed-loop system.
I will show you some pieces of the code. I have written a test program that runs standalone, which is fine for many applications, but you can also add the pieces to your application program, and have everything in one place.

First are the GPIO port (or pin) declarations that I used. Note that it’s not a good idea to use GPIO ports that already have a pull-up resistor, because that messes with the signal levels. Google for the GPIO specifications if you’re not sure which pins to use.

Code: Select all

# ----- GPIO Channels
PWR_sense_P = 17    # P1-11 input
PWR_lo_bat_P = 27  # P1-13 input
Next are the GPIO pin function and action declarations. Note that I use the GPIO “event_detect” methods, which act as an interrupt. There is no need for loops of any kind.

Code: Select all

if DEBUG : GPIO.setwarnings(True)
else : GPIO.setwarnings(False)

# Use the Raspberry BCM pins
GPIO.setmode(GPIO.BCM)

# Mains power supply sensing
GPIO.setup(PWR_sense_P, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

# low battery and manual shutdown button setup
GPIO.setup(PWR_lo_bat_P, GPIO.IN)

# --- this needs to be at the end of the port definitions, otherwise the detections don't work!
# Both events will act as an interrupt.
# setup the event to detect a falling edge on the main power sense input
GPIO.add_event_detect(PWR_sense_P, GPIO.BOTH, callback=mains_power_drop, bouncetime=2000)
# setup the event detection for the low batt level and also the shutdown button
GPIO.add_event_detect(PWR_lo_bat_P, GPIO.RISING, callback=low_bat_action, bouncetime=2000)
Next are the two interrupt service routines or callback functions.
One for the signaling of the main supply when it’s dropping, and one that covers the low battery voltage. With the documentation they should speak for themselves.

Code: Select all


def mains_power_drop(PWR_sense_P):
    '''
    This function controls the Power Supply.
    If a loss of mains power is detected, the supply acts as a UPS.

    When the voltage over the NiMH cells has reached about 0.9 V, a GPIO port goes high,
    and a shutdown -halt is issued to preserve the integrity of the programs running.

    The Pi will continue to drain the UPS until the input of the DC-DC step-up converter has reached
    about 2.7 V, at which point it shuts the output power off and the Pi will be powerless.

    When the Pi is powerless and the main power returns, the Pi will boot normally, and the NiMH's will start
    to charge (slowly).

    '''
    global MAIN_POWER

    try:
        # we get here because the GPIO system caused an interrupt when it saw any edge on the port
        # or when the S1 button got pressed for about 5 seconds
        sleep(5) # let the level settle, but realise that the GPIO system can continue to fire these interrupts!

        # if power was lost the edge was going down and the port should be low by now
        # we can play UPS for a while before we perform the shutdown.
        # if there was a power glitch, we'll let the Pi continue
        if GPIO.input(PWR_sense_P) == 0 :
            write_log("warning", "Loss of main power detected")
            if TRACE : print "Loss of main power detected"
            sleep(5) # wait it out a bit
            MAIN_POWER = False
            return

        # if power came back, the edge was going up and the port should be high by now
        if GPIO.input(PWR_sense_P) == 1 : # power came back
            if TRACE : print "Power came back during UPS period"
            write_log("system", "Power came back during UPS period")
            sleep(5)
            MAIN_POWER = True
            return

    except Exception as e:
        # in case that there is an exception, force the shutdown right now!
        #
        print "Exception in ups power_action : ", str(e)
        write_log ("error", "Exception in ups power_action, halt the Pi  : "+str(e))
        subprocess.call(['logger "Exception in ups power_action, halt the Pi : "'+str(e)], shell=True)
        subprocess.call(['shutdown -h now "System shutdown due to error in ups power_action" &'], shell=True)
        sleep(60)
        return

Code: Select all

def low_bat_action(PWR_lo_bat_P):
    '''
    This function detects if a low battery level is coming from the UPS supply.
    There will be only a short period of reliable power left, so the Pi must be
    shutdown and halted.

    When the main power returns, the Pi will NOT reboot automatically from the halt state.
    That will only happen when the steup-up converter has shut itself down when the battery
    level came below 2.7V or so.

    Manually, you can press S2 to wake it up again.

    '''

    try:
        # we get here because the GPIO system saw a rising edge on the port
        # however, this could be during the re-charging period, so do we have mains?
        if MAIN_POWER :
            if TRACE : print "S2 is pressed"
            write_log("warning", "S2 was pressed")
        else:
            write_log("warning", "Critical UPS battery level detected")
            if TRACE : print "Critical UPS battery level detected"
        #
        sleep(5) # let the level settle
        if GPIO.input(PWR_lo_bat_P) == 1 :
            if TRACE : print "Low bat is still active - shutdown now"
            write_log ("warning", "Low bat is still active - shutdown now")
            # do a shutdown and halt the Pi
            subprocess.call(['shutdown -h now "\nSystem halted by UPS supply"'], shell=True)
            write_log("warning", "Shutdown to halt process has started")
            sleep(60) # prevent the system from doing things
            # just in case...
        return


    except Exception as e:
        # in case that there is an exception, force the shutdown here!
        #
        write_log ("error", "Exception in ups low_batt_action : {0}".format(e))
        subprocess.call(['logger "Exception in ups low_bat_action : {0}"'.format(e)], shell=True)
        subprocess.call(['shutdown -h now "System shutdown due to error in ups power_action"'], shell=True)
        return
That's it really.

As a bonus, I highly recommend that you add the following code to any of your Python programs:

Code: Select all

def sig_handler (signum, frame):
    '''
    This function will catch the most important system signals, but NOT a shutdown!
    This handler catches the following signals from the OS:
        SIGHUB = (1) SSH Terminal logout
        SIGINT = (2) Ctrl-C
        SIGQUIT = (3) ctrl-\
        IOerror = (5) when terminating the SSH connection (input/output error)
        SIGTERM = (15) Deamon terminate (deamon --stop): is handled in deamon manager
    However, it cannot catch SIGKILL = (9), the kill -9 or the shutdown procedure
    '''
    try:
        if TRACE : print "\nSignal handler is called with signal : ", signum

        if signum == 1 :# SSH terminal logout"
            print "ignoring signal ", signum
            write_log ("system", "Ignoring SSH terminal logout : "+ str(signum))
            return

        if signum == 5 :# SSH i/o error"
            if TRACE : print "ignoring signal ", signum
            write_log ("system", "Ignoring SSH IOerror : "+ str(signum))
            return

        if signum == 2 or signum == 3:
            if TRACE : print "User issued a cntrl-c to terminate the program : ", signum
            write_log ("system", "User issued a cntrl-c to terminate the program : " + str(signum))
            GPIO.cleanup()
            sys.exit(0)

        if signum == 15 : # Daemon is terminating
            if TRACE : print "OS is causing a termination or shutdown : ", signum
            write_log ("system", "OS is causing a termination or shutdown : "+ str(signum))
            # do whatever is needed to let your program gracefully terminate.
            # the messages logfile gets overwritten at a new boot, so I'd like to preserve it.
            subprocess.call(['cp /var/log/messages /home/pi/.'], shell=True)
            write_log ("system", "Saved /var/log/messages")
            GPIO.cleanup()
            write_log ("system", "Done GPIO cleanup")
            sys.exit(0)

    except Exception as e:
        if TRACE : print "Exception is ups sig_handler : {0}".format(e)
        write_log ("warning", "Exception in sig_handler : {0}".format(e))
        subprocess.call(['logger "Exception in ups sig_handler : {0}"'.format(e)], shell=True)
        return
Put the following code in your main program:

Code: Select all

    # register the handler to catch the following signals:
    for sig in (signal.SIGTERM, signal.SIGINT, signal.SIGHUP, signal.SIGQUIT):
        signal.signal(sig, sig_handler)
This is a "signal" catcher that allows you to control the termination of your program in much more detail.
The comments should explain things.

Basically that’s it. A bit of off the shelf hardware, some electronic glue, and a couple of lines of code, and your reliable and precise Pi supply is up and running with a UPS to back things up. We have a reliable shutdown sequence and we have two buttons to manually control things.

If you decide to run this piece of code as a daemon that starts at boot time, it will always be there to keep your Pi running or have it shutdown (halted) under controlled circumstances.

I have another (older) design for a UPS in the forums which is more complicated to build due to the chip that is used there. If you were able to build that, it should be easy for you to add the above shutdown circuit with some resistor adjustments and also the code to that design without too many problems.
http://www.raspberrypi.org/forums/viewt ... 7&t=52860&

Comments and suggestions are welcome, otherwise enjoy and have fun!

Aug-26-2014 15:40 GMT
I have edited the schematic in two places from the original posting. The voltage for the LM311 circuit no longer comes from the cathode of D5, but now from the anode. This eliminates the 100mV difference in the voltage when the main supply goes away, and this would effect the setting of the threshold of the comparator. I also changed the value from R13 from 33K to 27K to reliably stay below the 3V3 maximum for the GPIO inputs. The schematic has been updated.
Last edited by paulv on Mon Sep 01, 2014 10:55 am, edited 2 times in total.

paulv
Posts: 564
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: A safe and precise Pi power supply with UPS & Shutdown

Mon Aug 25, 2014 9:05 am

This is intended for folks who know what they are doing, and can recalculate values of components.

If you want to be clever, and/or if your requirements allow it, you can omit the comparator circuit and all of it's components. In that case, you can use the drop of the mains to start the shutdown sequence right away, or after a delay you put in the software, to take care of glitches. You will then only use the batteries for 30-60 seconds, and if you're present, you can use a switch to take the power from the Pi yourself.
To manually start the shutdown sequence with a button, use S1 to bridge R5 and short it to ground.

Another change to the circuit above is to reduce the number of batteries. Using 3 cells should be OK to supply the step-up convertor. Going down to two depends on the operating voltage level of the step-up convertor. Two times 1.25 may not be enough. If you change the batteries, you need to modify the charging circuit.

However, if you find a convertor that does work, you will quickly reach that critical operating voltage when in UPS mode and that allows for a "more automated" way of starting the Pi when power returns.

Finally, and separately from the above, you can also eliminate the circuit for the trickle charge, if you regularly put the cells in a charger yourself, or if you want to contribute to the pollution of our planet by using non-rechargeable batteries.
Again, a lot depends on your requirements.

I will be interested to hear about your applications and comments.

paulv
Posts: 564
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: A safe and precise Pi power supply with UPS & Shutdown

Mon Sep 01, 2014 10:52 am

While building and testing the supply, I discovered one critical omission and found one welcome addition.

IMPORTANT
The omission is a 5V1 1W+ Zener diode that needs to be connected after fuse F2 (input of the DC-DC step-up convertor) and GND. The requirement for this Zener diode is evident when you take one or more batteries out of the holder.
If you do this (to disconnect this supply), there is no ground path for the T1 charging circuit, the voltage drop over D2 does not work, and so the voltage at F2 goes up to almost the 12V DC level.
This voltage will feed the DC-DC step-up convertor, and it will raise the output to the input level. This could very well be catastrophic for the Pi if you didn't protect the 5V0 supply with a 5V1 Zener diode as I mentioned in the original post.

The addition to the circuit is that you can use the P6 connector to wake-up the Pi (a reboot actually) after is has been put to sleep by the "shutdown -h", but you can also wire the button between P1-5 and P1-6 and do virtually the same. It leaves all connections to the Pi at the P1 connector.

Sorry about the omission.

Return to “Automation, sensing and robotics”