LED Snowboard with Motion-Reactive Animation
2022-01-06 | By Adafruit Industries
License: See Original Project Adafruit Feather
Courtesy of Adafruit
Guide by Erin St Blaine
Overview
Shred down the slopes this season with your own custom motion-reactive LED snowboard. This tutorial includes CircuitPython sample code with 8 different layered LED animation modes, plus a motion-sensitive carving mode that lights your active edge as you carve down the mountain.
Learn to protect your electronics from the elements using waterproof enclosures and connectors. Make your board robust enough that you can ride all night long.
Parts Needed
The Adafruit Feather M4 Express paired with the Prop-Maker Wing is a really powerful combo. The Prop-Maker wing is designed just for projects like this. It has an onboard NeoPixel connector, an Lis3dh accelerometer, level-shifting, and plenty of pins to add whatever functionality you desire. It also has onboard battery charging, so it's easy to charge up your board for maximum glowing enjoyment.
This build uses 60/m Skinny NeoPixels, mounted on their side so they shine outwards and illuminate the snow as you ride. Also get a 3-pin JST connector to plug into the Prop-Maker Wing.
You'll also need an on/off switch, a momentary switch for changing between modes, a battery, and a waterproof connector.
We'll be using a GoPro camera case as our electronics enclosure. It's a fully waterproof / submersible enclosure that already has waterproof buttons we can repurpose to our hearts' content. Grab a PG-7 cable gland to safely pass the NeoPixel wires through the case.
1 x On/Off Power Button
1 x Momentary Switch
1 x Cable Gland
1 x Battery
2 x Weatherproof Connector
To secure the electronics and buttons inside our case, we'll use moldable Thermoplastic. This stuff is great - just get it warm enough to melt, shape it however you'd like, and let it cool in place. Re-melt as needed until you get it just right.
Hand-Moldable Plastic - Low Temperature Thermoplastic
Also stock up on some large-sized heat shrink tubing. This stuff is invaluable for making good waterproof connections.
1 x Heat Shrink
You'll Also Need
A snowboard
GoPro camera case. I found fairly inexpensive "knockoff" ones available on Amazon
GoPro mount to stick the case to your board
Power drill with a 15/32 (12mm) drill bit for the cable gland install
Devcon Silicone Glue - Get at least 2-3 tubes
Soldering iron & accessories
Heat gun
Hot Glue gun & sticks
Wiring Diagram
The Prop-Maker FeatherWing stacks on top of the Feather M4 express using the included headers. More about Feather and FeatherWing assembly can be found in the How to Solder Headers guide.
The GoPro case is just barely big enough to fit all our components, so we'll stack the FeatherWing directly on top of the Feather to make as small a package as possible.
The NeoPixel strip plugs into the port on the Prop-Maker Wing. We'll connect power and ground to both ends of the strip for a balanced distribution of power and connect the data wire to the data IN end of the NeoPixel strip. Watch the arrows printed on the strip to be sure you're connecting to the correct end.
We'll use the G and EN pins on the FeatherWing for our on/off switch, and hook our momentary mode-change button up to G and A1.
Software
There are a few steps required to get your Feather M4 board ready to light your snowboard:
Update to the newest version of the CircuitPython operating system, so your board appears as a drive on your computer called CIRCUITPY.
Download and install a few libraries into the /lib folder on your CIRCUITPY drive.
Download and customize the code using the Mu editor (or another text editor), so the pixel setup lays out correctly on your snowboard.
Save the code as a file called code.py at the root of your CIRCUITPY drive.
CircuitPython on Feather M4 Express
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. It makes it easier than ever to get prototyping by requiring no upfront desktop software downloads. Simply copy and edit files on the CIRCUITPY drive to iterate.
The following instructions will show you how to install CircuitPython. If you've already installed CircuitPython but are looking to update it or reinstall it, the same steps work for that as well!
Set up CircuitPython Quick Start!
Follow this quick step-by-step for super-fast Python power :)
Download the latest version of CircuitPython for this board via CircuitPython.org
Click the link above and download the latest UF2 file.
Download and save it to your desktop (or wherever is handy).
Plug your Feather M4 into your computer using a known-good USB cable.
A lot of people end up using charge-only USB cables and it is very frustrating! So, make sure you have a USB cable you know is good for data sync.
Double-click the Reset button next to the USB connector on your board, and you will see the NeoPixel RGB LED turn green. If it turns red, check the USB cable, try another USB port, etc. Note: The little red LED next to the USB connector will pulse red. That's ok!
If double-clicking doesn't work the first time, try again. Sometimes it can take a few tries to get the rhythm right!
You will see a new disk drive appear called FEATHERBOOT.
Drag the adafruit_circuitpython_etc.uf2 file to FEATHERBOOT.
The LED will flash. Then, the FEATHERBOOT drive will disappear and a new disk drive called CIRCUITPY will appear.
That's it, you're done! :)
Further Information
For more detailed info on installing CircuitPython, check out Installing CircuitPython.
CircuitPython Libraries
As CircuitPython development continues and there are new releases, Adafruit will stop supporting older releases. Visit https://circuitpython.org/downloads to download the latest version of CircuitPython for your board. You must download the CircuitPython Library Bundle that matches your version of CircuitPython. Please update CircuitPython and then visit https://circuitpython.org/libraries to download the latest Library Bundle.
Each CircuitPython program you run needs to have a lot of information to work. The reason CircuitPython is so simple to use is that most of that information is stored in other files and works in the background. These files are called libraries. Some of them are built into CircuitPython. Others are stored on your CIRCUITPY drive in a folder called lib. Part of what makes CircuitPython so great is its ability to store code separately from the firmware itself. Storing code separately from the firmware makes it easier to update both the code you write and the libraries you depend.
Your board may ship with a lib folder already, it's in the base directory of the drive. If not, simply create the folder yourself. When you first install CircuitPython, an empty lib directory will be created for you.
CircuitPython libraries work in the same way as regular Python modules so the Python docs are an excellent reference for how it all should work. In Python terms, you can place our library files in the lib directory because it's part of the Python path by default.
One downside of this approach of separate libraries is that they are not built in. To use them, one needs to copy them to the CIRCUITPY drive before they can be used. Fortunately, there is a library bundle.
The bundle and the library releases on GitHub also feature optimized versions of the libraries with the .mpy file extension. These files take less space on the drive and have a smaller memory footprint as they are loaded.
Due to the regular updates and space constraints, Adafruit does not ship boards with the entire bundle. Therefore, you will need to load the libraries you need when you begin working with your board. You can find example code in the guides for your board that depends on external libraries.
Either way, as you start to explore CircuitPython, you'll want to know how to get libraries on board.
The Adafruit CircuitPython Library Bundle
Adafruit provides CircuitPython libraries for much of the hardware they provide, including sensors, breakouts and more. To eliminate the need for searching for each library individually, the libraries are available together in the Adafruit CircuitPython Library Bundle. The bundle contains all the files needed to use each library.
Downloading the Adafruit CircuitPython Library Bundle
You can download the latest Adafruit CircuitPython Library Bundle release by clicking the button below. The libraries are being constantly updated and improved, so you'll always want to download the latest bundle.
Match up the bundle version with the version of CircuitPython you are running. For example, you would download the 6.x library bundle if you're running any version of CircuitPython 6, or the 7.x library bundle if you're running any version of CircuitPython 7, etc. If you mix libraries with major CircuitPython versions, you will get incompatible mpy errors due to changes in library interfaces possible during major version changes.
Click to visit circuitpython.org for the latest Adafruit CircuitPython Library Bundle
Download the bundle version that matches your CircuitPython firmware version. If you don't know the version, check the version info in boot_out.txt file on the CIRCUITPY drive, or the initial prompt in the CircuitPython REPL. For example, if you're running v7.0.0, download the 7.x library bundle.
There's also a py bundle which contains the uncompressed python files, you probably don't want that unless you are doing advanced work on libraries.
The CircuitPython Community Library Bundle
The CircuitPython Community Library Bundle is made up of libraries written and provided by members of the CircuitPython community. These libraries are often written when community members encountered hardware not supported in the Adafruit Bundle, or to support a personal project. The authors all chose to submit these libraries to the Community Bundle make them available to the community.
These libraries are maintained by their authors and are not supported by Adafruit. As you would with any library, if you run into problems, feel free to file an issue on the GitHub repo for the library. Bear in mind, though, that most of these libraries are supported by a single person and you should be patient about receiving a response. Remember, these folks are not paid by Adafruit, and are volunteering their personal time when possible to provide support.
Downloading the CircuitPython Community Library Bundle
You can download the latest CircuitPython Community Library Bundle release by clicking the button below. The libraries are being constantly updated and improved, so you'll always want to download the latest bundle.
Click for the latest CircuitPython Community Library Bundle release
The link takes you to the latest release of the CircuitPython Community Library Bundle on GitHub. There are multiple versions of the bundle available. Download the bundle version that matches your CircuitPython firmware version. If you don't know the version, check the version info in boot_out.txt file on the CIRCUITPY drive, or the initial prompt in the CircuitPython REPL. For example, if you're running v7.0.0, download the 7.x library bundle.
Understanding the Bundle
After downloading the zip, extract its contents. This is usually done by double clicking on the zip. On Mac OSX, it places the file in the same directory as the zip.
Open the bundle folder. Inside you'll find two information files, and two folders. One folder is the lib bundle, and the other folder is the examples bundle.
Now open the lib folder. When you open the folder, you'll see a large number of .mpy files, and folders.
Example Files
All example files from each library are now included in the bundles in an examples directory (as seen above), as well as an examples-only bundle. These are included for two main reasons:
Allow for quick testing of devices
Provide an example base of code, that is easily built upon for individualized purposes
Copying Libraries to Your Board
First open the lib folder on your CIRCUITPY drive. Then, open the lib folder you extracted from the downloaded zip. Inside you'll find a number of folders and .mpy files. Find the library you'd like to use and copy it to the lib folder on CIRCUITPY.
If the library is a directory with multiple .mpy files in it, be sure to copy the entire folder to CIRCUITPY/lib.
This also applies to example files. Open the examples folder you extracted from the downloaded zip and copy the applicable file to your CIRCUITPY drive. Then, rename it to code.py to run it.
If a library has multiple .mpy files contained in a folder, be sure to copy the entire folder to CIRCUITPY/lib.
Understanding Which Libraries to Install
You now know how to load libraries on to your CircuitPython-compatible microcontroller board. You may now be wondering; how do you know which libraries you need to install? Unfortunately, it's not always straightforward. Fortunately, there is an obvious place to start, and a relatively simple way to figure out the rest. First up: the best place to start.
When you look at most CircuitPython examples, you'll see they begin with one or more import statements. These typically look like the following:
import library_or_module
However, import statements can also sometimes look like the following:
from library_or_module import name
from library_or_module.subpackage import name
from library_or_module import name as local_name
They can also have more complicated formats, such as including a try / except block, etc.
The important thing to know is that an import statement will always include the name of the module or library that you're importing.
Therefore, the best place to start is by reading through the import statements.
Here is an example import list for you to work with in this section. There is no setup or other code shown here, as the purpose of this section involves only the import list.
import time import board import neopixel import adafruit_lis3dh import usb_hid from adafruit_hid.consumer_control import ConsumerControl from adafruit_hid.consumer_control_code import ConsumerControlCode
Keep in mind, not all imported items are libraries. Some of them are almost always built-in CircuitPython modules. How do you know the difference? Time to visit the REPL.
In the Interacting with the REPL section on The REPL page in this guide, the help("modules") command is discussed. This command provides a list of all of the built-in modules available in CircuitPython for your board. So, if you connect to the serial console on your board, and enter the REPL, you can run help("modules") to see what modules are available for your board. Then, as you read through the import statements, you can, for the purposes of figuring out which libraries to load, ignore the statement that import modules.
The following is the list of modules built into CircuitPython for the Feather RP2040. Your list may look similar or be anything down to a significant subset of this list for smaller boards.
Now that you know what you're looking for, it's time to read through the import statements. The first two, time and board, are on the modules list above, so they're built-in.
The next one, neopixel, is not on the module list. That means it's your first library! So, you would head over to the bundle zip you downloaded, and search for neopixel. There is a neopixel.mpy file in the bundle zip. Copy it over to the lib folder on your CIRCUITPY drive. The following one, adafruit_lis3dh, is also not on the module list. Follow the same process for adafruit_lis3dh, where you'll find adafruit_lis3dh.mpy, and copy that over.
The fifth one is usb_hid, and it is in the modules list, so it is built in. Often all of the built-in modules come first in the import list, but sometimes they don't! Don't assume that everything after the first library is also a library and verify each import with the modules list to be sure. Otherwise, you'll search the bundle and come up empty!
The final two imports are not as clear. Remember, when import statements are formatted like this, the first thing after the from is the library name. In this case, the library name is adafruit_hid. A search of the bundle will find an adafruit_hid folder. When a library is a folder, you must copy the entire folder and its contents as it is in the bundle to the lib folder on your CIRCUITPY drive. In this case, you would copy the entire adafruit_hid folder to your CIRCUITPY/lib folder.
Notice that there are two imports that begin with adafruit_hid. Sometimes you will need to import more than one thing from the same library. Regardless of how many times you import the same library, you only need to load the library by copying over the adafruit_hid folder once.
That is how you can use your example code to figure out what libraries to load on your CircuitPython-compatible board!
There are cases, however, where libraries require other libraries internally. The internally required library is called a dependency. In the event of library dependencies, the easiest way to figure out what other libraries are required is to connect to the serial console and follow along with the ImportError printed there. The following is a very simple example of an ImportError, but the concept is the same for any missing library.
Example: ImportError Due to Missing Library
If you choose to load libraries as you need them, or you're starting fresh with an existing example, you may end up with code that tries to use a library you haven't yet loaded. This section will demonstrate what happens when you try to utilise a library that you don't have loaded on your board, and cover the steps required to resolve the issue.
This demonstration will only return an error if you do not have the required library loaded into the lib folder on your CIRCUITPY drive.
Let's use a modified version of the Blink example.
import board import time import simpleio led = simpleio.DigitalOut(board.LED) while True: led.value = True time.sleep(0.5) led.value = False time.sleep(0.5)
Save this file. Nothing happens to your board. Let's check the serial console to see what's going on.
You have an ImportError. It says there is no module named 'simpleio'. That's the one you just included in your code!
Click the link above to download the correct bundle. Extract the lib folder from the downloaded bundle file. Scroll down to find simpleio.mpy. This is the library file you're looking for! Follow the steps above to load an individual library file.
The LED starts blinking again! Let's check the serial console.
No errors! Excellent. You've successfully resolved an ImportError!
If you run into this error in the future, follow along with the steps above and choose the library that matches the one you're missing.
Library Install on Non-Express Boards
If you have an M0 non-Express board such as Trinket M0, Gemma M0, QT Py M0, or one of the M0 Trinkeys, you'll want to follow the same steps in the example above to install libraries as you need them. Remember, you don't need to wait for an ImportError if you know what library you added to your code. Open the library bundle you downloaded, find the library you need, and drag it to the lib folder on your CIRCUITPY drive.
You can still end up running out of space on your M0 non-Express board even if you only load libraries as you need them. There are a number of steps you can use to try to resolve this issue. You'll find suggestions on the Troubleshooting page.
Updating CircuitPython Libraries and Examples
Libraries and examples are updated from time to time, and it's important to update the files you have on your CIRCUITPY drive.
To update a single library or example, follow the same steps above. When you drag the library file to your lib folder, it will ask if you want to replace it. Say yes. That's it!
A new library bundle is released every time there's an update to a library. Updates include things like bug fixes and new features. It's important to check in every so often to see if the libraries you're using have been updated.
Code
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.
Electronics Assembly
If needed, solder the included headers to the underside of your Prop-Maker wing.
Stack the assembled Prop-Maker wing on top of the Feather board and solder them together. More about soldering headers can be found in our Headers Guide.
Solder a 3"-4" wire to each leg of your momentary switch and you’re on/off switch. Cover the connections with heat shrink.
Solder the momentary switch to the two holes on the Prop-Maker wing to A1 and G. These are the holes on either side of the hole marked with "(o)". It doesn't matter which wire goes to which hole.
Solder the two wires from the on/off switch to the G and EN pins on the Prop-Maker wing. Again, the wires are interchangeable here. The way these switches work is by briefly connecting the two wires together when the switch is pressed, and the code simply listens for the connection.
Since we've got tight quarters, we'll be placing the Feather right on top of the battery. To protect the battery and prevent shorts, glue a thin barrier on the battery. I used pink sparkly craft foam, because that's how I roll, but any non-conductive foam or tape or thick fabric should work here.
Plug your battery's JST connector into the connector on the side of the Feather. Make sure the lights on the board come on and turn off when you click the on/off switch.
Case Assembly
Find the female side of your waterproof connector. Slide the included cover nut onto the cable with the open side toward the connector, as shown.
Slide the white nut from your cable gland onto the connector facing the other direction, with the open side toward the bare wires.
Open up your GoPro case. Use a 15/32 (12mm) drill bit to drill a hole in the side of the case next to the on/off button. Get it as centered as possible so the nut has room to screw down flush with the case. I found it helped to drill a smaller pilot hole first to keep my drill from wandering.
Screw your cable gland's main section firmly into the hole as shown, with the shorter side on the inside of the case. Thread the included plastic hex nut onto the outside and tighten it with a wrench.
Slip the bare wire end of your waterproof connector into the cable gland from the outside, making sure not to dislodge the foam insert. Adjust it until just the bare wires are sticking out, with the main part of the cable inside the gland.
Double check that you've got both connector nuts on your connector: the white one from the cable gland, ready to screw to the case, and the black one that came with the connector, ready to screw to the male end of the connector.
Once you're sure it looks right, screw the white connector nut onto the cable gland to lock the connector in place. Tighten with a wrench.
Now that we've got a waterproof portal through our case, we can connect the 3-pin NeoPixel connector to the wires on the waterproof connector. Trim the yellow wire since we won't be using it. Connect white to white, red to red, and black to black. Cover the connections with heat shrink and solder.
Place the battery on the bottom of the case with the foam covering facing upward. Plug the NeoPixel connector into the Prop-Maker wing and nestle the Feather and Prop-Maker on top of the battery but leaving space around the two buttons on the case.
We'll use Thermoplastic to position our two switches inside the GoPro case to take advantage of the case's onboard buttons, so we can change modes and turn the board on and off without opening the case.
Use a heat gun or a pan of hot water to melt a small handful of plastic beads. They'll turn clear when they're ready to mold. Be careful - they can get pretty hot! Don't burn your fingers any more than necessary.
Position your momentary switch behind the button on the top side of the case. Mold some thermoplastic around it to hold it in exactly the right spot so the switch gets activated when you press the case button.
It's helpful to have your LED strip connected up so you can be sure the button is working. Let the thermoplastic cool to hardness once you've got it lined up.
I attempted to use the side button on the case in the same way with the on/off switch but the action on this switch is a little too firm and I couldn't get reliable on/off action. No worries - I can just open the case to turn the board on and off. I also added an "off" mode in the code, so I can save battery life while I'm on the lift without having to open the case.
It would be a bit slicker with the on/off switch hooked up to the case, so next time I use this method I will experiment a bit more with switches to find just the right one.
Snowboard Assembly
Find the IN end of your NeoPixel strip - it is the end with the arrows pointing away, down the strip. If your strip already has wires connected, trim off any connectors, and trim off any additional power wires that might come pre-soldered. You want a black wire, a white wire, and a red wire remaining.
Lay out your strip around the perimeter of your snowboard on its edge with the lights facing outwards. Cut the strip to the desired length, cutting carefully between the copper pads.
Solder a red wire and a black wire to the +5v and G pads on the OUT end of the strip.
Slide a piece of 3/4" clear heat shrink over each end of the pixel strip. Fill the heat shrink with hot glue, then while the glue is still wet, shrink the heat shrink with a heat gun. This will encase the wire connections in solid plastic, waterproofing your strip and keeping the connections safe.
Starting at the middle of the board between the bindings, lay a bead of silicone glue about 1/4" from the edge of the board. Don't get too close to the edge, or the snow will pull the LEDs right off.
Glue your pixels to the board as shown, starting between the bindings, and lining the edge of the board with the pixels facing outward. This will give you the most visibility and illuminate the snow as you glide over it.
Clean off any excess glue with 99% alcohol before it dries.
Strip some shielding off your wires. Twist the two red wires together and the two black wires together. We'll connect power and ground to both ends of the strip to help distribute power evenly along the pixels and to add a bit of robust-ness -- if any one of these four wires fails, the board will still light up.
Find the male half of your waterproof connector and slip the included silicone gasket into place.
Slip on another piece of 3/4" clear heat shrink, and also slide a piece of heat shrink onto the red, black, and white wires.
Solder both of the red wires from the pixel strip to the red wire on the connector, and both black wires to the black connector wire. Solder the white wire from the IN end of the strip to the connector's white wire.
Plug your connector into your GoPro case setup and test to be sure everything works. Once it's all solid, waterproof this connection in the same way you did the pixels, filling the 3/4" clear heat shrink with hot glue to make a solid plastic encasement for your connection.
Add some more silicone glue to secure the wire bundle to the board behind the NeoPixel strip.
Case Mounting
Initially I mounted the GoPro case directly to the board using the GoPro mount hardware that came with the case. After a few trial runs, I discovered that the electronics and battery seemed to get too cold being that close to the snow.
I used a second waterproof connector to make an extension cable for the GoPro case, so I can keep it in my pocket where it will stay quite a bit warmer. This made the board a bit more reliable for longer-term use.
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum