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 random import board import audiocore import audiobusio import audiomixer import pwmio from digitalio import DigitalInOut, Direction import neopixel from adafruit_motor import servo from adafruit_ticks import ticks_ms, ticks_add, ticks_diff from adafruit_led_animation.animation.pulse import Pulse from adafruit_led_animation.color import RED, BLACK, GREEN import adafruit_display_text.label import displayio import framebufferio import rgbmatrix import terminalio import adafruit_vl53l4cd distance_trigger = 90 # cm text="Here lies Fred" text_color = 0xff0000 # how often to check for a new trigger from ToF pause_time = 30 # seconds # speed for scrolling the text on the matrix scroll_time = 0.1 # seconds displayio.release_displays() # 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 i2c = board.I2C() vl53 = adafruit_vl53l4cd.VL53L4CD(i2c) vl53.inter_measurement = 0 vl53.timing_budget = 200 matrix = rgbmatrix.RGBMatrix( width=64, height=32, bit_depth=4, rgb_pins=[board.D6, board.D5, board.D9, board.D11, board.D10, board.D12], addr_pins=[board.D25, board.D24, board.A3, board.A2], clock_pin=board.D13, latch_pin=board.D0, output_enable_pin=board.D1) display = framebufferio.FramebufferDisplay(matrix, auto_refresh=True) line1 = adafruit_display_text.label.Label( terminalio.FONT, color=text_color, text=text) line1.x = 1 line1.y = 14 def scroll(line): line.x = line.x - 1 line_width = line.bounding_box[2] if line.x < -line_width: line.x = display.width g = displayio.Group() g.append(line1) display.root_group = g wavs = [] for filename in os.listdir('/tomb_sounds'): if filename.lower().endswith('.wav') and not filename.startswith('.'): wavs.append("/tomb_sounds/"+filename) 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, buffer_size=32768) mixer.voice[0].level = 1 audio.play(mixer) wav_length = len(wavs) - 1 def open_audio(num): n = wavs[num] f = open(n, "rb") w = audiocore.WaveFile(f) return w PIXEL_PIN = board.EXTERNAL_NEOPIXELS BRIGHTNESS = 0.3 NUM_PIXELS = 2 PIXELS = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, auto_write=True) pulse = Pulse(PIXELS, speed=0.05, color=RED, period=3) COLORS = [RED, GREEN, BLACK] SERVO_PIN = board.EXTERNAL_SERVO PWM = pwmio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50) SERVO = servo.Servo(PWM) SERVO.angle = 0 clock = ticks_ms() the_time = 5000 x = 0 scroll_clock = ticks_ms() scroll_time = int(scroll_time * 1000) pause_clock = ticks_ms() pause_time = pause_time * 1000 pause = False vl53.start_ranging() while True: vl53.clear_interrupt() if vl53.distance < distance_trigger: if not pause: print("Distance: {} cm".format(vl53.distance)) SERVO.angle = 90 wave = open_audio(random.randint(0, wav_length)) mixer.voice[0].play(wave) while mixer.playing: pulse.color = COLORS[x] pulse.animate() if ticks_diff(ticks_ms(), scroll_clock) >= scroll_time: scroll(line1) display.refresh(minimum_frames_per_second=0) scroll_clock = ticks_add(scroll_clock, scroll_time) x = (x + 1) % 2 pause = True print("paused") pause_clock = ticks_add(pause_clock, pause_time) else: if ticks_diff(ticks_ms(), pause_clock) >= pause_time: print("back to sensing") pause = False print("still paused") if ticks_diff(ticks_ms(), scroll_clock) >= scroll_time: print("Distance: {} cm".format(vl53.distance)) scroll(line1) display.refresh(minimum_frames_per_second=0) scroll_clock = ticks_add(scroll_clock, scroll_time) SERVO.angle = 0 pulse.color = COLORS[2] pulse.animate()
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
- tomb_sounds folder
- code.py
Your RP2040 Prop-Maker Feather CIRCUITPY drive should look like this after copying the lib folder, tomb_sounds folder and the code.py file.
How the CircuitPython Code Works
At the top of the code are a few parameters that you can modify to customize the tombstone. distance_trigger
is the distance in centimeters where the time of flight sensor will trigger. text
is the text that will scroll across the RGB matrix. text_color
is the color of the scrolling text. pause_time
is the time in seconds that the time of flight sensor will delay after being initially triggered. scroll_time
is the speed in seconds that the text scrolls across the RGB matrix.
distance_trigger = 90 # cm text="Here lies Fred" text_color = 0xff0000 # how often to check for a new trigger from ToF pause_time = 30 # seconds # speed for scrolling the text on the matrix scroll_time = 0.1 # seconds
Time of Flight and RGB Matrix Setup
The time of flight sensor is instantiated over I2C, and the RGB matrix object is set up to use the FeatherWing pinout for the RP2040.
i2c = board.I2C() vl53 = adafruit_vl53l4cd.VL53L4CD(i2c) vl53.inter_measurement = 0 vl53.timing_budget = 200 matrix = rgbmatrix.RGBMatrix( width=64, height=32, bit_depth=4, rgb_pins=[board.D6, board.D5, board.D9, board.D11, board.D10, board.D12], addr_pins=[board.D25, board.D24, board.A3, board.A2], clock_pin=board.D13, latch_pin=board.D0, output_enable_pin=board.D1)
DisplayIO on the RGB Matrix
The matrix
is passed as a FramebufferDisplay
so that displayio
can be used with the RGB matrix. The text is created as a Label
. The scroll
function moves the text by one pixel at a time across the display.
display = framebufferio.FramebufferDisplay(matrix, auto_refresh=True) line1 = adafruit_display_text.label.Label( terminalio.FONT, color=text_color, text=text) line1.x = 1 line1.y = 14 def scroll(line): line.x = line.x - 1 line_width = line.bounding_box[2] if line.x < -line_width: line.x = display.width g = displayio.Group() g.append(line1) display.root_group = g
Sound Effects
The audio files in the /tomb_sounds folder are added to the wavs
list. If you change or add more sound effects to the folder, they will be added to the list with no additional modification in the code. Audio playback is handled with the I2S amp on the Feather. Playback is routed through a Mixer
object to allow for software volume control. The open_audio
function takes an index location for a wave file in the wavs
list and preps it for playback.
wavs = [] for filename in os.listdir('/tomb_sounds'): if filename.lower().endswith('.wav') and not filename.startswith('.'): wavs.append("/tomb_sounds/"+filename) 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, buffer_size=32768) mixer.voice[0].level = 1 audio.play(mixer) wav_length = len(wavs) - 1 def open_audio(num): n = wavs[num] f = open(n, "rb") w = audiocore.WaveFile(f) return w
PIXEL_PIN = board.EXTERNAL_NEOPIXELS BRIGHTNESS = 0.3 NUM_PIXELS = 2 PIXELS = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, auto_write=True) pulse = Pulse(PIXELS, speed=0.05, color=RED, period=3) COLORS = [RED, GREEN, BLACK] SERVO_PIN = board.EXTERNAL_SERVO PWM = pwmio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50) SERVO = servo.Servo(PWM) SERVO.angle = 0
Time Keeping
The ticks
library is used for time tracking without blocking in the loop. The scroll_clock
handles the delay for scrolling the text, and the pause_clock
adds a delay for triggering the time of flight sensor. ticks
uses milliseconds for time keeping. The pause_time
and scroll_time
variables at the top of the code are multiplied by 1000
to convert them from seconds to milliseconds.
x = 0 scroll_clock = ticks_ms() scroll_time = int(scroll_time * 1000) pause_clock = ticks_ms() pause_time = pause_time * 1000 pause = False
The Loop
In the loop, if the time of flight sensor is triggered, then the servo moves to 90 degrees, and a randomized audio file from the wavs
array is played through the speaker. While the audio is playing, the NeoPixels will pulse in either red or green. The x
variable allows for switching between the two colors each time the time of flight sensor is triggered.
The pause_clock
is used to avoid multiple triggers back to back in case someone is standing in front of the tombstone for a while. When the time of flight sensor is initially triggered, pause
is set to True
. It is not reset to False
until the pause_clock
is greater than the pause_time
delay.
vl53.clear_interrupt() if vl53.distance < distance_trigger: if not pause: print("Distance: {} cm".format(vl53.distance)) SERVO.angle = 90 wave = open_audio(random.randint(0, wav_length)) mixer.voice[0].play(wave) while mixer.playing: pulse.color = COLORS[x] pulse.animate() if ticks_diff(ticks_ms(), scroll_clock) >= scroll_time: scroll(line1) display.refresh(minimum_frames_per_second=0) scroll_clock = ticks_add(scroll_clock, scroll_time) x = (x + 1) % 2 pause = True print("paused") pause_clock = ticks_add(pause_clock, pause_time) else: if ticks_diff(ticks_ms(), pause_clock) >= pause_time: print("back to sensing") pause = False print("still paused")
The scroll_clock
allows the matrix to scroll the text by one pixel without blocking the other aspects of the code. Every time the scroll_time
passes, the scroll
function is called and moves the text by one pixel to the left.
if ticks_diff(ticks_ms(), scroll_clock) >= scroll_time: print("Distance: {} cm".format(vl53.distance)) scroll(line1) scroll_clock = ticks_add(scroll_clock, scroll_time)
The servo and NeoPixels are reset after being triggered by the time of flight sensor. The servo is moved back to 0 degrees, and the NeoPixel color is changed to BLACK
or off.
SERVO.angle = 0 pulse.color = COLORS[2] pulse.animate()
Text editor powered by tinymce.