some of you maybe know about the software WLED you can run on ESP32s.
they have a feature i find very useful. it is “Maximum current”:with it i can have a 5m strip but only power it with a 2A power supply and configure 2A max current.
This way i never need to worry about having a too weak power supply for my projects.
I think there was a community discussion about this once before, and I think the main issue preventing this from being easy on Pixelblaze is an internal architecture decision.
Pixelblaze doesn’t buffer it’s computation internally. That means that as soon as the first pixel in a frame is computed, it can be sent out to the strip. This pipelining has some speed and architectural advantages. Unfortunately it also prevents you from knowing how to scale the first pixel’s intensity to satisfy power consumption constraints, because you need to know the planned intensity of all pixels in the frame before selecting a scale factor for them all.
It might be also feasible to always scale the next frame based on the prior frame’s values and a short term moving average. I wonder if a larger capacitor across the rails would help for this situation too.
A second and more minor challenge was that different models of pixels have slightly different maximum current draws. But that’s something we could allow people to measure themselves, or apply a conservative estimate, like 60 mA for 5 V pixels.
Thanks for the suggestion and telling us what you find useful in other controllers!
In my patterns that use a framebuffer (i.e. an array of pixel values that I populate, and stream out in the render() function) and are on an amperage-limited power supply, if the total T of all values is above some maximum M, I multiply all of the values by M/T.
I don’t calculate the amps or anything, and I don’t scale it differently for R/G/B. I just lower M until the power supply stops popping its breaker.
i think i understand what you mean, but i have no idea about how to do this.
could you share a example?
i am not good at programming (yet) but i am working on it
This isn’t a simple example, but … well … you just have to pay attention to how the fb_ arrays are treated and the code commented with “Calculate the sum of all pixels in the framebuffer … and scale them all down if necessary!”
There are language features that would make a lot of this more concise … you are fortunate that I haven’t used them so you’ll have a better chance of understanding the code.
Code
// This pattern is designed for a "sword" with an LED strip folded in half
// and demonstrates:
// - non-render-ending HSVtoRGB function
// - RGB framebuffer accumulation of HSVtoRGB results
// - Framebuffer attenuation to avoid pulling too many amps from the battery
export var accelerometer
export var x = 0
export var y = 0
export var z = 0
export var total
// configuration
var max_total = 12 // maximum total value to prevent overload
var streak_count = 20 // streak count
// tuning
var scale_acceleration = 50.0 // acceleration scale to ~ [-1,1]
var scale_speed = 12.0 // speed scale to ~10x
var scale_brightness = 8.0 // scale rgb by 1/scale_brightness before summing
// streak state storage
var s_age = array(streak_count)
var s_hue = array(streak_count)
var s_spd = array(streak_count)
var s_len = array(streak_count)
var hPc = pixelCount/2 // half pixelCount
// output framebuffer
var fb_Red = array(hPc)
var fb_Green = array(hPc)
var fb_Blue = array(hPc)
// other stuff I was hacking on
// export var angle = 0
// export var max_spd = 0
var r, g, b // filled by HSVtoRGB
function HSVtoRGB(h, s, v) {
var i, f, p, q, t;
i = floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
im6 = i % 6
if (im6 == 0) {
r = v; g = t; b = p;
} else { if (im6 == 1) {
r = q; g = v; b = p;
} else { if (im6 == 2) {
r = p; g = v; b = t;
} else { if (im6 == 3) {
r = p; g = q; b = v;
} else { if (im6 == 4) {
r = t; g = p; b = v;
} else {if (im6 == 5) {
r = v; g = p; b = q;
}}}}}}
}
export function beforeRender(delta) {
// current acceleration, with a 1-sample smoothing window
var nx = clamp((x + (accelerometer[2]*scale_acceleration))/2.0,-1,1)
var ny = clamp((y + (accelerometer[0]*scale_acceleration))/2.0,-1,1)
var nz = clamp((z + (accelerometer[1]*scale_acceleration))/2.0,-1,1)
// angle = acos(((x*nx)+(y*ny)+(z*nz))/(sqrt(x*x+y*y+z*z)*sqrt(nx*nx+ny*ny+nz*nz)))
// clear framebuffer
for (n = 0; n < hPc; n++) {
fb_Red[n] = 0
fb_Green[n] = 0
fb_Blue[n] = 0
}
// create a new streak if change in acceleration is low
if (abs(sqrt(x*x+y*y+z*z) - sqrt(nx*nx+ny*ny+nz*nz)) < 0.25) {
s_new = 1
}
// render streaks
for (n = 0; n < streak_count; n++) {
// if we are creating a new streak, and this slot is empty, fill it
if (s_new && s_age[n] == 0) {
s_age[n] = 1/1024
s_hue[n] = random(1) // or angle/PI // or (abs(nx-ny))
s_spd[n] = 1 + scale_speed * sqrt(abs(nz * nx * ny) * (1 + random(2)))
s_len[n] = max(12 - s_spd[n] + random(3), 3)
// max_spd = max(max_spd, s_spd[n])
s_new = 0
}
if (s_age[n] > 0) {
len = s_len[n]
pix = s_spd[n] / scale_speed * s_age[n] / 6
if ((pix-len) >= hPc) {
s_age[n] = 0
if (s_new) { n-- } // render this streak
} else {
s_age[n] += delta
hue = s_hue[n]
remaining = len
for (pn = ceil(pix); remaining > 0 && pn >= 0; remaining--) {
if (pn < hPc) {
fade = remaining/len
HSVtoRGB(hue, sqrt(clamp(1.5 - fade,0,1)), pow(clamp((1-pn/hPc) * fade * fade,0,1),1.5) )
fb_Red[pn] += r/scale_brightness
fb_Green[pn] += g/scale_brightness
fb_Blue[pn] += b/scale_brightness
}
pn--
}
}
}
}
// Calculate the sum of all pixels in the framebuffer ...
total = 0
for (n = 0; n < hPc; n++) {
fb_Red[n] = min(1,fb_Red[n])
fb_Green[n] = min(1,fb_Green[n])
fb_Blue[n] = min(1,fb_Blue[n])
total += fb_Red[n] + fb_Green[n] + fb_Blue[n]
}
// ... and scale them all down if necessary!
if (total > max_total) {
adjust = total / max_total
for (n = 0; n < hPc; n++) {
fb_Red[n] *= adjust
fb_Green[n] *= adjust
fb_Blue[n] *= adjust
}
}
// Store current values for smoothing on next frame
x = nx
y = ny
z = nz
}
export function render(index) {
// The PB is at one end of the strip, and the strip is folded in half
// Use "^hPc - [...]" instead of "[...] - 1$" if PB is at the other end
pindex = ( (index < hPc) ? (hPc - index) : (index - hPc + 1) ) - 1
rgb(fb_Red[pindex], fb_Green[pindex], fb_Blue[pindex])
}
Sorceror’s example is great example because it does it correctly with R, G, and B components. To try to simplify it even more and show the essential idea, I wrote this slightly more minimal example.
/*
Limiting current using a framebuffer.
Imagine the original pattern was:
export function render(index) {
pct = index / pixelCount
v = perlinTurbulence(pct * 8, time(.1), 0, 4, 1, 2)
hsv(0, 0, v) // Literal white noise
}
Instead, we'll calculate it all in beforeRender(), sum it up, and scale output
such that very intense output above 20% of all-white is scaled down.
This example is simplified to ignore how non-white RGB colors
draw different amounts.
*/
values = array(pixelCount)
export var currentLimitingFactor = 1 // Value to scale final output. Should be in 0..1.
var intensityLimit = .2 * pixelCount // 20% of max current limit
export function beforeRender(delta) {
frameIntensitySum = 0
for (i = 0; i < pixelCount; i++) {
pct = i / pixelCount
var thisValue = perlinTurbulence(pct * 8, time(.1), 0, 4, 1, 2)
thisValue = clamp(thisValue, 0, 1) // limit outliers to their real current draw
values[i] = thisValue
frameIntensitySum = frameIntensitySum + thisValue
}
currentLimitingFactor = 1
if (frameIntensitySum > intensityLimit) { // Over intensityLimit currrent draw
currentLimitingFactor = intensityLimit / frameIntensitySum
}
}
export function render(index) {
v = values[index]
hsv(0, 0, v * currentLimitingFactor)
}
What I do is set the Global Brightness to a low value, sometimes as low as 15 or 20% otherwise will trip the thermal breaker i use to limit overdriving the Power source. for example, i use 2048 led matrix that will suck way more than 100A and burn into a puff of smoke if you would let it. (Manufacturer says limit a panel of 256 to 7A, but the LEDs will consume a lot more) it’s powered by a 12A supply. with a 7A thermal limiter (circuit breaker) and an old school meter (for power consumption observation) and find running through most patterns works great at about 15-20%. If i set it higher, eventually i will find its tripped the breaker and need to reset it. i wrote a power test pattern and found the consumption is not quite linear and be careful if you set it to run all of the lights and the pattern locks up, it can be hard to reset, and need to get the board to crash and crash a few times to recover.