4 x 4 Matrix KeyPad

A tutorial on how to use the 4 x 4 Matrix with the Raspberry Pi Pico

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

ComponentQuantity
Raspberry Pi Pico W1
Micro USB Cable1
Breadboard1
WiresSeveral
Resistor17 (330Ω)
LED17

PCB Version

ComponentQuantity
Raspberry Pi Pico W1
Micro USB Cable1
Breadboard1
Resistor17 (330Ω)
LED17
KF350-3.5-2P1

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

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!