Multiple inputs and FPS

Hi all,

I’ve yet again embarked on a rather big project and could use your help/guidance.

I have 96 touch sensors layer on a 8x12 grid. Around each sensor will be a 12 or 16-led strip(ws2812b most probably). What I want to do, is for each strip to fade in, stay on for 2 seconds, then fade out every time the corresponding sensor is touched.

Here’s how I’m trying to implement this:

An ESP32 will take care of all sensors via 7 MCP23017 expansion boards. I’ll take the interrupt route, but if I see the ESP32 gets overwhelmed, I’ll put a second one and split the load.

The ESP32 will then send all sensor data to Pixelblaze via Serial, piggy-backing on the sensor board stream. I’m already doing something similar on a different project(way less inputs though) and it works kind of successfully.

Here’s the question.

It’s too many inputs. I will use all bits of the stream, 2 inputs per bit, then separate them in PB, and have PB do the following:
Keep all LEDs black, turn on only the 12 of the corresponding input for the set time w/ fade in-out. This can be a few inputs together(but hardly simultaneously) and pretty much never more than 4-6.

I’m pretty bad at coding, and the only way I can think of doing it looks like this in pseudo code:

Initialize the sensor board array

Take each bit, do the math to separate the inputs. Assign a variable for each input. Copy paste 96 times.

Have a few animations, choose which to use depending on a switch position. Animations will be global - a rainbow, 1 static color per line of the grid(12 in total), or something equally simple.

Have an endless code in render function with if statements like so:

If sensor 1 changed state(LOW to HIGH), fade in LEDs 0-11, stay on for 2 seconds, fade out

If sensor 2 changed state(LOW to HIGH), fade in LEDs 12-23, stay on for 2 seconds, fade out

And so on.

I’m doing this exact thing in another project, and frame rate drops dramatically with every new if statement I bring in. Given the leds are equally divided(12 times 96), is there a way to do this in an array, or something outside render function? Is there also a way to not have to declare so many variables when picking up the bits from the sensor board stream, which I’m sure drops the frame rate even further?

Lastly, as I’m having close to 1200 leds connected, the expansion board is a given, right?

Thanks!

Sounds like quite an awesome project! Yes, an output expander or a few would boost FPS a bit and give you more than one output which might help with wiring. Otherwise your LED data rate is limited to around 33k pixels/sec of the WS2812 protocol and you wouldn’t see more than 27 FPS even with the most optimal pattern code.

Are they capacitive touch inputs or switches? The io expander you have would work fine for switches/buttons but might not for capacitive touch.

Yes you could encode the 96 bits of data into some of the sensor board elements. Each is 16 bits so you would only need a handful. These are usually shifted to a value between 0-1, but can be shifted back to integers easily enough.

Encoding and decoding is possible with some bit shifts and bitwise AND operations.

What I would do is, if possible, divide it up evenly, then you can use math. So e.g. if you used 16 pixels you could divide the pixel index by 16 to know which sensor it was. Then if you have 16 inputs per SB data element, you could divide by 16 again to know which element to use. The bit position would go from 1-16 for each, so you could use the remainder (modulus math) on the index.

You can keep track of the fade state in an array, one element per 16 pixel segment (so 92 elements).

Most of the hard work can be done in beforeRender, and keeping the code in render simple helps with FPS.

Something like

var fades = array(96)
var fadeAmount
export function beforeRender(delta) {
  var sensor, element, bit, intValue
  fadeAmount = delta / 1000 //fade out over 1 second
  fades.mutate(v=>max(0, v - fadeAmount)) //fade down to zero based on time elapsed
  for (var sensor = 0; sensor > 96; sensor++) {
    element = floor(sensor/16)
    bit = sensor % 16
    intValue = frequencyData[element]<<16
    if (intValue & (1<<bit))
      fades[sensor] = 3; //1 for a 1 second fade, plus 2 to hold it solid for 2s)
  }
}

export function render(index) {
  var sensor = index / 16
  var v = fades[sensor]
  hsv(0,0,v)
}

I’m using these sensors. When I started this project I was going for white leds, but soon after I ordered them I decided to make it infinitely harder and go for PB/addressable:) After fiddling with it, I found that one of the pins of the board’s IC goes high(3.3v) when the sensor is active, so I’m using that as output(it floats when low, but I added a 1k pull down and testing it with an Arduino Pro Mini I had laying around produced stable results).

Coming to the math part, I’ll admit big time ignorance. Here’s an example of what I’m doing in another project:

On the Arduino side, I calculate how many leds are connected on 2 different outputs of the output expander, and I pack them for PB like so:

SB10Frame.freq[0] = (((Leds1 * 1000) + Leds2 * 10) & 0xFF);
 SB10Frame.freq[1] = ((((Leds1 * 1000) + Leds2 * 10) >> 8) & 0xFF);

Then on PB side, I do the following:

function updateLeds() {
    LedsInput = frequencyData[0] * 10000/1.5259
    LedsInput = floor(LedsInput)
    
    Leds2 = LedsInput%100
    Leds1 = (LedsInput - Leds2)/100
}

This was as far as I could go, I managed to use the first two digits of freq[0] for expander output 1, and the last 2 digits for ouput 2. I was really pleased with myself when I did that btw:) But how could I manage to squeeze anything more in there, I really have no clue. If I understand, you’re suggesting I pack the HIGH/LOW state of 16 sensors in there, but my mind only sees numbers from 1 to 65535, so with my poor thinking I would leave the first digit as is(or be greater than 0 anyway, otherwise it gets truncated, correct?), then use the last four to get the state of 4 sensors, eg. 61111 would be all HIGH, 60011 only the last 2 HIGH, etc. How is it possible to get more in there?

The other perplexing bit is this:

Say that you managed to pack all 96 sensors in only the first 6 bits or so of the frequencyData array.

How can you extract those and put them in a new array like so:

var fades = array(96)

Also, if I understand some of your code correctly, there is only a fade out, not fade in, correct? If there is, I really need serious studying:)

Thanks for - as always - the stellar help:)

I could really use some help as I can’t figure it out. Here’s where I’m at: I’m successfully sending the state of 96 sensors to PB via an ESP32. They take up the first 6 integers of freq, 16 per integer. Due to construction constrains, I’m turning on 12 LEDs per sensor instead of 16. I tried many iterations, even used chatGPT out of curiosity, but can’t manage to turn on the appropriate LEDs, and when I try to drop them from 16 to 12 I get an array out of bounds message. Here’s the code:

export var frequencyData = array(32)
var fades = array(96)
var fadeAmount
var t1

export function beforeRender(delta) {
  t1 = time(.1)
  fadeAmount = delta / 1000 // fade out over 1 second
  fades.mutate(v => max(0, v - fadeAmount)) // fade down to zero based on time elapsed

  for (var sensor = 0; sensor < 96; sensor++) {
    element = floor(sensor / 16)
    bit = sensor % 16
    intValue = frequencyData[element * 2] | (frequencyData[element * 2 + 1] << 8)
    if (intValue & (1 << bit))
      fades[sensor] = 3; // 1 for a 1 second fade, plus 2 to hold it solid for 2s)
  }
}

export function render(index) {
  var sensor = floor(index / 16)
  var v = fades[sensor]

  // Rainbow pattern
  h = 1 //t1 + index / pixelCount
  s = 1
  v = v // Use the fade value for brightness
  hsv(h, s, v)
}

More than half of the LEDs don’t even turn on.