/ Gemma_Firewalker_Lite_Sneakers / code.py
code.py
1 # SPDX-FileCopyrightText: 2018 Phillip Burgess for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 # Gemma "Firewalker Lite" sneakers 6 # - Uses the following Adafruit parts (X2 for two shoes): 7 # * Gemma M0 3V microcontroller (#3501) 8 # * 150 mAh LiPoly battery (#1317) or larger 9 # * Medium vibration sensor switch (#2384) 10 # * 60/m NeoPixel RGB LED strip (#1138 or #1461) 11 # * LiPoly charger such as #1304 12 # 13 # - originally written by Phil Burgess for Gemma using Arduino 14 # * https://learn.adafruit.com/gemma-led-sneakers 15 16 import board 17 import digitalio 18 import neopixel 19 20 try: 21 import urandom as random 22 except ImportError: 23 import random 24 25 # Declare a NeoPixel object on led_pin with num_leds as pixels 26 # No auto-write. 27 led_pin = board.D1 # Which pin your pixels are connected to 28 num_leds = 40 # How many LEDs you have 29 circumference = 40 # Shoe circumference, in pixels, may be > NUM_LEDS 30 frames_per_second = 50 # Animation frames per second 31 brightness = 0 # Current wave height 32 strip = neopixel.NeoPixel(led_pin, num_leds, brightness=1, auto_write=False) 33 offset = 0 34 35 # vibration sensor 36 motion_pin = board.D0 # Pin where vibration switch is connected 37 pin = digitalio.DigitalInOut(motion_pin) 38 pin.direction = digitalio.Direction.INPUT 39 pin.pull = digitalio.Pull.UP 40 ramping_up = False 41 42 center = 0 # Center point of wave in fixed-point space (0 - 255) 43 speed = 1 # Distance to move between frames (-128 - +127) 44 width = 2 # Width from peak to bottom of triangle wave (0 - 128) 45 hue = 3 # Current wave hue (color) see comments later 46 hue_target = 4 # Final hue we're aiming for 47 red = 5 # LED RGB color calculated from hue 48 green = 6 # LED RGB color calculated from hue 49 blue = 7 # LED RGB color calculated from hue 50 51 y = 0 52 brightness = 0 53 count = 0 54 55 # Gemma can animate 3 of these on 40 LEDs at 50 FPS 56 # More LEDs and/or more waves will need lower 57 wave = [0] * 8, [0] * 8, [0] * 8 58 59 # Note that the speeds of each wave are different prime numbers. 60 # This avoids repetition as the waves move around the 61 # perimeter...if they were even numbers or multiples of each 62 # other, there'd be obvious repetition in the pattern of motion... 63 # beat frequencies. 64 n_waves = len(wave) 65 66 # 90 distinct hues (0-89) around color wheel 67 hue_table = [255, 255, 255, 255, 255, 255, 255, 255, 237, 203, 68 169, 135, 101, 67, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69 0, 0, 0, 0, 0, 0, 18, 52, 86, 120, 154, 188, 222, 70 255, 255, 255, 255, 255, 255, 255, 255] 71 72 # Gamma-correction table 73 gammas = [ 74 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 75 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 76 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 77 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 78 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 79 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 80 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 81 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 82 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 83 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 84 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 85 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 86 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 87 135, 137, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 88 160, 162, 164, 167, 169, 171, 173, 175, 177, 180, 182, 184, 186, 89 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 90 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 91 255 92 ] 93 94 95 def h2rgb(colour_hue): 96 colour_hue %= 90 97 h = hue_table[colour_hue >> 1] 98 99 if colour_hue & 1: 100 ret = h & 15 101 else: 102 ret = (h >> 4) 103 104 return ret * 17 105 106 107 # pylint: disable=global-statement 108 def wave_setup(): 109 global wave 110 111 wave = [[0, 3, 60, 0, 0, 0, 0, 0], 112 [0, -5, 45, 0, 0, 0, 0, 0], 113 [0, 7, 30, 0, 0, 0, 0, 0]] 114 115 # assign random starting colors to waves 116 for wave_index in range(n_waves): 117 current_wave = wave[wave_index] 118 random_offset = random.randint(0, 90) 119 120 current_wave[hue] = current_wave[hue_target] = 90 + random_offset 121 current_wave[red] = h2rgb(current_wave[hue] - 30) 122 current_wave[green] = h2rgb(current_wave[hue]) 123 current_wave[blue] = h2rgb(current_wave[hue] + 30) 124 125 126 def vibration_detector(): 127 while True: 128 if not pin.value: 129 return True 130 131 132 while True: 133 134 # wait for vibration sensor to trigger 135 if not ramping_up: 136 ramping_up = vibration_detector() 137 wave_setup() 138 139 # But it's not just a straight shot that it ramps up. 140 # This is a low-pass filter...it makes the brightness 141 # value decelerate as it approaches a target (200 in 142 # this case). 207 is used here because integers round 143 # down on division and we'd never reach the target; 144 # it's an ersatz ceil() function: ((199*7)+200+7)/8 = 200; 145 brightness = int(((brightness * 7) + 207) / 8) 146 count += 1 147 148 if count == (circumference + num_leds + 5): 149 ramping_up = False 150 count = 0 151 152 # Wave positions and colors are updated... 153 for w in range(n_waves): 154 # Move wave; wraps around ends, is OK! 155 wave[w][center] += wave[w][speed] 156 157 # Hue not currently changing? 158 if wave[w][hue] == wave[w][hue_target]: 159 160 # There's a tiny random chance of picking a new hue... 161 if not random.randint(frames_per_second * 4, 255): 162 # Within 1/3 color wheel 163 wave[w][hue_target] = random.randint( 164 wave[w][hue] - 30, wave[w][hue] + 30) 165 166 # This wave's hue is currently shifting... 167 else: 168 169 if wave[w][hue] < wave[w][hue_target]: 170 wave[w][hue] += 1 # Move up or 171 else: 172 wave[w][hue] -= 1 # down as needed 173 174 # Reached destination? 175 if wave[w][hue] == wave[w][hue_target]: 176 wave[w][hue] = 90 + wave[w][hue] % 90 # Clamp to 90-180 range 177 wave[w][hue_target] = wave[w][hue] # Copy to target 178 179 wave[w][red] = h2rgb(wave[w][hue] - 30) 180 wave[w][green] = h2rgb(wave[w][hue]) 181 wave[w][blue] = h2rgb(wave[w][hue] + 30) 182 183 # Now render the LED strip using the current 184 # brightness & wave states. 185 # Each LED in strip is visited just once... 186 for i in range(num_leds): 187 188 # Transform 'i' (LED number in pixel space) to the 189 # equivalent point in 8-bit fixed-point space (0-255) 190 # "* 256" because that would be 191 # the start of the (N+1)th pixel 192 # "+ 127" to get pixel center. 193 x = (i * 256 + 127) / circumference 194 195 # LED assumed off, but wave colors will add up here 196 r = g = b = 0 197 198 # For each item in wave[] array... 199 for w_index in range(n_waves): 200 # Calculate distance from pixel center to wave 201 # center point, using both signed and unsigned 202 # 8-bit integers... 203 d1 = int(abs(x - wave[w_index][center])) 204 d2 = int(abs(x - wave[w_index][center])) 205 206 # Then take the lesser of the two, resulting in 207 # a distance (0-128) 208 # that 'wraps around' the ends of the strip as 209 # necessary...it's a contiguous ring, and waves 210 # can move smoothly across the gap. 211 if d2 < d1: 212 d1 = d2 # d1 is pixel-to-wave-center distance 213 214 # d2 distance, relative to wave width, is then 215 # proportional to the wave's brightness at this 216 # pixel (basic linear y=mx+b stuff). 217 # Is distance within wave's influence? 218 # d2 is opposite; distance to wave's end 219 if d1 < wave[w_index][width]: 220 d2 = wave[w_index][width] - d1 221 y = int(brightness * d2 / wave[w_index][width]) # 0 to 200 222 223 # y is a brightness scale value -- 224 # proportional to, but not exactly equal 225 # to, the resulting RGB value. 226 if y < 128: # Fade black to RGB color 227 # In HSV colorspace, this would be 228 # tweaking 'value' 229 n = int(y * 2 + 1) # 1-256 230 r += (wave[w_index][red] * n) >> 8 # More fixed-point math 231 # Wave color is scaled by 'n' 232 g += (wave[w_index][green] * n) >> 8 233 b += (wave[w_index][blue] * n) >> 8 # >>8 is equiv to /256 234 else: # Fade RGB color to white 235 # In HSV colorspace, this tweaks 'saturation' 236 n = int((y - 128) * 2) # 0-255 affects white level 237 m = 256 * n 238 n = 256 - n # 1-256 affects RGB level 239 r += (m + wave[w_index][red] * n) >> 8 240 g += (m + wave[w_index][green] * n) >> 8 241 b += (m + wave[w_index][blue] * n) >> 8 242 243 # r,g,b are 16-bit types that accumulate brightness 244 # from all waves that affect this pixel; may exceed 245 # 255. Now clip to 0-255 range: 246 if r > 255: 247 r = 255 248 if g > 255: 249 g = 255 250 if b > 255: 251 b = 255 252 253 # Store resulting RGB value and we're done with 254 # this pixel! 255 strip[i] = (r, g, b) 256 257 # Once rendering is complete, a second pass is made 258 # through pixel data applying gamma correction, for 259 # more perceptually linear colors. 260 # https://learn.adafruit.com/led-tricks-gamma-correction 261 for j in range(num_leds): 262 (red_gamma, green_gamma, blue_gamma) = strip[j] 263 red_gamma = gammas[red_gamma] 264 green_gamma = gammas[green_gamma] 265 blue_gamma = gammas[blue_gamma] 266 strip[j] = (red_gamma, green_gamma, blue_gamma) 267 268 strip.show()