Once you've finished setting up your RP2040 Prop-Maker Feather with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.
To do this, click on the Download Project Bundle button in the window below. It will download to your computer as a zipped folder.
# SPDX-FileCopyrightText: 2023 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import os import time import board import audiocore import audiobusio import audiomixer import adafruit_ds3231 from digitalio import DigitalInOut, Direction import neopixel import keypad # enable external power pin # provides power to the external components external_power = DigitalInOut(board.EXTERNAL_POWER) external_power.direction = Direction.OUTPUT external_power.value = True # setup external button pin as a keypad object key = keypad.Keys((board.EXTERNAL_BUTTON,), value_when_pressed=False, pull=True) # neopixel mouth setup pixels = neopixel.NeoPixel(board.EXTERNAL_NEOPIXELS, 8, auto_write=True, brightness=0.2) blue = (0, 0, 255) off = (0, 0, 0) pixels.fill(off) # rtc module i2c = board.I2C() rtc = adafruit_ds3231.DS3231(i2c) # pylint: disable-msg=using-constant-test if False: # change to True if you want to set the time! # year, mon, date, hour, min, sec, wday, yday, isdst t = time.struct_time((2023, 8, 22, 16, 47, 00, 1, -1, -1)) # you must set year, mon, date, hour, min, sec and weekday # yearday is not supported, isdst can be set but we don't do anything with it at this time print("Setting time to:", t) # uncomment for debugging rtc.datetime = t print() # pylint: enable-msg=using-constant-test # sound arrays bookend_sounds = [] hour_sounds = [] min_sounds = [] clock_queue = [] # bring in the audio files and put them into the different arrays for filename in os.listdir('/clock_sounds'): if filename.lower().endswith('.wav') and not filename.startswith('.'): if filename.startswith('h'): hour_sounds.append("/clock_sounds/"+filename) elif filename.startswith('m'): min_sounds.append("/clock_sounds/"+filename) else: bookend_sounds.append("/clock_sounds/"+filename) # sort the arrays alphabetically bookend_sounds.sort() hour_sounds.sort() min_sounds.sort() print(hour_sounds) print(min_sounds) print(bookend_sounds) # i2s amp setup with mixer audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1, bits_per_sample=16, samples_signed=True) audio.play(mixer) mixer.voice[0].level = 0.5 # function for queueing wave files def open_audio(wavs, num): n = wavs[num] f = open(n, "rb") w = audiocore.WaveFile(f) clock_queue.append(w) return w # queue boot up sound to play before going into the loop boot_sound = open_audio(bookend_sounds, 2) # function for gathering clock audio files def prep_clock(hour_num, minute_num): # queues "the time is.." open_audio(bookend_sounds, 1) # convert 24 hour time from RTC to 12 hour if hour_num >= 12: # convert 24 hour to 12 hour if hour_num > 12: hour_num = hour_num - 12 # set to PM ampm_num = 3 # otherwise its the morning else: # set AM ampm_num = 0 # queueing hour, single digit waves have a 0 before the number if hour_num <= 9: print(hour_num) h = hour_sounds.index(f'/clock_sounds/h0{hour_num}.wav') else: print(hour_num) h = hour_sounds.index(f'/clock_sounds/h{hour_num}.wav') # open wave file for the hour open_audio(hour_sounds, h) # if the minute is divisible by 10 (10, 20, 30, etc) if minute_num % 10 == 0: # check for on the hour (1:00, 2:00, etc) if minute_num == 0: m = min_sounds.index(f'/clock_sounds/m{minute_num}0.wav') else: m = min_sounds.index(f'/clock_sounds/m{minute_num}.wav') print(minute_num) # individual wave files exist for 1-19 elif minute_num <= 19: print(minute_num) # if it's a single digit number, bring in the "oh" wave file if minute_num <= 9: mm = min_sounds.index('/clock_sounds/m0x.wav') open_audio(min_sounds, mm) m = min_sounds.index(f'/clock_sounds/m{minute_num}.wav') # otherwise, for minutes 21-59 that are not divisible by 10 else: # we need to seperate the minutes digits digits = list(map(int, str(minute_num))) #print(digits) # get the tens spot (30, 40, etc) mm = min_sounds.index(f'/clock_sounds/m{digits[0]}x.wav') # add the wav to the array open_audio(min_sounds, mm) # get the ones spot (1, 2, etc) m = min_sounds.index(f'/clock_sounds/m{digits[1]}.wav') # combined it will say ex: "30...5" # add the wav to the array open_audio(min_sounds, m) # finish with am or pm open_audio(bookend_sounds, ampm_num) # initial RTC read t = rtc.datetime print(t) # say the time on boot pixels.fill(blue) prep_clock(t.tm_hour, t.tm_min) for i in range(len(clock_queue)): mixer.voice[0].play(clock_queue[i], loop=False) while mixer.playing: pass # clear the audio file queue clock_queue.clear() pixels.fill(off) while True: # listen for button input event = key.events.get() if event: # if the button is pressed if event.pressed: # read from RTC module t = rtc.datetime # turn on neopixels pixels.fill(blue) # gather the audio files based on RTC reading prep_clock(t.tm_hour, t.tm_min) # play through each wave one by one for i in range(len(clock_queue)): mixer.voice[0].play(clock_queue[i], loop=False) while mixer.playing: pass # afterwards clear out the queue clock_queue.clear() # turn off neopixels pixels.fill(off)
Upload the Code and Libraries to the RP2040 Prop-Maker Feather
After downloading the Project Bundle, plug your RP2040 Prop-Maker Feather into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called CIRCUITPY. Unzip the folder and copy the following items to the RP2040 Prop-Maker Feather's CIRCUITPY drive.
- lib folder
- clock_sounds folder
- code.py
Your RP2040 Prop-Maker Feather CIRCUITPY drive should look like this after copying the lib folder, clock_sounds folder and the code.py file.
How the CircuitPython Code Works
The code begins by enabling the external power pin on the Feather for the terminal block pins and instantiating the external button as a keypad
object, the NeoPixels and the RTC module over I2C.
# enable external power pin # provides power to the external components external_power = DigitalInOut(board.EXTERNAL_POWER) external_power.direction = Direction.OUTPUT external_power.value = True # setup external button pin as a keypad object key = keypad.Keys((board.EXTERNAL_BUTTON,), value_when_pressed=False, pull=True) # neopixel mouth setup pixels = neopixel.NeoPixel(board.EXTERNAL_NEOPIXELS, 8, auto_write=True, brightness=0.2) blue = (0, 0, 255) off = (0, 0, 0) pixels.fill(off) # rtc module i2c = board.I2C() rtc = adafruit_ds3231.DS3231(i2c)
Set the Clock
Included in the code is an if
statement that can reset the RTC module datetime. If you set it to True
, you can edit the struct_time
with your current time and save the code to save the settings to the RTC. Make sure to set it back to False
so that you don't accidentally reset the time if the Feather reboots.
if False: # change to True if you want to set the time! # year, mon, date, hour, min, sec, wday, yday, isdst t = time.struct_time((2023, 8, 22, 16, 47, 00, 1, -1, -1)) # you must set year, mon, date, hour, min, sec and weekday # yearday is not supported, isdst can be set but we don't do anything with it at this time print("Setting time to:", t) # uncomment for debugging rtc.datetime = t print()
Bring in the Waves
The wave files in the /clock_sounds folder are sorted into different arrays in the code. After that the files are sorted alphabetically.
# sound arrays bookend_sounds = [] hour_sounds = [] min_sounds = [] clock_queue = [] # bring in the audio files and put them into the different arrays for filename in os.listdir('/clock_sounds'): if filename.lower().endswith('.wav') and not filename.startswith('.'): if filename.startswith('h'): hour_sounds.append("/clock_sounds/"+filename) elif filename.startswith('m'): min_sounds.append("/clock_sounds/"+filename) else: bookend_sounds.append("/clock_sounds/"+filename) # sort the arrays alphabetically bookend_sounds.sort() hour_sounds.sort() min_sounds.sort() print(hour_sounds) print(min_sounds) print(bookend_sounds)
I2S and Mixer
Audio playback occurs with the onboard I2S amp on the Feather. Audio files are passed through a Mixer
object so that you can control volume through software.
# i2s amp setup with mixer audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA) mixer = audiomixer.Mixer(voice_count=1, sample_rate=22050, channel_count=1, bits_per_sample=16, samples_signed=True) audio.play(mixer) mixer.voice[0].level = 0.5
Playback Functions
Two functions are used for playing back the audio files. The first, open_audio()
, opens the selected audio file and adds it to the clock_queue
array.
# function for queueing wave files def open_audio(wavs, num): n = wavs[num] f = open(n, "rb") w = audiocore.WaveFile(f) clock_queue.append(w) return w
The next function, prep_clock()
, takes the time from the RTC module and searches the audio file arrays for the matching wave files. f strings
are used to find a matching audio file. Then, the index location of that audio file is passed to the open_audio()
function to add the chosen file to the clock_queue
array.
# function for gathering clock audio files def prep_clock(hour_num, minute_num): # queues "the time is.." open_audio(bookend_sounds, 1) # convert 24 hour time from RTC to 12 hour if hour_num > 12: hour_num = hour_num - 12 # set PM ampm_num = 3 # otherwise its the morning else: # set AM ampm_num = 0 # queueing hour, single digit waves have a 0 before the number if hour_num <= 9: print(hour_num) h = hour_sounds.index(f'/clock_sounds/h0{hour_num}.wav') else: print(hour_num) h = hour_sounds.index(f'/clock_sounds/h{hour_num}.wav') # open wave file for the hour open_audio(hour_sounds, h) # if the minute is divisible by 10 (10, 20, 30, etc) if minute_num % 10 == 0: # check for on the hour (1:00, 2:00, etc) if minute_num == 0: m = min_sounds.index(f'/clock_sounds/m{minute_num}0.wav') else: m = min_sounds.index(f'/clock_sounds/m{minute_num}.wav') print(minute_num) # individual wave files exist for 1-19 elif minute_num <= 19: print(minute_num) # if it's a single digit number, bring in the "oh" wave file if minute_num <= 9: mm = min_sounds.index('/clock_sounds/m0x.wav') open_audio(min_sounds, mm) m = min_sounds.index(f'/clock_sounds/m{minute_num}.wav') # otherwise, for minutes 21-59 that are not divisible by 10 else: # we need to seperate the minutes digits digits = list(map(int, str(minute_num))) #print(digits) # get the tens spot (30, 40, etc) mm = min_sounds.index(f'/clock_sounds/m{digits[0]}x.wav') # add the wav to the array open_audio(min_sounds, mm) # get the ones spot (1, 2, etc) m = min_sounds.index(f'/clock_sounds/m{digits[1]}.wav') # combined it will say ex: "30...5" # add the wav to the array open_audio(min_sounds, m) # finish with am or pm open_audio(bookend_sounds, ampm_num)
The Loop
In the loop, the code listens for a keypad
event from the button. When the button is pressed, the NeoPixels turn blue to illuminate Adabot's mouth. The RTC module is read and its hour and minute reading are passed to the prep_clock
function to prepare the audio files for playback. Then, each queued audio file is played one by one. To finish up, the clock_queue
array is cleared and the NeoPixels are turned off.
while True: # listen for button input event = key.events.get() if event: # if the button is pressed if event.pressed: # read from RTC module t = rtc.datetime # turn on neopixels pixels.fill(blue) # gather the audio files based on RTC reading prep_clock(t.tm_hour, t.tm_min) # play through each wave one by one for i in range(len(clock_queue)): mixer.voice[0].play(clock_queue[i], loop=False) while mixer.playing: pass # afterwards clear out the queue clock_queue.clear() # turn off neopixels pixels.fill(off)
Text editor powered by tinymce.