Skip to content

Instantly share code, notes, and snippets.

@anderm3
Last active September 7, 2022 16:05
Show Gist options
  • Save anderm3/d29872bc00f5ad086934aac7944b1c26 to your computer and use it in GitHub Desktop.
Save anderm3/d29872bc00f5ad086934aac7944b1c26 to your computer and use it in GitHub Desktop.
# SPDX-FileCopyrightText: 2018 Phillip Burgess for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# Fetures/Bugs added 2022 Matt Anderson
# Gemma "Firewalker Lite" sneakers
# - Uses the following Adafruit parts (X2 for two shoes):
# * Gemma M0 3V microcontroller (#3501)
# * 150 mAh LiPoly battery (#1317) or larger
# * Medium vibration sensor switch (#2384)
# * 60/m NeoPixel RGB LED strip (#1138 or #1461)
# * LiPoly charger such as #1304
#
# - originally written by Phil Burgess for Gemma using Arduino
# * https://learn.adafruit.com/gemma-led-sneakers
#
# - Updated to include heel index and to wrap animation around
# shoe starting at the heel going to the toe
import board
import digitalio
import neopixel
import adafruit_dotstar
from rainbowio import colorwheel
try:
import urandom as random
except ImportError:
import random
# Disable on-board LED
iled = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1)
# Declare a NeoPixel object on led_pin with num_leds as pixels
# No auto-write.
led_pin = board.D1 # Which pin your pixels are connected to
num_leds = 16 # How many LEDs you have
circumference = 31 # Shoe circumference, in pixels, may be > NUM_LEDS
heel_index = 5 # Which LED is the back of the shoe
frames_per_second = 50 # Animation frames per second
brightness = 0 # Current wave height
strip = neopixel.NeoPixel(led_pin, circumference, brightness=1, auto_write=False)
offset = 0
# vibration sensor
motion_pin = board.D0 # Pin where vibration switch is connected
pin = digitalio.DigitalInOut(motion_pin)
pin.direction = digitalio.Direction.INPUT
pin.pull = digitalio.Pull.UP
ramping_up = False
center = 0 # Center point of wave in fixed-point space (0 - 255)
speed = 2 # Distance to move between frames (-128 - +127)
width = 2 # Width from peak to bottom of triangle wave (0 - 128)
hue = 3 # Current wave hue (color) see comments later
hue_target = 4 # Final hue we're aiming for
red = 5 # LED RGB color calculated from hue
green = 6 # LED RGB color calculated from hue
blue = 7 # LED RGB color calculated from hue
y = 0
brightness = 0
count = 0
# Gemma can animate 3 of these on 40 LEDs at 50 FPS
# More LEDs and/or more waves will need lower
wave = [0] * 8, [0] * 8, [0] * 8
# Note that the speeds of each wave are different prime numbers.
# This avoids repetition as the waves move around the
# perimeter...if they were even numbers or multiples of each
# other, there'd be obvious repetition in the pattern of motion...
# beat frequencies.
n_waves = len(wave)
# 90 distinct hues (0-89) around color wheel
hue_table = [255, 255, 255, 255, 255, 255, 255, 255, 237, 203,
169, 135, 101, 67, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 18, 52, 86, 120, 154, 188, 222,
255, 255, 255, 255, 255, 255, 255, 255]
# Gamma-correction table
gammas = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110,
112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133,
135, 137, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158,
160, 162, 164, 167, 169, 171, 173, 175, 177, 180, 182, 184, 186,
189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218,
220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252,
255
]
def h2rgb(colour_hue):
colour_hue %= 90
h = hue_table[colour_hue >> 1]
if colour_hue & 1:
ret = h & 15
else:
ret = (h >> 4)
return ret * 17
# pylint: disable=global-statement
def wave_setup():
global wave
wave = [[0, 3, 60, 0, 0, 0, 0, 0],
[0, -5, 45, 0, 0, 0, 0, 0],
[0, 7, 30, 0, 0, 0, 0, 0]]
# assign random starting colors to waves
for wave_index in range(n_waves):
current_wave = wave[wave_index]
random_offset = random.randint(0, 90)
current_wave[hue] = current_wave[hue_target] = 90 + random_offset
current_wave[red] = h2rgb(current_wave[hue] - 30)
current_wave[green] = h2rgb(current_wave[hue])
current_wave[blue] = h2rgb(current_wave[hue] + 30)
def vibration_detector():
while True:
if not pin.value:
return True
while True:
# wait for vibration sensor to trigger
if not ramping_up:
ramping_up = vibration_detector()
wave_setup()
# But it's not just a straight shot that it ramps up.
# This is a low-pass filter...it makes the brightness
# value decelerate as it approaches a target (200 in
# this case). 207 is used here because integers round
# down on division and we'd never reach the target;
# it's an ersatz ceil() function: ((199*7)+200+7)/8 = 200;
#
# J/k we're going w 167 because 207 is a bit too bright
brightness = int(((brightness * 7) + 167) / 8)
count += 1
if count == num_leds:
ramping_up = False
count = 0
# Wave positions and colors are updated...
for w in range(n_waves):
# Move wave; wraps around ends, is OK!
wave[w][center] += wave[w][speed]
# Hue not currently changing?
if wave[w][hue] == wave[w][hue_target]:
# There's a tiny random chance of picking a new hue...
if not random.randint(frames_per_second * 4, 255):
# Within 1/3 color wheel
wave[w][hue_target] = random.randint(
wave[w][hue] - 30, wave[w][hue] + 30)
# This wave's hue is currently shifting...
else:
if wave[w][hue] < wave[w][hue_target]:
wave[w][hue] += 1 # Move up or
else:
wave[w][hue] -= 1 # down as needed
# Reached destination?
if wave[w][hue] == wave[w][hue_target]:
wave[w][hue] = 90 + wave[w][hue] % 90 # Clamp to 90-180 range
wave[w][hue_target] = wave[w][hue] # Copy to target
wave[w][red] = h2rgb(wave[w][hue] - 30)
wave[w][green] = h2rgb(wave[w][hue])
wave[w][blue] = h2rgb(wave[w][hue] + 30)
# Now render the LED strip using the current
# brightness & wave states.
# Each LED in strip is visited just once...
for i in range(num_leds):
# Transform 'i' (LED number in pixel space) to the
# equivalent point in 8-bit fixed-point space (0-255)
# "* 256" because that would be
# the start of the (N+1)th pixel
# "+ 127" to get pixel center.
x = (i * 256 + 127) / circumference
# LED assumed off, but wave colors will add up here
r = g = b = 0
# For each item in wave[] array...
for w_index in range(n_waves):
# Calculate distance from pixel center to wave
# center point, using both signed and unsigned
# 8-bit integers...
d1 = int(abs(x - wave[w_index][center]))
d2 = int(abs(x - wave[w_index][center]))
# Then take the lesser of the two, resulting in
# a distance (0-128)
# that 'wraps around' the ends of the strip as
# necessary...it's a contiguous ring, and waves
# can move smoothly across the gap.
if d2 < d1:
d1 = d2 # d1 is pixel-to-wave-center distance
# d2 distance, relative to wave width, is then
# proportional to the wave's brightness at this
# pixel (basic linear y=mx+b stuff).
# Is distance within wave's influence?
# d2 is opposite; distance to wave's end
if d1 < wave[w_index][width]:
d2 = wave[w_index][width] - d1
y = int(brightness * d2 / wave[w_index][width]) # 0 to 200
# y is a brightness scale value --
# proportional to, but not exactly equal
# to, the resulting RGB value.
if y < 128: # Fade black to RGB color
# In HSV colorspace, this would be
# tweaking 'value'
n = int(y * 2 + 1) # 1-256
r += (wave[w_index][red] * n) >> 8 # More fixed-point math
# Wave color is scaled by 'n'
g += (wave[w_index][green] * n) >> 8
b += (wave[w_index][blue] * n) >> 8 # >>8 is equiv to /256
else: # Fade RGB color to white
# In HSV colorspace, this tweaks 'saturation'
n = int((y - 128) * 2) # 0-255 affects white level
m = 256 * n
n = 256 - n # 1-256 affects RGB level
r += (m + wave[w_index][red] * n) >> 8
g += (m + wave[w_index][green] * n) >> 8
b += (m + wave[w_index][blue] * n) >> 8
# r,g,b are 16-bit types that accumulate brightness
# from all waves that affect this pixel; may exceed
# 255. Now clip to 0-255 range:
if r > 255:
r = 255
if g > 255:
g = 255
if b > 255:
b = 255
# Store resulting RGB value
# mirror inside and outside led
outside = (i + heel_index) % circumference
inside = (heel_index - i ) % circumference
strip[outside] = (r, g, b)
strip[inside] = (r, g, b)
# Once rendering is complete, a second pass is made
# through pixel data applying gamma correction, for
# more perceptually linear colors.
# https://learn.adafruit.com/led-tricks-gamma-correction
for j in range(num_leds):
outside = (j + heel_index) % circumference
inside = (heel_index - j + circumference) % circumference
(red_gamma, green_gamma, blue_gamma) = strip[outside]
red_gamma = gammas[red_gamma]
green_gamma = gammas[green_gamma]
blue_gamma = gammas[blue_gamma]
strip[outside] = (red_gamma, green_gamma, blue_gamma)
strip[inside] = (red_gamma, green_gamma, blue_gamma)
strip.show()
# debug light
#iled.brightness = 0.5
#i = (count) % 256 # run from 0 to 255
#iled.fill(colorwheel(i))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment