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.

Install Libraries

You'll need a few CircuitPython libraries in the lib folder on the Feather CIRCUITPY drive for the code to work. Head to https://circuitpython.org/libraries to download the latest library bundle matching the major version of CircuitPython now on your board (6 for CircuitPython 6.x, etc.).

Once you've downloaded the libraries bundle, add these libraries to the lib folder on the Feather:

  • adafruit_bus_device (folder)
  • adafruit_debouncer
  • adafruit_led_animation (folder)
  • adafruit_lis3dh.mpy
  • neopixel.mpy

Your Feather M4 Express CIRCUITPY drive should look like this after you load the code below.:

Download Feather M4 Code from GitHub

Once your Feather M4 Express is all setup with CircuitPython and the necessary libraries, you can click on the Download: Project Zip link above in the code to get the file.

"""
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()
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.

 

This guide was first published on Apr 07, 2021. It was last updated on Apr 07, 2021.

This page (Code) was last updated on Oct 22, 2021.

Text editor powered by tinymce.