Pico 4 digit clock

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.
'''


1 Like

Hi Mark
Re the pic.
The resistor colour coding looks a bit off. The numerals look like 220Ω but the code rings suggest 2MΩ. The tolerance ring is missing. Or is this a home grown sketch.
Cheers Bob

That’s because I was too lazy to put the right colour bands on…hence the 220 for clarity.

Visio not Fritzing.

1 Like

Hi Mark

You Mean Confusion Ha Ha Ha.
Cheers Bob

Deleted

Hi Mark
Oh Well. Your coloured bands now say 10Ω. Bn = 1, Blk = 0 and Blk = 0 zeros. = 10Ω
What you need is Red, Red, Brown, Red = 2, Red= 2, Brown = 1 zero. 3 ring system plus tolerance ring. With the %ring being 10% or 5%.

If you are interested you would do well to acquire a colour code chart. There are the 4 and 5 ring systems around these days with extra rings for tolerance. 10%, 5%, 1% 0.1$ and there used to be 2% once, not sure about that one now. The extra rings are needed for the low tolerance types as there are more preferred values and you will find an actual digit in the third space so this has to be accommodated. Example 475k is a preferred value in 1% range so the rings would be Ye (4), Vio (7), Gn (5) and Or ( 3 zeros) = 475000.

Some capacitors use this colour system also although most these days use a number code such as 102 would mean 1, 0 and 2 being 2 zeros all meaning 1000pF or this might be marked 1n.

All probably confusing but it does make some sense when you see all this tabulated on a chart.
Cheers Bob