TRON DISC
2023-08-15 | By Adafruit Industries
License: See Original Project 3D Printing LED Strips Programmers Wearables Adafruit Feather
Courtesy of Adafruit
Guide by Ruiz Brothers
Overview
You can build a Tron-inspired prop using NeoPixel LEDs and CircuitPython.
Powered by the Adafruit Feather and Prop-Maker FeatherWing, this Tron disc is fully 3D printed.
With the power of neodymium magnets, this disc can actually attach to your back!
Using a mobile device and the Bluefruit app, you can change the color of the LEDs, so it's easy to switch your team color.
The Prop-Maker FeatherWing and Feather Bluefruit are the perfect pair for making advanced props with motion-activated lights and sounds that can be controlled wirelessly from your phone or watch.
The code is written in CircuitPython, so it’s easy to modify to fit just about any prop with lights and sounds.
It uses Bluetooth (BLE) and the LED animation library to trigger lighting effects with the Prop-Maker’s on-board accelerometer.
Parts
1 x Running Vest
8 x Magnets
1 x Slide Switch
1 x JST PH 2mm 3-Pin
4 x M2x10mm Screws
CircuitPython for Feather nRF52840
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-costmicrocontrollers. 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.
The following instructions will show you how to install CircuitPython. If you've already installed CircuitPython but are looking to update it or reinstall it, the same steps work for that as well!
Set up CircuitPython Quick Start!
Follow this quick step-by-step for super-fast Python power :)
Download the latest version of CircuitPython for this board via CircuitPython.org
Click the link above to download the latest UF2 file.
Download and save it to your desktop (or wherever is handy).
Plug your Feather nRF52840 into your computer using a known-good USB cable.
A lot of people end up using charge-only USB cables and it is very frustrating! So, make sure you have a USB cable you know is good for data sync.
Double-click the Reset button next to the USB connector on your board, and you will see the NeoPixel RGB LED turn green (identified by the arrow in the image). If it turns red, check the USB cable, try another USB port, etc. Note: The little red LED next to the USB connector will pulse red. That's ok!
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
You will see a new disk drive appear called FTHR840BOOT.
Drag the adafruit_circuitpython_etc.uf2 file to FTHR840BOOT.
The LED will flash. Then, the FTHR840BOOT drive will disappear, and a new disk drive called CIRCUITPY will appear.
That's it, you're done! :)
Code
Once you've set up your Feather nRF52840 with CircuitPython, you can access the code and necessary libraries bydownloading 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: 2021 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
Prop-Maker based Tron Disk
Adapted from the Darksaber code
Adafruit invests time and resources providing this open source code.
Please support Adafruit and open source hardware by purchasing
products from Adafruit!
Written by Liz Clark for Adafruit Industries
Copyright (c) 2023 Adafruit Industries
Licensed under the MIT license.
All text above must be included in any redistribution.
"""
import time
import random
import board
from digitalio import DigitalInOut, Direction
import neopixel
import adafruit_lis3dh
from adafruit_led_animation.animation.solid import Solid
from adafruit_led_animation.animation.pulse import Pulse
from adafruit_led_animation.animation.comet import Comet
from adafruit_bluefruit_connect.packet import Packet
from adafruit_bluefruit_connect.color_packet import ColorPacket
from adafruit_ble import BLERadio
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
from adafruit_ble.services.nordic import UARTService
# BLE setup
ble = BLERadio()
uart_service = UARTService()
advertisement = ProvideServicesAdvertisement(uart_service)
# CUSTOMISE SENSITIVITY HERE: smaller numbers = more sensitive to motion
HIT_THRESHOLD = 250
SWING_THRESHOLD = 150
# Set to the length in seconds of the "on.wav" file
POWER_ON_SOUND_DURATION = 1.7
# NeoPixel setup
NUM_PIXELS = 37 # Number of pixels used in project
NEOPIXEL_PIN = board.D5
POWER_PIN = board.D10
enable = DigitalInOut(POWER_PIN)
enable.direction = Direction.OUTPUT
enable.value = False
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=.5, auto_write=False)
strip.fill(0) # NeoPixels off ASAP on startup
strip.show()
# default NeoPixel color is white
COLOR = (0, 161, 255)
# NeoPixel animations
pulse = Pulse(strip, speed=0.05, color=COLOR, period=3)
solid = Solid(strip, color=COLOR)
comet = Comet(strip, speed=0.05, color=COLOR, tail_length=40)
#audio
try:
from audiocore import WaveFile
except ImportError:
from audioio import WaveFile
try:
from audioio import AudioOut
except ImportError:
try:
from audiopwmio import PWMAudioOut as AudioOut
except ImportError:
pass # not always supported by every board!
audio = AudioOut(board.A0) # Speaker
wave_file = None
# Set up accelerometer on I2C bus, 4G range:
i2c = board.I2C() # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller
accel = adafruit_lis3dh.LIS3DH_I2C(i2c)
accel.range = adafruit_lis3dh.RANGE_4_G
def play_wav(name, loop=False):
"""
Play a WAV file in the 'sounds' directory.
:param name: partial file name string, complete name will be built around
this, e.g. passing 'foo' will play file 'sounds/foo.wav'.
:param loop: if True, sound will repeat indefinitely (until interrupted
by another sound).
"""
global wave_file # pylint: disable=global-statement
print("playing", name)
if wave_file:
wave_file.close()
try:
wave_file = open('sounds/' + name + '.wav', 'rb')
wave = WaveFile(wave_file)
audio.play(wave, loop=loop)
except OSError:
pass # we'll just skip playing then
def power_on(sound, duration):
"""
Animate NeoPixels with accompanying sound effect for power on.
:param sound: sound name (similar format to play_wav() above)
:param duration: estimated duration of sound, in seconds (>0.0)
"""
start_time = time.monotonic() # Save audio start time
play_wav(sound)
while True:
elapsed = time.monotonic() - start_time # Time spent playing sound
if elapsed > duration: # Past sound duration?
break # Stop animating
comet.animate()
# List of swing wav files without the .wav in the name for use with play_wav()
swing_sounds = [
'swing1',
'swing2',
'swing3',
'swing4',
]
# List of hit wav files without the .wav in the name for use with play_wav()
hit_sounds = [
'hit1',
'hit2',
'hit3',
'hit4',
]
mode = 0 # Initial mode = OFF
#RGB LED
red_led = DigitalInOut(board.D11)
green_led = DigitalInOut(board.D12)
blue_led = DigitalInOut(board.D13)
red_led.direction = Direction.OUTPUT
green_led.direction = Direction.OUTPUT
blue_led.direction = Direction.OUTPUT
blue_led.value = True
red_led.value = True
green_led.value = True
# Darksaber start-up before loop
if mode == 0: # If currently off...
enable.value = True
power_on('on', POWER_ON_SOUND_DURATION) # Power up!
play_wav('idle', loop=True) # Play idle sound now
mode = 1 # Idle mode
while True:
# begin advertising BLE
ble.start_advertising(advertisement)
# if no BLE connection...
# allows it to be used without the bluefruit app connection
while not ble.connected:
if mode >= 1: # If not OFF mode...
x, y, z = accel.acceleration # Read accelerometer
accel_total = x * x + z * z
# (Y axis isn't needed, due to the orientation that the Prop-Maker
# Wing is mounted. Also, square root isn't needed, since we're
# comparing thresholds...use squared values instead.)
if accel_total > HIT_THRESHOLD: # Large acceleration = HIT
TRIGGER_TIME = time.monotonic() # Save initial time of hit
play_wav(random.choice(hit_sounds)) # Start playing 'hit' sound
# NeoPixels are solid on with a hit
solid.animate()
mode = 3 # HIT mode
elif mode == 1 and accel_total > SWING_THRESHOLD: # Mild = SWING
TRIGGER_TIME = time.monotonic() # Save initial time of swing
play_wav(random.choice(swing_sounds)) # Randomly choose from available swing sounds
while audio.playing:
pass # wait till we're done
mode = 2 # we'll go back to idle mode
elif mode == 1:
# pulse animation when idling or swinging
pulse.animate()
elif mode > 1: # If in SWING or HIT mode...
if audio.playing: # And sound currently playing...
blend = time.monotonic() - TRIGGER_TIME # Time since triggered
if mode == 2: # If SWING,
blend = abs(0.5 - blend) * 2.0 # ramp up, down
else: # No sound now, but still SWING or HIT modes
play_wav('idle', loop=True) # Resume idle sound
mode = 1 # Return to idle mode
ble.stop_advertising()
# if BLE is connected...
while ble.connected:
# color picker from bluefruit app
if uart_service.in_waiting:
packet = Packet.from_stream(uart_service)
# if a color packet is recieved...
if isinstance(packet, ColorPacket):
print(packet.color)
# color for the different animations are updated
comet.color = packet.color
solid.color = packet.color
pulse.color = packet.color
solid.animate()
# repeat of the above code
if mode >= 1: # If not OFF mode...
x, y, z = accel.acceleration # Read accelerometer
accel_total = x * x + z * z
# (Y axis isn't needed, due to the orientation that the Prop-Maker
# Wing is mounted. Also, square root isn't needed, since we're
# comparing thresholds...use squared values instead.)
if accel_total > HIT_THRESHOLD: # Large acceleration = HIT
TRIGGER_TIME = time.monotonic() # Save initial time of hit
play_wav(random.choice(hit_sounds)) # Start playing 'hit' sound
# NeoPixels are solid on with a hit
solid.animate()
mode = 3 # HIT mode
elif mode == 1 and accel_total > SWING_THRESHOLD: # Mild = SWING
TRIGGER_TIME = time.monotonic() # Save initial time of swing
play_wav(random.choice(swing_sounds)) # Randomly choose from available swing sounds
while audio.playing:
pass # wait till we're done
mode = 2 # we'll go back to idle mode
elif mode == 1:
# pulse animation when idling or swinging
pulse.animate()
elif mode > 1: # If in SWING or HIT mode...
if audio.playing: # And sound currently playing...
blend = time.monotonic() - TRIGGER_TIME # Time since triggered
if mode == 2: # If SWING,
blend = abs(0.5 - blend) * 2.0 # ramp up, down
else: # No sound now, but still SWING or HIT modes
play_wav('idle', loop=True) # Resume idle sound
mode = 1 # Return to idle modeUpload the Code and Libraries to the Feather nRF52840
After downloading the Project Bundle, plug your 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 nRF52840's CIRCUITPY drive.
code.py
lib directory
sound directory
Your Feather nRF52840 CIRCUITPY drive should look like this after copying the lib folder, sound folder and the code.py file.
Circuit Diagram
The wiring diagram below provides a visual reference for connecting the components. It is not true to scale; it is justmeant to be used as reference. This diagram was created using the Fritzing software package.
Take a moment to review the components in the circuit diagram. This illustration is meant for referencing wiredconnections - the length of wire, position and size of components are not exact.
3D Printing
Parts List
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. Original design source may be downloaded using the links below.
Slice with settings for PLA material
The parts were sliced using CURA using the slice settings below.
PLA filament 220c extruder
0.2-layer height
10% gyroid infill
60mm/s print speed
60c heated bed
Filament Change
Use the filament change script to print the disc in multiple colors.
Go to Extensions -> Post Processing -> Modify G-Code
Select Filament Change.
Top Cover Layers
Enter 12 in the Layer section to print the first 12 layers in white PLA.
Click on add script and select Filament Change to add one more color change.
Layers 13 to 123 will print in black PLA.
Enter 123 in the Layer section to print the rest of the cover in white PLA.
Layers 124 and up will print the remainder in white PLA.
Bottom Cover Layers
Add a filament change script and enter 3 in the Layer section.
Layers 1 to 3 will print in black PLA. Layers 4 and up will print in white PLA.
Assembly
Solder Headers
Align socket header pins to the Prop-Maker wing. Use a breadboard to help solder the short side of the headers to the Prop-Maker.
Solder socket headers to the top side of the Feather nRF52840 board. Then, solder pin headers to the bottom side of the Prop-Maker FeatherWing so it can be plugged into the Feather.
Mount Feather
Align the Feather board to the stand-offs on the bottom cover part, with the USB port facing the slide switch mount.
Measure LED strip
Place the LED strips over the inner and outer walls, measure and cut to length.
The side light LEDs will mount to the inner wall.
Remove Sheath
Remove the sheath to fit the LED strips around the walls.
LED wires
Use a 3 pin JST connector to connect the LED strips to the Prop-Maker port.
Solder LED strips
Measure a short silicone ribbon to connect both LED strips.
Place LEDs
Plug the 3 pin JST connector into the STEMMA port on the Prop-Maker.
USB extension
Measure a short 5 wire silicon ribbon cable to extend the USB port.
Mount the USB breakout
The USB breakout board mounts with two M2.5x4mm long screws.
Speaker Mount
Connect the speaker to the port on the Prop-Maker.
Slide switch mount
Measure a short 2 wire cable to enable the slide switch.
EN and G on the Prop-Maker connect to any two pins on the slide switch.
The slide switch press fits at an angle with the slide positioned in the middle.
Battery extension
Shorten a 2 pin JST wire to connect the battery.
Battery mount
A 2200mAh battery slides between the clips on the base.
Place Magnets
Carefully orient the magnets in each mount. The caps tightly press fit over the magnets.
Attach cover
Align the slide switch and USB port to the top cover.
Cover screws
Align the case to the four screws holes on the side.
Use four M2x10mm long nylon screws to attach the top and bottom cover together.
Harness assemble
The harness Plate is attached to the back of a Running Vest with nylon string. Pass the string through the mesh fabric and loops on the Plate.
Place magnets into each mount and press-fit a printed cap over each one.
Complete!
Use
Bluetooth Control
This project uses the Adafruit Bluefruit LE connect app (available free for Android and iOS) to trigger the lights and sounds. It uses the Color Picker module to choose different colors. If you haven't downloaded the app yet, use the button below to install it on your mobile device.
Download BLE Connect App for Android
Using Bluefruit LE Connect App
Turn on the Feather by either connecting it via USB to your computer or with the slide switch.
Open the Bluefruit LE Connect app and locate the device named CIRCUITPY and tap the connect button. Locate and tap on Controller. Under module, tap on Color Picker.
Color Picker
You can change the color of the NeoPixel LEDs by using the Color Picker. Tap connect, select Controller. Under module, tap on Color Picker. Use the color wheel to select a color and the slider to adjust the brightness. Tap the send color button to trigger the color.
Apple Watch
If you have an Apple Watch, you can use the Bluefruit LE Connect App just like the mobile app. The Apple Watch app is included with the iOS app download.

