How to run multiple chase patterns at the same time

Hi,

I created a function to respond to events happening in another software.

For example, when “Event A” happens, I want a red chase animation (a strip of 10-pixel length) to run the full length of the strip (in this case, 864 pixels). When “Event B” happens, I want a blue chase animation to run the length of the strip.

My problem is being able to make the chase animations occur in real time. Two chase animations won’t run at the same time. If Event B happens while a prior chase animation is still running, Event B’s chase animation won’t start until the prior chase animation is done.

How can I make multiple chase animations run in real-time so that each animation doesn’t have to wait for a prior one to finish?

Hopefully this makes sense.

You’d just have to interleave the individual steps of each seperate animation

// pseudo code
int pos[2] = { 100, 0 } // give anim [0] a 100px headstart
uint32_t msRefresh = 50; // one step every 50ms
void loop() {
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return; // non-blocking delay
  msDelay = millis();

  setPixels4Anim(0);  // better in a loop, but for illustration purposes more elaborate
  setPixels4Anim(1);
  pos[0]++;
  pos[1]++;
  showStrip();
}

Hi @ScruffR,

Thanks. I’m still confused on how to implement millis() with a function responding to cloud events.
This is the code I have…

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW


Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);


void setup() {
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}

void loop() {
    
}

int addComet(String command) {

  if (command=="login") {
  // Do not run more than 15 seconds of these, or the b/g tasks
  // will be blocked.
  //--------------------------------------------------------------
    chase(strip.Color(0, 0, 255, 0)); // Blue
    return 0;
  }
  else if (command=="idea") {
    chase(strip.Color(255, 0, 0, 0)); // Green
    return 1;
  }
  else if (command=="comment") {
    chase(strip.Color(0, 255, 0, 0)); // Red
    return 2;
  }
  else if (command=="outcome") {
    chase(strip.Color(0, 0, 0, 255)); // White
    return 3;
  }
  else if (command=="projection") {
    chase(strip.Color(255, 255, 0, 0)); // Yellow
    return 4;
  }
  else {
    return -1;
  }
}


void chase(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels()+9; i++) {
    strip.setPixelColor(i  , c); // Draw new pixel
    strip.setPixelColor(i-9, 0); // Erase pixel a few steps back
    strip.show();
    delay(25);
  };
};

You’d only set some flags in the cloud function and let the animation happen in via loop()

I’ll try giving your code a make-over


Update:
@kkwak, give this a try

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
    uint32_t color;
    int      pos;
    int      len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim  = 100;
int            animHead =   0;
int            animTail =   0;
ANIMATION_DATA anim[maxAnim];

const uint32_t msRefresh = 25;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}

void loop() {
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    for (int i = animTail; i != animHead; ) {
      a = &anim[i];
      if (a->len) {
        if (a->pos < strip.numPixels()) {
          strip.setPixelColor(a->pos++, a->color);
        }
        else
          a->len--;
          
        if (a->pos - a->len >= 0) strip.setPixelColor(a->pos - a->len, 0);
    
        if (!a->len) {
          *a = { 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }
      i++;
      i %= maxAnim;
    }
    strip.show();
  }
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
  // Do not run more than 15 seconds of these, or the b/g tasks
  // will be blocked.
  //--------------------------------------------------------------
    anim[animHead] = { strip.Color(0, 0, 255, 0), 0, 10 }; // blue
    retVal = 0;
  }
  else if (command=="idea") {
    anim[animHead] = { strip.Color(255, 0, 0, 0), 0, 10 }; // green
    retVal = 1;
  }
  else if (command=="comment") {
    anim[animHead] = { strip.Color(0, 255, 0, 0), 0, 10 }; // red
    retVal = 2;
  }
  else if (command=="outcome") {
    anim[animHead] = { strip.Color(0, 0, 0, 255), 0, 10 }; // white
    retVal = 3;
  }
  else if (command=="projection") {
    anim[animHead] = { strip.Color(255, 255, 0, 0), 0, 10 }; // yellow
    retVal = 4;
  }
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}
2 Likes

@ScruffR THIS IS WORKING BEAUTIFULLY!! THANK YOU!

2 Likes

I must admit, inline docu is missing, so if you’ve got any questions about the code, just ask :wink:

Yes, trying to read through it now. I’ll let you know if I have questions. Thanks again!

Hi @ScruffR,

So in another animation, I turned the chase animation to not just be a strip of the same color, but a comet pattern.

How can I maintain this comet pattern in the code you gave me?

void chase(uint32_t c) {
  for(uint16_t i=0; i<strip.numPixels()+9; i=i+2) {
    strip.setPixelColor(i, strip.Color(g, r, b, 200)); // Draw new pixel
    strip.setPixelColor(i-1, strip.Color(g, r, b, 100)); // Draw new pixel
    strip.setPixelColor(i-2, strip.Color(g, r, b, 75)); // Draw new pixel
    strip.setPixelColor(i-3, strip.Color(g, r, b, 50)); // Draw new pixel
    strip.setPixelColor(i-4, strip.Color(g, r, b, 25)); // Draw new pixel
    strip.setPixelColor(i-5, strip.Color(g, r, b, 25)); // Draw new pixel
    strip.setPixelColor(i-6, strip.Color(g, r, b, 10)); // Draw new pixel
    strip.setPixelColor(i-7, strip.Color(g, r, b, 0)); // Draw new pixel
    strip.setPixelColor(i-8, strip.Color(g, r, b, 0)); // Draw new pixel
    strip.setPixelColor(i-9, strip.Color(g, r, b, 0)); // Draw new pixel
    strip.setPixelColor(i-10, 0);// Erase pixel a few steps back
    strip.setPixelColor(i-11, 0);// Erase pixel a few steps back
    strip.show();
    delay(25);
  };
};

You’d just need to replace this part with a for() loop that iterates from a->pos backwards a->len times and set the colours according to the index (and addapt the increment/decrements to fit your desired step width)

        if (a->pos < strip.numPixels()) {
          strip.setPixelColor(a->pos++, a->color);
        }
        else
          a->len--;

        if (a->pos - a->len >= 0) strip.setPixelColor(a->pos - a->len, 0);    

To make things easier with the colour/brightness, I’d stick with the single colour with a zero brightness component and just binary OR the brightness to that value.

Thanks for the help, @ScruffR

I’m new to c++ so just trying to navigate my way.

Here is the newest code I have:

Do you have comments on where to go from here? The tail isn’t being erased.

Also, I would like the chase to be faster. How can I increment the movement of the pixels by 2, instead of just 1? I tried ‘pos+=2’ but that doesn’t work.

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
    uint32_t g;
    uint32_t r;
    uint32_t b;
    int      pos;
    int      len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim  = 100;
int            animHead =   0;
int            animTail =   0;
ANIMATION_DATA anim[maxAnim];

const uint32_t msRefresh = 25;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}

void loop() {
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    for (int i = animTail; i != animHead; ) {
      a = &anim[i];
      if (a->len) {
        if (a->pos < strip.numPixels()) {
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 200);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 100);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 75);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 50);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 25);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 10);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 0);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 0);
          strip.setPixelColor(a->pos++, a->g, a->r, a->b, 0);
        }
        else
          a->len--;
          
        if (a->pos - a->len >= 0) strip.setPixelColor(a->pos - a->len, 0);
    
        if (!a->len) {
          *a = { 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }
      i++;
      i %= maxAnim;
    }
    strip.show();
  }
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
  // Do not run more than 15 seconds of these, or the b/g tasks
  // will be blocked.
  //--------------------------------------------------------------
    anim[animHead] = { 0, 0, 0, 0, 10 }; // white
    retVal = 0;
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, 10 }; // yellow
    retVal = 1;
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, 10 }; // blue
    retVal = 2;
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, 10 }; // green
    retVal = 3;
  }
  else if (command=="projection") {
    anim[animHead] = { 255, 0, 0, 0, 10 }; // green
    retVal = 4;
  }
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}

a->pos += 2 would be one possible way, but you can’t have all these a->pos++ statements as this would push the whole animation forward one step with each instruction which causes the subsequent clear step to clear a pixel that’s at the head of the animation rather than the end.

But to speed up the animation, I’d probably rather shorten the msRefresh time than making the animation more jerky (and running the risk to not clear all pixels) by increasing the step width.

As I said, you should rather use a loop instead of a mere copy/paste instruction :wink:

BTW, the r, g and b components would rather be uint8_t instead of uint32_t

1 Like

hi @ScruffR

What do you mean by “stick with single colour with a zero brightness component and just binary OR the brightness to that value”?

I’m confused on how to use the for loop with a single color while customizing how much ‘white’ is in the rgbw color.

The RGBW colour is stored in a uint32_t where each byte represents one component.
As hexadecimal the combined colour would be written as 0xRRGGBBWW (or any other order depending on the actual encoding - which you’d need to find out by checking some sample colours or looking at the lib code).
So with the above colour you could have a colour of { 255, 11, 33, 00 } as a->color = 0xFF0B2100; and in order to “superimpose” the white value of 200 onto that you could just write
uint32_t newColor = a->color | 200;
And since the original colour stays unaltered you could do a->color | 100 on next iteration.

However, since you already provided some code with the seperate components I went ahead and did this to your code.
Give it a try

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
    uint8_t g;
    uint8_t r;
    uint8_t b;
    int     pos;
    int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim      = 100;
int            animHead     = 0;
int            animTail     = 0;
ANIMATION_DATA anim[maxAnim];

const uint8_t  commetAnim[] = { 200, 100, 75, 50, 25, 10, 0, 0, 0 };
const int      commetLen    = sizeof(commetAnim) / sizeof(commetAnim[0]);
const uint32_t msRefresh    = 15;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}


void loop() {
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    for (int i = animTail; i != animHead; ) {
      a = &anim[i];
      if (a->len >= 0) {
        int px;
        for (int p = a->len-1; p >= 0; p--) {
          px = constrain(a->pos - p, 0, strip.numPixels()-1);  
          strip.setPixelColor(px, a->g, a->r, a->b, commetAnim[p]);
        }

        if (a->pos < strip.numPixels()) 
          a->pos++;
        else
          a->len--;
          
        if (a->pos - a->len >= 0) strip.setPixelColor(a->pos - a->len, 0);
    
        if (a->len <= 0) {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }
      i++;
      i %= maxAnim;
    }
    strip.show();
  }
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
  // Do not run more than 15 seconds of these, or the b/g tasks
  // will be blocked.
  //--------------------------------------------------------------
    anim[animHead] = { 0, 0, 0, 0, commetLen }; // white
    retVal = 0;
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, commetLen }; // yellow
    retVal = 1;
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, commetLen }; // blue
    retVal = 2;
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, commetLen }; // green
    retVal = 3;
  }
  else if (command=="projection") {
    anim[animHead] = { 255, 0, 0, 0, commetLen }; // green
    retVal = 4;
  }
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}
1 Like

Thanks for your help @ScruffR!

The code you gave me isn’t erasing the tail after the length. So the tail goes on for the full length of the strip.

On working on the code, I’ve been able to come up with the code below. It’s actually working. I’m still trying to figure out how to make the ‘comets’ go faster, though.:

/* ======================= includes ================================= */

#include "Particle.h"
#include <neopixel.h>

SYSTEM_MODE(AUTOMATIC);

#define PIXEL_COUNT 864
#define PIXEL_PIN D1
#define PIXEL_TYPE SK6812RGBW

Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

struct ANIMATION_DATA {
    uint8_t g;
    uint8_t r;
    uint8_t b;
    int     pos;
    int     len;
};

// circular buffer with 100 slots will overwrite unfinished animations when full
const int      maxAnim  = 100;
int            animHead =   0;
int            animTail =   0;
ANIMATION_DATA anim[maxAnim];
uint8_t        whites[10] = { 200, 100, 75, 50, 25, 25, 10, 0, 0, 0 };

const uint32_t msRefresh = 25;

void setup() {
  memset(anim, 0, sizeof(anim));
  
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
}

void loop() {
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  if (animHead != animTail) {
    ANIMATION_DATA *a; 
    for (int i = animTail; i != animHead; ) {
      a = &anim[i];
      if (a->len) {
        
        if (a->pos < strip.numPixels()) {
          for(uint16_t j = 0; j < a->len; j++) {
            strip.setPixelColor(a->pos-j, a->g, a->r, a->b, whites[j]);
          }
          a->pos++;
        }
        else
          a->len--;
          
        if (a->pos - a->len >= 0) strip.setPixelColor(a->pos - a->len, 0);

        if (!a->len) {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }
      i++;
      i %= maxAnim;
    }
    strip.show();
  }
}

int addComet(String command) {
  int retVal = -1;
  
  if (command=="login") {
  // Do not run more than 15 seconds of these, or the b/g tasks
  // will be blocked.
  //--------------------------------------------------------------
    anim[animHead] = { 0, 0, 0, 0, 10 }; // white
    retVal = 0;
  }
  else if (command=="idea") {
    anim[animHead] = { 255, 255, 0, 0, 10 }; // yellow
    retVal = 1;
  }
  else if (command=="comment") {
    anim[animHead] = { 0, 0, 255, 0, 10 }; // blue
    retVal = 2;
  }
  else if (command=="outcome") {
    anim[animHead] = { 255, 0, 0, 0, 10 }; // green
    retVal = 3;
  }
  else if (command=="projection") {
    anim[animHead] = { 255, 0, 0, 0, 10 }; // green
    retVal = 4;
  }
  else {
    return -1;
  }

  animHead++;
  animHead %= maxAnim;
  
  return retVal;
}

Like this

I slightly altered my code, could you try this again?

What did you change? The same problem of the tail not being erased after specified length is occurring.

I know I can decrease msRefresh variable. But I want it to go even faster than 1 millisecond.

This is what I changed.

One other thing to try would be to swap this round

        if (a->pos - a->len >= 0) strip.setPixelColor(a->pos - a->len, 0);

        if (a->pos < strip.numPixels()) 
          a->pos++;
        else
          a->len--;

About timing:
You could set msRefresh = 0; but beyond that you’d have to go for wider steps, which may make things messier.
Do you really want the animation to cover the whole strip in considerably less than 0.9sec?

Yes, that works:

I also added this to your code to erase the full tail at the end of the entire 864-pixel-long strip:

if (a->pos - a->len >= 0) strip.setPixelColor(a->pos - a->len-1, 0);  // added -1

Update: if I swap those lines like you said in your above post, then I don’t need to add the ‘-1’ like I just said in this post.

About timing:

When I set msRefresh to 1, it takes about 30 seconds for the chase animation to run through all 864 pixels. I want it to go faster than that.

Thanks @ScruffR for all your help! I figured out how to make it faster. I incremented the pos by a ‘stepSpeed’ variable (in this case 2). And I erase the tail accordingly as well.

if (a->pos - a->len >= 0) {
    for (int k = 0; k < stepSpeed; k++) {
        strip.setPixelColor(a->pos-k - a->len, 0);
    }
}

if (a->pos < strip.numPixels()) 
  a->pos+=stepSpeed;
else
  a->len--;
1 Like

30sec seems excessive - I wouldn’t see what can take that long :flushed:
Unless the neopixel library is a lot slower than I’d think or my loops aren’t dropping out as planned.
I think this calls for some debugging :wink:


Update:
I guess (without actually debugging ) the strip.show() does cause the “unexpected” delay since the rest of the code should be in the sub-millisecond region.
So you could try to keep the logic unaltered but only call strip.show() every stepSpeed turns.

I usually use APA102 strips without any library (overhead) which makes things really fast :wink:

1 Like