Configuring multiple PixelBlazes for different segments of long strip

Hey! Questions are bolded

I’m trying to put about 2400 LEDs (~40 meters) of HD107S LEDs (APA102 compatible) around the ceiling perimeter for a very large room.

There are some challenges related to this, especially since my ideal is to have each of 2400 pixels individually addressable - no symmetry - and sound reactive FPS >24. Power will be no problem though.

Issue: The Data Speed maximums.

  • 8 MHz for 300 LEDs
  • 4 MHz for 600 LEDs
  • 2 MHz for >600 LEDs (tested up to 2100)

Is this normal? I’ve never used real APA102, so I’m not sure if it is related to the HD107S. Should I expect higher Data Speeds? Higher and they start flickering random colors in some places.

Solution: Use 4 pixelblazes with 600 LEDs each. Control with Firestorm

Problem: Having each pixelblaze configured for specific segments (possibly a feature request)
example:

  • PixelBlaze 1 controls indexes 0-599
  • PixelBlaze 2 controls indexes 600-1199
  • PixelBlaze 3 controls indexes 1200-1799
  • PixelBlaze 4 controls indexes 1800-2399

Solution: Change the individual code of each of the 4 to reflect the segmenting.

Question: Does FireStorm make sure the time() functions are synced? This would be important Edit: found a source saying yes it does.
Question: Is there any native support in the future or possible by editing the FireStorm code for this segmented behavior?
Question: Would this be a use case for the Output Expander? I didn’t think it would increase data speed.

Thank you!

Hi @kylarleds,
For a massive number of LEDs, data speed is one thing, but you will also hit cpu/processing constraints. Your best bet is to use multiple Pixelblaze and sync them with Firestorm.

(Edit to help others who stumble across this post: Now, nearly 3 years later, you would want to try the sync features built-in to Pixelblaze itself.)

APA102, and it seems that some clones as well, do suffer from skew that slightly shifts the phase of the clock signal for each LED which can limit the total strip bandwidth for longer strips. 2MHz data speed isn’t going to be much of a constraining factor with complex patterns.

Yes, animations based on time() will synchronize automatically with Firestorm. Without changing anything, multiple PB running the same pattern will appear identical. You guessed right, some small changes to the pattern code will cause them to draw different pieces, so that you can make it appear as a single pattern.

I think the easiest way would be to override pixelCount and index inside render. e.g. to stretch a rainbow across 2 PBs at 600px each, something like this:

 var pbIndex = 1 //set to 0 for the first, 1 for the second, etc - change per controller
var networkPixelCount = 1200
var pixelOffset = pbIndex * 600
export function beforeRender(delta) {
  t1 = time(.1)
  pixelCount = networkPixelCount //override pixelCount to be the network size
}

export function render(index) {
  index += pixelOffset
  h = t1 + index/pixelCount
  s = 1
  v = 1
  hsv(h, s, v)
}

Similar tricks can be done with 2D/3D patterns by creating pixel maps for just the segment, then translating + scaling the coordinates in render to “move” to what would be the segments location in a larger coordinate space.

For sound reactive patterns, the data line from the sensor board can be fed to multiple Pixelblazes. The output impedance of the sensor board is set pretty high so that a programmer can override it, but for multiple PBs and longer distances you will probably want to maximize the drive power with a small modification to the board.

Shorting that out (blob some solder on it) will increase the transmit output power.

Making that whole thing more automatic is on the roadmap. Ideally Firestorm could help coordinate the, uh, coordinates across a network of Pixelblaze and further assist with programming them in a more seamless way. Further simplifying things would be a network broadcast of the sensor board data, such that extra wiring wouldn’t be necessary to share the same sensor data.

Given that you are pushing CPU and output bandwidth limits, the output expander won’t increase your FPS. The output expander also runs at 2Mbaud, so wouldn’t get you anything over a single string of 2400 LEDs at 2MHz.

2 Likes

Thanks wizard,

I’ll be doing a proof of concept with 4 PixelBlazes today and post the results + code :slight_smile:

I’m always amazed at how fast you reply and with such quality. I’m probably just going to use 4 sensor boards just to be lazy about the wiring around the room lol - even if they will be different I’ll just use patterns where it doesn’t matter very much (just capturing the beat is fine).

1 Like

Result of proof of concept: SUCCESS

This code demonstrates storing each pixel’s hue/sat/val in arrays and still being able to be a segment in a long strip.

I also was able to use your index offset code to make “fast pulse” work.

So whether a pattern is array based or timer based or a mix - networking pixels like this works :slight_smile:

//@kylarleds
var networkPixels = 2400
var networkPosition = 0
var networkSegmentSize = 600
var networkLow = networkPosition*networkSegmentSize
var networkHigh = networkLow+networkSegmentSize // Exclusive max
var networkIndexOffset = networkSegmentSize*networkPosition

function isMine(i){
  return (i >= networkLow) && (i < networkHigh);
}

function makeMine(i){
  return i % networkSegmentSize
}

pixelsval = array(pixelCount)
pixelshue = array(pixelCount)
pixelssat = array(pixelCount)

speed = .06
fade = 0.9

export function sliderSpeed(v){
  speed = (1.1-v)/3
}

export function sliderFade(v){
  fade = 1-(v/20)
}

lastidx = 0
export function beforeRender(delta) {
  
  hue = time(0.14)
  pos = triangle(time(speed))
  idx = floor(pos*networkPixels) // The current pixel in front


  
  i = min(lastidx, idx)
  end = max(lastidx, idx)
  
  if(end-i < networkPixels-3){
    while(i <= end){            // This is to account for missed pixels in case we are moving really fast
      if(isMine(i)){
        pixelsval[makeMine(i)] = 1
        pixelshue[makeMine(i)] = hue // This is the starting color. 0=red, 0.33=green, 0.66=blue
        pixelssat[makeMine(i)] = 0.8 // The leading pixel is given some saturation
      }
      i++;
    }
  }
  
  lastidx = idx
  
}

export function render(index) {
  //index += networkIndexOffset
  
  v = pixelsval[index]
  v = v*v
  pixelshue[index] -= 0.02      // Change this to make the rainbow longer or shorter
  pixelssat[index] *= 1.1       // Change this to mess with the white at the front
  pixelssat[index] = pixelssat[index] > 1 ? 1 : pixelssat[index]  // Set the max value to 1
  pixelsval[index] = max(0, pixelsval[index]*fade) 
  hsv(pixelshue[index], pixelssat[index], v)
}

Feature request: Creating groups in Firestorm. I have many Pixelblazes now set up in my house, but I only want to control these 4 with Firestorm

1 Like

Workaround for groups: clicking a named pattern activates it only where it exists. So by naming things a certain way you can kind of get a group control going.

Very cool! Can’t wait to see more!

1 Like

I see what you mean, that’ll be good enough :slight_smile: :slight_smile:

More is definitely to come!

No rush in replying (purely theoretical for now, but I do have plans for future multiple items living in the same “virtual space” in an art project)

@wizard You said: Similar tricks can be done with 2D/3D patterns by creating pixel maps for just the segment, then translating + scaling the coordinates in render to “move” to what would be the segments location in a larger coordinate space.

Since the auto remap to 0…1 happens, how would you best suggest an offset to preserve the relative spacing?

Example: 2 units, one of which is “left” of the other… Item 1 gets told it’s pixels are -30 to 0, the 2nd gets 1-30 (let’s keep it 1d for simplicity, but same issue exists with 2d/3d coordinates and more so, much much less easy to adjust for.) On remapping, both get 0…1 remapped but lose their relative reference. (Both are 0…1) Is there a way to ensure both get mapped in a range of -30 to 30 so the 0…1 range is correct? (ie unit 1 has pixels from 0 to .5 and unit 2 gets pixels .5+ to 1)

Adding “ghost” pixels with the range extremes (ie add a phony pixel with 30 to unit 1, a phony pixel with -30 to unit 2) would fix this, but wondering if a better answer exists.

To give an example, let’s assume you make that separate map for each object, and hey, if that’s easiest as -30…30, no problem. The map’s then normalized to 0…1 like you said.

/*
  Define a bounding box for this Pixelblaze's section of an overall (x, y) = (0..1, 0..1) world
  Would be set for each PB separately via websockets
  E.g. If this board is driving an object in the upper right 1% square of space,
    x0 = .9    // This device's sub-world starts 90% of the way right
    xLen = .1  // And extends to the rightmost limit
    y0 = 0     // This device's sub-world starts at the top of space
    yLen = .1  // And extends down 10% of all Y space
*/
export var x0, xLen, y0, yLen

export function render2D(index, _x, _y) {
  x = x0 + _x * xLen
  y = y0 + _y * yLen

  // (Some pattern that calculates an entire (0..1, 0..1) world in terms of x and y goes here)
}

What’s cool about this is you can give each one a separate map, but the pattern code for each can be cloned from Firestorm each time you improve it, because the exact same pattern code is being used on all the boards (though you’d need to re-initialize the individual values to each board over websockets).

Interesting approach but not sure that solves the problem I asked about… But I see a kernel of what might work… Need to think about it more.

Ok, I slept on it, and yes, your approach not only works but is probably the only scalable (literally) solution.

My thought experiment: a simple set of panels, let’s say 8x8, but could be larger…

I make a Mandelbrot set pattern. Each pane is set to it’s unique position, place… And calculates it’s own lights.

If I want to add/remove panels, the overall “location” of each panel shifts, but no pixelmap changes are needed, just the values sent.
If I want to scale differently, same…

My method requires adjusting all panels’ pixelmap, yours doesn’t.

The cost is some calculation time, but it scales so much better. Imagine having to adjust 10 or 25 maps over and over. Also, in the Mandelbrot example, adjusting “location” and “zoom” are trivial now. Tweaking zoom/location of a given panel could be a few sliders.

Nice answer.

2 Likes