Pattern limitation causing flicker issue

I am having a flicking issue with my PixelBlaze 2. I created a Christmas Tree pattern with has been running fine until I added more logic to the pattern.

When I tried to make the pattern bigger (adding a slow-fall effect), then it starts to flicker.
I slowed the data-rate to 488/976nS which helped. But if I go any slower, it won’t work at all.

I think I’ve hit the limit for the number of “if” conditionals I have in the pattern.
For example, there’s a lot of these type of lines:
if ((index == 437 + shift) && t1 < 0.2)
{
v = sin(PI* t1 * 5); s = 0
}

The pattern file is ~260 lines, all similar to above example.

Here’s some links to show when its working (no snow-fall effect):
https://youtu.be/CN6zBwwpp7c

And here’s when I added the snow-fall effect and it broke (flickers):
https://youtu.be/FOBgJa6cmEA

I’m running firmware 2.28
LEDS: NeoPixel 2812
number of LEDs: 480
FPS: 12.9

I suppose jumping to v3 hardware would probably fix this(?)
Any other suggestions?

So yes, a v3 would help, since it has more CPU…

Without seeing the code, can’t tell if optimizing would help, sounds like it though.

Reducing math is likely the best direction

For example t1 (assuming that’s a timer) doesn’t change during the entire run of a single frame (ie each render() ) so doing the sin(PI* t1 * 5) code repeatedly for a value is redundant, and it should be done in beforeRender, and assigned to a variable, which render() uses instead of recalcing it umpteen times. In other words, figure out what math you can move to beforeRender and do just once.

That’s a first step.

1 Like

While it could definitely be something timing or code related, I’m also wondering if it has anything to do with increased current draw from the additional white added to the pattern. Does the flickering change frequency or intensity if you lower the global brightness slider?

If you post the entire code we can take a look for additional opportunities for compute efficiency like Scruffy mentioned, or (less likely) a mistake that could be causing flickering, like overflow, unexpected behaviors of negative numbers, accidental variable scope, etc.

2 Likes

Below is the code.
This style coding worked from my jack’o lantern just fine, but the Christmas tree has a lot more conditionals.

I haven’t tried any of the efficiency improvements noted by Scruffynerf, nor tried anything with beforeRender. I’ll have to look for some examples.

FYI, the LED strands are wrapped around the sphere from bottom to top, so each “line” on the sphere has a different number of LEDs.
I have a “shift” variable that’s really just static. Its a carry-over from the jack’o lantern which would have a rotation effect of 0 to 4 pixels. I don’t think I want the Christmas tree to rotate so I could just adjust the conditionals and get rid of the “shift” variable.
And I know there’s some nested conditions which I could combine with && instead.
Once I added the conditionals for an additional snowflake (420 to 440 pixel range), it got bogged down and would flicker.

I was also wondering if the sin function requires more cycle time than an alternate function that would do something similar for the fade in/out effect.

Anyway, here it is. Hopefully you won’t groan too much (ha ha). Recommendations welcomed.


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

export function render(index) {
  h = 0.95 
  s = 1
  v = 0.0 
  
  face_v = 0.6
  shift = 4
  
  if (index >= (352 + shift) && index <= (354 + shift)) // line 2
    {
      s = 0.8
      v = face_v
      h = 0.35// + (t1/10)
    }

  if (index >= (312 + shift) && index <= (315 + shift)) // line 3
    {
      s = 0.8
      v = face_v
      h = 0.35// + (t1/10)
    }

  if (index >= (267 + shift) && index <= (272 + shift)) // line 4
    {
      s = 0.8
      v = face_v
      h = 0.35// + (t1/10)
    }
  if (index >= (222 + shift) &&  index <= (228 + shift)) // line 5
    {
      s = 0.8
      v = face_v
      h = 0.35// + (t1/10)
    }

  if (index >= (174 + shift) &&  index <= (182 + shift)) // line 6
    {
      s = 0.8
      v = face_v
      h = 0.35// + (t1/10)
    }

  if (index >= (126 + shift) &&  index <= (136 + shift)) // line 7
    {
      s = 0.8
      v = face_v
      h = 0.35// + (t1/10)
    }

  if (index >= (84 + shift) &&  index <= (86 + shift)) // line 8
    {
      s = 0.8
      v = face_v
      h = 0.85// + (t1/10)
    }
  if (index  >= (42 + shift) && (index <= 43 + shift )) // line 9
    {
      s = 0.8
      v = face_v
      h = 0.85// + (t1/10)
    }
  if (index >= (3 + shift) &&  index <= (5 + shift)) // line 10
    {
      s = 0.8
      v = face_v
      h = 0.85// + (t1/10)
    }

// star
 if (index == (388 + shift))
 {
      s = 0
      v = face_v * 1.5
      h = 0.0
 }

// lights
face_v = 0.8
 if (index == (313 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.25
 }

 else if (index == (267 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.5
 }

 else if (index == (271 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1)
 }

 else if (index == (223 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.3
 }

 else if (index == (225 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.7
 }

 else if (index == (228 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.2
 }

 else if (index == (175 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0
 }

 else if (index == (177 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.45
 }

 else if (index == (179 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.05
 }

 else if (index == (181 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.75
 }

 else if (index == (126 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.5
 }

 else if (index == (129 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.75
 }

 else if (index == (131 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.25
 }

 else if (index == (133 + shift))
 {
      s = 1
      v = face_v
      h = index / pixelCount + time(0.1) + 0.5
 }

 else if (index == (136 + shift))
 {
      s = 1
      v = face_v
      h = index/pixelCount + time(0.1)
 }

// snow
if (index >= 0 && index <= 37)  // ground cover
{
      s = 0
      v = face_v / 2
}

// snow flakes
// position based
if (index == 437 + shift  || index == 440 + shift)
{
  if(t1 < 0.2)
     v = sin(PI* t1 * 5); s = 0
}
else if (index == 415 + shift || index == 420 + shift)
{
  if(t1 >= 0.1 && t1 < 0.3)
     v = sin(PI* (t1 -0.1) * 5); s = 0
}
else if (index == 386 + shift)
{
  if(t1 >= 0.2 && t1 < 0.4)
     v = sin(PI* (t1 -0.2) * 5); s = 0
}
else if (index == 350 + shift)
{
  if(t1 >= 0.3 && t1 < 0.5)
     v = sin(PI* (t1 -0.3) * 5); s = 0
}
else if (index == 310 + shift)
{
  if(t1 >= 0.4 && t1 < 0.6)
     v = sin(PI* (t1 -0.4) * 5); s = 0
}
else if (index == 266 + shift)
{
  if(t1 >= 0.5 && t1 < 0.7)
     v = sin(PI* (t1 -0.5) * 5); s = 0
}
else if (index == 221 + shift)
{
  if(t1 >= 0.6 && t1 < 0.8)
     v = sin(PI* (t1 -0.6) * 5); s = 0
}
else if (index == 81 + shift)  // reappear from behind tree
{
  if(t1 >= 0.9)
     v = sin(PI* (t1 -0.9) * 5) ; s = 0
  if(t1 < 0.1)
  {
     v = sin((PI* (t1 -0.1) * 5) + (1.0 * PI)); s = 0
  }
} 
else if (index == 39 + shift)  
{
  if(t1 <= 0.2)
     v = sin(PI* (t1) * 5) ; s = 0
} 
 
  hsv(h, s, v)
}

[Edited by Scruffynerf to make this into code in the post]

1 Like

Oh yeah, tons of places to cleanup and optimize in here.
One example is to collapse all of those time() calls, get them out of render and make a single variable in beforeRender and just use it.

Another is to make S and V default to a particular (most common) value and that way you don’t have to set it unless it’s different. Examine if S = 0 does much, also.

Another: calculate (index / pixelCount) once not multiple times.

1 Like

What LED type setting do you use for WS2812? Buffered or unbuffered?

If running unbuffered, when a pattern takes longer to render a pixel than the LED’s latch time, the LED will cut off the data stream at that point and the next pixels will restart from that pixel on. This can scramble what LED is getting what data, causing glitches like in your video where pixels that aren’t even part of the tree get some bits of data intended for tree pixels, and visa versa.

Some LEDs have long latch times, but some are as short as 5 microseconds. Thats not a lot of time to run a complex pattern and render a pixel on a microcontroller CPU.

Lowering the data rate can help to a point by stretching the bits out and keeping the LED from latching the data. At some point the LEDs won’t be able to interpret the data stream properly, and rendering might still take too long to get the next pixel out in time.

Buffered mode alleviates this problem by rendering everything first. Pixel data can then be sent out quickly from the buffer and can use a higher data rate. The only downside is that for some patterns this can lower FPS since it no longer renders pixels in parallel with transmission. Overall it is much more compatible and will work with complex/slow patterns.

2 Likes

You are on the right track with eliminating redundancy like the ‘+ shift’ everywhere. You have a lot of potential for else ifs where the conditionals are exclusive. For example right at the top:

if (index >= (352 + shift) && index <= (354 + shift))
{...}
ELSE if (index >= (312 + shift) && index <= (315 + shift))

Also … the default s value is 1, so you never need to assign 1 to it, right?

Though (as in most patterns) you have hsv(...) at the end of render(), it’s not really being returned. It sets the values immediately. So at any point where you have a final color value, you can just call hsv() and return. This might be a good alternative to if…else if … else if. So again up at the top:

  if (index >= (352 + shift) && index <= (354 + shift)) // line 2
  { hsv(0.8, face_v, 0.25); return; }
  if (index >= (312 + shift) && index <= (315 + shift)) // line 3
  { hsv(0.8, face_v, 0.25); return; }

Uh, those are the same values so as you mentioned it’s also worth replacing the mulitple if()s with
if ( (index >= 11 && index <= 22) || (index >= 33 && index <= 44) || ... ) {...}

Even better … make it more manageable (and waaaaaaay shorter) you could even put the begin/end pairs in an array and iterate over it:

var rows = [ [ 11, 22 ], [ 33, 44 ], [ ... ] ]
rows.foreach((range) => if(index >= range[0] && index <= range[1]) { hsv(0.8, face_v, 0.25); return; })

(correct syntax may not be exactly as written :relaxed:)

Take one step at a time and you’ll be maxing out the framerate in no time!

2 Likes

Previously I was using “WS2812/NeoPixel”. But I just tried “Buffered (x2 rate) WS2812/NeoPixel”.
The buffered works at all data speeds except the slowest 1.3us/2.6us.
I also made some made some improvements in the coding to improve efficiency.
Looks like I can keep adding to the complexity :slight_smile:

1 Like