I've been fiddling around with a 7-segment clock display, which is really just a display that combines 4 different 7-segment displays into one neat little package. With an extra couple of dots thrown in for good measure.
In my previous post, I was able to show all of the digits from 0 to 9 on each clock position in order, and while that is very exciting and brings so many of us endless joy, it's not really very useful that way, and thus I continue on with my quest, to make it look more like an actual clock display.
However, showing an actual time requires displaying each position for a very short amount of time and repeating it constantly so that our persistence of vision allows us to see all 4 digits on at once.
I forget the name for this time-slicing kind of thing Help me out with a comment please!
So for this next phase I decided to turn it into a countdown timer, because I'm too lazy to make an actual clock that shows actual time because then I have to add buttons to set the time and deal with all of that kind of non-sense.
Instead, I hard-coded a start time, and it simply counts down the time until it hits zero, and then it just sits there at 0 when done.
This was a lot more effort than I thought it would be, partly because I tried to use a Timer to refresh the display, which created problems - I later simplified the code by just running the display refresh in the "while true" never ending loop so that it can just use up whatever spare cycles are available. I think I might have been setting the display refresh timer to refresh too often, and/or maybe not leaving enough time to complete before the next round began, so that the Pi Pico was eventually not able to keep up.
The first part was easy though - counting time can be done by setting up a Timer that ticks on a regular schedule. In my case I set it to use a frequency of 2, so that the time-divider colon on the display can flash 2x per second. Plus since this timer counts seconds for its lowest division, that's plenty accurate. For this, look for timeTick() and startSecondsTicker(). You'll notice that MicroPython on the Pico has a handy "time" library with a function time() that gives us the number of seconds since the epoch. Don't ask me what that epoch is - for this project it's enough to have some fixed reference point to start counting from.
In my first iteration I just made that timer function toggle the division colon on and off, and that looked pretty cool. And it showed that the timer function was working. Very cool.
However, once I started time-slicing to show the 4 position digits, I noticed that the colon was a lot brighter than the 4 digits. I was able to fix that by giving the colon the same amount of time slice in the display loop as the rest of the digits. No more special treatment for you, time-bits-dividing-colon-LED-thing!
I ran into a fair number of syntax errors and the like while figuring this out. Python isn't my native language and so I end up typing silly things that only make sense in C and Swift and the like - that's one thing I don't love about using an interpreted language like Python - these issues only appear once you run the code.
To help debug, I slowed things down a bit. Here's a video of when I first got the digits showing up:
Then speeding things up, it looks like this following video, which actually is a fairly nice clock display effect - it's a little bit dim but I suppose we could switch the 220R resistors for something with a lower value, but I'm not that fussy.
The video shows the digits glowing a bit funny, but I think that's interference with the timing of the camera on my phone.
Here is the code I used to make it happen. It ultimately didn't add a whole lot more code compared to the previous step - mostly some time counting variables and lines, a buffer to hold the 4 display digits, some math to figure out hours, minutes, and seconds (int the showTime() function), and then there's the bits where it steps through and displays each digit in the correct display location - see displayRefresh for that bit of magic. Oh and I added a "blank" digit so that we don't get that awkward leading zero in the first position of the clock display.
# # Clock display test # from machine import Pin, Timer import time # For my clock display, the common wire connects to VCC # -> Note that this means we have to turn a pin "off" to light the segment LED, and "on" to turn it off # # You can test the display by using a small 3.3v button battery (CR2032 seems cheap and plentiful) # Hopefully the display you have will have a model number you can look up to find the pinout. # Define the segment GPIO connections # hook up the segments as per the defined constants below # Use a current limiting resistor for each segment (you'll need 7!) # The resistors are also necessary to keep a constant brightness on the display SEG_A_PIN = 13 SEG_B_PIN = 19 SEG_C_PIN = 17 SEG_D_PIN = 16 SEG_E_PIN = 12 # oops, I put the display too close to the Pi Pico, so pin 15 is covered. SEG_F_PIN = 14 SEG_G_PIN = 18 # The clock display has 4 digit positions # I'm calling them positions, as it could be hr:mm or mm:ss POSITION_1 = 10 POSITION_2 = 11 POSITION_3 = 20 POSITION_4 = 21 DIVIDER_COLON = 22 # Python allows us to define global variables anywhere all willy-nilly, # but for clarity lets define them here at the top like good little programmers # The type is here just for clarity too - Python allows us to change it at any time DIGITS :[[Pin]] = [] BLANK_DIGIT : [Pin] = [] POSITIONS : [Pin] = [] # time.time() returns float number of seconds since epoch MINUTE : float = 60 HOUR = MINUTE * 60 TOTAL_TIME = 3 * MINUTE time_left = TOTAL_TIME start_time : float = 0 # in seconds since epoch colon_visible = False display_buffer : [[Pin]] = [BLANK_DIGIT, BLANK_DIGIT, BLANK_DIGIT, BLANK_DIGIT] # this will hold the 4 digits currently being displayed - the display needs continuous refreshing in order to work. def setup(): # Define each segment SEG_A = Pin(SEG_A_PIN, Pin.OUT) SEG_B = Pin(SEG_B_PIN, Pin.OUT) SEG_C = Pin(SEG_C_PIN, Pin.OUT) SEG_D = Pin(SEG_D_PIN, Pin.OUT) SEG_E = Pin(SEG_E_PIN, Pin.OUT) SEG_F = Pin(SEG_F_PIN, Pin.OUT) SEG_G = Pin(SEG_G_PIN, Pin.OUT) # Define which segments make up each digit DIGIT_0 = [SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F ] DIGIT_1 = [ SEG_B, SEG_C ] DIGIT_2 = [SEG_A, SEG_B, SEG_D, SEG_E, SEG_G] DIGIT_3 = [SEG_A, SEG_B, SEG_C, SEG_D, SEG_G] DIGIT_4 = [ SEG_B, SEG_C, SEG_F, SEG_G] DIGIT_5 = [SEG_A, SEG_C, SEG_D, SEG_F, SEG_G] DIGIT_6 = [SEG_A, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G] DIGIT_7 = [SEG_A, SEG_B, SEG_C ] DIGIT_8 = [SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G] DIGIT_9 = [SEG_A, SEG_B, SEG_C, SEG_D, SEG_F, SEG_G] # Note that we are not limited to decimal digits. We could continue to add A through F for hexadecimal POS_1 = Pin(POSITION_1, Pin.OUT) POS_2 = Pin(POSITION_2, Pin.OUT) POS_3 = Pin(POSITION_3, Pin.OUT) POS_4 = Pin(POSITION_4, Pin.OUT) global divider_colon divider_colon = Pin(DIVIDER_COLON, Pin.OUT) global DIGITS DIGITS = [DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4, DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9, BLANK_DIGIT] global POSITIONS POSITIONS = [POS_1, POS_2, POS_3, POS_4] displayOff() global start_time start_time = time.time() def showPosition(position): positionsOff() position.on() def displayDigit(digit): #start by turning off all the segments segmentsOff() for segment in digit: segment.off() # gpio "off" turns on the LED def positionsOff(): for pos in POSITIONS: pos.off() def segmentsOff(): for segment in DIGITS[8]: segment.on() # gpio "on" turns off the LED def displayOff(): # turn off all the digit positions positionsOff() # turn off all the segments segmentsOff() def timeTick(timer): global time_left, start_time, TOTAL_TIME, colon_visible colon_visible = not colon_visible current_time = time.time() # using the time() function will likely be more accurate than counting the ticks, plus we can mess with the tick frequency now. elapsed_time = current_time - start_time time_left : int = TOTAL_TIME - elapsed_time if time_left < 0 : time_left = 0 showTime(time_left) def startSecondsTicker(): Timer().init(freq=2, mode=Timer.PERIODIC, callback=timeTick) def displayRefresh(): global display_buffer, POSITIONS, colon_visible, divider_colon led_on_time = 0.0025 # too much on time will cause flickering - the display must keep each position lit up just long enough to repeat it again fast enough to fool the eye. for index, position in enumerate(POSITIONS) : showPosition(position) displayDigit(display_buffer[index]) time.sleep(led_on_time) # You can use a bigger delay here for debugging purposes, to make it easier to see the digits being shown in turn. displayOff() # otherwise it will linger on the last digit, making it brighter than the rest if colon_visible : divider_colon.off() # turns the LEDs on time.sleep(led_on_time) divider_colon.on() # turns the LEDs off def showTime(total_seconds): #print("show time total seconds = ", total_seconds) # convert seconds to hours, minutes, and seconds hours = total_seconds // 3600 # the // is "floor division" which avoids fractions leftover = total_seconds % 3600 minutes = leftover // 60 seconds = leftover % 60 # print("time = ", hours, ":", minutes, ":", seconds) if hours > 0 : first_number = hours second_number = minutes else : first_number = minutes second_number = seconds global digits if hours > 99 : display_buffer[0] = DIGITS[8] display_buffer[1] = DIGITS[8] display_buffer[2] = DIGITS[8] display_buffer[3] = DIGITS[8] else : if first_number > 9 : display_buffer[0] = DIGITS[first_number // 10] else : display_buffer[0] = BLANK_DIGIT display_buffer[1] = DIGITS[first_number % 10] display_buffer[2] = DIGITS[second_number // 10] display_buffer[3] = DIGITS[second_number % 10] # Start main code setup() startSecondsTicker() showTime(TOTAL_TIME) # Using a Timer to refresh the display seems to cause it to hang after a while # but using this loop we can go nuts refreshing the display with all the spare clock cycles while True: displayRefresh()
I wonder if there isn't already some sort of library available that handles clock displays like this?
Oh well, in any case it's an interesting journey and shows us the kind of cool engineering challenges that we might not think about otherwise.
Cheers,
-Nico
edit: In my next post, I help make the world a better place by bringing back memories of the 1980's
Top Comments