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: Copyright (c) 2023 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import os import random import board import audiocore import audiobusio import audiomixer from digitalio import DigitalInOut, Direction import neopixel 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, GREEN import adafruit_character_lcd.character_lcd_i2c as character_lcd import adafruit_lis3dh from adafruit_seesaw.seesaw import Seesaw from adafruit_seesaw.rotaryio import IncrementalEncoder import keypad puzzle_time = 5 # seconds lcd_columns = 16 lcd_rows = 2 # 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 ss_enc0 = Seesaw(i2c, addr=0x36) enc0 = IncrementalEncoder(ss_enc0) button = keypad.Keys((board.EXTERNAL_BUTTON, board.D13,), value_when_pressed=False, pull=True) lcd = character_lcd.Character_LCD_I2C(i2c, lcd_columns, lcd_rows) lcd.backlight = True puzzle_msgs = ["UNLOCK\nDOOR", "DOOR\nUNLOCKED", "UNLOCKING"] wavs = [] for filename in os.listdir('/faz_sounds'): if filename.lower().endswith('.wav') and not filename.startswith('.'): wavs.append("/faz_sounds/"+filename) wavs.sort() print(wavs) 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) volume = 0.5 mixer.voice[0].level = volume 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 = 8 PIXELS = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, auto_write=True) pulse = Pulse(PIXELS, speed=0.001, color=RED, period=3) puzzle_clock = ticks_ms() puzzle_time = puzzle_time * 1000 puzzle = False wave = open_audio(0) pos0 = volume last_pos0 = pos0 node_num = 0 def normalize(val, min_v, max_v): return max(min(max_v, val), min_v) def puzzle_string(length): _string = "" for _ in range(length/2): b = random.randint(0, 1) if b == 0: r = chr(random.randint(ord('A'), ord('Z'))) else: r = str(random.randint(0, 9)) _string += r _string += "\n" for _ in range(length/2): b = random.randint(0, 1) if b == 0: r = chr(random.randint(ord('A'), ord('Z'))) else: r = str(random.randint(0, 9)) _string += r lcd.message = _string return _string while True: event = button.events.get() if event and event.pressed: number = event.key_number if number == 0 and not puzzle: pulse.fill(GREEN) puzzle = True lcd.clear() lcd.message = puzzle_msgs[2] wave = open_audio(1) mixer.voice[0].play(wave) while mixer.playing: pass puzzle_clock = ticks_add(ticks_ms(), puzzle_time) if number == 1: lcd.clear() node_num = (node_num + 1) % 5 print(node_num) if puzzle: x, y, z = [ value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration ] puzzle_string(lcd_columns*lcd_rows) if z > 0: wave = open_audio(2) print("playing up") pulse.fill(GREEN) else: wave = open_audio(3) print("playing down") pulse.fill(RED) mixer.voice[0].play(wave) while mixer.playing: puzzle_string(lcd_columns*lcd_rows) x, y, z = [ value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration ] if z > 0: pulse.fill(GREEN) else: pulse.fill(RED) if ticks_diff(ticks_ms(), puzzle_clock) >= puzzle_time: lcd.clear() puzzle = False lcd.message = puzzle_msgs[1] wave = open_audio(4) mixer.voice[0].play(wave) while mixer.playing: pass print("puzzle done") wave = open_audio(0) lcd.clear() pulse.fill(RED) if not puzzle: pulse.animate() mixer.voice[0].play(wave, loop=True) if node_num > 3: lcd.message = "SECURITY\nBREACHED" else: lcd.message = f"DEACTIVATED:\n{node_num} of 4" pos0 = -enc0.position if pos0 != last_pos0: if pos0 > last_pos0: volume = volume + 0.1 else: volume = volume - 0.1 volume = normalize(volume, 0.0, 1.0) mixer.voice[0].level = volume last_pos0 = pos0
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
- faz_sounds folder
- code.py
Your RP2040 Prop-Maker Feather CIRCUITPY drive should look like this after copying the lib folder, faz_sounds folder and the code.py file.
How the CircuitPython Code Works
The code begins by instantiating the LCD screen, accelerometer, and encoder over I2C. The two buttons are added to a keypad
object.
# 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 ss_enc0 = Seesaw(i2c, addr=0x36) enc0 = IncrementalEncoder(ss_enc0) button = keypad.Keys((board.EXTERNAL_BUTTON, board.D13,), value_when_pressed=False, pull=True) lcd = character_lcd.Character_LCD_I2C(i2c, lcd_columns, lcd_rows) lcd.backlight = True
Audio
The sound effects WAV files are imported from the faz_sounds folder. Audio is output to the I2S amp on the Feather thru a Mixer object. The open_audio()
function is used throughout the loop to open and play the sound effects.
wavs = [] for filename in os.listdir('/faz_sounds'): if filename.lower().endswith('.wav') and not filename.startswith('.'): wavs.append("/faz_sounds/"+filename) wavs.sort() print(wavs) 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) volume = 0.5 mixer.voice[0].level = volume 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 = 8 PIXELS = neopixel.NeoPixel(PIXEL_PIN, NUM_PIXELS, auto_write=True) pulse = Pulse(PIXELS, speed=0.001, color=RED, period=3)
Generate a Puzzle
The puzzle_string()
function generates a random series of letters and numbers to fill the LCD when the Faz Wrench is in puzzle mode.
def puzzle_string(length): _string = "" for _ in range(length/2): b = random.randint(0, 1) if b == 0: r = chr(random.randint(ord('A'), ord('Z'))) else: r = str(random.randint(0, 9)) _string += r _string += "\n" for _ in range(length/2): b = random.randint(0, 1) if b == 0: r = chr(random.randint(ord('A'), ord('Z'))) else: r = str(random.randint(0, 9)) _string += r lcd.message = _string return _string
Keypad Events
The loop is checking for two things: a keypad event
and if puzzle
mode is active. The keypad events are tied to a key_number
. If key_number 0
is pressed, then puzzle
mode is enacted. If key_number 1
is pressed, then the value of node_num
is increased by 1
. This updates text on the LCD.
event = button.events.get() if event and event.pressed: number = event.key_number if number == 0 and not puzzle: pulse.fill(GREEN) puzzle = True lcd.clear() lcd.message = puzzle_msgs[2] wave = open_audio(1) mixer.voice[0].play(wave) while mixer.playing: pass puzzle_clock = ticks_add(ticks_ms(), puzzle_time) if number == 1: lcd.clear() node_num = (node_num + 1) % 5 print(node_num)
Default Mode
If puzzle
is False
, then the Faz Wrench is in its default mode. The LCD displays the text "DEACTIVATED: 0 OF 4
", with 0
being the value of node_num
. node_num
increases with a button press. When node_num
is greater than 3
, the text "SECURITY BREACHED
" is displayed. The rotary encoder is used in this mode to control the volume of the sound effects.
if not puzzle: pulse.animate() mixer.voice[0].play(wave, loop=True) if node_num > 3: lcd.message = "SECURITY\nBREACHED" else: lcd.message = f"DEACTIVATED:\n{node_num} of 4" pos0 = -enc0.position if pos0 != last_pos0: if pos0 > last_pos0: volume = volume + 0.1 else: volume = volume - 0.1 volume = normalize(volume, 0.0, 1.0) mixer.voice[0].level = volume last_pos0 = pos0
Puzzle Mode
In puzzle
mode, the sound effect and NeoPixel color changes based on the orientation of the accelerometer. The LCD text displays random letters and numbers that are generated with the puzzle_string()
function.
if puzzle: x, y, z = [ value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration ] puzzle_string(lcd_columns*lcd_rows) if z > 0: wave = open_audio(2) print("playing up") pulse.fill(GREEN) else: wave = open_audio(3) print("playing down") pulse.fill(RED) mixer.voice[0].play(wave) while mixer.playing: puzzle_string(lcd_columns*lcd_rows) x, y, z = [ value / adafruit_lis3dh.STANDARD_GRAVITY for value in lis3dh.acceleration ] if z > 0: pulse.fill(GREEN) else: pulse.fill(RED) if ticks_diff(ticks_ms(), puzzle_clock) >= puzzle_time: lcd.clear() puzzle = False lcd.message = puzzle_msgs[1] wave = open_audio(4) mixer.voice[0].play(wave) while mixer.playing: pass print("puzzle done") wave = open_audio(0) lcd.clear() pulse.fill(RED)
Text editor powered by tinymce.