I know you all secretly miss the 80's, with the big oversized electronics that our parents didn't know how to use.
Back then you'd really get something for your money - the cost per pound of electronic equipment was so much better. Today, however, it's a different story... consider the Apple Watch - that little thing can cost you over $10,000 per pound! But I digress...
So I'm here to help bring back some of those memories for you, bringing back some nostalgia. You're welcome
In my previous blog post I built a basic count-down timer using a 4-digit 7-segment clock display and a Pi Pico.
Over the weekend, I added a few small extensions to the code.
Firstly, I wanted it to flash all zeroes when the timer hits zero. Just to make it a bit more obvious that the countdown has completed.
But then I realized that, for a tiny bit of extra coding effort, I can single-handedly improve the quality of life for those of us from the 70s and 80s, and thus I made a minor adjustment to make it emulate a VCR :D
All of it was actually a relatively small and easy change - I added a flag to indicate when it's flashing, and I added a vcr_mode flag. Then a bit of extra code that uses the flashing divider colon logic to hide/show all of the digits when the colon is showing or hidden.
The flashing flag gets set when the timer hits zero.
A little bit of tweaking in the function that sets the digits into the display buffer ensures all four digits flash 0 when the time is up, and I added a separate function to set the display buffer to 12:00 for the vcr mode.
So, essentially, after completing the flashing-zeroes detail, the VCR emulator code probably comes down to just few lines of simple code.
One detail I keep bumping into with Python is the idea of global variables! It seems you can access global variables all will nily from anywhere, but if you try to change a global variable inside of a function, it goes and creates a local variable... that leads to some head-scratching-moment type of bugs! You'll see that in most cases I try to define and declare them at the top in a way similar to the Arduino or other compiled languages, and then I explicitly list the ones being used in the functions - mostly to keep myself from getting confused.
The Code:
# # VCR Emulator # # from machine import Pin, Timer import time # For my clock display, the common wires connects to pins that must be set HIGH to enable each position # -> 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 = 1 * 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. flashing = False vcr_mode = False # set to True for some nostalgic effects 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, flashing colon_visible = not colon_visible if vcr_mode or time_left == 0 : return 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 flashing = True showTime(time_left) def startSecondsTicker(): Timer().init(freq=2, mode=Timer.PERIODIC, callback=timeTick) def displayRefresh(): global display_buffer, POSITIONS, colon_visible, divider_colon, flashing if not colon_visible and flashing: displayOff() divider_colon.on() # turns the LEDs off return 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 showTwelve(): display_buffer[0] = DIGITS[1] display_buffer[1] = DIGITS[2] display_buffer[2] = DIGITS[0] display_buffer[3] = DIGITS[0] def showTime(total_seconds): global display_buffer #print("show time total seconds = ", total_seconds) if total_seconds <= 0 : display_buffer[0] = DIGITS[0] display_buffer[1] = DIGITS[0] display_buffer[2] = DIGITS[0] display_buffer[3] = DIGITS[0] return # 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 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() if vcr_mode : flashing = True showTwelve() else : 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()
Cheers,
-Nico
Top Comments