Detailed Beginner Tutorial for Making Wearables with PixelBlaze

I ran a workshop this weekend for my Burning Man camp, that I called Making Wearable LEDs, the “I’ve never done this before and it’s under month until Burning Man” Way and had a ton of fun doing it. If that describes you or any of your friends, I hope this tutorial is helpful! I’d love any feedback, comments, suggestions; I’ll keep iterating on it. For now, the world has comment access to the presentation.

Note that this is VERY MUCH not the only way, or even the best way, to make wearables – it’s just one path which is meant to be really straightforward for beginners.


I also linked this in the appendix of the post, but if you want to modify any existing Patterns to use the new Palette functionality, start by pasting this code at the top of the Pattern:

Primary code for palettes and an automated palette switcher
//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)    
  }
}

Then go down to “BeforeRender”

And paste this into the start of the "BeforeRender" 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
  }
  

Finally, replace the “Hue” call with paint(h,v) in your Render function(s),

I also posted this on the LEDs are Awesome Facebook group, with less code etc.

It builds off my previous post, Beginners Guide to Making a LED Festival Coat , as well.

5 Likes

BTW – @wizard and @zranger1 I’m taking the new palette functionality and the code @zranger1 mainly wrote for me in How to modify existing patterns to use Palette functions in v3.30 as an anchor for my “plug-and-play palette functionality” that I added to a lot of palettes for my camp and provided above. I think it’s a minor improvement over the Pattern library “Fast Palette Blending”, although it’s extremely similar, of course.

(@zranger1 if I’m going to beg a favor and someone like you to help me with code, at least I’m generalizing it and making it useful to everyone!)

2 Likes

Love this! It reminds me that I should share my learnings and failures in wearables, mainly around how to attach leds to clothing in ways that work well and don’t work well.

1 Like

@hex337 – I’d love to hear more about said learnings and failures, especially if you don’t mind me taking your notes and adding them to my Slides! As I said, I’m NOT an expert here – and I know there’s people in this group who do better work than I do, and most good tutorials are built on explaining all the ways we have messed up in the past.

@ZacharyRD, and other palette code users, I just found and fixed a sneaky off-by-one bug in the palette manager. You’ll probably want to fix this in your code too. It affects the last (highest) entry in the interpolated palette, and can cause pixels to be incorrectly rendered as black.

To fix the problem, in the function buildBlendedPalette(pal1, pal2, blend), change the line that reads:
var v = i / PALETTE_SIZE;
to:
var v = i / (PALETTE_SIZE - 1);

1 Like

That… explains some weird behavior I was seeing and couldn’t troubleshoot properly.

Thanks a lot — super helpful to know and fix.

The testing and updating process here will be “interesting”, because I think I optimized around this bug by accident when choosing which palettes to include in my stock set I put on 5 patterns / 15 PixelBlaze, and I’m going to test them all again to make sure they still all fit the look I’m going for. I’m traveling the next few days but will update my code block above as well after I retest.

Thanks again for all you do for the community — even with the bug the overall system works!

I’m building an LED jacket using WS2811 DC5V lights, but I’m having trouble finding a faster option than AliExpress. I found these DC12V WS2811/WS2812 Pebble Pixel Lights with a 2.5cm pitch on Amazon. Will the 12V version work for my project if I switch from 5V? Also, does anyone know where I can get 5V lights that will ship quicker than AliExpress?

Since I wrote this 5v pebble style LEDs have become easy to buy on Amazon. BTF-lighting is a very reliable and good enough quality vendor — this is a listing for what you want. Amazon.com

I wouldn’t use 12v in this style wearable for beginners because it requires some extra work on the power management instead of plug-and-play USB battery packs.

1 Like

Thanks for the previous help with my PixelBlaze LED coat project! I’m using a BTF-LIGHTING WS2812B setup with 300 LEDs powered by an Anker 20,000mAh power bank (Model A1647) and also have an Anker A1215 available as a backup.

My Setup and Questions:

I’ve connected everything as follows:

  • 5V Battery Bank → USB Cable → PixelBlaze
  • PixelBlaze Terminal Block to:
    • Red wire (Power) to LED strip
    • White wire (Data) to LED strip
    • White wire (Ground) to LED strip
  • The LEDs are connected through a JST SM connector.

The issue I’m facing is that when I try to run all 300 pixels at about 25% brightness, the last 200 LEDs are noticeably dimmer than the first 100.

Would you recommend adding a power injection point near the end of the LED strip to help maintain consistent brightness? Also, does the JST SM connector impact power consistency across the LEDs, and would it be better to solder the connections for stability?

Here’s the setup diagram:

Here’s an ASCII diagram to represent the power setup, including the PixelBlaze, LED strip, and an additional power injection point near the end of the LED strip.

Also, I haven’t soldered the terminal block yet (I’m just testing right now), so I’m not sure if this could also be causing the lower brightness on the later LEDs. Any advice would be appreciated!

5V Battery Bank
      |
      +----> USB Cable ----> PixelBlaze
                            ├──> 5V Power Out (+) ----┐
                            │                        |
                            ├──> Data Out ---------->│----> LED Strip Data (White)
                            │                        |
                            └──> Ground (GND) ------>│----> LED Strip Ground (White)
                                                     |
                                                     |
Start of LED Strip                                   |
   ┌─────────────────────────────────────────────────────────────────────┐
   │                                                                     │
   │  Red (Power) ----> +5V ----------->-------------------------------┐ │
   │  White (Data) ----> Data (D) ------------------------------------>│ │
   │  White (Ground) --> Ground (GND) -------------------------------->│ │
   │                                                                     │
   └─────────────────────────────────────────────────────────────────────┘
   
Power Injection Point (near end of LED strip)
   ┌─────────────────────────────────────────────────────────────────────┐
   │                                                                     │
   │  +5V (Power) ----------------------------------------------------->│ │
   │  Ground (GND) ---------------------------------------------------->│ │
   │                                                                     │
   └─────────────────────────────────────────────────────────────────────┘


Was planning on using this as a injector and connecting it to this point of the strip

Power Bank Specs:

  1. Anker A1215 Power Bank:
  • Capacity: 10,000mAh
  • Output: Standard 5V USB output, suitable for smaller projects or limited test runs with LEDs.
  1. Anker A1647 Power Bank (20,000mAh, 22.5W):
  • Capacity: 20,000mAh
  • Output: 22.5W max, with USB-C and USB-A ports, allowing for high-speed charging.

If you’re seeing voltage drop – and you are – you’ll want to inject power at the far end from the battery. I’ll note that you can PROBABLY pull this off without doing so if you stick to patterns that are mainly “off” or don’t light up all the LEDs at once (unlike your example photo), but what you’re describing to inject power will 100% work and is probably the right next step. Note that you need a common ground wire between power sources, which can make this a bit more tricky to do.

I’d also check that you’re able to get a full 2.1a of power out of those power banks.

You will get marginally better results by replacing the JST connector points with soldering the connections directly, so if you can inject power, that’s probably the simplest plan.

You will want to directly connect the string of LEDs to the power source ground and power. Pulling the power from the pixelblaze is limited to around 500ma, which is not enough to power 300 LEDs.

The way I usually like to set things up for wearables is to have a ground and power connection hub - Usually two of these.

USB from the battery pack connects to the hub (can be as simple as a waygo connector), then from there, you connect one set of power and ground to the pixelblaze, and another to the strip. If your battery pack supports dual fast charging, then I would also do a second hub and connect that to the tail end of your led strip.

Note: you will want to connect the ground from the pixelblaze to the strip, but don’t need to connect the power.

Not sure where that limit is coming from.

The micro USB connector is rated for 1.8A, but in personal testing works fine for a bit higher than that. I wouldn’t expect any issues with a 2.1A supply related to Pixelblaze.

Hrm… could have sworn I saw that somewhere in the past, but maybe that was just what the blaze draws and you should account for. I defer to the person who knows more about this!

1 Like

Hey everyone!
Just wanted to say thanks for all the guidance here. I finished my first jacket using Pixelblaze and WS2812B pebble pixels — the build came out amazing. Your advice made a huge difference!

Here’s the wiring layout I used:

5V Battery Bank
      |
      +----> USB Cable ----> PixelBlaze
                            ├──> 5V Power Out (+) ----┐
                            │                        |
                            ├──> Data Out ---------->│----> LED Strip Data (White)
                            │                        |
                            └──> Ground (GND) ------>│----> LED Strip Ground (White)
                                                     |
                                                     |
Start of LED Strip                                   |
   ┌─────────────────────────────────────────────────────────────────────┐
   │                                                                     │
   │  Red (Power) ----> +5V ----------->-------------------------------┐ │
   │  White (Data) ----> Data (D) ------------------------------------>│ │
   │  White (Ground) --> Ground (GND) -------------------------------->│ │
   │                                                                     │
   └─────────────────────────────────────────────────────────────────────┘
   
Power Injection Point (near end of LED strip)
   ┌─────────────────────────────────────────────────────────────────────┐
   │                                                                     │
   │  +5V (Power) ----------------------------------------------------->│ │
   │  Ground (GND) ---------------------------------------------------->│ │
   │                                                                     │
   └─────────────────────────────────────────────────────────────────────┘
                                                     ▲
                                                     │
                         Return path to Pixelblaze ──┘
                         (Power and Ground lines from end of strip)

:coat: Now Building: 1000–2000 LED Pebble Jacket

I’m now planning a new jacket build using:

  • DC5V WS2811 LED Pebble Pixel String Lights from Ray Wu
  • Planning to run all 1000, and possibly add a second string for 2000 total pixels

:question:My Question:

How should I power this safely and effectively for a wearable setup?

  • Should I inject power every 200–300 pixels?
  • Would it be better to split into multiple parallel segments?
  • Is a battery pack still viable, or do I need a beefy 5V PSU?
  • How do I best wire the grounds and data for signal stability?
  • Any specific recommendations for wiring, fusing, or connectors?

Would love to hear from anyone who’s done high-count WS2811 builds or powered big pebble pixel strings. Thanks again for the amazing community :raised_hands:

2 Likes

If you need to maximize, real-world measurements will always be better than using the specs (which are typically low accuracy on Ray’s product pages), but here’s an example using Ray’s specs.

  • Assume 50% max brightness to save battery life - bang for buck above this isn’t worth it
  • Inject every 200 pixels
  • Assume 7mA typical, 10mA max per pixel
  • Overall: 2000 pixels = 20 A. We’re way above what we can draw from a USB pack. You could try to get a 20V USB-PD decoy to output 20V/5A and then use a buck converter to get to 5V/20A, but you’re approaching a size and complexity where a 3S (11V) LiPo might be simpler. Note that people’s experience getting 100W from most USB-PD 20V decoys is mixed - you’ll need to try a few models. If you’re giving this to others, this route might be worth pursuing, but if you’re wearing it yourself, you’d want to use hobby LiPos.
  • Definitely use 1 or 2 output expanders (1000 or 2000 pixels) with 200 pixels per channel to address them in multiple parallel segments, otherwise your frame rate will tank (and data probably just won’t propagate successfully through 2000 pixels, 1000 would require testing/verification). Again - needs testing to verify but I think I remember that 100 can run powered single sided without reinjecting at the far end.
  • Grounds are always all common; the output expander should only pass Data, not 5V for this setup
  • Fuse with inline or mini auto fuses
  • Power side connectors: I’d use XT30. For each strand of 200 if you’re trying to make them modular to replace (ups the work quite a bit), I’d stick with JST-SMs for simplicity, availability, and current rating.

Worst case power: 2000 pixels @0.01A ea = 100W; Assume typical Anker 24K 100W USB-PD power bank (86 Wh). 86Wh / 100 W *.9 (buck inefficiency) = 46 minutes runtime with all-white, >= 1.5H full brightness colors / patterns. 1000 pixels will be twice that.

When you select your pebble density, just know the tighter pitch ones (20mm) can be difficult to solder. Try it yourself, and if it’s driving you crazy, you can use the pebble soldering helper PCBs (search web).

Alternatives to consider: 12V pebbles + 3S LiPos. Pros: No buck converter for LEDs (just need miniBuck for PC itself), longer 200 LED runs without injection; WS2818 with backup data available from Ray. Cons: Need to add a LVC cutoff or LiPo cell alarm for safety.

1 Like

First off: this looks great, and thanks for sharing that the tutorial was useful – it’s always really meaningful to know that something you wrote up helped a new person on a new project.

Jeff, I think, has actually answered most of your questions – but I’ll say that just limiting yourself to ~500 pixels is also a good thing on jackets – less is often more.

Small point to add even if you manage to handle power issues (with this amount of LED you need only a fraction of the power per LED or else you gonna be more visible than the stage): WS2811 and WS2812 operate at 800 kHz.

It means than 800 LED will limit you to about 40 FPS on one data output, no matter how simple is your pattern: this is a limitation of the IC of each pixel. I don’t remember how this is calculated but you can trust me, I have a 815 pixel on one data pin installation in front of me.

If you go to 1600 LED, you’re down to 20 FPS and it starts to become pretty laggy. At 2000 LED your effects won’t be smooth at all anymore. So if you plan anything with smooth movement (like KITT effect for example), it will not look nice.

So i’d recommend to stay under 800-1000 pixels, or else go with clocked LED like APA102 (might be wrong), that don’t exist as pebble led as far as i know.