Nov 182015
 
7-segment display countdown ticker on Raspberry Pi using Python

Continuing with our theme of 7-segment displays driven directly from the Raspberry Pi’s GPIO using Python, I was asked for an explanation of the code from the previous post. In order to explain something, you first have to fully understand it, so I took some time to have a good look at the ‘business end’ of Bertwert’s code and figured out exactly how it works.

I’ve now put a full code walkthrough of that script in the comments section of the previous post.

Having done that, and having had a tweet from David Meiklejohn saying he’d done something similar in the past…

…I had a look at David’s code, which reminded me that you can use Python list variables with RPi.GPIO to switch multiple ports at once. More on this, a bit later.

And Now Let’s Make a Countdown Ticker

So having ‘done’ a clock last time, today, I thought we’d have some fun with a simple countdown ticker. I wanted to see how fast we could drive the display, so I’ve made it count down from 9999 to 0 and placed it on an imaginary ‘stick of dynamite’. See it in action in the video…

Want To Have A Go?

If you’d like to have a go at this project, I have a limited number of kits available for £12 including global shipping. Here’s what’s in the kit. Pick yours up today and start playing with 7-segment displays on your Pi…

RasPiO 7-segments display tinkering kit

RasPiO 7-segments display tinkering kit

How To Create Characters

I also wanted to have a go at creating some other characters. You’re a bit limited with what letters you can create on a 7-segment display, but it’s fun to see what can be done. Let’s have a look at how it’s done…

We need a diagram to help us here…

White lettering showing segment IDs

White lettering showing segment IDs

Look at the white lettering on the right-hand side. Each of the segments has a name a-g or dp.

In our dictionary in the Python script, each entry has an 8 digit list of 1s and 0s. The first list element corresponds to a and they go in alphabetical order, ending with dp (decimal point).

   #(a,b,c,d,e,f,g,dp)
'L':(0,0,0,1,1,1,0,0),

Our letter L has a, b & c inactive (0), d, e & f active (1), g and dp inactive (0).

So let’s suppose we wanted to create the letter P. Looking at the diagram, we can make a P if we light all of the segments except for c, d and dp. But we have to map this in the correct order for our dictionary. So here’s what we arrive at…

   #(a,b,c,d,e,f,g,dp)
'P':(1,1,0,0,1,1,1,0),

So you add that entry to your dictionary – it doesn’t matter where, but make sure you have a comma at the end of the line to match the correct format (the last entry has a }).

And that’s really all there is to it. Some letters are difficult/impossible. I couldn’t do an X, so my X is the same as the standard 7seg H. All letters with diagonal lines on are tricky for this kind of 7 segment display.

Now onto the code tweaks…

RPi.GPIO Can Use A List Variable

I knew this was possible, but I’d never used it before. Using this feature of RPi.GPIO allows us to completely eliminate our for loop in range(0,7): loop. This makes our code shorter, simpler and easier to understand.

for loop in range(0,7):
    GPIO.output(segments[loop], num[s[digit]][loop])

…becomes…

GPIO.output(segments, (num[s[digit]]))

…which means one less loop and one less level of slicing to get our heads around. In (num[s[digit]]) we’re finding the segment values in our dictionary num for the nth digit of our display string s.

Another way we can simplify the code is to change…

for segment in segments:
    GPIO.setup(segment, GPIO.OUT)
    GPIO.output(segment, 0)

We can do all this with one line of code…

GPIO.setup(segments, GPIO.OUT, initial=0)

…using our list variable segments and taking advantage of the fact that we can set an initial value for each port at the time of setup, using initial=0 or initial=1.

For these two tweaks to work correctly, we have to add a ‘,0‘ to the end of each character defined in our dictionary num because we defined 8 ports in our list variable segments (the last entry represents the decimal point).

I’ve incorporated all of the above changes into the new code. I’ve also changed s to display_string to make the code easier to read and understand.

Here’s The Ticker Code

from RPi import GPIO
import time
GPIO.setmode(GPIO.BCM)

# GPIO ports for the 7seg pins
segments =  (11,4,23,8,7,10,18,25)
# 7seg_segment_pins (11,7,4,2,1,10,5,3) +  100R inline
GPIO.setup(segments, GPIO.OUT, initial=0)

# GPIO ports for the digit 0-3 pins 
digits = (22,27,17,24)
# 7seg_digit_pins (12,9,8,6) digits 0-3 respectively
GPIO.setup(digits, GPIO.OUT, initial=1)

#          (a,b,c,d,e,f,g,dp)
num = {' ':(0,0,0,0,0,0,0,0),
    '0':(1,1,1,1,1,1,0,0),
    '1':(0,1,1,0,0,0,0,0),
    '2':(1,1,0,1,1,0,1,0),
    '3':(1,1,1,1,0,0,1,0),
    '4':(0,1,1,0,0,1,1,0),
    '5':(1,0,1,1,0,1,1,0),
    '6':(1,0,1,1,1,1,1,0),
    '7':(1,1,1,0,0,0,0,0),
    '8':(1,1,1,1,1,1,1,0),
    '9':(1,1,1,1,0,1,1,0),
    'b':(0,0,1,1,1,1,1,0),
    'y':(0,1,1,1,0,1,1,0),
    'E':(1,0,0,1,1,1,1,0),
    'A':(1,1,1,0,1,1,1,0),
    'L':(0,0,0,1,1,1,0,0),
    'X':(0,1,1,0,1,1,1,0)}

def seg():
    for digit in range(4):
        GPIO.output(segments, (num[display_string[digit]]))
        GPIO.output(digits[digit], 0)
        time.sleep(0.001)
        GPIO.output(digits[digit], 1)
try:
    n = 9999
    while n >= 0:
        display_string = str(n).rjust(4)
        if n == 0:
            display_string = ' byE'
        seg()
        n -= 1
    n = 1000
    while n >= 0:
        if n <= 500:
            display_string = 'ALEX'
        seg()
        n -= 1
finally:
    GPIO.cleanup()

Code Walk-through

Lines 1-2 import the libraries we need
Line 3 sets up BCM mode for RPi.GPIO
Lines 6-8 create a list variable for the segment ports and initialise them all to outputs at 0
Lines 11-13 create a list variable for the digit ports and initialise them all to outputs at 1 (remember setting these ports to 0 activates the digits on the 7-seg)
Lines 16-32 define our dictionary num of characters to display. Notice we’ve added a few new ones from last time, namely b, y, E, A, L, & X.
Lines 34-39 I’ve taken the main segment/digit driving code and put it in a function. This is so that we can reuse the same code in two different loops in our main code block without repeating it.
Line 35 iterates through the digits.
Line 36 GPIO.output(segments, (num[display_string[digit]])) sets all of the segments’ GPIO ports in one line using list variable segments and looking up the appropriate display_string character for the current digit in our dictionary num
Lines 37-39 activate our digit for a millisecond and then deactivate it. Then the loop can go to the next iteration
Lines 40-55 contain our main program block, wrapped with a try: finally: so that we can clean up our GPIO ports on exit, error, or exception.
Lines 41-47 First we set our counter n to 9999, then we start a loop that continues until we reach 0. Line 43 displays the current value of our counter. 44-45 check if we’ve got to 0 yet, and if we have, changes the display_string to ” byE”. 46 calls the seg() function we defined in 34-39 to display the current display_string. 47 decrements the counter value by 1. Then the loop starts again.
Lines 49-53 determine what happens when our countdown is completed. In 48 we set n back to 1000, so that we can display ” byE” for 500 iterations. In line 50, once n goes below 500 we change the display_string to “ALEX” (although the X is more of an H – it’s the best we can do on a 7-seg). Line 52 calls our seg() function to display the current display_string.

Want To Have A Go?

If you’d like to have a go at this project, I have a limited number of kits available for £12 including global shipping. Here’s what’s in the kit. Pick yours up today and start playing with 7-segment displays on your Pi…

RasPiO 7-segments display tinkering kit

RasPiO 7-segments display tinkering kit

  48 Responses to “7 segment display Python Raspberry Pi – countdown ticker”

  1. “imaginary ‘stick of dynamite’” A countdown ticker for cooking eggs may be a better way to use a raspberry

  2. Nice, hope you didn’t get too badly injured by your polite bomb! ;-)

    Today’s tips – good idea to separate seg() out into its own function, but what would be even better would be to pass display_string as a parameter to the function, rather than treating it as a global variable, so you could then do:

    seg('Andy')
    instead of having to do:
    display_string = 'Andy'
    seg()
    

    (I guess my name is better 7-seg-able than yours!)

    Although thinking about it a bit more, if you did do that I guess you’d need to rearrange your loop to:

    n = 9999
    while n >= 0:
        seg(str(n).rjust(4))
        n -= 1
    n = 1000
    while n >= 0:
        if n > 500:
            seg(' byE')
        else:
            seg('ALEX')
        n -= 1
    

    And you could make it even shorter as:

    for n in range(9999, -1, -1):
        seg(str(n).rjust(4))
    for n in range(1000):
        if n < 500:
            seg(' byE')
        else:
            seg('ALEX')
    

    7-segment displays are also able to easily display the characters "A b C d E F" which makes them useful for printing hexadecimal digits, should you ever need to do that.

    And regarding your "I wanted to see how fast we could drive the display", I guess you could make it count down even faster if you made the time.sleep() value even smaller?

    P.S. Any plans yet for a RasPiO Breakout Plus? (i.e. with the full 40-pin header)

  3. …and to help build the num dictionary for custom characters, here’s a little handy helper function (that you can run in the interactive Python prompt):

    def convert(char):
        chars = set(char)
        out = list()
        for c in "abcdefgp":
            out.append(int(c in chars))
        return tuple(out)
    

    (Note that it uses ‘p’ instead of ‘dp’ for the decimal point)

    So for example to get a ‘0’ we call convert(‘abcdef’) as the segments we want turned on, and it returns us (1, 1, 1, 1, 1, 1, 0, 0)
    However it doesn’t matter what order you pass in the segment-string, so you could call convert(‘fabedc’) and it would still give us (1, 1, 1, 1, 1, 1, 0, 0)

    To get the value for just lighting up the decimal point, we’d call convert(‘p’), and it returns (0, 0, 0, 0, 0, 0, 0, 1), so we could add:
    ‘.’: (0, 0, 0, 0, 0, 0, 0, 1),
    to your num dictionary above.

    Or to get the value to light up just the horizontal segments, you can use convert(‘agd’).

    P.S. Please could you do your magic indentation-markup-fixing on my comments? :)

  4. Alex – I got the kit going, and it works fine. I also have a 4-dig 7-seg common ANODE display, and I know all its pin-outs. What changes to the wiring/code would I have to make to get it going?

    • I would expect you’d need to invert the logic in the GPIO commands so that 1 becomes 0 and 0 becomes 1. (I think this would include the dictionary entries as well.)

  5. Thanks Alex – you are right – the dictionary entries need inverted as well as the GPIO.output() digits. It works a treat!

  6. Now wouldn’t that be an interesting thing to do with the RasPiODuino?

    • Yes it would :) I’ve not got to it yet, but I do want to have a go at driving one of these from a duino as well :)

      • I’ve just spent a few hours over the holiday weekend getting the Duino to talk to a 7-segment display. If you would like a copy of my code and Word document with the listings and comments please contact me at arduinolink (at)gmail.com and I will send you the files. The sketches include suppressing leading zeros and displaying floating point values with the decimal point.

  7. Hi Alex – thanks for the kit and your helpful post. I’ve added a button to my breadboard, and the display does nothing til you press it. Then it counts down from 60 seconds until it gets to 0 when it clears the display and waits again for you to hit the button, starting the count down again.

  8. i had no idea what python or raspberry pi was until 10 minutes ago. i have no coding experience at all. at 40, after doing some intense reading for the last 10 minutes, i must say i am very intrigued. very intimidated, but intrigued. now looking at your project and the language, i’m blown away. it actually looks like hours of fun!!! do you still have these kits available?!

  9. Amazing.. What about connect this countdown to the “next train arrival”?
    Suppose to connect the component to the web, to the train website…. you can get the next arrival time (considering also the delay :-D). Then you can count the difference in seconds between the real arrival time and the current timestamp.
    What do you think about this?

    Thank you
    Best Regards
    Riccardo

  10. Can Anyone Tell me please how i can make a Timer which starts from 15:00 and goes on 15minutes?

  11. That was a nice little problem for a dull afternoon.

    I think this is what you want.

    I’ve probably got the segment and columns connected to different GPIO pins but they are all commented on at the top of the script. Just change the pin numbers to your connection in the script.

    # Retro Display 15min timer V2
    # Tony Goodhew 29 April 2017
    
    # Import required libraries
    import RPi.GPIO as GPIO
    import time         
    
    GPIO.setwarnings(False)
    # Connections
    #    A
    #  F   B
    #    G    Segment positions
    #  E   C
    #    D  dp
    
    #Colour Br R  O  Y  G  Bl P G
    #       A  B  C  D  E  F  G dp
    LEDs = [14,15,18,23,24,25,5,6] #GPIO pins
    #Colour W  Bk Br R
    #       Th H  T  U
    cols = [12,16,20,21]   #GPIO pins
    
    # Tell GPIO library to use GPIO references
    GPIO.setmode(GPIO.BCM)
    # Setup LED pins as outputs
    for x in range(8):
        GPIO.setup(LEDs[x], GPIO.OUT)
        GPIO.output(LEDs[x], 0)
    # Setup col pins as output pins
    for x in range(4):
        GPIO.setup(cols[x], GPIO.OUT)
        GPIO.output(cols[x], 0)
    
    #one row per digit - 0 to 9
    nums =[1,1,1,1,1,1,0,  # 0
           0,1,1,0,0,0,0,  # 1
           1,1,0,1,1,0,1,  # 2
           1,1,1,1,0,0,1,  # 3
           0,1,1,0,0,1,1,  # 4
           1,0,1,1,0,1,1,  # 5
           1,0,1,1,1,1,1,  # 6
           1,1,1,0,0,0,0,  # 7
           1,1,1,1,1,1,1,  # 8
           1,1,1,0,0,1,1]  # 9
    
    def show_num(val,col,dp): #Displays one digit briefly
        for x in range(4): GPIO.output(cols[x],1)
        GPIO.output(cols[col], 0) # Turn col ON
        offset = val * 7
        for p in range(offset,offset + 7):
            x=p-offset
            if nums[p] == 1:
                GPIO.output(LEDs[x], 1)
            else:
                GPIO.output(LEDs[x], 0)
        # Decimal point needed?
        if dp == True:
            GPIO.output(LEDs[7], 1)
        else:
            GPIO.output(LEDs[7], 0)
        time.sleep(0.005) #temp delay
        GPIO.output(cols[col], 1)  # Turn col off 
        
    def show_number(val):  #Base 10
        digits =[0,0,0,0]
        abs_val = abs(val)
        temp_val = abs_val
        digits[0] = temp_val//1000
        temp_val = temp_val - digits[0] * 1000
        digits[1] = temp_val // 100
        temp_val = temp_val - digits[1] * 100
        digits[2] = temp_val // 10
        digits[3] = temp_val % 10
        for cycle in range(5):
            for col in range(4):
                show_num(digits[col],col,(col == 1))
    
    # +++ Main +++
    mins = 15
    sec = 0
    n = mins * 100 + sec
    x = time.time()
    y = x + 1.0
    try:
        while mins > -1:
            print(n)
            while x < y:
                show_number(n)
                x = time.time()      
            sec = sec - 1
            if (sec == -1):
                sec = 59
                mins = mins - 1
            n = mins * 100 + sec
            y = y + 1.0
    
    except KeyboardInterrupt:
        GPIO.cleanup()
    
    print('\nDone')
    
    

    You should be able to copy and paste this script into Python 3.

    Best of luck. Please let me know how you get on with it.

  12. Hello Tony.

    I have try it and it’s Work!
    Many thanks for your Help!

    Have a good Day.

  13. Hello. I wanna start a .wav on the same time with the Timer.
    Where should i buil in this code: os.system(‘aplay /home/pi/sound.wav’)

    Have you a idea?

  14. hello if you add a few buttons that its function is to increase or decrease the account, how would the program?
    Greetings

  15. Do you want an ‘increase’ button and a ‘decrease’ button to just change the value displayed?

  16. This is the right file . Sorry Alex

    
    # Retro Display Up/Down switches V2
    # Tony Goodhew 31 October 2017
    # Display range -999..0..999
    
    # Import required libraries
    import RPi.GPIO as GPIO
    import time         
    
    GPIO.setwarnings(False)
    # Connections
    #    A
    #  F   B
    #    G    Segment positions
    #  E   C
    #    D  dp
    
    #Colour Br R  O  Y  G  Bl P G
    #       A  B  C  D  E  F  G dp
    LEDs = [14,15,18,23,24,25,5,6] #GPIO pins
    #Colour W  Bk Br R
    #       Th H  T  U
    cols = [12,16,20,21]   #GPIO pins
    
    # Tell GPIO library to use GPIO references
    GPIO.setmode(GPIO.BCM)
    # Setup LED pins as outputs
    for x in range(8):
        GPIO.setup(LEDs[x], GPIO.OUT)
        GPIO.output(LEDs[x], 0)
    # Setup col pins as output pins
    for x in range(4):
        GPIO.setup(cols[x], GPIO.OUT)
        GPIO.output(cols[x], 0)
    
    # Set up buttons
    up = 3     # Up button on GPIO pin 3 - 10K Ohm physical pull up
    down = 2   # Down button on GPIO pin 2 - 10K Ohm physical pull up
    GPIO.setup(up, GPIO.IN) # Set switches as inputs
    GPIO.setup(down, GPIO.IN)
    
    #one row per digit - 0 to 9
    nums =[1,1,1,1,1,1,0,  # 0
           0,1,1,0,0,0,0,  # 1
           1,1,0,1,1,0,1,  # 2
           1,1,1,1,0,0,1,  # 3
           0,1,1,0,0,1,1,  # 4
           1,0,1,1,0,1,1,  # 5
           1,0,1,1,1,1,1,  # 6
           1,1,1,0,0,0,0,  # 7
           1,1,1,1,1,1,1,  # 8
           1,1,1,0,0,1,1,  # 9
           0,0,0,0,0,0,1,  # -
           0,0,0,0,0,0,0]  # Blank
    
    def show_num(val,col,dp): #Displays one digit briefly
        for x in range(4): GPIO.output(cols[x],1)
        GPIO.output(cols[col], 0) # Turn col ON
        offset = val * 7
        for p in range(offset,offset + 7):
            x=p-offset
            if nums[p] == 1:
                GPIO.output(LEDs[x], 1)
            else:
                GPIO.output(LEDs[x], 0)
        # Decimal point needed?
        if dp == True:
            GPIO.output(LEDs[7], 1)
        else:
            GPIO.output(LEDs[7], 0)
        time.sleep(0.005) #temp delay
        GPIO.output(cols[col], 1)  # Turn col off 
        
    def show_number(val):  #Base 10
        digits =[11,0,0,0] # Left display digit blank
        if val &lt; 0:
            digits[0] = 10 # Left display digit &quot;-&quot;
        abs_val = abs(val)
        temp_val = abs_val
        digits[1] = temp_val // 100
        temp_val = temp_val - digits[1] * 100
        digits[2] = temp_val // 10
        digits[3] = temp_val % 10
        for cycle in range(5):
            for col in range(4):
                show_num(digits[col],col,0)
    
    # +++ Main +++
    n = 0 # Initial value of display
    print(&quot;Left button decrease value&quot;)
    print(&quot;Right button to increase value&quot;)
    print(&quot;      CTRL+C to HALT&quot;)
    try:
        while True:
            switches = GPIO.input(down) + GPIO.input(up) * 2
            if switches == 2:
                n = n-1
                if n  999:
                    n = 999
            show_number(n)      
    
    except KeyboardInterrupt:
        GPIO.cleanup()
    
    print("\nDone")
    
    
  17. I’ve got no idea what you’re trying to do with “switches = GPIO.input(down) + GPIO.input(up) * 2” ?? It would be much easier to just read each switch independently in separate if-blocks.

    • You do not have to deal with both switches down together. Illogical but somebody will try it!

      • Ahh, then given that you’ve documented the switch GPIOs as being wired to pull-ups, I guess:
        switches == 3 means nothing is pressed
        switches == 2 means only the down-button is pressed
        switches == 1 means only the up-button is pressed
        switches == 0 means both buttons are pressed
        ?

        Maybe it was a copy’n’paste error, but there seems to be no handling of ‘switches == 1’ in your code?

        • That is correct. Only two of the possible outcomes need to be dealt with, the others can be ignored.

          arduinolink(at)gmail.com is my secondary email address. Send me a message and I’ll email you the full code.

          The problem is that this comment software uses ‘greater than’ and’ less than’ as formatting indicators and I do not know what code to use to format python correctly.

          Best wishes

          Tony

  18. Some of my code is missing from the upload. it has also lost greater than and less thans. Missing code is between these characters. What do I wrap the code in to get it to display correctly?

  19. hi, excuse me send an email to your email, Thanks

  20. How do you change a letter into numbers? (like how you did it in your code) I want it to show ‘Hello’ when my Pi starts up. Thanks a lot!
    Like this:
    ‘b’:(0,0,1,1,1,1,1,0),
        ‘y’:(0,1,1,1,0,1,1,0),
        ‘E’:(1,0,0,1,1,1,1,0),
        ‘A’:(1,1,1,0,1,1,1,0),
        ‘L’:(0,0,0,1,1,1,0,0),
        ‘X’:(0,1,1,0,1,1,1,0)}

  21. Hello Tony
    Could you please put again the code in the next link https://pastebin.com/20HrQTHE
    it is not available now.
    Thanks

  22. Hi Jos
    send me and email, see above, ardui…@gmail and I will send it to you

    Sorry for delay – I’ve been away – retirement is so much fun!

    Regards

    Tony

  23. How would I turn this into an up-counter, with the same message being displayed after reaching 0-9999? I think I’ve gotten it to properly count 0 to 9999, but the message doesn’t display afterwards. What gives?

Leave a Reply