Hi all.
For want of nothing better to do I thought I’d cobble together a clock using a Pico and a 4 digit, 7 segment display. For whatever reason I could not find any direct examples of MicroPython code for this so I did my own.
Here it is…FYI.
BOQ:
-
Pico 2 W
-
74HC595 shift register
-
Breadboard
-
Pro jumpers
-
Solid core wires.
-
4 x 220 ohm resistors
-
3461AS display 0.36", Red (Ultra-Bright), Common-Cathode (CC)
Code:
I’m sure someone more knowledgeable than me could condense this into something more “concise” but I prefer long-winded with lots of notes.
import machine
import time
import ntptime
from do_connect import *
Picoled = machine.Pin("LED", machine.Pin.OUT) # Define the Pico LED.
# Set up the hex codes. Numerals 0 to 9.
SEGCODE = [0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f]
# Set up the time digits and put into a list.
d1 = 0
d2 = 0
d3 = 0
d4 = 0
time_digits = [] # Create an empty list for use in current_time().
# Set up the shift register pins.
data_pin = machine.Pin(18, machine.Pin.OUT) # SDI / DS
latch_pin = machine.Pin(19, machine.Pin.OUT) # RCLK / ST_CP
clock_pin = machine.Pin(20, machine.Pin.OUT) # SRCLK / SH_CP
# Set up the digit pins using a list.
placePin = [] # Empty the list "placePin".
pin = [10,11,12,13]
for i in range(4):
placePin.append(None) # Sets the list placePin to none.
placePin[i] = machine.Pin(pin[i], machine.Pin.OUT) # Adds each of the 4 GP IO pin numbers to the list.
# Set all 4 pins to off.
for i in range(4):
placePin[i].value(1)
# RTC setup.
rtc = machine.RTC() # Initialise
ntptime.host = '216.239.35.0'
TIMEZONE_OFFSET = 10 * 3600 # Brisbane time is UTC+10 hours.
# Connect to the internet via do_connect.py
MAX_RETRIES = 5
retry_delay = 5 # seconds
def wifi_connect():
for attempt in range(MAX_RETRIES):
print(f"main.py says Attempt {attempt + 1} to connect...")
ip = do_connect()
if ip:
print(f"main.py says Connected successfully with IP: {ip}")
# Toggle the Pico LED x 10 to indicate connection.
Picoled.value(0)
for _ in range(10):
Picoled.toggle()
time.sleep_ms(100)
Picoled.value(0)
break
else:
print("main.py says Connection failed, retrying...")
# Toggle the Pico LED x 2 to indicate connection.
Picoled.value(0)
for _ in range(4):
Picoled.toggle()
time.sleep_ms(100)
Picoled.value(0)
time.sleep(retry_delay)
else:
# Failed so trigger the timer hat to shutdown, then it'll restart and try again.
print("do_connect failed so triggering shutdown......")
sdown.value(1)
def sync_time(): # Only required at startup.
try:
print("Updating time from NTP server...")
time.sleep(6)
ntptime.settime() # Sync time with an NTP server
print("NTPtime synced...")
except Exception as e:
print(f"Failed to sync time: {e}")
return
def current_time():
global d1, d2, d3, d4, time_digits # add time_digits here
current_time = time.localtime(time.time() + TIMEZONE_OFFSET)
year, month, day, hour, minute, second, _, _ = current_time
ctime = int("{:02d}{:02d}".format(hour, minute)) # Formats hhmm as an integer
'''See note 5 re the following.'''
d1 = SEGCODE[(ctime // 1000) % 10] # 1234/1000=1.234. 1234//1000=1. %10 is not required here.
d2 = SEGCODE[(ctime // 100) % 10] # 1234/100=12.34. 1234//100=12. 12 %10=2.
d3 = SEGCODE[(ctime // 10) % 10] # 1234/10=123.4. 123.4//10=123. 123 %10=3.
d4 = SEGCODE[ctime % 10] # 1234 %10=4.
time_digits = [d1, d2, d3, d4] # ← rebuild the list with updated values
def send_byte(byte_val):
'''Shift 8 bits into the register MSB first, then latch. See note 1'''
latch_pin.value(0) # Hold latch low while shifting
for i in range(7, -1, -1): # MSB first (bit 7 down to bit 0). See note 2.
data_pin.value((byte_val >> i) & 1) # Set data pin to current bit. See note 3.
time.sleep_us(10)
clock_pin.value(1) # Rising edge clocks the bit in
time.sleep_us(10)
clock_pin.value(0)
time.sleep_us(10)
latch_pin.value(1) # Rising edge latches output
time.sleep_us(10)
latch_pin.value(0)
# Only required at startup
wifi_connect()
sync_time()
# The following is blank, activate, load, wait, deactivate. Runs a loop for 100 x 45ms = ~4s.
while True:
current_time()
for j in range(4):
send_byte(0x00) # Clear segments first
placePin[j].value(0) # Activate digit
if j == 1: # 1 being the 2nd digit.
send_byte(time_digits[j] | 0x80) # The | (binary OR) adds the DP if this is digit 2.
else:
send_byte(time_digits[j]) # Now send the hhmm data
time.sleep_ms(5)
placePin[j].value(1) # Deactivate digit
''' Explanatory notes.
#1 - send_byte() where MSB = Most Significant Bit reading R to L.
#2 - send_byte() where for i in range(7, -1, -1). range(start, stop, step) counts 7, 6, 5, 4, 3, 2, 1, 0. Stops before hitting -1, so it includes 0. The step -1 makes it count down.
So i simply represents the bit position, starting from the most significant (bit 7) down to the least significant (bit 0).
#3 - send_byte() where data_pin.value((byte_val >> i) & 1). byte_val is the SEGCODE key as called in the for loop below, being 1-10.
byte_val >> i shifts all the bits in byte_val to the right by i positions, bringing the bit you care about down to the bit 0 position. I only care about bit 0.
The "& 1" masks off everything else, leaving you with purely a 0 or 1.
#4 - send_byte() where print formatting. Python f-string allows embedding variables and expressions directly inside a string using {} curly braces.
{i} — plain and simple, just inserts the value of i, being the key number, not the numerical value of hex.
{SEGCODE[i]:02x} — format spec after the colon. The format spec :02x means x format as hexadecimal. 2 means at least 2 characters wide and
0 means pad with zeros if it's less than 2 characters wide
#5 - current_time() where "//" is the modulo operator. It divides one into the other then removes the remainder to the right of the decimal place.
"%10" keeps only RH digit. The "10" give one digit's worth of the number in base 10.
'''
