Raspberry Pi's load on a 7-segment display

I believe that Raspberry Pi is ideal device to test some distributed computing techniques and therefore I wanted to somehow quickly keep track of multiple devices without turning on my computer.

I guess the easiest way is to use 7-segment display with at least two blocks and show current 1 minute load with some Python program. I know there are some integrated circuits to make using 7 segment displays easier (like BCD coders and what not) but I wanted to make it from scratch just using "wires".

If you just want to see source codes check this gist.

This is what it looks like when everything is connected. I used 35 wires in total (8 from GPIO to some shared place, 8 for each block and 3 to activate each block).

The 3 orange ones are activation wires, green are just to help me easily wire all 3 blocks, white and black wires are a, b, c and d segments and red and yellow colored wires are e, f, g and dp segments.

I used 3 pins to activate blocks: 3, 5 and 7
and another 8 pins to activate each segment: 12, 13, 15, 16, 18, 19, 21 and 22.

I strongly recommend you to watch Raspberry Pi - How to use the GPIO on youtube.com.

To use Pi's GPIO in Python I'm using RPi.GPIO library. If you've ever tried something with Raspberry Pi and Python you probably have this library installed already.

So first I'm going use this table that defines what segments I need to lit in order to form each digit.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
# segment representation of each digit
# The original table was for some C library but I can't find it anywhere
# to give credit to the author. If you recognize it, leave me a comment please.

#  --a--
# |     |
# f     b
# |     |
#  --g--
# |     |
# e     c
# |     |
#  --d--  p

segmentDigits = [
    #a b c d e f g p Segments
    [0, 0, 0, 0, 0, 0, 1, 1], # 0
    [1, 0, 0, 1, 1, 1, 1, 1], # 1
    [0, 0, 1, 0, 0, 1, 0, 1], # 2
    [0, 0, 0, 0, 1, 1, 0, 1], # 3
    [1, 0, 0, 1, 1, 0, 0, 1], # 4
    [0, 1, 0, 0, 1, 0, 0, 1], # 5
    [0, 1, 0, 0, 0, 0, 0, 1], # 6
    [0, 0, 0, 1, 1, 1, 1, 1], # 7
    [0, 0, 0, 0, 0, 0, 0, 1], # 8
    [0, 0, 0, 0, 1, 0, 0, 1], # 9
    [0, 0, 0, 1, 0, 0, 0, 1], # A
    [1, 1, 0, 0, 0, 0, 0, 1], # b
    [0, 1, 1, 0, 0, 0, 1, 1], # C
    [1, 0, 0, 0, 0, 1, 0, 1], # d
    [0, 1, 1, 0, 0, 0, 0, 1], # E
    [0, 1, 1, 1, 0, 0, 0, 1], # F
    [1, 1, 1, 1, 1, 1, 1, 1], # blank
];

Notice, that I'm setting 0 where I want the segment to lit. This is because each block of my display has shared "+" for all segments and individual "-" for each segment. Your display might be different so make sure you're doing it right.

Then I need to set what pins are going to do what and set default values.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
GPIO.setwarnings(False)
GPIO.cleanup()
GPIO.setmode(GPIO.BOARD)

# blocks activation pins
blockActivationPins = [ 3, 5, 7 ]

# setup all blocks
for i in range(0, len(blockActivationPins)):
    GPIO.setup(blockActivationPins[i], GPIO.OUT)
    GPIO.output(blockActivationPins[i], False)

# segments activation pins
segmentPins = [ 12, 13, 15, 16, 22, 21, 19, 18 ]

# setup all segments
for i in range(0, len(segmentPins)):
    GPIO.setup(segmentPins[i], GPIO.OUT)
    GPIO.output(segmentPins[i], True)

Also I need some method that gives me current 1 minute load. This method just runs system's uptime command and takes 11th column from the result. Return format of uptime might not be the same across different Linux distributions but I tested it on Raspberian and OS X.

 1 
 2 
 3 
 4 
 5 
def get_minute_load():
    p = subprocess.Popen(['uptime'], stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
    out, err = p.communicate()
    return out.split(' ')[11]

Main loop #1

Now there's the main loop. I can't lit all segments of all blocks because of the maximum current you can drain from USB. My display emits red light that has probably the lowest drain of all colors but maybe with new displays it's different, I'm not an expert on this.

Anyway, what this means is that I always have to lit just one block at a time, wait a few milliseconds, lit another block and so on. For your eyes it looks like all blocks are lit at the same time.

I tried to put comments where I think it's appropriate.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
# main loop
# to terminate the loop press CTRL+C
while True:
    # load = '1.23'
    load = get_minute_load()
    # remove decimal point from load
    # loadSimplified = '123'
    loadSimplified = load.replace('.', '');
    # position of decimal point
    decPoint = load.find('.')

    # each block
    for segment in range(0,3):
        char = loadSimplified[segment]
        # appropriate segments to lit for this digit
        segmentsToLit = segmentDigits[int(char)]

        # activate block
        GPIO.output(blockActivationPins[segment], True)

        # set decimal point
        if segment == decPoint - 1:
            GPIO.output(segmentPins[7], False)
        else:
            GPIO.output(segmentPins[7], True)

        # iterate all segments in this block
        for led in range(0, 7):
            # True or False based on segmentDigits table
            val = bool(segmentsToLit[led])
            GPIO.output(segmentPins[led], val)

        # short pause
        time.sleep(0.005)

        # deactivate block
        GPIO.output(blockActivationPins[segment], False)

You can see complete source code on github.com

I basically take each decimal, remove decimal point and check what segments correspond to this decimal in segmentDigits table. Then I activate block, lid segments I want, wait for 5ms and deactivate the block again.

This loop works, but it has one small flaw. It blinks a little bit. The problem is that calling get_minute_load takes a few milliseconds and you can see this gaps. If you want to use it to show some hardcoded information, then it's perfectly fine but I don't think this is very common use case.

One more important note here. If your display's current drain is so high, that you can't lit all segments in a single block, then you probably have to move that time.sleep inside the for loop above it and try maybe 1ms delays. Or maybe lit just 4 segments at a time, it's up to you.

Therefore I made an "upgraded loop".

Main loop #2: Threads

The main ideas is exactly the same as in the first loop. But this time I'm using a separate thread that updates 1 minute load independently on the rest of the loop.

I have to transform get_minute_load method into a class that extends threading.Thread in order to run it in another thread.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
# threaded class that checks current 1 minute load
class MinuteLoadThread(threading.Thread):

    load = None
    kill_received = False

    def run(self):
        while not self.kill_received:
            p = subprocess.Popen(['uptime'], stdout=subprocess.PIPE,
                                             stderr=subprocess.PIPE)
            out, err = p.communicate()
            self.load = out.split(' ')[11]
            time.sleep(1)

Notice that kill_received property. I need to tell the thread that I want to exit the program after pressing CTRL+C because otherwise it stucks on pthread join and will not respond to anything (you have to kill it from another terminal). For more information on Python threads I highly recommend this talk Inside the Python GIL.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
while thread.load is None:
    time.sleep(0.1)

# main loop
def run_loop():
    while True:
        # …
        # …

try:
    run_loop();
except KeyboardInterrupt:
    thread.kill_received = True

# deactivate all GPIO pins
GPIO.cleanup()

At the top I want to make sure that before I run the main loop I already have current load instead of trying to display None, which would cause an error.

The main loop stays almost the same, I just put it inside a method and wrapped the method call by try - except statement to handle KeyboardInterrupt exception properly. Now when you press CTRL+C it tells the thread to end the run method properly and the program exits as expected.

Here's full source code linked from github.com where you can find both scripts:

What about some wiring?

I didn't really tried to describe how to connect all those wires together because I think it's very simple and I'm sure there are many different types of 7-segment displays that I've never heard of and I believe that you can do it on your own. Just maybe keep in mind that GPIO output voltage is 3.3V and I think most 7-segment displays require less (2.5V in my case).

blog comments powered by Disqus