# Using frequency data to turn on LEDs

Hi all,

I asked this question in another thread with different topic and decided to start a new one in order to get some help.

Here’s my situation:

I have an ESP32 connected to 96 sensors(touch, with a simple on/off state). The state of the sensors is fed to PB via serial, in the first 6 bits of the freq array(16 sensors per bit). On PB I have 1152 LEDs connected via an output expander.

Every time a sensor turns on, the corresponding 12 LEDS(1152/96 sensors) should turn on, stay on for as long as the sensor is on. If the sensor turns off, LEDs should stay on for an additional 2 seconds then have a 1 second fade out.

@wizard helped me with providing the code below, but no LEDs will turn on no matter which sensor changes state(this code assumes that there are 16 LEDs per sensor btw, but I have moved to 12 per due to space).

``````export var frequencyData = array(32)
var t1

export function beforeRender(delta) {
t1 = time(.1)

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)

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

``````

Unfortunately I can’t get my head around the math involved and I’m quite bad at coding so I have no idea how to address this. I did many lame attempts using chatgpt with no meaningful result.

For reference, here’s what I’m getting at Vars Watch for frequencyData[0] when some of sensors 1-16 are on:

Sensor 1: 0.000015
Sensor 2: 0.000031
Sensors 1&2: 0.000046
Sensor 10: 0.007813
Sensor 16: 0.5

Any help would be highly appreciated:)

This might be just a slight mix-up about how sensor data is encoded in the frequencyData array. Here’s a version of the pattern that might help you debug.

It has a built-in decode test (if the decode works the way I think it does) that will step through each sensor, set the appropriate bit in frequencyData and light up its pixels.

First, set `NUM_SENSORS` and `PIXELS_PER_SENSOR` to match your setup.

Then run the pattern with sensors unhooked and `TEST_MODE` set to 1 to see if it lights your pixels appropriately.

If that works, set `TEST_MODE` to 0, hook up your sensors and see how it behaves with real data.

If it’s still not working, there may be an issue on the encoding end - if you post the relevant section of code, I’d be glad to take a look!

``````export var frequencyData = array(32)
var t1

// Set this to the actual number of sensors you're using
var NUM_SENSORS = 64

// Set this to the number of pixels you want each sensor to light
var PIXELS_PER_SENSOR = 4

// Set this to 1 to use synthetic data that lights each sensor in turn
// Set to 0 for "production mode", using real sensor input
var TEST_MODE = 1

// Set the bit for a specified sensor to either off (v == 0),
// or on (v > 0)
export var element
function setSensor(sensor,v) {
element = floor(sensor / 16)
bit = sensor % 16
mask = ((1 >> 8) << bit)
if (v > 0) {
}
else {
}
}

var lastT2 = -1;
export function beforeRender(delta) {
t1 = time(.1)
t2 = floor(time(0.2) * NUM_SENSORS)

if (TEST_MODE && lastT2 != t2) {
setSensor(t2,1)
lastT2 = t2
}

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

if (TEST_MODE) setSensor(t2,0)

}

export function render(index) {
var sensor = floor(index / PIXELS_PER_SENSOR)

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

Test mode works, here’s what I get with real data:

Sensors 1-8 on every bit don’t turn on, sensors 9-16 do. Here’s the relevant code on the ESP32 side:

``````  // Pack buttons into SB10Frame.freq array
int arrayIndex = ((buttonNumber - 1) / 8);                   // Calculate array index based on button number
int bitIndex = ((buttonNumber - 1) % 8);                     // Calculate bit position within the integer
SB10Frame.freq[arrayIndex] |= (ledState[i][j] << bitIndex);  // Set the corresponding bit in the array
``````

Even though it looks equally divided to me, it seems that the lower bits are not correctly interpreted.

Thanks a lot for the help btw!

This might be pretty simple – each element of `SB10.Frame.freq[]` has 16 bits, not 8. If you change the 8 to 16 in both the `arrayIndex` and `bitIndex` calculations, that ought to fix it!

Ok, I’m seriously confused now. The freq array in the sensor board’s GitHub page has 64 elements, but in PB 32, so I always assumed that on the Arduino side it was broken in lower/upper bit…

I changed it to 16, now I’m getting the following:

frequencyData 0, 1 and 2 get populated with the sensor states.

Only sensors 1-8, 17-24, 33-40, 49-56, 65-72 and 81-88 populate the array, out of which only 17-24, 49-56 and 81-88 turn on the LEDs.

The sensor board frequency data is definitely set up as an array of 32 16-bit (uint16_t) integers, per:

Make sure `SB10Frame.freq` is allocated as uint16_t freq[32], and the 16-based `arrayIndex` and `bitIndex` ought to generate the right locations for the switches. There’s another possible (easily fixable) issue with byte order, but even in that case, all the segments should light up. They just might not light in the right order.

I had it as byte freq[64], copied from a post here I believe, but even turning it to uint16t still only LEDs on the upper bits turn on(9-16, 25-32 etc, with freq0-5 getting populated. The lower bit sensors change the values of freq, they just don’t turn any LEDs on.

``````// Define the data structure to be sent over serial
struct {
uint16_t freq[32];
uint16_t energyAverage;
uint16_t maxFrequencyMagnitude;
uint16_t maxFrequency;  //in hz
int16_t accelerometer[3];
uint16_t light;
char end[4] = "END";                    // End of frame marker
} __attribute__((__packed__)) SB10Frame;  // Attribute to ensure proper packing of the struct in memory
``````

I’ve got some ESP/Arduino code around for faking a sensor board. I’ll hook it up with some test data “sensors” in the morning and see what I can come up with!

Thank you so much for going through all this trouble:)

I’m glad to help – really liked your last project, and looking forward to seeing this one up and running!

Try this version of the pattern and let me know what it does. The critical changes are on lines 46/47. This one works - lights up all the segments – with my test arduino program, which sequentially triggers all the sensors. (And if it still doesn’t work with your sender, let me know, and I can send you my code to compare.)

``````export var frequencyData = array(32)
var t1

// Set this to the actual number of sensors you're using
var NUM_SENSORS = 64

// Set this to the number of pixels you want each sensor to light
var PIXELS_PER_SENSOR = 4

// Set this to 1 to use synthetic data that lights each sensor in turn
// Set to 0 for "production mode", using real sensor input
var TEST_MODE = 0

// Set the bit for a specified sensor to either off (v == 0),
// or on (v > 0)
function setSensor(sensor,v) {
element = floor(sensor / 16)
bit = sensor % 16
mask = ((1 >> 8) << bit)
if (v > 0) {
}
else {
}
}

var lastT2 = -1;
export function beforeRender(delta) {
t1 = time(.1)
t2 = floor(time(0.2) * NUM_SENSORS)

if (TEST_MODE && lastT2 != t2) {
setSensor(t2,1)
lastT2 = t2
}

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

if (TEST_MODE) setSensor(t2,0)

}

export function render(index) {
var sensor = floor(index / PIXELS_PER_SENSOR)

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

Ha!!!

You have no idea how happy I am after 1+ month of trying to figure this out:))

I added a second set of sensors on bits 7-12. How can I have different hue on each set?

Here’s where I am at the moment, I expected that the ` if (index == fades[sensor])` approach would work, tried a few other ways that failed as well…

``````export var frequencyData = array(32)
var t1

// Set this to the actual number of sensors you're using
var NUM_SENSORS = 96

// Set this to the number of pixels you want each sensor to light
var PIXELS_PER_SENSOR = 12

// Set the bit for a specified sensor to either off (v == 0),
// or on (v > 0)
function setSensor(sensor,v) {
element = floor(sensor / 16)
bit = (sensor % 16)
mask = ((1 >> 8) << bit)
if (v > 0) {
}
else {
}
}

function setSensor2(sensor2,v) {
element2 = floor(sensor2 / 16) + 6
bit2 = (sensor2 % 16)
mask2 = ((1 >> 8) << bit)
if (v > 0) {
}
else {
}
}

export function beforeRender(delta) {
t1 = time(.1)

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

for (var sensor2 = 0; sensor2 < NUM_SENSORS; sensor2++) {
element2 = floor(sensor2 / 16) + 6
bit2 = sensor2 % 16
intValue = frequencyData[element2] << 16
if (intValue & (1<< bit2)) {
b = element2
fades2[sensor2] = 3; // 1 for a 1 second fade, plus 2 to hold it solid for 2s)
}
}

}

export function render(index) {
var sensor = floor(index / PIXELS_PER_SENSOR)

var sensor2 = floor(index / PIXELS_PER_SENSOR)

v = v1 + v2

h = 1
} else {
h = t1 + index / pixelCount
}

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

This will eventually be an interactive game for my kids, one of a couple of things I’m building at the moment(including a variation of the last project, again for my kids) that hopefully won’t take a year to finish:)

Here’s one way to color the first set of sensors red and the second blue:

(This includes a function that will let you set a hue for individual sensors or groups of any size and change them at any time, which could eventually be fun for gameplay.)

Click for Pattern Code
``````export var frequencyData = array(32)

// Set this to the actual number of sensors you're using
var NUM_SENSORS = 96 * 2

// Set this to the number of pixels you want each sensor to light
var PIXELS_PER_SENSOR = 12

var hues  = array(NUM_SENSORS)

// Use setSensorHue() to set a color for groups of sensors.  You can
// have as many colors as necessary, up to one per sensor, and change
// them at any time.
//

// set first 96 sensors to red
setSensorHue(0, 0, 96)

// set second 96 sensors to blue
setSensorHue(0.6667, 96, 96)

// sets the hue for a range of sensors of length 'count', starting at index 'start'
function setSensorHue(h,start,count) {
for (i = 0; i < count; i++) {
hues[start + i]  = h
}
}

export function beforeRender(delta) {

for (var sensor = 0; sensor < NUM_SENSORS; sensor++) {
element = floor(sensor / 16) //+ 6
bit = sensor % 16
intValue = frequencyData[element] << 16
if (intValue & (1<< bit)) {
b = element
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 / PIXELS_PER_SENSOR)

if (sensor >= 96) {
// do something with pixels not attached to a sensor
// in this case just leave them off
return;
}

// get data for the second set of sensors
var sensor2 = sensor + 96

// this will set h to the color of the most recently activated sensor
h = max (hues[sensor] * (v > 0), hues[sensor2] * (v2 > 0))
s = 1
hsv(h, s, v + v2)
}
``````

Fantastic once again:)

What happens when I want to have a more complex animation happening in the individual sets?

If for example I wanted to do a simple rainbow on set 1, I cannot just do that:

setSensorHue(t1 + index / pixelCount, 0, 96) as index can only be used within render.

My plan was to adapt existing patterns to the sets, that’s why I went for the if statement within render, so that I could adapt anything and just add a v = v1+ v2 or something similar.

Here’s a variation to take a look at: Instead of setting just a color for active and inactive sensors, this lets you set a pattern for each state (on/off) of each sensor.

You can set different active/inactive patterns for single sensors, or for groups of sensors. And there’s really not much limiting the number of patterns you can have going at the same time.

Here, I’ve got it set up to:

• run a pulsing blue background across the entire object where no sensor is active
• show a rainbow pattern over the sensor’s associated pixels for sensors 0-96 when active
• color the sensor’s pixel’s bright red for the second bank of sensors

But you could have as many patterns doing different things as there are sensors with this setup. Hopefully, this will give you some ideas to play with!

More Pattern Code!
``````export var frequencyData = array(32)

// Set this to the actual number of sensors you're using
var NUM_SENSORS = 96 * 2

// Set this to the number of pixels you want each sensor to light
var PIXELS_PER_SENSOR = 2

var activePattern  = array(NUM_SENSORS)
var inactivePattern  = array(NUM_SENSORS)

// variables to hold final hue, saturation and brightness
// these should be set by individual pattern functions
var hue,sat,bri

// sets a pattern function for a range of activated sensors of length count,
// starting at index start
function setActivePattern(pFn,start,count) {
for (i = 0; i < count; i++) {
activePattern[start + i] = pFn
}
}

// sets a pattern function for a range of activated sensors of length count,
// starting at index start
function setInactivePattern(pFn,start,count) {
for (i = 0; i < count; i++) {
inactivePattern[start + i] = pFn
}
}

//////////////////////////////////////////////////////////////
// a few pattern functions as examples.  You can do anything
// you want in these, as long as you eventually set the
// hue, sat and bri values.

// Displays a rainbow pattern over the pixels associated with
// a single sensor
function rainbowPatternByGroup(index,x,y,z) {
hue = t1 + (index % PIXELS_PER_SENSOR) / (PIXELS_PER_SENSOR - 1)
sat = 1
bri = 1
}

// RED
function solidRed(index,x,y,z) {
hue = 0;
sat = 1;
bri = 1;
}

// BLUE
function pulsingBlue(index,x,y,z) {
hue = 0.6667;
sat = 1;
bri = 1 - (0.925 * wave(t1));
}

// Rainbow pattern over all pixels
function rainbowAllPixels(index,x,y,z) {
hue = t1 + index/pixelCount
sat = 0.8
bri = 0.2
}

// set patterns for active and inactive sensors
// - you can change these in beforeRender() at any point
// - you can run as many of these as there are sensors
// - you can have as many in your pattern as you want.
setInactivePattern(pulsingBlue,0,NUM_SENSORS)

setActivePattern(rainbowPatternByGroup,0,96)
setActivePattern(solidRed,96,96)

// timer value, set in beforeRender() and used by several pattern functions
var t1

export function beforeRender(delta) {
t1 = time(0.04)

for (var sensor = 0; sensor < NUM_SENSORS; sensor++) {
element = floor(sensor / 16) //+ 6
bit = sensor % 16
intValue = frequencyData[element] << 16
if (intValue & (1<< bit)) {
b = element
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 / PIXELS_PER_SENSOR)

if (sensor >= 96) {
// do something with pixels not attached to a sensor
// in this case just leave them off
return;
}

// get data for the second set of sensors
var sensor2 = sensor + 96

// get current brightness for each sensor bank

// see if a sensor in either bank has been activated
if ((v + v2) > 0) {

// get current activation state for each sensor bank
// (this will be 1 if on, 0 if off)
on = v > 0
on2 = v2 > 0

// pick the color of the most recently activated sensor
if (v >= v2) {
activePattern[sensor](index)
}
else {
activePattern[sensor2](index)
}
bri = v + v2
}

// if no sensor has been activated, use the inactive pattern
else {
inactivePattern[sensor](index)
}

hsv(hue,sat,bri)
}
``````

This is absolutely fantastic and quite explanatory, thank you for all the comments, it makes it easier for me to understand:)

Playing with the inactive LEDs opens new possibilities for me, hadn’t even thought of it:))

Last(I promise) question:

When a sensor becomes active, what’s the way to turn on its LEDs progressively from the 2 ends(for 12 LEDs per sensor, 1-12, 2-11, 3-10 etc to 6)?

To get fade-from-ends-to-center working, you’ll need a couple of things:

First, a pattern function like this to actually do the fade. (This one just fades from red – you could easily do something fancier with hue, as long as you keep the brightness logic.)

``````// Light an active region in red, then fade it from the edges
// to the center at the same speed the whole segment is fading.

// calculate position in the sensor's group of pixels
var pos = (index % PIXELS_PER_SENSOR) / (PIXELS_PER_SENSOR - 1)
sensor = floor(index / PIXELS_PER_SENSOR)

hue = 0
sat = 1

// fade from edges to center at the same speed the whole
bri = 1 - abs(pos - 0.5) - (1 - fades[sensor] / 3)
}
``````

and then a change to the blending logic for active patterns. At around line 129 in the previous pattern’s code, we need to change the line
`bri = v + v2`
to
`bri *= v + v2`
so that the final brightness takes both the overall fade state and any brightness change we compute inside a pattern function into account.

Once you’ve made the code changes, just set some sensors to use `redFadeFromEdges` as their active pattern, and you should see the fade.

Sorry, sometimes my descriptions are less than desired:) I meant to fade in in that way(from the edges) in a controllable speed, then fade out using fades[sensor].

Oh, to do that, just use the function below, `redGrowFromCenter()`, instead of the previous `redFadeFromEdges()`!

``````// On activation, "grow" the lighted region of the segment outward
// from the center.
function redGrowFromCenter(index,x,y,z) {

// calculate position in the sensor's group of pixels
var pos = (index % PIXELS_PER_SENSOR) / (PIXELS_PER_SENSOR - 1)
sensor = floor(index / PIXELS_PER_SENSOR)

hue = 0
sat = 1

// on activation, grow from center at speed set by growSpeed
// higher values are faster, lower are slower.
// (you could put this on a slider or make it global.  It's
// just here to keep the code short)
var growSpeed = 2
bri = 1-abs(pos - 0.5) - growSpeed * (1 - (3.5 - fades[sensor]))
}
``````

This is pure gold for me, can’t thank you enough:)

Here’s what I’m getting:

Growspeed > 0.96, LEDs are off, never turn on. < 0.96 down to 0, gradually get brighter until all on(not animated, each value will just set the brightness and will remain there).

I’m trying super hard to understand the math behind it-not getting it but trying. Why is it 3.5 - fades[sensor]? What is the 3.5?

You named it growFromCenter, is that literal? I meant from the sides to the center:)

First, be sure you’ve made the other necessary change to the code in the render() function. It’ll be way down towards the bottom. This is essential. The line with:
`bri = v + v2`
must be changed to:
`bri *= v1 + v2`
or none of this will work right.

The 3.5 is because we’re using the sensor’s fades[sensorNo] value, which, once the sensor is triggered, will remain greater than zero for 3 seconds. (It’d be clearer if we changed all the 3 second values in the code to a constant named “MASTER_FADEOUT_TIME” or something like that.)

The expression `3 - fades[x]` value gives us a sawtooth wave that goes upward as the fade progresses. Since the value starts super small and grows very slowly, I added 0.5 to bias it to start lighting LEDs earlier in the activation cycle.

If you’ve got everything working right a growSpeed value of 1 to 2 really ought to provide a reasonable starting point for fine-tuning. Values much less than 1 will be a little weird, because eventually, they’ll cause the fade-in to take longer than the overall activation cycle.

The version I gave you is set up to grow from the center outward. I guess I misunderstood. But you can “reverse the polarity” by changing its brightness calculation:
` bri = 1-abs(pos - 0.5) - growSpeed * (1 - (3.5 - fades[sensor]))`
to
`bri = abs(pos - 0.5) - growSpeed * (1 - (3.5 - fades[sensor]))`

(just remove the `1-` at the start!)