Nerdy Clock - Binary Clock Using Pico W

by Guitarman9119 in Circuits > Raspberry Pi

1293 Views, 7 Favorites, 0 Comments

Nerdy Clock - Binary Clock Using Pico W

I made the Nerdiest Clock ever

In this project, we'll be using a Raspberry Pi Pico and some LEDs to create our own binary clock. Why make your own binary clock? Well, for one thing, it's a fun and nerdy way to display the time. But beyond that, it's also a great way to learn about binary and how it works. Plus, building a binary clock is a great way to practice your programming and electronics skills!


How does Binary work?


In decimal (or base 10) notation, we use 10 digits (0-9) to represent numbers. Each digit has a value based on its position in the number. For example, in the number 123, the digit "1" represents 1 hundred, the digit "2" represents 2 tens, and the digit "3" represents 3 ones.


In binary (or base 2) notation, we use only 2 digits (0 and 1) to represent numbers. Each digit still has a value based on its position, but the positions are powers of 2 instead of powers of 10. For example, in the binary number 1011, the leftmost digit represents 8, the second digit from the left represents 4, the third digit represents 2, and the rightmost digit represents 1. Adding up these values gives us the decimal equivalent of the binary number, which is 11.

Supplies

I have added two lists below for the components needed either for a breadboard version or a PCB version. The components used are easy to find in most common electronic stores.

Breadboard:

1 - Raspberry Pi Pico W

1 - Breadboard

1 - Micro USB Cable

Wires serveral

17 - 330 Ohm resistors

17 - LEDs


PCB

1 - Raspberry Pi Pico W

1 - Micro USB Cable

Wires serveral

17 - 330 Ohm resistors

17 - LEDs

PCB


For the PCB version, you will require a few tools to solder the components to the PCB.

Schematic Diagram

schem.gif
bradboard schematic 1.png
bradboard schematic 2.png

Breadboard:

The following schematic diagram (Diagram 1,2) was used for the breadboard example.This is similar for the PCB except for the LED pin assignment which will be discussed in the code step.

Code

code.gif

In this section we will look at the code required.


The following files should be in your project:

binarypcb.py

config.json

urequests.py

binary_clock.py


Copy these code and upload to the raspberry pi pico with the correct file name. Other option is to download the code from my github repository here: Download. Pick the one relevant to your built the binarypcb.py if you are using the PCB I designed or the binary_clock.py if you are following along on a breadboard.


The code for the binarypcb and binary_clock (breadboard) is exactly the same except for the pinout of the LEDs.


import utime
from machine import Pin, RTC, SPI
import urequests
import network, json, time


# Load the configuration from config.json
with open('config.json') as f:
    config = json.load(f)


# Check if config.json has been updated with valid WiFi credentials
if config['ssid'] == 'Enter_Wifi_SSID':
    assert False, ("config.json has not been updated with your unique keys and data")


# Create a WiFi connection and turn it on
wlan = network.WLAN(network.STA_IF)
wlan.active(True)


# Connect to the WiFi router
print ("Connecting to WiFi: {}".format(config['ssid']))
wlan.connect(config['ssid'], config['ssid_password'])


# Wait until WiFi is connected
while not wlan.isconnected:
    pass


# Function to sync the RTC with the worldtimeapi.org API
def sync_time_with_worldtimeapi_org(rtc, blocking=True):
    TIME_API = "http://worldtimeapi.org/api/timezone/Asia/Shanghai"


    response = None
    while True:
        try:
            response = urequests.get(TIME_API)
            break
        except:
            if blocking:
                response.close()
                continue
            else:
                response.close()
                return


    json = response.json()
    current_time = json["datetime"]
    the_date, the_time = current_time.split("T")
    year, month, mday = [int(x) for x in the_date.split("-")]
    the_time = the_time.split(".")[0]
    hours, minutes, seconds = [int(x) for x in the_time.split(":")]


    # We can also fill in these extra nice things
    year_day = json["day_of_year"]
    week_day = json["day_of_week"]
    is_dst = json["dst"]
    response.close()
    rtc.datetime((year, month, mday, week_day, hours, minutes, seconds, 0))


# Initialize the RTC and sync it with the worldtimeapi.org API
rtc = RTC()
sync_time_with_worldtimeapi_org(rtc)


# Counter to force a sync with the worldtimeapi.org API every day
force_sync_counter = 0
 
# Define the update interval in seconds
update_interval = 1


# Define the GPIO pins for the LEDs
hour_pins = [Pin(pin, Pin.OUT) for pin in [15, 14, 13, 12, 11]]
minute_pins = [Pin(pin, Pin.OUT) for pin in [10, 9, 8, 7, 6, 5]]
second_pins = [Pin(pin, Pin.OUT) for pin in [16, 17, 18, 19, 20, 21]]


# Define a function to update the LEDs based on the current time
def update_leds():
    # Get the current time from the RTC
    Y, M, D, W, H, M, S, SS = rtc.datetime()


    # Convert the hours, minutes, and seconds to binary strings
    hour_binary = '{0:05b}'.format(H)
    minute_binary = '{0:06b}'.format(M)
    second_binary = '{0:06b}'.format(S)


    # Set the LED states based on the binary values
for i in range(5):
    hour_pins[i].value(int(hour_binary[i])) # set the ith hour pin to the ith character in the hour binary string
for i in range(6):
    minute_pins[i].value(int(minute_binary[i])) # set the ith minute pin to the ith character in the minute binary string
for i in range(6):
    second_pins[i].value(int(second_binary[i])) # set the ith second pin to the ith character in the second binary string


# Loop indefinitely, updating the LEDs every second
while True:
    update_leds()
    time.sleep(1)


config.json

{
    "ssid": "Open_Internet",
    "ssid_password": "Your_Wifi Password",
    "query_interval_sec": 120
}


urequests.py

import usocket


class Response:


    def __init__(self, f):
        self.raw = f
        self.encoding = "utf-8"
        self._cached = None


    def close(self):
        if self.raw:
            self.raw.close()
            self.raw = None
        self._cached = None


    @property
    def content(self):
        if self._cached is None:
            try:
                self._cached = self.raw.read()
            finally:
                self.raw.close()
                self.raw = None
        return self._cached


    @property
    def text(self):
        return str(self.content, self.encoding)


    def json(self):
        import ujson
        return ujson.loads(self.content)


def request(method, url, data=None, json=None, headers={}, stream=None):
    try:
        proto, dummy, host, path = url.split("/", 3)
    except ValueError:
        proto, dummy, host = url.split("/", 2)
        path = ""
    if proto == "http:":
        port = 80
    elif proto == "https:":
        import ussl
        port = 443
    else:
        raise ValueError("Unsupported protocol: " + proto)


    if ":" in host:
        host, port = host.split(":", 1)
        port = int(port)


    ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM)


    try:
        ai = ai[0]
    except:
        print("Count not resolve getaddrinfo for {} {}".format(host,port)) 


    s = usocket.socket(ai[0], ai[1], ai[2])
    try:
        s.connect(ai[-1])
        if proto == "https:":
            s = ussl.wrap_socket(s, server_hostname=host)
        s.write(b"%s /%s HTTP/1.0\r\n" % (method, path))
        if not "Host" in headers:
            s.write(b"Host: %s\r\n" % host)
        # Iterate over keys to avoid tuple alloc
        for k in headers:
            s.write(k)
            s.write(b": ")
            s.write(headers[k])
            s.write(b"\r\n")
        if json is not None:
            assert data is None
            import ujson
            data = ujson.dumps(json)
            s.write(b"Content-Type: application/json\r\n")
        if data:
            s.write(b"Content-Length: %d\r\n" % len(data))
        s.write(b"\r\n")
        if data:
            s.write(data)


        l = s.readline()
        #print(l)
        l = l.split(None, 2)
        status = int(l[1])
        reason = ""
        if len(l) > 2:
            reason = l[2].rstrip()
        while True:
            l = s.readline()
            if not l or l == b"\r\n":
                break
            #print(l)
            if l.startswith(b"Transfer-Encoding:"):
                if b"chunked" in l:
                    raise ValueError("Unsupported " + l)
            elif l.startswith(b"Location:") and not 200 <= status <= 299:
                raise NotImplementedError("Redirects not yet supported")
    except OSError:
        s.close()
        raise


    resp = Response(s)
    resp.status_code = status
    resp.reason = reason
    return resp


def head(url, **kw):
    return request("HEAD", url, **kw)


def get(url, **kw):
    return request("GET", url, **kw)


def post(url, **kw):
    return request("POST", url, **kw)


def put(url, **kw):
    return request("PUT", url, **kw)


def patch(url, **kw):
    return request("PATCH", url, **kw)


def delete(url, **kw):
    return request("DELETE", url, **kw)




Code Explanation


The first thing you will need to do is update the config.json file with your wifi name (ssid) and your wifi password

    "ssid": "Update Here",         
    "ssid_password": "Update Here",
    "query_interval_sec": 120


We will now look at the main.py file and explain the code in block sections.

Import necessary modules:

import utime
from machine import Pin, RTC, SPI
import urequests
import network, json, time


This imports the following modules:

  • utime: Provides access to the microcontroller's built-in real-time clock (RTC) and other timing-related functions.


  • machine.Pin: Allows the user to control the pins of the microcontroller, which can be used to interact with external devices or sensors.


  • machine.RTC: Provides access to the microcontroller's real-time clock (RTC), which keeps track of the current date and time.


  • machine.SPI: Allows the user to communicate with external devices that use the Serial Peripheral Interface (SPI) protocol.


  • urequests: Provides a simple way to make HTTP requests to a remote server.


  • network: Provides access to the microcontroller's network interfaces, allowing it to connect to the Internet.


  • json: Provides methods to encode Python objects as JSON strings, and decode JSON strings back into Python objects.


  • time: Provides access to time-related functions.


Load the configuration from config.json:

with open('config.json') as f:
    config = json.load(f)

This opens the config.json file and reads its contents, which are stored in a Python dictionary called config.


Check if config.json has been updated with valid WiFi credentials:

if config['ssid'] == 'Enter_Wifi_SSID':
    assert False, ("config.json has not been updated with your unique keys and data")

This checks if the ssid key in config is equal to the string 'Enter_Wifi_SSID'. If it is, an assertion error is raised with the message "config.json has not been updated with your unique keys and data". This is to remind the user to update config.json with their own WiFi credentials.


Create a WiFi connection and turn it on:

wlan = network.WLAN(network.STA_IF)
wlan.active(True)

This creates a WiFi interface object called wlan using the network.WLAN class, and then turns it on using the active() method.


Connect to the WiFi router:

print ("Connecting to WiFi: {}".format(config['ssid']))
wlan.connect(config['ssid'], config['ssid_password'])

This connects to the WiFi router specified in config, using the SSID and password stored in config['ssid'] and config['ssid_password'], respectively. The progress of the connection attempt is printed to the console.


Wait until WiFi is connected:

while not wlan.isconnected:
    pass

This waits in a loop until the isconnected attribute of wlan is True, indicating that a connection to the WiFi router has been established.


Define a function to sync the RTC with the worldtimeapi.org API:

def sync_time_with_worldtimeapi_org(rtc, blocking=True):
    TIME_API = "http://worldtimeapi.org/api/timezone/Asia/Shanghai"


    response = None
    while True:
        try:
            response = urequests.get(TIME_API)
            break
        except:pyt
            if blocking:
                response.close()
                continue
            else:
                response.close()
                return


    json = response.json()
    current_time = json["datetime"]
    the_date, the_time = current_time.split("T")
    year, month, mday = [int(x) for x in the_date.split("-")]
    the_time = the_time.split(".")[0]
    hours, minutes, seconds = [int(x) for x in the_time.split(":")]


    # We can also fill in these extra nice things
    year_day = json["day_of_year"]
    week_day = json["day_of_week"]
    is_dst = json["dst"]
    response.close()
    rtc.datetime((year, month, mday, week_day, hours, minutes, seconds, 0))


  • The sync_time_with_worldtimeapi_org function takes an RTC object rtc as input and an optional Boolean blocking flag that defaults to True.


  • The function sets the TIME_API variable to the URL of the worldtimeapi.org API for the Shanghai timezone.
  • It sets the response variable to None.


  • The function enters a loop that tries to get the current time from the worldtimeapi.org API by sending a GET request to TIME_API using the urequests library.


  • If the GET request succeeds, the function extracts the current time from the JSON response using the json method of the response object.


  • It splits the date and time into separate strings, converts the date and time strings into integers, and stores them in the year, month, mday, hours, minutes, and seconds variables.


  • It also extracts the day of the year, day of the week, and daylight savings time status from the JSON response and stores them in the year_day, week_day, and is_dst variables.


  • The function closes the response object and sets the RTC object's date and time using the datetime method.


  • If the blocking flag is True and the GET request fails, the function continues to retry indefinitely until it succeeds. If blocking is False, the function closes the response object and returns without setting the RTC time.
rtc = RTC()
sync_time_with_worldtimeapi_org(rtc)


force_sync_counter = 0
 
if force_sync_counter > 85000: # A little less than a day
    force_sync_counter = 0
    sync_time_with_worldtimeapi_org(rtc, blocking=False)
force_sync_counter = force_sync_counter + 1
  • The code creates an RTC object using the RTC class.


  • It then calls the sync_time_with_worldtimeapi_org function to set the RTC time using the worldtimeapi.org API.


  • The force_sync_counter variable is initialized to 0.


  • The code checks if the force_sync_counter variable is greater than 85000. If it is, it sets the counter to 0, calls the sync_time_with_worldtimeapi_org function again with the blocking flag set to False, and increments the counter by 1.


  • If the force_sync_counter variable is not greater than 85000, the code simply increments it by 1 and does not call the sync_time_with_worldtimeapi_org function.


Define the GPIO pins for the LEDs

hour_pins = [Pin(pin, Pin.OUT) for pin in [15,14,13,12,11]]
minute_pins = [Pin(pin, Pin.OUT) for pin in [10,9,8,7,6,5]]
second_pins = [Pin(pin, Pin.OUT) for pin in [16,17,18,19,20,21]]


This section defines the GPIO pins for the LEDs by creating three lists, one for each time unit (hours, minutes, seconds). Each list is created using a list comprehension to create Pin objects with the specified pin numbers in Pin.OUT mode.


Define a function to update the LEDs based on the current time

def update_leds():
    # Get the current time
    Y, M, D, W, H, M, S, SS = rtc.datetime()


    # Convert the hours, minutes, and seconds to binary strings
    hour_binary = '{0:05b}'.format(H)
    minute_binary = '{0:06b}'.format(M)
    second_binary = '{0:06b}'.format(S)
    print(S)


    # Set the LED states based on the binary values
    for i in range(5):
        hour_pins[i].value(int(hour_binary[i]))
    for i in range(6):
        minute_pins[i].value(int(minute_binary[i]))
    for i in range(6):
        second_pins[i].value(int(second_binary[i]))

This section defines a function update_leds that gets the current time from the RTC using rtc.datetime(). The hours, minutes, and seconds values are then converted to binary strings using the format() method. The function then sets the LED states based on the binary values, using a for loop and the value() method of each Pin object.


Loop indefinitely, updating the LEDs every second

while True:
    update_leds()
    time.sleep(1)

Finally, this section contains an infinite loop that calls the update_leds() function once every second using the time.sleep() function. This loop ensures that the LEDs are updated with the current time every second.

PCB Design

ezgif.com-video-to-gif.gif
front pcb.png
back pcb.png

To reduce the complexity of wiring, reducing errors, and improving reliability, I designed a PCB based on the schematic. Without a PCB, building the circuit requires manually wiring the components together on a breadboard or a prototyping board, which can be time-consuming and prone to errors. A PCB can save time and ensure that the circuit is consistent and reliable.




Case Design

solid.gif
assemble.gif

A case has been specifically designed in SolidWorks to house the PCB. You can download the STL file for the case design Here. The case features a simple design that allows for easy mounting of the PCB using M3 screws. Threaded inserts have been used in the original design, but you also have the option to use a M3-25mm screw and nut.


If anyone decides to recreate this project and comes up with an improved design for the case, please feel free to share it with me. I would love to see what you create!"

Conclusion

final.gif

I am planning on using this to build a cyberpunk diorama to display the time using Binary. How will you use your binary clock, let me know in the comments below on instructables or on my YouTube Channel.