CircuitPython Code

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.

This project requires CircuitPython 5.x or higher. When visiting https://circuitpython.org/board/feather_m4_express/ choose 5.x or higher, which may mean choosing the "unstable" release.

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

This code requires some CircuitPython libraries to function. These libraries are not included with CircuitPython, so you'll need to load them yourself before the code will work. For more information, check out the Welcome To CircuitPython: CircuitPython Libraries page.

Click the button below to download the CircuitPython library bundle. Download the version that matches the version of CircuitPython that you are using, e.g. if you are using CircuitPython 5.x, download the 5.x bundle.

The libraries needed for this project are:

  • Adafruit CircuitPython Bus Device
  • Adafruit Display Text
  • Adafruit Displayio SSD1306
  • Adafruit DS3231
  • Adafruit Register

Download the latest library bundle, open the resulting zip file, and find the enclosed lib folder. Create a lib folder on your CIRCUITPY drive if there is not already one present. Copy the following files and folders to CIRCUITPY/lib:

  • adafruit_bus_device
  • adafruit_display_text
  • adafruit_displayio_ssd1306
  • adafruit_ds3231
  • adafruit_register

Before continuing, ensure you have these files and folders in the lib folder on your CIRCUITPY drive (similar to the picture above). Once copied, you're ready to continue!

The Code

Copy the following code as code.py on your CIRCUITPY drive.

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()
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.show(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.

Download: file
import board
import displayio
import adafruit_displayio_ssd1306
import terminalio
import adafruit_ds3231
from adafruit_display_text import label

Immediately following the imports 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.

Download: file
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.

Download: file
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:

Download: file
# 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.

Download: file
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.

Download: file
    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.

Download: file
    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.

Download: file
    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.

Download: file
(_, _, 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 call oled.show(). show() requires you to provide the group you're expecting to display, e.g. oled.show(watch_group).

Download: file
    watch_group = displayio.Group()
    watch_group.append(clock)
    watch_group.append(date)
    watch_group.append(text)

    oled.show(watch_group)

That's it! Your watch is set up and running, and now you're ready to get it assembled!

This guide was first published on Nov 23, 2016. It was last updated on Nov 23, 2016.
This page (CircuitPython Code) was last updated on Jul 29, 2020.