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

Angled shot of a Adafruit Feather nRF52840 Express.
The Adafruit Feather nRF52840 Express is the new Feather family member with Bluetooth Low Energy and native USB support featuring the nRF52840!  It's...
$24.95
In Stock
Angled shot of a Adafruit Prop-Maker FeatherWing.
The Adafruit Feather series gives you lots of options for a small, portable, rechargeable microcontroller board. Perfect for fitting into your next prop build! This FeatherWing will...
$9.95
In Stock
Top down view of an Adafruit NeoPixel Digital RGB  60 LED-per-meter strip.
Fancy new side light LED strips are a great alternative for folks who have loved and used Adafruit LED strips for a few years but want gorgeous, glowy light emitting at...
$17.95
In Stock
Mini Oval Speaker with pico blade connector
Hear the good news! This wee speaker is a great addition to any audio project where you need 8 ohm impedance and 1W or less of power. We particularly like...
$1.95
In Stock
Lithium Ion Cylindrical Battery - 3.7v 2200mAh with JST PH connector
Need a big battery for your project? This lithium-ion battery contains a 2200mAh and a protection circuit that provides over-voltage, under-voltage, and over-current protection. Yet,...
$9.95
In Stock
1 x Running Vest
Running Vest
8 x Magnets
High-strength 'rare earth' magnet
1 x Slide Switch
Slide Switch
1 x JST PH 2mm 3-Pin
JST PH 2mm 3-Pin
1 x USB Micro-B Breakout Board
USB Micro-B Breakout Board
1 x SiliconeRibbon Cable - 10 Wire
Silicone Cover Stranded-Core Ribbon Cable - 10 Wire 1 Meter Long - 28AWG Black
1 x JST-PH Battery Extension Cable
JST-PH Battery Extension Cable - 500mm
4 x M2x10mm Screws
M2x10mm Screws

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.

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 :)

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! :)

Once you've set up your Feather nRF52840 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: 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 mode

Upload 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.

The wiring diagram below provides a visual reference for connecting the components. It is not true to scale, it is just meant 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 wired connections - the length of wire, position and size of components are not exact. 

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. 

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 magents 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!

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.

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, tab 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.

This guide was first published on Mar 07, 2023. It was last updated on Mar 07, 2023.