Take your Feather and plug it into your computer via a known good data + power USB cable. Your operating system will show a drive named CIRCUITPY when a board is plugged in. If you get a drive named FEATHERBOOT you'll likely need to install CircuitPython.
To use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory LED_Snowboard/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
CIRCUITPY

# SPDX-FileCopyrightText: 2021 Erin St Blaine for Adafruit Industries # SPDX-FileCopyrightText: 2021 Limor Fried for Adafruit Industries # # SPDX-License-Identifier: MIT """ LED Snowboard with Feather M4 and PropMaker Wing Adafruit invests time and resources providing this open source code. Please support Adafruit and open source hardware by purchasing products from Adafruit! Written by Erin St Blaine & Limor Fried for Adafruit Industries Copyright (c) 2020-2021 Adafruit Industries Licensed under the MIT license. All text above must be included in any redistribution. """ # pylint: disable=import-error # pylint: disable=no-member import time import board import digitalio from digitalio import DigitalInOut, Direction, Pull from rainbowio import colorwheel import adafruit_lis3dh import neopixel from adafruit_led_animation.helper import PixelMap from adafruit_led_animation.sequence import AnimationSequence from adafruit_led_animation.group import AnimationGroup from adafruit_led_animation.animation.sparkle import Sparkle from adafruit_led_animation.animation.rainbow import Rainbow from adafruit_led_animation.animation.rainbowchase import RainbowChase from adafruit_led_animation.animation.rainbowcomet import RainbowComet from adafruit_led_animation.animation.solid import Solid from adafruit_led_animation.animation.chase import Chase from adafruit_led_animation.animation.comet import Comet from adafruit_led_animation.color import ( BLACK, RED, GREEN, ORANGE, BLUE, PURPLE, WHITE, ) btn = DigitalInOut(board.A1) btn.direction = Direction.INPUT btn.pull = Pull.UP prev_state = btn.value THRESHOLD = -1 #shake threshold CARVE_THRESHOLD = 5 # Set to the length in seconds for the animations POWER_ON_DURATION = 2 NUM_PIXELS = 211 # Number of pixels used in project NEOPIXEL_PIN = board.D5 POWER_PIN = board.D10 enable = digitalio.DigitalInOut(POWER_PIN) enable.direction = digitalio.Direction.OUTPUT enable.value = False # 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 accel.set_tap(2, 15) pixels = neopixel.NeoPixel(NEOPIXEL_PIN, NUM_PIXELS, brightness=1, auto_write=False) pixels.fill(0) # NeoPixels off ASAP on startup pixels.show() #PIXEL MAPS: Used for reordering pixels so the animations can run in different configurations. #My LED strips inside the neck are accidentally swapped left-right, #so these maps also correct for that #Bottom up along both sides at once pixel_map_right = PixelMap(pixels, [ 150, 151, 152, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, ], individual_pixels=True) #Starts at the bottom and goes around clockwise pixel_map_left = PixelMap(pixels, [ 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, ], individual_pixels=True) #Radiates from the center outwards like a starburst pixel_map_radiate = PixelMap(pixels, [ 206, 98, 205, 99, 207, 97, 204, 100, 208, 96, 203, 101, 209, 95, 202, 102, 210, 94, 0, 93, 92, 201, 103, 1, 91, 200, 104, 2, 90, 199, 105, 3, 89, 198, 106, 4, 88, 197, 107, 5, 87, 196, 108, 6, 86, 195, 109, 7, 85, 194, 110, 8, 84, 193, 111, 9, 83, 192, 112, 10, 82, 191, 113, 11, 81, 190, 114, 12, 80, 189, 115, 13, 79, 188, 116, 14, 78, 187, 117, 15, 77, 186, 118, 16, 76, 185, 119, 17, 75, 184, 120, 18, 74, 183, 121, 19, 73, 182, 122, 20, 72, 181, 123, 21, 71, 180, 124, 22, 70, 179, 125, 23, 69, 178, 126, 24, 68, 177, 127, 25, 67, 176, 128, 26, 66, 175, 129, 27, 65, 174, 130, 28, 64, 173, 131, 29, 63, 172, 132, 30, 62, 171, 133, 31, 61, 170, 134, 32, 60, 169, 135, 33, 69, 168, 136, 34, 58, 167, 137, 35, 57, 166, 138, 36, 56, 165, 139, 37, 55, 164, 140, 38, 54, 163, 141, 39, 53, 162, 142, 40, 52, 161, 143, 41, 51, 160, 144, 42, 50, 159, 145, 43, 49, 158, 146, 44, 48, 157, 147, 45, 47, 156, 148, 46, 46, 155, 149, 47, 154, 149, 153, 150, 152, 151, ], individual_pixels=True) #Top down along both sides at once pixel_map_sweep = PixelMap(pixels, [ 151, 152, 150, 153, 149, 154, 148, 155, 147, 156, 146, 157, 145, 158, 144, 159, 143, 160, 142, 161, 141, 162, 140, 163, 139, 164, 138, 165, 137, 166, 136, 167, 135, 168, 134, 169, 133, 170, 132, 171, 131, 172, 130, 173, 129, 174, 128, 175, 127, 176, 126, 177, 125, 178, 124, 179, 123, 180, 122, 181, 121, 182, 120, 183, 119, 184, 118, 185, 117, 186, 116, 187, 115, 188, 114, 189, 113, 190, 112, 191, 111, 192, 110, 193, 109, 194, 108, 195, 107, 196, 106, 197, 105, 198, 104, 199, 103, 200, 102, 201, 101, 202, 100, 203, 99, 204, 98, 205, 97, 206, 96, 207, 95, 208, 94, 209, 93, 210, 92, 91, 0, 90, 1, 89, 2, 88, 3, 87, 4, 86, 5, 85, 6, 84, 7, 83, 8, 82, 9, 81, 10, 80, 11, 79, 12, 78, 13, 77, 14, 76, 15, 75, 16, 74, 17, 73, 18, 72, 19, 71, 20, 70, 21, 69, 22, 68, 23, 67, 24, 66, 25, 65, 24, 64, 25, 63, 26, 62, 27, 61, 28, 60, 29, 59, 30, 58, 31, 57, 32, 56, 33, 55, 34, 54, 35, 53, 36, 52, 37, 51, 38, 50, 39, 49, 40, 48, 41, 47, 42, 46, 43, 45, 44, ], individual_pixels=True) pixel_map_tail = PixelMap(pixels, [ 15, 75, 16, 74, 17, 73, 18, 72, 19, 71, 20, 70, 21, 69, 22, 68, 23, 67, 24, 66, 25, 65, 24, 64, 25, 63, 26, 62, 27, 61, 28, 60, 29, 59, 30, 58, 31, 57, 32, 56, 33, 55, 34, 54, 35, 53, 36, 52, 37, 51, 38, 50, 39, 49, 40, 48, 41, 47, 42, 46, 43, 45, 44, ], individual_pixels=True) pixel_map = [ pixel_map_right, pixel_map_left, pixel_map_radiate, pixel_map_sweep, pixel_map_tail, ] def power_on(duration): """ Animate NeoPixels for power on. """ start_time = time.monotonic() # Save start time while True: elapsed = time.monotonic() - start_time # Time spent if elapsed > duration: # Past duration? break # Stop animating powerup.animate() # Cusomize LED Animations ------------------------------------------------------ powerup = RainbowComet(pixel_map[3], speed=0, tail_length=25, bounce=False) rainbow = Rainbow(pixel_map[2], speed=0, period=6, name="rainbow", step=2.4) rainbow_chase = RainbowChase(pixels, speed=0, size=6, spacing=15, step=10) rainbow_chase2 = RainbowChase(pixels, speed=0, size=10, spacing=1, step=18, name="rainbow_chase2") chase = RainbowChase(pixel_map[3], speed=0, size=20, spacing=20) chase2 = Chase(pixels, speed=0.1, color=ORANGE, size=6, spacing=6) rainbow_comet = RainbowComet(pixel_map[2], speed=0, tail_length=200, bounce=True) rainbow_comet2 = RainbowComet( pixels, speed=0, tail_length=104, colorwheel_offset=80, bounce=True ) rainbow_comet3 = RainbowComet( pixel_map[2], speed=0, tail_length=80, colorwheel_offset=80, step=4, bounce=False ) strum = RainbowComet( pixel_map[3], speed=0, tail_length=50, bounce=False, colorwheel_offset=50, step=4 ) fuego = RainbowComet( pixel_map[4], speed=0.05, colorwheel_offset=40, step=2, tail_length=40 ) fuego2 = RainbowComet( pixel_map[4], speed=0.02, colorwheel_offset=40, step=2, tail_length=40 ) lava = Comet(pixel_map[4], speed=0, color=ORANGE, tail_length=40, bounce=False) sparkle = Sparkle(pixel_map[3], speed=0.05, color=BLUE, num_sparkles=10) sparkle2 = Sparkle(pixels, speed=0.05, color=PURPLE, num_sparkles=4) sparkle3 = Sparkle(pixels, speed=0, color=WHITE, num_sparkles=1) carve_left = Solid(pixel_map[0], color=GREEN) carve_right = Solid(pixel_map[1], color=RED) black_left = Solid(pixel_map[0], color=BLACK, name="BLACK") black_right = Solid(pixel_map[1], color=BLACK) # Animations Playlist - reorder as desired. AnimationGroups play at the same time animations = AnimationSequence( AnimationGroup( fuego, fuego2, lava, sparkle, ), chase, rainbow_chase2, rainbow, AnimationGroup( rainbow_comet, sparkle3, ), AnimationGroup( rainbow_comet2, sparkle3, ), AnimationGroup( sparkle, strum, ), AnimationGroup( sparkle2, rainbow_comet3, ), AnimationGroup( black_left, black_right, ), black_left, auto_clear=True, auto_reset=True, ) MODE = 0 LASTMODE = 1 i = 0 # Main loop while True: i = (i + 0.5) % 256 # run from 0 to 255 TILT_COLOR = colorwheel(i) if MODE == 0: # If currently off... enable.value = True power_on(POWER_ON_DURATION) # Power up! MODE = LASTMODE elif MODE >= 1: # If not OFF MODE... # Read button cur_state = btn.value if cur_state != prev_state: if not cur_state: animations.next() print("BTN is down") else: print("BTN is up") prev_state = cur_state # Read accelerometer x, y, z = accel.acceleration accel_total = z # x=tilt, y=rotate accel_total_y = y # x=tilt, y=rotate print(accel_total_y) if accel_total > THRESHOLD: print("THRESHOLD: ", accel_total) if MODE == 1: animations.animate() if animations.current_animation.name == "BLACK": MODE = 2 if MODE == 2: if y > CARVE_THRESHOLD: black_right.animate() carve_left.animate() if y < (CARVE_THRESHOLD * -1): black_left.animate() carve_right.animate() #print (MODE) cur_state = btn.value if cur_state != prev_state: if not cur_state: MODE = 1
NUM_PIXELS = 211 # Number of pixels used in project NEOPIXEL_PIN = board.D5 POWER_PIN = board.D10
Customizing Your Code
Unless your snowboard is exactly the same size as mine, you'll need to make a few tweaks to the code to get the animations to lay out correctly.
First, look near the top of the code for this section:
Change NUM_PIXELS
to match the number of pixels you have on your board.
DELAY_MIN = 0.01 # In seconds, is the shortest DELAY between servo moves DELAY_MAX = 0.1 # In seconds, is the longest DELAY between servo moves
Pixel Mapping
This project uses animations from the LED Animations Library to create various effects. You can learn a lot more about building and layering your own animations in that guide. It's easy to add new animations or customize the existing ones.
One of the best features in this library is the Pixel Mapping function. This makes it easy to light up the pixels in any order you'd like, or to only light certain pixels instead of all of them. I've set up 5 different pixel maps:
- pixel_map_right (right side only)
- pixel_map_left (left side only)
- pixel_map_radiate (both sides, starting at the middle and lighting outwards)
- pixel_map_sweep (both sides, starting at the front and sweeping back)
- pixel_map_tail (only the pixels behind the back binding)
It takes a little work to get the pixels mapped out perfectly, but once you've got the maps set up you can apply any animation to any map, giving each animation 5 different ways to lay out. This is a really powerful feature!
To adjust the pixel maps for your board, find this section:
#PIXEL MAPS: Used for reordering pixels so the animations can run in different configurations. #My LED strips inside the neck are accidentally swapped left-right, #so these maps also correct for that #Bottom up along both sides at once pixel_map_right = PixelMap(pixels, [ 150, 151, 152, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, ], individual_pixels=True) #Starts at the bottom and goes around clockwise pixel_map_left = PixelMap(pixels, [ 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, ], individual_pixels=True) #Radiates from the center outwards like a starburst pixel_map_radiate = PixelMap(pixels, [ 206, 98, 205, 99, 207, 97, 204, 100, 208, 96, 203, 101, 209, 95, 202, 102, 210, 94, 0, 93, 92, 201, 103, 1, 91, 200, 104, 2, 90, 199, 105, 3, 89, 198, 106, 4, 88, 197, 107, 5, 87, 196, 108, 6, 86, 195, 109, 7, 85, 194, 110, 8, 84, 193, 111, 9, 83, 192, 112, 10, 82, 191, 113, 11, 81, 190, 114, 12, 80, 189, 115, 13, 79, 188, 116, 14, 78, 187, 117, 15, 77, 186, 118, 16, 76, 185, 119, 17, 75, 184, 120, 18, 74, 183, 121, 19, 73, 182, 122, 20, 72, 181, 123, 21, 71, 180, 124, 22, 70, 179, 125, 23, 69, 178, 126, 24, 68, 177, 127, 25, 67, 176, 128, 26, 66, 175, 129, 27, 65, 174, 130, 28, 64, 173, 131, 29, 63, 172, 132, 30, 62, 171, 133, 31, 61, 170, 134, 32, 60, 169, 135, 33, 69, 168, 136, 34, 58, 167, 137, 35, 57, 166, 138, 36, 56, 165, 139, 37, 55, 164, 140, 38, 54, 163, 141, 39, 53, 162, 142, 40, 52, 161, 143, 41, 51, 160, 144, 42, 50, 159, 145, 43, 49, 158, 146, 44, 48, 157, 147, 45, 47, 156, 148, 46, 46, 155, 149, 47, 154, 149, 153, 150, 152, 151, ], individual_pixels=True) #Top down along both sides at once pixel_map_sweep = PixelMap(pixels, [ 151, 152, 150, 153, 149, 154, 148, 155, 147, 156, 146, 157, 145, 158, 144, 159, 143, 160, 142, 161, 141, 162, 140, 163, 139, 164, 138, 165, 137, 166, 136, 167, 135, 168, 134, 169, 133, 170, 132, 171, 131, 172, 130, 173, 129, 174, 128, 175, 127, 176, 126, 177, 125, 178, 124, 179, 123, 180, 122, 181, 121, 182, 120, 183, 119, 184, 118, 185, 117, 186, 116, 187, 115, 188, 114, 189, 113, 190, 112, 191, 111, 192, 110, 193, 109, 194, 108, 195, 107, 196, 106, 197, 105, 198, 104, 199, 103, 200, 102, 201, 101, 202, 100, 203, 99, 204, 98, 205, 97, 206, 96, 207, 95, 208, 94, 209, 93, 210, 92, 91, 0, 90, 1, 89, 2, 88, 3, 87, 4, 86, 5, 85, 6, 84, 7, 83, 8, 82, 9, 81, 10, 80, 11, 79, 12, 78, 13, 77, 14, 76, 15, 75, 16, 74, 17, 73, 18, 72, 19, 71, 20, 70, 21, 69, 22, 68, 23, 67, 24, 66, 25, 65, 24, 64, 25, 63, 26, 62, 27, 61, 28, 60, 29, 59, 30, 58, 31, 57, 32, 56, 33, 55, 34, 54, 35, 53, 36, 52, 37, 51, 38, 50, 39, 49, 40, 48, 41, 47, 42, 46, 43, 45, 44, ], individual_pixels=True) pixel_map_tail = PixelMap(pixels, [ 15, 75, 16, 74, 17, 73, 18, 72, 19, 71, 20, 70, 21, 69, 22, 68, 23, 67, 24, 66, 25, 65, 24, 64, 25, 63, 26, 62, 27, 61, 28, 60, 29, 59, 30, 58, 31, 57, 32, 56, 33, 55, 34, 54, 35, 53, 36, 52, 37, 51, 38, 50, 39, 49, 40, 48, 41, 47, 42, 46, 43, 45, 44, ], individual_pixels=True) pixel_map = [ pixel_map_right, pixel_map_left, pixel_map_radiate, pixel_map_sweep, pixel_map_tail, ]
The pixels start with number 0 at the IN end of the strip and finish with 210 (in my case) at the end of the strip. So, for example, pixel 150 is right at the front of my board, so pixel_map_right starts with pixel number 150 and counts downwards (or "up" the strip) until I get to pixel 46, which is located right at the back of my board.
If you have fewer pixels than 211, the code won't compile correctly, so if you're getting errors this may be why. Play around with the numbers until the animations look the way you want on your board.
The last line in the code chooses a random number of seconds to wait between flapping cycles. Change this to make the pauses shorter or longer.
Modes
This code includes 8 color modes, most of which are layered AnimationGroup modes. The LED Animations library has the ability to run more than one animation at a time, and I find it fun to layer chase-style animations over sparkle-style animations. The variety you can get from playing with different combinations is endless.
The ninth mode is a faux "off" mode. This makes all the pixels go dark to save battery life. However, this mode isn't truly "off" since the Feather and Prop-Maker wing are still powered up. Click the on/off switch to turn the board fully off.
The tenth mode is a motion-reactive mode. It uses the Lis3dh accelerometer built into the Prop-Maker wing to light one edge or the other depending on the orientation of the board.
Troubleshooting
If your CIRCUITPY drive does not appear, and you've already assembled your electronics, try clicking the on/off switch once. The switch needs to be in the ON position (even if you see an orange light on the board).
If you have blinking lights on your Feather and the pixels aren't lighting up, there may be a problem with your code. The MU editor has some great debugging tools. Here's some info about how to access them.
If you've changed NUM_PIXELS and not updated your pixel maps, the code will likely throw errors or not all the lights will come on.
You can find more help in the CircuitPython main guide here.
Page last edited January 21, 2025
Text editor powered by tinymce.