CircuitPython makes it easy to get your watch up and running. You'll set the time and date, and you can change the text string displayed under the time and date. This page will walk you through getting your Feather M4 Express set up with CircuitPython, and getting the right libraries installed. Then, it will walk you through the code section by section to explain what's going on.
Let's get started!
Installing CircuitPython
You'll want to make sure you're running the latest version of CircuitPython.
For instructions on installing CircuitPython, check out the CircuitPython page in the Feather M4 Express guide. Once you've got it installed, you're all set to continue.
CircuitPython Libraries
To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory CircuitPython_OLED_Watch/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
CIRCUITPY
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries # # SPDX-License-Identifier: MIT import board import displayio import adafruit_displayio_ssd1306 import terminalio import adafruit_ds3231 from adafruit_display_text import label font = terminalio.FONT displayio.release_displays() i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller display_bus = displayio.I2CDisplay(i2c, device_address=0x3c) oled = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32) rtc = adafruit_ds3231.DS3231(i2c) # The first time you run this code, you must set the time! # You must set year, month, date, hour, minute, second and weekday. # struct_time order: year, month, day (date), hour, minute, second, weekday , yearday, isdst # yearday is not supported, isdst can be set but we don't do anything with it at this time # UNCOMMENT THE FOLLOWING FOUR LINES THE FIRST TIME YOU RUN THE CODE TO SET THE TIME! # import time # set_time = time.struct_time((2019, 8, 16, 23, 59, 45, 4, -1, -1)) # print("Setting time to:", set_time) # rtc.datetime = set_time # Comment out the above four lines again after setting the time! while True: current = rtc.datetime hour = current.tm_hour % 12 if hour == 0: hour = 12 am_pm = "AM" if current.tm_hour / 12 >= 1: am_pm = "PM" time_display = "{:d}:{:02d}:{:02d} {}".format(hour, current.tm_min, current.tm_sec, am_pm) date_display = "{:d}/{:d}/{:d}".format(current.tm_mon, current.tm_mday, current.tm_year) text_display = "CircuitPython Time" clock = label.Label(font, text=time_display) date = label.Label(font, text=date_display) text = label.Label(font, text=text_display) (_, _, width, _) = clock.bounding_box clock.x = oled.width // 2 - width // 2 clock.y = 5 (_, _, width, _) = date.bounding_box date.x = oled.width // 2 - width // 2 date.y = 15 (_, _, width, _) = text.bounding_box text.x = oled.width // 2 - width // 2 text.y = 25 watch_group = displayio.Group() watch_group.append(clock) watch_group.append(date) watch_group.append(text) oled.root_group = watch_group
Let's take a look at the code!
Setup
First, import all the necessary libraries into the code. Note that you're not importing all the libraries you copied to your CIRCUITPY drive - the libraries you do import rely on the ones that you don't.
import board import displayio import adafruit_displayio_ssd1306 import terminalio import adafruit_ds3231 from adafruit_display_text import label
Immediately following the import
s is the line where the font is set. This program uses a font built into terminalio
. This is great because it eliminates the need for separate font files.
font = terminalio.FONT
Next, you'll set up the hardware.
First, you must include the displayio.release_displays()
because the displays do not automatically reset like everything else does. Without this line, each time the code runs, it creates another instance of the display.
Then you create the OLED display and the real time clock objects.
displayio.release_displays() i2c = board.I2C() display_bus = displayio.I2CDisplay(i2c, device_address=0x3c) oled = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32) rtc = adafruit_ds3231.DS3231(i2c)
Setting the Time
The next section of the code allows you to set the date and time. You must set the date and time manually initially. You'll use time.struct_time
to set the real time clock time.
time.struct_time
expects you to set, in order:
- year, month, day, hour, minute, second, weekday, yearday, isDST
To set the time, you'll change the data provided to time.struct_time
to match a time slightly in the future. Uncomment the four lines of code under # UNCOMMENT THE FOLLOWING FOUR LINES THE FIRST TIME YOU RUN THE CODE TO SET THE TIME!
Then, when it is that time, save the file.
For example, if you wanted to set the date and time to August 16, 2019, 11:59:45pm, you would change the code to the following:
# UNCOMMENT THE FOLLOWING FOUR LINES THE FIRST TIME YOU RUN THE CODE TO SET THE TIME! import time set_time = time.struct_time((2019, 8, 16, 23, 59, 45, 4, -1, -1)) print("Setting time to:", set_time) rtc.datetime = set_time # Comment out the above four lines again after setting the time!
Once you've set the date and time, comment out the four lines again or it will change the time back to the manual time you set every time the code runs.
Main Loop
Next up, we start the main loop. First, set a variable to be the real time clock date-time.
while True: current = rtc.datetime
The next section has two parts. The first handles taking the 24-hour time provided by rtc.datetime
and turning it into 12-hour time. The second part uses the 12-hour converted time to determine whether to display AM or PM.
hour = current.tm_hour % 12 if hour == 0: hour = 12 am_pm = "AM" if current.tm_hour / 12 >= 1: am_pm = "PM"
The next section is everything needed to get text displaying on the OLED. Let's take a look!
First, you assign variables to the text strings displayed on the OLED.
time_display = "{:d}:{:02d}:{:02d} {}".format(hour, current.tm_min, current.tm_sec, am_pm) date_display = "{:d}/{:d}/{:d}".format(current.tm_mon, current.tm_mday, current.tm_year) text_display = "CircuitPython Time"
You can customise this section. You can change text_display
string to any string that fits on the display. You can also change the order of the current
time values, e.g. if you wanted the date order to be day/month/year, you would change the date_display
line to:
date_display = "{:d}/{:d}/{:d}".format(current.tm_mday, current.tm_mon, current.tm_year)
The next section creates instances of label
which is used to display the text. label
requires providing a font and a string of text. Here you are using the default font specified earlier in the code, and the text strings found immediately above this section.
clock = label.Label(font, text=time_display) date = label.Label(font, text=date_display) text = label.Label(font, text=text_display)
Now that you've created the label
instances, you can begin to manipulate things about them like location of each string on the display.
bounding_box
allows you to determine the x, y
location of the label, and the width
and height
of the string. The width
and height
are the amount of space taken up by the font being used on the display. This example does not use height
, so it is ignored.
Next you set the location of the text. The x
value is the location left to right on the display, the y
value is the location top to bottom. The y
value is simply a value to position the lines of text on the display from top to bottom. The x
value, however, requires some math to ensure that no matter how long the string is, it is always centered.
For the x
value, you take the width of the display (in pixels) and dividing it by 2 to determine the center of the display. You then divide the width of the bounding box (the width of the string of text) by 2 in the same manner to determine the center of the bounding box. You then subtract that value from the width of the display to center the text on the display. Using //
ensures that you end up with whole numbers, as you cannot place something on half of a pixel.
(_, _, width, _) = clock.bounding_box clock.x = oled.width // 2 - width // 2 clock.y = 5 (_, _, width, _) = date.bounding_box date.x = oled.width // 2 - width // 2 date.y = 15 (_, _, width, _) = text.bounding_box text.x = oled.width // 2 - width // 2 text.y = 25
Finally, you create a group to hold all of the lines of text to display them. You create the main group, watch_group
, and then append the three labels into the group.
Lastly, to display the watch_group
, you must set oled.root_group
to the group you're expecting to display, e.g. oled.root_group = watch_group
.
watch_group = displayio.Group() watch_group.append(clock) watch_group.append(date) watch_group.append(text) oled.root_group = watch_group
That's it! Your watch is set up and running, and now you're ready to get it assembled!
Page last edited January 21, 2025
Text editor powered by tinymce.