Overview
Build a prop replica of the Memory Gun from the Gravity Falls TV show. Powered by the Feather PropMaker RP2040 and CircuitPython, this 3D printed prop lights up and plays blaster sound effects.
The 1.3in OLED screen displays a list of audio files stored on the Feather internal flash storage. Use the rotary encoder to cycle through the list of audio files. Press the button to trigger sound effects and light up the LED noodle.
The prop parts are designed to be 3D printed without any support material. The light bulb (plastic holiday ornament) features an LED noodle that is fitted inside 3d printed holder.
The parts are 3D printed using various glittery PLA filament without any sanding or post-processing. The pink "blaster shield" is laser cut acrylic, but can also be 3D printed in translucent PLA filament.
The Feather PropMaker RP2040 is housed inside the canister with access to the USB-C port for battery charging. A slide switch is hidden right below the USB-C port to power the prop on and off.
Page last edited December 16, 2024
Text editor powered by tinymce.
Circuit Diagram
The diagram below provides a general visual reference for wiring of the components once you get to the Assembly page. This diagram was created using the software package Fritzing.
Adafruit Library for Fritzing
Adafruit uses the Adafruit's Fritzing parts library to create circuit diagrams for projects. You can download the library or just grab individual parts. Get the library and parts from GitHub - Adafruit Fritzing Parts.
Wired Connections
- Slide switch connects to EN an GND pins on Feather
- Button connects to Btn and GND pins on Feather screw-block terminals
- Speaker connects to speaker (+ and –) pins on Feather screw-block terminals
- LED connects to 5V through a resistor and GND pins on Feather screw-block terminals
STEMMA QT Connections
- The rotary encoder connects to the OLED breakout with STEMMA QT
- OLED breakout connects to Feather with STEMMA QT
Battery Power
The Feather PropMaker RP2040 is powered with a 2200mAh LiPo battery.
Page last edited December 16, 2024
Text editor powered by tinymce.
Install CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.
CircuitPython Quickstart
Follow this step-by-step to quickly get CircuitPython running on your board.
Click the link above to download the latest CircuitPython UF2 file.
Save it wherever is convenient for you.
To enter the bootloader, hold down the BOOT/BOOTSEL button (highlighted in red above), and while continuing to hold it (don't let go!), press and release the reset button (highlighted in red or blue above). Continue to hold the BOOT/BOOTSEL button until the RPI-RP2 drive appears!
If the drive does not appear, release all the buttons, and then repeat the process above.
You can also start with your board unplugged from USB, press and hold the BOOTSEL button (highlighted in red above), continue to hold it while plugging it into USB, and wait for the drive to appear before releasing the button.
A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you have a USB cable you know is good for data sync.
You will see a new disk drive appear called RPI-RP2.
Drag the adafruit_circuitpython_etc.uf2 file to RPI-RP2.
The RPI-RP2 drive will disappear and a new disk drive called CIRCUITPY will appear.
That's it, you're done! :)
Safe Mode
You want to edit your code.py or modify the files on your CIRCUITPY drive, but find that you can't. Perhaps your board has gotten into a state where CIRCUITPY is read-only. You may have turned off the CIRCUITPY drive altogether. Whatever the reason, safe mode can help.
Safe mode in CircuitPython does not run any user code on startup, and disables auto-reload. This means a few things. First, safe mode bypasses any code in boot.py (where you can set CIRCUITPY read-only or turn it off completely). Second, it does not run the code in code.py. And finally, it does not automatically soft-reload when data is written to the CIRCUITPY drive.
Therefore, whatever you may have done to put your board in a non-interactive state, safe mode gives you the opportunity to correct it without losing all of the data on the CIRCUITPY drive.
To enter safe mode when using CircuitPython, plug in your board or hit reset (highlighted in red above). Immediately after the board starts up or resets, it waits 1000ms. On some boards, the onboard status LED (highlighted in green above) will blink yellow during that time. If you press reset during that 1000ms, the board will start up in safe mode. It can be difficult to react to the yellow LED, so you may want to think of it simply as a slow double click of the reset button. (Remember, a fast double click of reset enters the bootloader.)
In Safe Mode
If you successfully enter safe mode on CircuitPython, the LED will intermittently blink yellow three times.
If you connect to the serial console, you'll find the following message.
Auto-reload is off. Running in safe mode! Not running saved code. CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode. Press any key to enter the REPL. Use CTRL-D to reload.
You can now edit the contents of the CIRCUITPY drive. Remember, your code will not run until you press the reset button, or unplug and plug in your board, to get out of safe mode.
Flash Resetting UF2
If your board ever gets into a really weird state and CIRCUITPY doesn't show up as a disk drive after installing CircuitPython, try loading this 'nuke' UF2 to RPI-RP2. which will do a 'deep clean' on your Flash Memory. You will lose all the files on the board, but at least you'll be able to revive it! After loading this UF2, follow the steps above to re-install CircuitPython.
Page last edited December 16, 2024
Text editor powered by tinymce.
Code the Memory Gun
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) 2024 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import os
import board
import audiocore
import audiobusio
from digitalio import DigitalInOut, Direction
import displayio
import i2cdisplaybus
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
import adafruit_displayio_ssd1306
from adafruit_seesaw import seesaw, rotaryio
import vectorio
import keypad
#display setup
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 = False
# rotary encoder
i2c = board.STEMMA_I2C()
seesaw = seesaw.Seesaw(i2c, addr=0x36)
encoder = rotaryio.IncrementalEncoder(seesaw)
last_position = 0
# oled
oled_reset = board.D9
display_bus = i2cdisplaybus.I2CDisplayBus(i2c, device_address=0x3D, reset=oled_reset)
WIDTH = 128
HEIGHT = 64
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT)
# trigger button
switch = keypad.Keys((board.EXTERNAL_BUTTON,), value_when_pressed=False, pull=True)
# audio!
wavs = []
wav_names = []
for filename in os.listdir('/wavs'):
if filename.lower().endswith('.wav') and not filename.startswith('.'):
wavs.append("/wavs/"+filename)
wav_names.append(filename.replace('.wav', ''))
audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA)
num_wavs = len(wavs)
wav_index = 0
# function to open and play the audio files
def open_audio(num):
n = wavs[num]
f = open(n, "rb")
w = audiocore.WaveFile(f)
return w
wave = open_audio(wav_index)
audio.play(wave)
# make the display context
splash = displayio.Group()
display.root_group = splash
palette = displayio.Palette(1)
palette[0] = 0xFFFFFF
rect = vectorio.Rectangle(pixel_shader=palette, width=display.width, height=23, x=6, y=21)
splash.append(rect)
font = bitmap_font.load_font("/Arial-14.bdf")
color = 0xFFFFFF
text_0 = wav_names[(wav_index - 1) % num_wavs]
text_area_top_left = label.Label(font, text=text_0, color=color)
text_area_top_left.anchor_point = (0.0, 0.0)
text_area_top_left.anchored_position = (6, 0)
splash.append(text_area_top_left)
text_1 = wav_names[wav_index]
text_area_middle_left = label.Label(font, text=text_1,color=0x000000)
text_area_middle_left.anchor_point = (0.0, 0.5)
text_area_middle_left.anchored_position = (6, display.height / 2)
splash.append(text_area_middle_left)
text_2 = wav_names[(wav_index+1) % num_wavs]
text_area_bottom_left = label.Label(font, text=text_2, color=color)
text_area_bottom_left.anchor_point = (0.0, 1.0)
text_area_bottom_left.anchored_position = (6, display.height)
splash.append(text_area_bottom_left)
while True:
event = switch.events.get()
position = encoder.position
if position != last_position:
if position > last_position:
wav_index = (wav_index + 1) % num_wavs
else:
wav_index = (wav_index - 1) % num_wavs
text_area_top_left.text = wav_names[(wav_index-1) % num_wavs]
text_area_middle_left.text = wav_names[wav_index]
text_area_bottom_left.text = wav_names[(wav_index+1) % num_wavs]
last_position = position
if event:
if event.pressed:
external_power.value = True
wave = open_audio(wav_index)
audio.play(wave)
if event.released:
external_power.value = 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 Feather's CIRCUITPY drive:
- lib folder
- wavs folder
- code.py
- Arial-14.bdf
Your RP2040 Prop-Maker Feather CIRCUITPY drive should look like this after copying the lib folder, wav folder, .bdf font file and the code.py file.
How the CircuitPython Code Works
The code begins by instantiating the components and pins. The EXTERNAL_POWER pin is setup as an output. The rotary encoder and OLED display are instantiated over I2C and the EXTERNAL_BUTTON is setup as 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 = False # rotary encoder i2c = board.STEMMA_I2C() seesaw = seesaw.Seesaw(i2c, addr=0x36) encoder = rotaryio.IncrementalEncoder(seesaw) last_position = 0 # oled oled_reset = board.D9 display_bus = i2cdisplaybus.I2CDisplayBus(i2c, device_address=0x3D, reset=oled_reset) WIDTH = 128 HEIGHT = 64 display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT) # trigger button switch = keypad.Keys((board.EXTERNAL_BUTTON,), value_when_pressed=False, pull=True)
Audio
Any audio files that you have in the /wavs folder are added to the wavs and wav_names arrays. num_wavs holds the length of the wavs array. This means that you can change up the sound effects in your project without having to edit any of the code elements.
# audio!
wavs = []
wav_names = []
for filename in os.listdir('/wavs'):
if filename.lower().endswith('.wav') and not filename.startswith('.'):
wavs.append("/wavs/"+filename)
wav_names.append(filename.replace('.wav', ''))
audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA)
num_wavs = len(wavs)
wav_index = 0
# function to open and play the audio files
def open_audio(num):
n = wavs[num]
f = open(n, "rb")
w = audiocore.WaveFile(f)
return w
wave = open_audio(wav_index)
audio.play(wave)
Graphics
The OLED displays the sound effect names, which are stored in the wav_names array. There are three text elements used to show three names at a time. A vectorio rectangle "highlights" the center text element to select the sound effect.
# make the display context
splash = displayio.Group()
display.root_group = splash
palette = displayio.Palette(1)
palette[0] = 0xFFFFFF
rect = vectorio.Rectangle(pixel_shader=palette, width=display.width, height=23, x=6, y=21)
splash.append(rect)
font = bitmap_font.load_font("/Arial-14.bdf")
color = 0xFFFFFF
text_0 = wav_names[(wav_index - 1) % num_wavs]
text_area_top_left = label.Label(font, text=text_0, color=color)
text_area_top_left.anchor_point = (0.0, 0.0)
text_area_top_left.anchored_position = (6, 0)
splash.append(text_area_top_left)
text_1 = wav_names[wav_index]
text_area_middle_left = label.Label(font, text=text_1,color=0x000000)
text_area_middle_left.anchor_point = (0.0, 0.5)
text_area_middle_left.anchored_position = (6, display.height / 2)
splash.append(text_area_middle_left)
text_2 = wav_names[(wav_index+1) % num_wavs]
text_area_bottom_left = label.Label(font, text=text_2, color=color)
text_area_bottom_left.anchor_point = (0.0, 1.0)
text_area_bottom_left.anchored_position = (6, display.height)
splash.append(text_area_bottom_left)
The Loop
In the loop, the switch.events.get() is used to listen for any button input from the trigger button. position holds the value of the rotary encoder. When you rotate the encoder, the wav_index value increases or decreases. The wav_index determines the selected sound effect file and the highlighted sound effect name on the OLED.
event = switch.events.get()
position = encoder.position
if position != last_position:
if position > last_position:
wav_index = (wav_index + 1) % num_wavs
else:
wav_index = (wav_index - 1) % num_wavs
text_area_top_left.text = wav_names[(wav_index-1) % num_wavs]
text_area_middle_left.text = wav_names[wav_index]
text_area_bottom_left.text = wav_names[(wav_index+1) % num_wavs]
last_position = position
When the trigger button is pressed, the EXTERNAL_POWER pin value is True. This lights up the nOOd LED. The selected sound is opened and played thru the I2S amp. When the button is released, the EXTERNAL_POWER pin is False, turning off the nOOd and audio playback.
if event:
if event.pressed:
external_power.value = True
wave = open_audio(wav_index)
audio.play(wave)
if event.released:
external_power.value = False
Page last edited December 16, 2024
Text editor powered by tinymce.
CAD Files
3D Printed Parts
STL files for 3D printing are oriented to print "as-is" on FDM style machines. Parts are designed to 3D print without any support material using PLA filament. Original design source may be downloaded using the links below.
Build Volume
The parts require a 3D printer with a minimum build volume.
- 150mm (X) x 150mm (Y) x 80mm (Z)
Build Plate Adhesion
For best bed adhesion, apply a brim (6 line count) to the canister halves. This ensures the thin parts will stay on the build plate while 3D printing.
Translucent Filaments
The LED noodle holder is 3D printed using clear translucent PLA filament to allow best illumination from the LED noodle.
The shield is 3D printed using translucent red/pink PLA filament to best resemble the prop from the TV show.
PLA Filament
These filaments were used for the gold and dark bronze colored parts.
SVG Files
Vector files are available for the handle grips and blaster shield parts. The handle grips can be made using a vinyl cutter using desired material. The shield can optionally be made using a laser cutter or CNC milling machine.
Design Source Files
The project assembly was designed in Fusion 360. This can be downloaded in different formats like STEP, STL and more.
Electronic components like Adafruit's boards, displays, connectors and more can be downloaded from the Adafruit CAD parts GitHub Repo.
Page last edited December 16, 2024
Text editor powered by tinymce.
Wiring
LED Noodle Wiring
Make two wires that are 7.5 inches (19cm) in length, preferably in red and black colors.
Trim the leads from the 100 Ohm resistor so they're short.
Solder the black wire to the resistor, then solder the resistor to the cathode pin on the LED Noodle (no hole end).
Solder the red wire to the LED's anode pin (the one with the hole).
Wired LED Noodle
Take a moment to ensure the wires and resistor has been properly soldered to the LED noodle.
Wiring Button
Make two wires that are 8 inches (20cm) in length, preferably in white and black colored wires.
Solder the white wire to either of the two leads on the button.
Solder the black wire to the remaining lead on the button.
Slide Switch Wiring
Create two wires that are 2.5 inches (6cm) in length. These can both be in black.
Snip off one of the leads on the slide switch, either the far left or right but not the middle. Then, trim the two remaining leads short, about half their length.
Solder the two wires to each of the leads on the slide switch.
Wire Switch to Feather
Solder the two wires from the slide to the EN and GND pins on the bottom of the Feather.
Wired Slide Switch & Feather
Take a moment to ensure the wires from the slide switch has been properly soldered to the pins on the bottom of the Feather.
Speaker Wiring
Cut off the connector from the speaker. Using wire stripper, remove a bit of insulation from each wire.
Tin the exposed wires by adding a bit of solder.
Page last edited December 16, 2024
Text editor powered by tinymce.
Assembly
Installing Battery
Get the 2200 mAh battery and Handle B ready.
Orient the battery so the cable is upright.
Then, place the battery onto the built-in clips on the half of the handle.
Installing Button
Get the wired push button ready.
Insert the included hex nut to the body of the push button.
Place the push button into the designated spot on the Handle B part.
Ensure the hex nut is tightened to the wall of the 3D printed part.
Position the battery and button wires through the opening near the top of the part.
Close Handle Halves
Fit and join the other half of the handle with the battery and button wires fitted through the opening.
OLED Screen Cover
Use 4x M2.5 x 6mm long screws to secure the OLED PCB to the Screen Cover.
Orient the OLED PCB with the 3D printed screen cover.
Secure OLED to Cover
Place the OLED over the screen covers built-in standoffs with the mounting holes lined up.
Insert and fasten the M2.5 machine screws to secure the OLED to the screen cover.
Secure Rotary & OLED Cases
Position the rotary and OLED cases so the mounting holes are lined up correctly.
Insert the rotary case into the OLED case.
Use the M2.5 machine screws to secure the two parts together.
PCB Mount Hardware
Get the various 3D printed parts ready. Use M3 x 20mm long machine screws to secure these parts together.
- PCB Mount
- PCB Standoff
- Canister A
- Canister Coupler
PCB Mount Installation
Insert the two M3 machine screws through the PCB mount and the PCB standoff.
Place the Canister A part through the threads of the M3 screws.
Ensure the orientation of the parts match the assembly photo.
Install Canister Coupler
Place the Canister Coupler onto the flat side of the Canister A part.
Ensure the M3 screw threads are going through the couplers mounting holes.
Attach Coupler to OLED Casing
Begin placing the Canister Coupler onto the side of the OLED case with the M3 screws fitted through the mounting holes.
Ensure the parts orientation are matching the assembly photo.
Install LED Noodle to Holder
Get the wired LED noodle and LED noodle holder ready.
Press the noodle into the channel of the LED noodle holder.
Ensure the length of the LED noodle is equally divided in half on the 3D printed holder.
Bulb & Coupler
Get the plastic bulb and 3D printed bulb coupler ready.
Install LED Holder to Bulb
Fit the 3D printed LED holder into the plastic bulb. Push the holder all the way through the plastic bulb.
Install Shield & Coupler
Fit the 3D printed shield onto the thread of the plastic bulb.
Thread the LED wires through the bulb coupler, then fasten the 3D printed coupler onto the thread of the plastic bulb.
Install Speaker
Peel the off the protective layer from the face of the speaker.
Position the speaker so it's facing the slit inside the OLED case.
Firmly press the speaker onto the inside of the OLED case to stick it in place.
Install Speaker Cable
Thread the speaker cable through the hole inside OLED case.
Pull the speaker cable through the canister coupler, PCB standoff and PCB mount.
Install LED Assembly
Begin joining the bulb coupler to the rotary case.
Insert the LED noodle wires through the hole in the rotary case.
Pull the LED noodle wires through the rotary case and out of the OLED case.
Install Bulb to Rotary Case
Press fit the bulb coupler into the hole of the rotary case.
Thread and pull the LED wires through the canister coupler, PCB standoff, and PCB mount.
Installing Rotary Cable
Insert the STEMMA QT cable from the rotary encoder through the slot opening in the rotary case and pull it through the OLED case.
Place the rotary encoder breakout onto the rotary case's built-in standoffs and line up the mounting holes.
Secure Rotary
Use four M2.5 x 6mm long machine screws to secure the rotary encoder STEMMA QT breakout to the built-in standoffs.
Install Handle Cables
Begin joining the handle subassembly to the OLED case.
Insert and pull the battery cable and button wires through the hole on the OLED case.
Secure Handle
Fit the handle subassembly in between the mounting tabs on the OLED case.
Position the handle so the mounting holes are lined up with the mounting tabs on the OLED case.
Use four M2.5mm x 6mm long machine screws to secure the handle subassembly to the OLED case.
PCB Mount Handle Cables
Insert and pull the battery cable and button wires through the hole in the canister coupler, PCB standoff and PCB mount.
OLED STEMMA QT
Connect the second STEMMA QT cable to the port on the left of the OLED breakout.
Connect the STEMMA QT cable from the rotary encoder to the port on the right of the OLED breakout.
PCB Mount OLED Cable
Insert and pull the STEMMA QT cable through the canister coupler, PCB standoff and PCB mount.
Install OLED Cover
Firmly press the OLED cover onto the OLED casing to snap fit them together.
Ensure the OLED cover is oriented correctly with the OLED case.
Install Switch
Fit the slide switch onto the PCB mount.
The body of the slide switch should fit in between the two standoffs.
Connect Speaker Cable
Insert the two speaker wires into the designated speaker pins on the Feather screw-block terminals.
Use a small flat head screwdriver to secure the wires to the Feather screw-block terminals.
Connect Button
Insert one of the wires from the button to the designated button pin on the Feather screw-block terminal.
Secure the wire to the screw-block terminal.
Connect LED
Insert the anode wire from the LED noodle to the designated 5V pin on the Feather screw-block terminal.
Secure the wire to the screw-block terminal.
Connect Ground Wires
Insert the ground wire from the button and LED noodle to the designated ground pin on the Feather's screw-block terminal.
Secure the two wires to the screw-block terminal.
Secure Feather to PCB Mount
Place the Feather onto the PCB mount's standoffs.
Use two M2.5 x 6mm long machine screws to secure the Feather to the PCB mount.
Install Canister Bottom Cover
Orient the canister bottom cover with the Feather USB-C port and the slide switch.
Press the canister bottom cover under the two canister halves.
Install Rotary Cover
Orient the rotary cover with the rotary case so the snap fit parts are lined up.
Firmly press the rotary cover onto the rotary case.
Install Rotary Knob
Orient the knob with the shaft of the rotary encoder.
Press fit the 3D printed knob onto the shaft of the rotary encoder.
OLED Test
Use the slide switch to power on the Feather. The OLED will display the audio wave file names.
Page last edited December 16, 2024
Text editor powered by tinymce.