Making Wearable LEDs, the "I've never done this before and it's under month until Burning Man" Way
While still looking better than 95% of the people on the Playa and their nighttime illumination.
Zachary Reiss-Davis, July 2023
PixelBlaze - Simple, Web-based, Javascript, Wifi
Start with other people's patterns, modify to taste.
Use the patterns I provided and pre-loaded.*
Can set up its own mesh wifi network, so it can be controlled without internet.
Can "follow the leader" and wireless sync multiple devices.
WLED can do most of the same things with $15 ESP32 hardware and free software and a substantial amount of extra cussing and fussing.
Arduinos + FastLED can as well, with even more cursing and firmware headaches.
* If you're reading this online, use Patterns from the Pattern library, and if you want to add Palettes, see Appendix slide; if you know me in real life, message me for a PBB backup file.
Tutorial: How to re-configure WiFi
If you're on the same WiFi network as the PixelBlaze already, go to http://discover.electromage.com/ , find the PixelBlaze, go to the WiFi tab.
If you need to reset the WiFi, press and hold the on-board button for 5 seconds. The orange status LED will blink 3 times to let you know you are in Setup Mode.
Before going to Burning Man, set the PixelBlaze to be set up in AP Mode. Make the wifi name and password something easy.
See https://electromage.com/docs/quickstart-v3-standard#step1 for more.
Connect to wifi or create a network (for mobile use)
5-second pressing on the button on the board will go back to wifi setup mode. Can be glitchy on MacOS — use iOS if not seeing the PB.
(Clicking the button changes the pattern).
Set the brightness, pixel count, and pixel type
Set the number of pixels to the number of pixels you have. You may need to set to N+1 pixels (bug).
Try Brightness 40 to start. Don't go too bright with Micro-USB power and >100 LEDs.
Our LED type is WS2812/NeoPixel for LEDs I'm supplying.
Determine color order experimentally (if not labeled) with a test pattern. You can't hurt anything by messing with this.
Use pre-made palettes; rotate them with a playlist
Create & modify patterns
It's a form of Javascript, there's great documentation online, and in the code editor itself (scroll down).
To just MODIFY patterns is easy. The patterns update in real time, easy to test.
Creating them? Ask a programmer.
Several "teaching" patterns provided; Blink Fade & An Intro to PixelBlaze code.
The Easiest way to adjust looks: Palettes
Many patterns I pre-set have many built-in palettes.
Pick which palettes you like the look of to dramatically change the impact.
Right now, it blends between each each every 15 seconds.
* If you're reading this online, use Patterns from the Pattern library, and if you want to add Palettes to existing patterns, see Appendix slide.
Tutorial: Notes on Palettes
If you want a palette-based pattern (see appendix) to have just one color palette in it, click "edit" on the pattern, and change "Var Palettes" array to just include the palette(s) you want.
You can change the "var PALETTE_HOLD_TIME = 15" which controls how many seconds each palette is shown before rotating, in seconds.
This is critical if you want your project to have just a single color palette, not rotating through 10 of them.
Adding your OWN palettes is easy; directions for how to do so are in a comment in the code at the top of the palette section of the patterns I provided — and see here: https://fastled.io/tools/paletteknife/
Pattern Library, brightness, and playlists
Sync multiple PixelBlaze: FYI only. Lets you have the exact same pattern on multiple PB at once.
Build 2D maps with a photo mapper
Wearable LEDs look way cooler if in a 2D vs a 1D effect. PixelBlaze makes this easy.
2D shapes do not need to be regular; glue down LEDs first, map them AFTER.
Map algorithmically for pre-made grids.
Use this: Ben Hencke Pixel Map Tool
* Advanced mapping tool for some situations http://pixelblazemapper.spoodoolabs.com/
Pixel Mapping Example
Tutorial: Detailed guidance for 2D mapping
More info: Intro to Mapping in PixelBlaze and Ben Hencke Pixel Map Tool
Easiest for power: 5.0v / 2a. = USB-A / Mini-USB
Use 5v, and stay under 2a, to go battery pack (E.g. This) → MicroUSB → PixelBlaze → LEDs.
(If using a Pico, solder on connections).
We are NOT:
Using 12v lights. �If so, connect common ground, power separately or with 12v → 5v converter. Don't give 12v directly to the PB.
Using 3.7v batteries.�LiPo batteries: Saves weight, less safe, more complicated than 5v USB battery packs.
Using >4-5m of wire, >300 LEDs, or brightness >50%.�Causes voltage drop issues and need to inject power.
Use less power. And then less again. Bright is garish.
These LEDs use 60ma/5v at full white, and only average <10ma in our patterns + brightness. (The PB itself is ~150ma/5v with WiFi)
Learn more: Overview | Sipping Power With NeoPixels | Adafruit Learning System
LEDs: strips, strands, grids
We have lots of choices today. The string / strand style — with full IP67 coatings — are the easiest for wearables.*
Aliexpress (Shop Ray Wu) is MUCH cheaper than Amazon, but takes a month. Best reliable Amazon brand is BTF-Lighting (backup: ALITOVE). String lights are hard to find on Amazon; links welcome.
* Note: Density tighter than 25mm are harder to solder leads onto for beginners.
LEDs are way prettier diffused; Be creative.
This is Burning Man, One Day Build (thanks Adam Savage) — Cut corners liberally.
For diffusers: LED diffuser acrylic; white fabric; faux fur, black mesh fabric.
Test lay out your design with rope / twine, use that to know how many LEDs to use.
Test, test, test before and after attaching anything.
Liberally use hot glue and other hacks. E6000 glue is great for flexible applications.
The crafty folks can sew, fabric tape, etc.
Safety Pins (metal) are risky; can cause shorts.
Bare LEDs showing rainbow test patterns are ugly.
Sound reactivity: "Sounds" cooler in theory than practice
The human brain wants to see patterns, and believe things are timed to a beat.
Hippies think things are sound reactive when they're not.
It's hard to get clean audio input.
BUT! Some of the patterns are sound reactive, if you want to go that route, use the Sensor Expansion board with a mic and accelerometer.
Wiring things with PixelBlaze v3 XL
LEDs runs are DIRECTIONAL for data (not for power and ground).
For strings, add extension wires to the LEDs with Self-Solder Heat Shrink Butt Connectors. (Strip wires, add connector, apply heat gun).
"Tin" the end you're going to attach to screw terminals, with soldering iron; for stability.
Screw everything down with mini screwdriver.
WARNING: if you connect the wires wrong, you can let out the magic smoke.
Tutorial: Connecting wires with self-solder butt connectors
Wiring things with V3 Pico
The PixelBlaze Pico needs to be soldered to the same power line as the LEDs.
For easy mode, we're still using USB power.
Snip off the end of one of these cables, use it.
(I bought a bunch from Aliexpress; for more money, Amazon).
Dust, water, intoxicated people: Keep your project alive
There's no such thing as overkill in hardening your project.
Strain relief (tape, hot glue, extra heat shrink tube over any and all connections).
Waterproofing / dust proofing. (heavy duty plastic bags).
Impact resistance (bubble wrap, cases, fabric… don't let a hug break you).
Don't overly flex wires, twist strands, bend panels.
PixelBlaze themselves — especially the Pico — run temperature "hot".
Batteries shouldn't get dust in them.
Seriously — Burning Man is hell on electronics.
This is two expensive photos. Go have some fun.
You can attach your LEDs inside the lining of a coat, to suspenders, to a helmet, to a hat, to a bike basket, to a tutu; the world and your imagination is your oyster.
Finished Project Examples (coming soon)
Appendix: Adding Palette code to existing Patterns
Add ALL OF this block near the top of your pattern.�Includes hand-picked palettes as starting points.
//next whole section is for palettes.
/*
Palettes via http://soliton.vm.bytemark.co.uk/pub/cpt-city/; some picked from
ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb
Patterns with decimals converted for FastLED with gammas (2.6, 2.2, 2.5)
Code simplification by ZacharyRD (Zachary Reiss-Davis), Palette Blending Design by zranger1
Patterns squished into single lines using double-tabs where line breaks would be in expanded spacing.
This is a cosmetic choice to make them fit into a single page that can be skipped over.
The palettes are also indented to just be a logical group; there's no functions involved.
*/
// blue purple teal pop of yellow, balanced.
var inferno = [ 0.0, 0/255, 0/255, 4/255, 0.1, 22/255, 11/255, 57/255, 0.2, 66/255, 10/255, 104/255, 0.3, 106/255, 23/255, 110/255, 0.4, 147/255, 38/255, 103/255, 0.5, 188/255, 55/255, 84/255, 0.6, 221/255, 81/255, 58/255, 0.7, 243/255, 120/255, 25/255, 0.8, 252/255, 165/255, 10/255, 0.9, 246/255, 215/255, 70/255, 1.0, 252/255, 255/255, 164/255, ]
//yellow-orange-red-purple-navy
//http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_04.png.index.html
var bhw1_04_gp = [0, 229,227, 1, 15, 227,101, 3, 142, 40, 1, 80, 198, 17, 1, 79, 255, 0, 0, 45]
arrayMutate(bhw1_04_gp,(v, i ,a) => v / 255);
// blue-purple-red
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html
var Sunset_Real = [0.0, 0.471, 0.0, 0.0, 0.086, 0.702, 0.086, 0.0, 0.2, 1.0, 0.408, 0.0, 0.333, 0.655, 0.086, 0.071, 0.529, 0.392, 0.0, 0.404, 0.776, 0.063, 0.0, 0.51, 1.0, 0.0, 0.0, 0.627,];
// Battery Saver: black-blue-purple-pink-white Top pick.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html
var Analogous_1 = [0.0, 0.012, 0.0, 1.0, 0.247, 0.09, 0.0, 1.0, 0.498, 0.263, 0.0, 1.0, 0.749, 0.557, 0.0, 0.176, 1.0, 1.0, 0.0, 0.0,];
// this is a really good one. Orange Pink Green. Should be garish but isn't.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html
var rainbowsherbet = [0.0, 1.0, 0.129, 0.016, 0.169, 1.0, 0.267, 0.098, 0.337, 1.0, 0.027, 0.098, 0.498, 1.0, 0.322, 0.404, 0.667, 1.0, 1.0, 0.949, 0.82, 0.165, 1.0, 0.086, 1.0, 0.341, 1.0, 0.255,];
// really good blending, purples blues and pinks. Mild but good.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html
var BlacK_Blue_Magenta_White = [0.0, 0.0, 0.0, 0.0, 0.165, 0.0, 0.0, 0.176, 0.329, 0.0, 0.0, 1.0, 0.498, 0.165, 0.0, 1.0, 0.667, 1.0, 0.0, 1.0, 0.831, 1.0, 0.216, 1.0, 1.0, 1.0, 1.0, 1.0,];
// Battery Saver: black magenta red yellow.
//better than just black magenta red.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html
var gr65_hult = [0.0, 0.969, 0.69, 0.969, 0.188, 1.0, 0.533, 1.0, 0.349, 0.863, 0.114, 0.886, 0.627, 0.027, 0.322, 0.698, 0.847, 0.004, 0.486, 0.427, 1.0, 0.004, 0.486, 0.427,];
// yellow to greens to blues. Very little red.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html
var GMT_drywet = [0.0, 0.184, 0.118, 0.008, 0.165, 0.835, 0.576, 0.094, 0.329, 0.404, 0.859, 0.204, 0.498, 0.012, 0.859, 0.812, 0.667, 0.004, 0.188, 0.839, 0.831, 0.004, 0.004, 0.435, 1.0, 0.004, 0.027, 0.129,];
// Battery Saver: an excellent fire look, but too much black in it for many patterns. Black - red - orange - yellow - white.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html
var lava = [0.0, 0.0, 0.0, 0.0, 0.18, 0.071, 0.0, 0.0, 0.376, 0.443, 0.0, 0.0, 0.424, 0.557, 0.012, 0.004, 0.467, 0.686, 0.067, 0.004, 0.573, 0.835, 0.173, 0.008, 0.682, 1.0, 0.322, 0.016, 0.737, 1.0, 0.451, 0.016, 0.792, 1.0, 0.612, 0.016, 0.855, 1.0, 0.796, 0.016, 0.918, 1.0, 1.0, 0.016, 0.957, 1.0, 1.0, 0.278, 1.0, 1.0, 1.0, 1.0,];
// reds to oranges to yellows to purple blue. No black in it.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html
var BlacK_Red_Magenta_Yellow = [0.0, 0.0, 0.0, 0.0, 0.165, 0.165, 0.0, 0.0, 0.329, 1.0, 0.0, 0.0, 0.498, 1.0, 0.0, 0.176, 0.667, 1.0, 0.0, 1.0, 0.831, 1.0, 0.216, 0.176, 1.0, 1.0, 1.0, 0.0,];
// as described, blue cyan yellow -- slightly blue biased.
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html
var Blue_Cyan_Yellow = [0.0, 0.0, 0.0, 1.0, 0.247, 0.0, 0.216, 1.0, 0.498, 0.0, 1.0, 1.0, 0.749, 0.165, 1.0, 0.176, 1.0, 1.0, 1.0, 0.0,];
var palettes = [
inferno,
bhw1_04_gp,
Sunset_Real,
Analogous_1,
rainbowsherbet,
BlacK_Blue_Magenta_White,
gr65_hult,
GMT_drywet,
lava,
BlacK_Red_Magenta_Yellow, ]
// control variables for palette switch timing (these are in seconds)
var PALETTE_HOLD_TIME = 15
var PALETTE_TRANSITION_TIME = 2;
// internal variables used by the palette manager.
// Usually not necessary to change these.
export var currentIndex = 0;
var nextIndex = (currentIndex + 1) % palettes.length;
// primarily useful for testing, go to the next palette in the main array. Skips the blend step.
export function triggerIncrementPalette(){
currentIndex = (currentIndex + 1) % palettes.length;
runTime = 0;
}
// arrays to hold palette rgb interpolation results
var pixel1 = array(3);
var pixel2 = array(3);
// array to hold calculated blended palette
var PALETTE_SIZE = 16;
var currentPalette = array(4 * PALETTE_SIZE)
// palette timing related variables
var inTransition = 0;
var blendValue = 0;
runTime = 0
// Startup initialization for palette manager
setPalette(currentPalette);
buildBlendedPalette(palettes[currentIndex],palettes[nextIndex],blendValue)
// user space version of Pixelblaze's paint function. Stores
// interpolated rgb color in rgbArray
function paint2(v, rgbArray, pal) {
var k,u,l;
var rows = pal.length / 4;
// find the top bounding palette row
for (i = 0; i < rows;i++) {
k = pal[i * 4];
if (k >= v) break;
}
// fast path for special cases
if ((i == 0) || (i >= rows) || (k == v)) {
i = 4 * min(rows - 1, i);
rgbArray[0] = pal[i+1];
rgbArray[1] = pal[i+2];
rgbArray[2] = pal[i+3];
}
else {
i = 4 * (i-1);
l = pal[i] // lower bound
u = pal[i+4]; // upper bound
pct = 1 -(u - v) / (u-l);
rgbArray[0] = mix(pal[i+1],pal[i+5],pct);
rgbArray[1] = mix(pal[i+2],pal[i+6],pct);
rgbArray[2] = mix(pal[i+3],pal[i+7],pct);
}
}
// utility function:
// interpolate colors within and between two palettes
// and set the LEDs directly with the result. To be
// used in render() functions
function paletteMix(pal1, pal2, colorPct,palettePct) {
paint2(colorPct,pixel1,pal1);
paint2(colorPct,pixel2,pal2);
rgb(mix(pixel1[0],pixel2[0],palettePct),
mix(pixel1[1],pixel2[1],palettePct),
mix(pixel1[2],pixel2[2],palettePct)
)
}
// construct a new palette in the currentPalette array by blending
// between pal1 and pal2 in proportion specified by blend
function buildBlendedPalette(pal1, pal2, blend) {
var entry = 0;
for (var i = 0; i < PALETTE_SIZE;i++) {
var v = i / (PALETTE_SIZE - 1);
paint2(v,pixel1,pal1);
paint2(v,pixel2,pal2);
// build new palette at currrent blend level
currentPalette[entry++] = v;
currentPalette[entry++] = mix(pixel1[0],pixel2[0],blend)
currentPalette[entry++] = mix(pixel1[1],pixel2[1],blend)
currentPalette[entry++] = mix(pixel1[2],pixel2[2],blend)
}
}
Add this code block to your Before Render function
//INSERT THIS PALETTE BLOCK WITHIN BEFORE RENDER AT THE END.
// here till end of beforerender is for palette blending.
runTime = (runTime + delta / 1000) % 3600;
// Palette Manager - handle palette switching and blending with a
// tiny state machine
if (inTransition) {
if (runTime >= PALETTE_TRANSITION_TIME) {
// at the end of a palette transition, switch to the
// next set of palettes and reset everything for the
// normal hold period.
runTime = 0;
inTransition = 0
blendValue = 0
currentIndex = (currentIndex + 1) % palettes.length
nextIndex = (nextIndex + 1) % palettes.length
}
else {
// evaluate blend level during transition
blendValue = runTime / PALETTE_TRANSITION_TIME
}
// blended palette is only recalculated during transition times. The rest of
// the time, we run with the current palette at full speed.
buildBlendedPalette(palettes[currentIndex],palettes[nextIndex],blendValue)
}
else if (runTime >= PALETTE_HOLD_TIME) {
// when hold period ends, switch to palette transition
runTime = 0
inTransition = 1
}
In your Render function(s), Replace the Hue call with a Paint call
// hsv(h, s, v)
paint(h,v)
Appendix: Other Tutorials and Guides I find helpful
Many of these use different hardware / software choices, but the overall ideas in them may be useful:
Appendix: Product listings