Setup Adafruit Feather M4 for CircuitPython
Your Feather M4 should already come with CircuitPython but maybe there's a new version, or you overwrote your board with Arduino code! In that case, see the below for how to reinstall or update CircuitPython. Otherwise you can skip this and proceed with the build.
CircuitPython Libraries
Install the necessary Adafruit CircuitPython libraries by downloading the latest bundle. Unzip the file and locate the needed libraries. Drop the libraries into a folder named "lib" on the CIRCUITPY drive.
For non-express boards like the Trinket M0 or Gemma M0, you'll need to manually install the necessary libraries from the bundle.
Required CircuitPython Libraries:
- neopixel
- adafruit_lis3dh
- adafruit_bus_device
Before continuing make sure your board's lib folder or root filesystem has the neopixel, adafruit_lis3dh, and adafruit_bus_device files and folders copied over.
Sound Effects
The code was written to call on five different sound effects depending on the actions. You can make your own audio files or use the royalty-free ones we've provided. Be sure to create a new folder named "sounds" at the top level of the CIRCUITPY drive and drop in the audio files.
Adafruit CircuitPython supports 16-bit, Mono, 22.050kHz .wav audio format. Additionally, the looping sample idle.wav should be a multiple of 512 samples long.
- Power on – on.wav
- Idle humming – idle.wav
- Swing whoosh – swing.wav
- Crash strike – hit.wav
- Power off – off.wav
Upload The Code
Download a zip of the project by clicking 'Download: Project Zip' in the preview of code.py below.
Copy code.py to the CIRCUITPY drive and copy the .wav files to the top-level sounds directory in the CIRCUITPY drive.
Once the files has been uploaded to the drive, the board will automatically reboot and run the code.
# SPDX-FileCopyrightText: 2018 John Edgar Park for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""LASER SWORD (pew pew) example for Adafruit Hallowing & NeoPixel strip"""
# pylint: disable=bare-except
import time
import math
import gc
from digitalio import DigitalInOut, Direction, Pull
import audioio
import audiocore
import busio
import board
import neopixel
import adafruit_lis3dh
# CUSTOMIZE YOUR COLOR HERE:
# (red, green, blue) -- each 0 (off) to 255 (brightest)
# COLOR = (255, 0, 0) # red
COLOR = (100, 0, 255) # purple
# COLOR = (0, 100, 255) #cyan
# CUSTOMIZE SENSITIVITY HERE: smaller numbers = more sensitive to motion
HIT_THRESHOLD = 350 # 250
SWING_THRESHOLD = 125
NUM_PIXELS = 114
# NUM_PIXELS = 85
NEOPIXEL_PIN = board.D5
POWER_PIN = board.D10
SWITCH_PIN = board.D9
enable = DigitalInOut(POWER_PIN)
enable.direction = Direction.OUTPUT
enable.value =False
red_led = DigitalInOut(board.D11)
red_led.direction = Direction.OUTPUT
green_led = DigitalInOut(board.D12)
green_led.direction = Direction.OUTPUT
blue_led = DigitalInOut(board.D13)
blue_led.direction = Direction.OUTPUT
audio = audioio.AudioOut(board.A0) # Speaker
mode = 0 # Initial mode = OFF
strip = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False)
strip.fill(0) # NeoPixels off ASAP on startup
strip.show()
switch = DigitalInOut(SWITCH_PIN)
switch.direction = Direction.INPUT
switch.pull = Pull.UP
time.sleep(0.1)
# Set up accelerometer on I2C bus, 4G range:
i2c = busio.I2C(board.SCL, board.SDA)
accel = adafruit_lis3dh.LIS3DH_I2C(i2c)
accel.range = adafruit_lis3dh.RANGE_4_G
# "Idle" color is 1/4 brightness, "swinging" color is full brightness...
COLOR_IDLE = (int(COLOR[0] / 1), int(COLOR[1] / 1), int(COLOR[2] / 1))
COLOR_SWING = COLOR
COLOR_HIT = (255, 255, 255) # "hit" color is white
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).
"""
print("playing", name)
try:
wave_file = open('sounds/' + name + '.wav', 'rb')
wave = audiocore.WaveFile(wave_file)
audio.play(wave, loop=loop)
except:
return
def power(sound, duration, reverse):
"""
Animate NeoPixels with accompanying sound effect for power on / off.
@param sound: sound name (similar format to play_wav() above)
@param duration: estimated duration of sound, in seconds (>0.0)
@param reverse: if True, do power-off effect (reverses animation)
"""
if reverse:
prev = NUM_PIXELS
else:
prev = 0
gc.collect() # Tidy up RAM now so animation's smoother
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
fraction = elapsed / duration # Animation time, 0.0 to 1.0
if reverse:
fraction = 1.0 - fraction # 1.0 to 0.0 if reverse
fraction = math.pow(fraction, 0.5) # Apply nonlinear curve
threshold = int(NUM_PIXELS * fraction + 0.5)
num = threshold - prev # Number of pixels to light on this pass
if num != 0:
if reverse:
strip[threshold:prev] = [0] * -num
else:
strip[prev:threshold] = [COLOR_IDLE] * num
strip.show()
# NeoPixel writes throw off time.monotonic() ever so slightly
# because interrupts are disabled during the transfer.
# We can compensate somewhat by adjusting the start time
# back by 30 microseconds per pixel.
start_time -= NUM_PIXELS * 0.00003
prev = threshold
if reverse:
strip.fill(0) # At end, ensure strip is off
else:
strip.fill(COLOR_IDLE) # or all pixels set on
strip.show()
while audio.playing: # Wait until audio done
pass
def mix(color_1, color_2, weight_2):
"""
Blend between two colors with a given ratio.
@param color_1: first color, as an (r,g,b) tuple
@param color_2: second color, as an (r,g,b) tuple
@param weight_2: Blend weight (ratio) of second color, 0.0 to 1.0
@return: (r,g,b) tuple, blended color
"""
if weight_2 < 0.0:
weight_2 = 0.0
elif weight_2 > 1.0:
weight_2 = 1.0
weight_1 = 1.0 - weight_2
return (int(color_1[0] * weight_1 + color_2[0] * weight_2),
int(color_1[1] * weight_1 + color_2[1] * weight_2),
int(color_1[2] * weight_1 + color_2[2] * weight_2))
# Main program loop, repeats indefinitely
while True:
red_led.value = True
if not switch.value: # button pressed?
if mode == 0: # If currently off...
enable.value = True
power('on', 1.7, False) # Power up!
play_wav('idle', loop=True) # Play background hum sound
mode = 1 # ON (idle) mode now
else: # else is currently on...
power('off', 1.15, True) # Power down
mode = 0 # OFF mode now
enable.value = False
while not switch.value: # Wait for button release
time.sleep(0.2) # to avoid repeated triggering
elif mode >= 1: # If not OFF mode...
x, y, z = accel.acceleration # Read accelerometer
accel_total = x * x + z * z
# (Y axis isn't needed for this, assuming Hallowing is mounted
# sideways to stick. Also, square root isn't needed, since we're
# just comparing thresholds...use squared values instead, save math.)
if accel_total > HIT_THRESHOLD: # Large acceleration = HIT
TRIGGER_TIME = time.monotonic() # Save initial time of hit
play_wav('hit') # Start playing 'hit' sound
COLOR_ACTIVE = COLOR_HIT # Set color to fade from
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('swing') # Start playing 'swing' sound
COLOR_ACTIVE = COLOR_SWING # Set color to fade from
mode = 2 # SWING mode
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
strip.fill(mix(COLOR_ACTIVE, COLOR_IDLE, blend))
strip.show()
else: # No sound now, but still MODE > 1
play_wav('idle', loop=True) # Resume background hum
strip.fill(COLOR_IDLE) # Set to idle color
strip.show()
mode = 1 # IDLE mode now
Page last edited January 22, 2025
Text editor powered by tinymce.