Download a zip of the project by clicking Download Project Bundle below.
After unzipping the file, you'll need to edit code.py. I'll explain the edits that need to be made below.
Before you do that though, you should copy the required libraries over:
- adafruit_bus_device
- adafruit_register
- adafruit_lc709203f.mpy
- adafruit_pcf8523.mpy
- adafruit_pixelbuf.mpy
- neopixel.mpy
- simpleio.mpy
- adafruit_led_animation
# SPDX-FileCopyrightText: 2022 Eva Herrada for Adafruit Industries # # SPDX-License-Identifier: MIT import time import board from adafruit_lc709203f import LC709203F from adafruit_pcf8523.pcf8523 import PCF8523 from simpleio import tone import neopixel from adafruit_led_animation.animation.rainbow import Rainbow i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller rtc = PCF8523(i2c) battery = LC709203F(i2c) indicator = neopixel.NeoPixel(board.A1, 1) LEDs = neopixel.NeoPixel(board.A2, 20) LEDs.fill((0, 0, 0)) rainbow = Rainbow(LEDs, speed=0.1, period=2) days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] sleep = { 0: (22, 50), 1: (23, 8), 2: (22, 50), 3: (22, 50), 4: (22, 50), 5: None, 6: None, } wake = { 0: (11, 30), 1: (9, 50), 2: (9, 50), 3: (9, 50), 4: (9, 50), 5: (9, 50), 6: (11, 30), } BPM = 160 ringtone = [ (330, 0.5), (294, 0.5), (185, 1), (208, 1), (277, 0.5), (247, 0.5), (147, 1), (165, 1), (247, 0.5), (220, 0.5), (139, 1), (165, 1), (220, 2), ] SET_DATE = False # Set this to True on first run and False for subsequent runs if SET_DATE: # Make sure to set this to the current date and time before using YEAR = 2022 MONTH = 3 DAY = 21 HOUR = 16 MINUTE = 54 SEC = 0 WDAY = 1 # 0 Sunday, 1 Monday, etc. t = time.struct_time((YEAR, MONTH, DAY, HOUR, MINUTE, SEC, WDAY, -1, -1)) print("Setting time to:", t) # uncomment for debugging rtc.datetime = t print() on_always = True # Set to False for animation only to be on when alarm rings indicate = True start = time.monotonic() wait = 1 while True: if on_always: rainbow.animate() if time.monotonic() - start > wait: start = time.monotonic() wait = 1 t = rtc.datetime bat = min(max(int(battery.cell_percent), 0), 100) g = int((bat / 100) * 255) r = int((1 - (bat / 100)) * 255) if bat >= 15: indicator.fill([r, g, 0]) if bat < 15: if indicate: indicator.fill([0, 0, 0]) indicate = False else: indicator.fill([r, g, 0]) indicate = True print(f"Date: {days[t.tm_wday]} {t.tm_mday}/{t.tm_mon}/{t.tm_year}") print(f"Time: {t.tm_hour}:{t.tm_min}:{t.tm_sec}") print(f"Batt: {bat}%\n") night = sleep[t.tm_wday] morning = wake[t.tm_wday] if night: if night[0] == t.tm_hour and night[1] == t.tm_min: for i in ringtone: print(i[0]) tone(board.A0, i[0], i[1] * (60 / BPM)) wait = 60 if not on_always: while time.monotonic() - start < 5: rainbow.animate() rainbow.fill([0, 0, 0]) if morning: if morning[0] == t.tm_hour and morning[1] == t.tm_min: for i in ringtone: print(i[0]) tone(board.A0, i[0], i[1] * (60 / BPM)) wait = 60 if not on_always: while time.monotonic() - start < 5: rainbow.animate() rainbow.fill([0, 0, 0])
Set all the variables circled below to the current date and time.
Now that you have the time set in the code, copy cody.py to the CIRCUITPY drive which appears in your operating system File Explorer or Finder when the Feather is connected to your computer via a USB cable that is known to work for data transfer.
You'll now want to set the SET_DATE
variable to False
and copy it over to the CIRCUITPY drive again.
After you've copied all that over, your CIRCUITPY drive should look something like this:

Code run-through
In this section I'll go through how the code works and what does what. I'd recommend reading this if you're planning on modifying the code but it's also very useful to read since part of this project involves setting the time and day for your alarms to go off.
First, the code makes the required imports.
import time import board from adafruit_lc709203f import LC709203F import adafruit_pcf8523 from simpleio import tone import neopixel from adafruit_led_animation.animation.rainbow import Rainbow
Next, the code sets up the RTC, the LiPo charge indicator, the LED indicator, and the LED strip.
rtc = adafruit_pcf8523.PCF8523(board.I2C()) battery = LC709203F(board.I2C()) indicator = neopixel.NeoPixel(board.A1, 1) LEDs = neopixel.NeoPixel(board.A2, 20) LEDs.fill((0, 0, 0)) rainbow = Rainbow(LEDs, speed=0.1, period=2)
This next section is where the alarms are defined. The list titled days
is used to make printing the day easier and shouldn't be changed.
The dictionary titled sleep
is intended to be used as a reminder for the evening, and the dictionary titled wake
is intended to be used as a reminder in the morning.
You set the alarm by setting it as a tuple in the value of the dict item. The key (0, 1, 2, etc.) denotes the day, with 0 being Sunday. The first item in the tuple is the hour, and the second item is the minute.
If you don't want an alarm to ring on that day, set the value of the dict item for that day to None
.
days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] sleep = { 0: (22, 50), 1: (22, 50), 2: (22, 50), 3: (22, 50), 4: (22, 50), 5: None, 6: None, } wake = { 0: (11, 30), 1: (9, 50), 2: (9, 50), 3: (9, 50), 4: (9, 50), 5: (9, 50), 6: (11, 30), }
Next, the BPM that the alarm sound will play in is set and the alarm itself is set as well. Each tuple in the list is a note. The first item in the tuple is the frequency, and the second item is the duration, in beats. If you'd like to change this, one website I found very handy was this tone generator. It has a list of notes and their frequencies if you click the box next to the music note.
BPM = 160 ringtone = [ (330, 0.5), (294, 0.5), (185, 1), (208, 1), (277, 0.5), (247, 0.5), (147, 1), (165, 1), (247, 0.5), (220, 0.5), (139, 1), (165, 1), (220, 2), ]
This next section sets up the RTC. The first time you run the code, make sure to edit all the values (YEAR
, MONTH
, etc.) to the current date and time. After you first copy this over to the RP2040, you'll want to set SET_DATE
to False
and copy it over again. This ensures that the RTC won't get incorrectly set accidentally.
SET_DATE = True if SET_DATE: # Make sure to set this to the current date and time before using YEAR = 2022 MONTH = 3 DAY = 10 HOUR = 22 MINUTE = 49 SEC = 55 WDAY = 0 # 0 Sunday, 1 Monday, etc. t = time.struct_time((YEAR, MONTH, DAY, HOUR, MINUTE, SEC, WDAY, -1, -1)) print("Setting time to:", t) # uncomment for debugging rtc.datetime = t print()
The code then sets a few variables and enters the main loop. First, the code updates the animation if that is set to be always on, then checks if enough time has passed to run the alarm logic again. If it has, the code gets the datetime from the RTC and the battery percentage from the LiPo meter. It then sets the color of the indicator LED to a gradient between red and green (full red being 0%, and full green being 100%).
If the battery is below 15%, the indicator LED will flash on and off.
on_always = True # Set to False for animation only to be on when alarm rings indicate = True start = time.monotonic() wait = 1 while True: if on_always: rainbow.animate() if time.monotonic() - start > wait: start = time.monotonic() wait = 1 t = rtc.datetime bat = min(max(int(battery.cell_percent), 0), 100) g = int((bat / 100) * 255) r = int((1 - (bat / 100)) * 255) if bat >= 15: indicator.fill([r, g, 0]) if bat < 15: if indicate: indicator.fill([0, 0, 0]) indicate = False else: indicator.fill([r, g, 0]) indicate = True
For the second part of the main loop, the code prints a bunch of information that's useful for when you're connected to the RP2040 from a serial console and sets two variables that are the alarm times in the sleep
and wake
lists.
⠀ print(f"The date is {days[t.tm_wday]} {t.tm_mday}/{t.tm_mon}/{t.tm_year}") print(f"The time is {t.tm_hour}:{t.tm_min}:{t.tm_sec}") print(f"Battery: {bat}%") night = sleep[t.tm_wday] morning = wake[t.tm_wday]
The code then checks each of the two variables defined above to see if the user wants an alarm to be rung today. If so it then checks to see if it's time to ring that alarm. If it is, it plays the notes from the alarm sound and waits 60 seconds to ensure the alarm doesn't go off for the entire minute.
The code then waits for a second and runs the loop again.
⠀ if night: if night[0] == t.tm_hour and night[1] == t.tm_min: for i in ringtone: print(i[0]) tone(board.A0, i[0], i[1] * (60 / BPM)) wait = 60 if not on_always: while time.monotonic() - start < 5: rainbow.animate() rainbow.fill([0, 0, 0]) if morning: if morning[0] == t.tm_hour and morning[1] == t.tm_min: for i in ringtone: print(i[0]) tone(board.A0, i[0], i[1] * (60 / BPM)) wait = 60 if not on_always: while time.monotonic() - start < 5: rainbow.animate() rainbow.fill([0, 0, 0])
Page last edited January 21, 2025
Text editor powered by tinymce.