Binary Clock
A binary clock project based on the Raspberry Pi Pico
YouTube Video
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!
Introduction
Have you ever heard of binary? It’s a system that uses only two digits, 0 and 1, to represent numbers. This may sound strange at first, but it’s actually a fundamental concept in computing and digital electronics.
In a binary clock, the time is displayed using binary digits instead of decimal digits. For example, instead of showing “12:30:45”, a binary clock would display “01100 111110 101101”, where each group of digits represents the hours, minutes, and seconds in binary form.
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.
Components Needed
Breadboard Version
Component | Quantity |
---|---|
Raspberry Pi Pico W | 1 |
Micro USB Cable | 1 |
Breadboard | 1 |
Wires | Several |
Resistor | 17 (330Ω) |
LED | 17 |
PCB Version
Component | Quantity |
---|---|
Raspberry Pi Pico W | 1 |
Micro USB Cable | 1 |
Breadboard | 1 |
Resistor | 17 (330Ω) |
LED | 17 |
KF350-3.5-2P | 1 |
For the PCB version, you will require a few tools to solder the components to the PCB.
Schematic Diagram - PCB
The following schematic diagram is design to be used with the Raspberry Pi Pico with an external DS1302 RTC. In this tutorial we will be using the Raspberry Pi Pico W and will not need to have external RTC. The diagram will be shown in sections.
Schematic Diagram - Breadboard
The following schematic diagram was used for the breadboard example. The diagram will be shown in sections.
Code
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)) # (year, month, day, weekday, hours, minutes, seconds, subseconds)
# 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)
Code Explanation
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)yth
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)) # (year, month, day, weekday, hours, minutes, seconds, subseconds)
- The
sync_time_with_worldtimeapi_org
function takes an RTC objectrtc
as input and an optional Booleanblocking
flag that defaults toTrue
. - The function sets the
TIME_API
variable to the URL of the worldtimeapi.org API for the Shanghai timezone. - It sets the
response
variable toNone
. - 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 theurequests
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
, andseconds
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
, andis_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 isTrue
and the GET request fails, the function continues to retry indefinitely until it succeeds. Ifblocking
isFalse
, 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 to0
. - The code checks if the
force_sync_counter
variable is greater than85000
. If it is, it sets the counter to0
, calls thesync_time_with_worldtimeapi_org
function again with theblocking
flag set toFalse
, and increments the counter by1
. - If the
force_sync_counter
variable is not greater than 85000, the code simply increments it by 1 and does not call thesync_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
To reduce th 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. You can download the PCB here.
In case you would like to order a PCB and support the channel, I will be posting it on gumroad, which you can purchase here.
Conclusion
By following this tutorial and soldering all the components, you have successfully created a fun and unique clock that uses binary to display the time. Now that you have learned the basics of binary and how to use it, you can use this knowledge to expand on this project or apply it to other projects. For example, you can create a cyberpunk diorama that displays the time using binary, or incorporate binary into other electronic projects. To stay updated on my future projects, be sure to subscribe to my channel. Thank you for following along!