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 import neopixel import adafruit_lis3dh from adafruit_ticks import ticks_ms, ticks_add, ticks_diff from digitalio import DigitalInOut, Direction, Pull from adafruit_motor import servo from adafruit_led_animation.animation.comet import Comet from adafruit_led_animation.animation.pulse import Pulse from adafruit_led_animation.animation.sparkle import Sparkle from adafruit_led_animation.color import RED, BLUE, BLACK # 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() int1 = DigitalInOut(board.ACCELEROMETER_INTERRUPT) lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, int1=int1) lis3dh.range = adafruit_lis3dh.RANGE_2_G switch = DigitalInOut(board.EXTERNAL_BUTTON) switch.direction = Direction.INPUT switch.pull = Pull.UP switch_state = False wavs = [] for filename in os.listdir('/WAVs'): if filename.lower().endswith('.wav') and not filename.startswith('.'): wavs.append("/WAVs/"+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 track_number = 0 wav_filename = wavs[track_number] wav_file = open(wav_filename, "rb") wave = audiocore.WaveFile(wav_file) audio.play(mixer) mixer.voice[0].play(wave) def open_audio(num): n = wavs[num] f = open(n, "rb") w = audiocore.WaveFile(f) return w PIXEL_PIN = board.EXTERNAL_NEOPIXELS SERVO_PIN = board.EXTERNAL_SERVO NUM_PIXELS = 8 ORDER = neopixel.GRB BRIGHTNESS = 0.3 PWM = pwmio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50) SERVO = servo.Servo(PWM) pixel = neopixel.NeoPixel(board.NEOPIXEL, 1) pixel.brightness = 1 PIXELS = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, auto_write=False, pixel_order=ORDER) LARSON = Comet(PIXELS, bounce=True, speed=0.07, tail_length=NUM_PIXELS//2, color=(BLUE[0] * BRIGHTNESS, BLUE[1] * BRIGHTNESS, BLUE[2] * BRIGHTNESS)) pulse = Pulse(PIXELS, speed=0.05, color=(BLUE[0] * BRIGHTNESS, BLUE[1] * BRIGHTNESS, BLUE[2] * BRIGHTNESS), period=3) sparkle = Sparkle(PIXELS, speed=0.2, color=(RED[0] * BRIGHTNESS, RED[1] * BRIGHTNESS, RED[2] * BRIGHTNESS), num_sparkles=10) SERVO.angle = POSITION = NEXT_POSITION = 90 MOVING = False START_TIME = ticks_ms() DURATION = 1000 adabot_talk = False clock = ticks_ms() prop_time = 1000 adabot_nap = False mixer.voice[0].play(wave) while mixer.playing: LARSON.animate() while True: if ticks_diff(ticks_ms(), clock) >= prop_time: x, y, z = [ value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration ] if z > 0.9: adabot_nap = True SERVO.angle = POSITION = NEXT_POSITION = 90 else: adabot_nap = False if not adabot_nap: MOVING = not MOVING if MOVING: POSITION = NEXT_POSITION while abs(POSITION - NEXT_POSITION) < 10: NEXT_POSITION = random.uniform(0, 180) DURATION = 0.2 + 0.6 * abs(POSITION - NEXT_POSITION) / 180 else: SERVO.angle = NEXT_POSITION DURATION = random.uniform(0.5, 2.5) clock = ticks_add(clock, prop_time) if MOVING: FRACTION = 0.0 / DURATION FRACTION = (3 * FRACTION ** 2) - (2 * FRACTION ** 3) SERVO.angle = POSITION + (NEXT_POSITION - POSITION) * FRACTION if adabot_talk: wave = open_audio(random.randint(1, 17)) mixer.voice[0].play(wave) while mixer.playing: sparkle.animate() if not mixer.playing: adabot_talk = False PIXELS.fill(BLACK) PIXELS.show() elif adabot_nap: pulse.animate() else: LARSON.animate() if not switch.value and switch_state is False: PIXELS.fill(BLACK) PIXELS.show() adabot_talk = True switch_state = True if switch.value and switch_state is True: switch_state = False
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
- WAVs folder
- code.py
Your RP2040 Prop-Maker Feather CIRCUITPY drive should look like this after copying the lib folder, WAVs folder and the code.py file.
How the CircuitPython Code Works
The code begins by enabling the EXTERNAL_POWER
pin on the Feather. This pin enables power to all of the external components. Next, the onboard LIS3DH accelerometer is instantiated over I2C and the EXTERNAL_BUTTON
pin is setup as an input.
# 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() int1 = DigitalInOut(board.ACCELEROMETER_INTERRUPT) lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, int1=int1) lis3dh.range = adafruit_lis3dh.RANGE_2_G switch = DigitalInOut(board.EXTERNAL_BUTTON) switch.direction = Direction.INPUT switch.pull = Pull.UP switch_state = False
Audio!
Next is getting the audio ready. All of the audio files are in a folder called WAVs. These file names are added to the wavs
array. The onboard I2S amp is being used for audio playback along with a Mixer
. The Mixer
lets you control playback and volume level in software.
Note: to make your own compatible wav files, check out this guide.
The open_audio()
function is used in the main loop to play a random audio file from the WAVs folder. You'll pass a track number to the function, which will return an opened WaveFile
to play through the Mixer
.
wavs = [] for filename in os.listdir('/WAVs'): if filename.lower().endswith('.wav') and not filename.startswith('.'): wavs.append("/WAVs/"+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 track_number = 0 wav_filename = wavs[track_number] wav_file = open(wav_filename, "rb") wave = audiocore.WaveFile(wav_file) audio.play(mixer) mixer.voice[0].play(wave) def open_audio(num): n = wavs[num] f = open(n, "rb") w = audiocore.WaveFile(f) return w
Lights!
The NeoPixel stick is attached to the EXTERNAL_NEOPIXELS
terminal block pin. The NeoPixels are using the LED Animations library to show fancy effects. LARSON
is a modified Comet
animation to mimic a Larson scanner effect and Pulse
shows a calming blink that fades in and out.
PIXEL_PIN = board.EXTERNAL_NEOPIXELS NUM_PIXELS = 8 ORDER = neopixel.GRB BRIGHTNESS = 0.6 PIXELS = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, auto_write=False, pixel_order=ORDER) LARSON = Comet(PIXELS, bounce=True, speed=0.6/NUM_PIXELS, tail_length=NUM_PIXELS//2, color=(RED[0] * BRIGHTNESS, RED[1] * BRIGHTNESS, RED[2] * BRIGHTNESS)) pulse = Pulse(PIXELS, speed=0.1, color=BLUE, period=3)
SERVO_PIN = board.EXTERNAL_SERVO PWM = pwmio.PWMOut(SERVO_PIN, duty_cycle=2 ** 15, frequency=50) SERVO = servo.Servo(PWM)
Variables and Time
The adafruit_ticks library is used for non-blocking time tracking in the loop. A few Boolean states are used to track various modes in the loop.
SERVO.angle = POSITION = NEXT_POSITION = 90 MOVING = False START_TIME = ticks_ms() DURATION = 1000 adabot_talk = False clock = ticks_ms() prop_time = 1000 adabot_nap = False
Adabot Speaks
Right before the loop, Adabot says hello. While the Mixer
is playing audio, the NeoPixel mouth shows the LARSON
animation in RED
.
mixer.voice[0].play(wave) while mixer.playing: LARSON.animate()
The Loop
Every second, the LIS3DH is read. If the Z
axis registers that Adabot is lying down, then the adabot_nap
state is set to True
. When Adabot is napping, the NeoPixels display the LARSON
animation in BLUE
and the servo stops moving.
if ticks_diff(ticks_ms(), clock) >= prop_time: x, y, z = [ value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration ] if z > 0.9: adabot_nap = True SERVO.angle = POSITION = NEXT_POSITION = 90 LARSON.color=(BLUE[0] * BRIGHTNESS, BLUE[1] * BRIGHTNESS, BLUE[2] * BRIGHTNESS)
If Adabot is not napping, then the servo alternates between moving and not moving, essentially moving every two seconds. The movement is random and tracks its previous position.
if not adabot_nap: MOVING = not MOVING if MOVING: POSITION = NEXT_POSITION while abs(POSITION - NEXT_POSITION) < 10: NEXT_POSITION = random.uniform(0, 180) DURATION = 0.2 + 0.6 * abs(POSITION - NEXT_POSITION) / 180 else: SERVO.angle = NEXT_POSITION DURATION = random.uniform(0.5, 2.5) clock = ticks_add(clock, prop_time) if MOVING: FRACTION = 0.0 / DURATION FRACTION = (3 * FRACTION ** 2) - (2 * FRACTION ** 3) SERVO.angle = POSITION + (NEXT_POSITION - POSITION) * FRACTION
Chat with Adabot
When you press the button, Adabot will speak. adabot_talk
is set to True
and the open_audio
function is used to load up one of the Adabot phrases from the WAVs folder. While the Mixer
is playing the audio, the NeoPixels show the LARSON
animation in RED
. After the audio finishes, adabot_talk
is set to False
and the pulse
animation begins again.
if adabot_talk: wave = open_audio(random.randint(1, 7)) mixer.voice[0].play(wave) while mixer.playing: LARSON.animate() if not mixer.playing: adabot_talk = False PIXELS.fill(BLACK) PIXELS.show() elif adabot_nap: LARSON.animate() else: pulse.animate() if not switch.value and switch_state is False: PIXELS.fill(BLACK) PIXELS.show() adabot_talk = True switch_state = True if switch.value and switch_state is True: switch_state = False
Text editor powered by tinymce.