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