When I connect chain of LED strips to power from both ends, do I need to make the power line disconnect in the middle?

Are you suggesting I create my own function to set a pixel color with brightness included? Or, redoing my entire code to not use the library? Honestly, I’m a little afraid to try to redo my code entirely.

Nothing to be afraid of. You are not using that many functions of the NeoPixel library, so replacing them with the respective functions provided in my sketch isn’t anything like starting from scratch.
My setAPA102Color() function replaces both strip.SetColorDimmed() and strip.setPixelColor() (without the White component tho’) and SPI.transfer() replaces strip.show().

I get this error: no matching function for call to 'SPIClass::transfer()'
Do I need to make a declaration of SPI?

#define NUMPIXELS 1728 // Number of LEDs in strip


//-------------------------------------------------------------------
// Here's how to control the LEDs from SPI pins (Hardware SPI):
//-------------------------------------------------------------------
// Hardware SPI is a little faster, but must be wired to specific pins
// (Core/Photon/P1/Electron = pin A5 for data, A3 for clock)

#define DATAPIN   A5
#define CLOCKPIN  A3
uint32_t pxBuffer[NUMPIXELS];

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  brightAnim[] = 
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 245, 240
, 240, 240, 240, 235, 230, 230, 230, 230, 225, 220
, 220, 220, 220, 215, 210, 210, 210, 210, 205, 200
, 200, 200, 200, 200, 195, 195, 190, 190, 190, 190
, 190, 195, 180, 180, 180, 175, 170, 165, 160, 155
, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105
, 100,  95,  90,  85,  80,  75,  70 
};

const int      cometLen = sizeof(brightAnim) / sizeof(brightAnim[0]);
const uint32_t msRefresh = 0;
const int stepSpeed = 10;
static int animationStep = 0;


//Set Color of pixel at position px, including brightness level
uint32_t setAPA102Color(int px, uint8_t g, uint8_t r, uint8_t b, uint8_t brightness = 31) {
  uint32_t color = 0;
  
  // due to big endianness in revers order 
  color |= (brightness <<   0) | 0xE0;  //top 3 bits must be 111
  color |= (r          <<   8);
  color |= (g          <<  16);
  color |= (b          <<  24);

  pxBuffer[px] = color;

  return color;
}






void setup()
{
  memset(anim, 0, sizeof(anim));

  SPI.begin();
  SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer)); // Initialize all pixels to 'off'
  Particle.function("led", addComet);
  //strip.setBrightness(30);
}

void loop()
{
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  static bool sparkleState = false;
  static int  sparklePixel = 0;

  if (animHead != animTail)
  {
    ANIMATION_DATA *a;
    for (int animationStep = animTail; animationStep != animHead; ) 
    {
      a = &anim[animationStep];
      if (a->len)
      {
        int px;

        //Draw the Comet
        for (int p = a->len - 1; p >= 0; p--)
        {
          px = constrain(a->pos - p, 0, NUMPIXELS - 1);
          setAPA102Color(px, a->g, a->r, a->b, brightAnim[p]);
        }

        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0)
          for (int k = 0; k < stepSpeed; k++)
            setAPA102Color(a->pos - k - a->len, 0, 0, 0, 0);

        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < NUMPIXELS + stepSpeed)
          a->pos += stepSpeed;
        else
          a->len -= stepSpeed;

        if (a->len <= 0)
        {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }

      animationStep++;
      animationStep %= maxAnim;
    }
  }

  sparkleState = !sparkleState;  // flip state
  if (sparkleState)
  {
    sparklePixel = random(NUMPIXELS);
    setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
  }
  else
    setAPA102Color(sparklePixel, 0, 0, 0, 0);

  SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer));
}

struct topic {
  char    title[16];
  uint8_t rgb[3];
};

const topic topicList[] = 
{ { "login"  , { 200, 200, 200 } }  // white
, { "idea"   , { 255, 255,   0 } }  // yellow
, { "comment", {   0,   0, 255 } }  // blue
, { "outcome", { 255,   0,   0 } }  // green
, { "project", { 255,   0,   0 } }  // green
, { "status" , { 100, 255,   0 } }  // orange
, { "step"   , {   0,  75, 255 } }  // purple
, { "vote"   , {   0, 255,   0 } }  // red
, { "view"   , { 105, 255, 200 } }  // pink
};

const int   topics = sizeof(topicList)/sizeof(topicList[0]);


//Add a comet to the strip whenever initated by cloud event
int addComet(String command)
{
  int retVal = -1;

  for (int i = 0; i < topics; i++)
  {
    if (strcmp((const char*)command, topicList[i].title) == 0)
    {
      anim[animHead] = { topicList[i].rgb[0], topicList[i].rgb[1], topicList[i].rgb[2], 0, cometLen };
      Particle.publish(command);
      animHead++;
      animHead %= maxAnim;
      retVal = i;
      break;
    }
  }
  return retVal;
}

If you press the SHOW RAW button in Web IDE you’d get some extra notes why this sort of function is not found

Look what I wrote above

  SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), refreshDone);

If you want to call it synchronous you’d write

  SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), NULL);

Does the ‘pxbuffer’ and SPI.transfer() replace what memset() does?

No.
Take memset(anim, 0, sizeof(anim))
memset() fills the buffer (e.g. anim) with a particular byte value (e.g. 0) a number of times (e.g. sizeof(anim)).
While SPI.transfer() - as the name suggests - transfers the current content of the buffer - with the length sizeof(buffer) - via SPI to the outside world.

This is the reason why I earlier already posted the link to the docs for SPI.transfer()
https://docs.particle.io/reference/firmware/photon/#transfer-void-void-size_t-std-function-

Ok.

I think I’m confused on ‘needRefresh’ and ‘canRefresh’. What is their purpose? This doesn’t relate to ‘msrefresh’ right?

#define NUMPIXELS 144 // Number of LEDs in strip


//-------------------------------------------------------------------
// Here's how to control the LEDs from SPI pins (Hardware SPI):
//-------------------------------------------------------------------
// Hardware SPI is a little faster, but must be wired to specific pins
// (Core/Photon/P1/Electron = pin A5 for data, A3 for clock)

#define DATAPIN   A5
#define CLOCKPIN  A3
uint32_t pxBuffer[NUMPIXELS];

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  brightAnim[] = 
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 245, 240
, 240, 240, 240, 235, 230, 230, 230, 230, 225, 220
, 220, 220, 220, 215, 210, 210, 210, 210, 205, 200
, 200, 200, 200, 200, 195, 195, 190, 190, 190, 190
, 190, 195, 180, 180, 180, 175, 170, 165, 160, 155
, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105
, 100,  95,  90,  85,  80,  75,  70 
};

const int      cometLen = sizeof(brightAnim) / sizeof(brightAnim[0]);
const uint32_t msRefresh = 0;
const int stepSpeed = 10;
static int animationStep = 0;


//Set Color of pixel at position px, including brightness level
uint32_t setAPA102Color(int px, uint8_t g, uint8_t r, uint8_t b, uint8_t brightness) {
  uint32_t color = 0;
  
  // due to big endianness in revers order 
  color |= (brightness <<   0) | 0xE0;  //top 3 bits must be 111
  color |= (r          <<   8);
  color |= (g          <<  16);
  color |= (b          <<  24);

  pxBuffer[px] = color;

  return color;
}

bool needRefresh;
volatile bool canRefresh = true;

void refreshDone() {
  canRefresh = true;
}


void setup()
{
  memset(anim, 0, sizeof(anim));
  SPI.begin();
  SPI.setClockSpeed(8, MHZ);
  //SPI.setDataMode(0);
  SPI.setBitOrder(MSBFIRST);
  
  Particle.function("led", addComet);
}

void loop()
{
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  static bool sparkleState = false;
  static int  sparklePixel = 0;

  if (animHead != animTail)
  {
    ANIMATION_DATA *a;
    for (int animationStep = animTail; animationStep != animHead; ) 
    {
      a = &anim[animationStep];
      if (a->len)
      {
        int px;

        //Draw the Comet
        for (int p = a->len - 1; p >= 0; p--)
        {
          px = constrain(a->pos - p, 0, NUMPIXELS - 1);
          setAPA102Color(px, a->g, a->r, a->b, brightAnim[p]);
        }

        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0)
          for (int k = 0; k < stepSpeed; k++)
            setAPA102Color(a->pos - k - a->len, 0, 0, 0, 0);

        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < NUMPIXELS + stepSpeed)
          a->pos += stepSpeed;
        else
          a->len -= stepSpeed;

        if (a->len <= 0)
        {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }

      animationStep++;
      animationStep %= maxAnim;
    }
  }

  sparkleState = !sparkleState;  // flip state
  if (sparkleState)
  {
    sparklePixel = random(NUMPIXELS);
    setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
  }
  else
    setAPA102Color(sparklePixel, 0, 0, 0, 0);
  
  if (needRefresh && canRefresh) {
      canRefresh = true;
      needRefresh = false;
      SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), refreshDone);
  }

}

struct topic {
  char    title[16];
  uint8_t rgb[3];
};

const topic topicList[] = 
{ { "login"  , { 200, 200, 200 } }  // white
, { "idea"   , { 255, 255,   0 } }  // yellow
, { "comment", {   0,   0, 255 } }  // blue
, { "outcome", { 255,   0,   0 } }  // green
, { "project", { 255,   0,   0 } }  // green
, { "status" , { 100, 255,   0 } }  // orange
, { "step"   , {   0,  75, 255 } }  // purple
, { "vote"   , {   0, 255,   0 } }  // red
, { "view"   , { 105, 255, 200 } }  // pink
};

const int   topics = sizeof(topicList)/sizeof(topicList[0]);


//Add a comet to the strip whenever initated by cloud event
int addComet(String command)
{
  int retVal = -1;

  for (int i = 0; i < topics; i++)
  {
    if (strcmp((const char*)command, topicList[i].title) == 0)
    {
      anim[animHead] = { topicList[i].rgb[0], topicList[i].rgb[1], topicList[i].rgb[2], 0, cometLen };
      Particle.publish(command);
      animHead++;
      animHead %= maxAnim;
      retVal = i;
      break;
    }
  }
  return retVal;
}

needRefresh is a flag that should be set whenever you changed anything inside the pxBuffer (aka dirty flag or new data to display).
canRefresh is a flag which indicates whether it’s safe to start a new background transfer (==true) or not due to an active transfer being performed in the background (==false).

While msRefresh (which was in your original code already and you should have undestood that then already) is a millisecond (hence ms...) value to provide some means to slow down the animation refresh rate (if needed).

I see.

I replaced the dotstar library functions with SPI and 'setAPA102Color functions.

My microcontroller crashes when I run the code, though.

#define NUMPIXELS 144 // Number of LEDs in strip


//-------------------------------------------------------------------
// Here's how to control the LEDs from SPI pins (Hardware SPI):
//-------------------------------------------------------------------
// Hardware SPI is a little faster, but must be wired to specific pins
// (Core/Photon/P1/Electron = pin A5 for data, A3 for clock)

#define DATAPIN   A5
#define CLOCKPIN  A3
uint32_t pxBuffer[NUMPIXELS];

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  brightAnim[] = 
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 245, 240
, 240, 240, 240, 235, 230, 230, 230, 230, 225, 220
, 220, 220, 220, 215, 210, 210, 210, 210, 205, 200
, 200, 200, 200, 200, 195, 195, 190, 190, 190, 190
, 190, 195, 180, 180, 180, 175, 170, 165, 160, 155
, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105
, 100,  95,  90,  85,  80,  75,  70 
};

const int      cometLen = sizeof(brightAnim) / sizeof(brightAnim[0]);
const uint32_t msRefresh = 0;
const int stepSpeed = 10;
static int animationStep = 0;


//Set Color of pixel at position px, including brightness level
uint32_t setAPA102Color(int px, uint8_t g, uint8_t r, uint8_t b, uint8_t brightness) {
  uint32_t color = 0;
  
  // due to big endianness in revers order 
  color |= (brightness <<   0) | 0xE0;  //top 3 bits must be 111
  color |= (r          <<   8);
  color |= (g          <<  16);
  color |= (b          <<  24);

  pxBuffer[px] = color;

  return color;
}

bool needRefresh;
volatile bool canRefresh = true;

void refreshDone() {
  canRefresh = true;
}


void setup()
{
  memset(anim, 0, sizeof(anim));
  SPI.begin();
  SPI.setClockSpeed(8, MHZ);
  //SPI.setDataMode(0);
  SPI.setBitOrder(MSBFIRST);
  
  Particle.function("led", addComet);
}

void loop()
{
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  static bool sparkleState = false;
  static int  sparklePixel = 0;

  if (animHead != animTail)
  {
    ANIMATION_DATA *a;
    for (int animationStep = animTail; animationStep != animHead; ) 
    {
      a = &anim[animationStep];
      if (a->len)
      {
        int px;

        //Draw the Comet
        for (int p = a->len - 1; p >= 0; p--)
        {
          px = constrain(a->pos - p, 0, NUMPIXELS - 1);
          setAPA102Color(px, a->g, a->r, a->b, brightAnim[p]);
        }

        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0)
          for (int k = 0; k < stepSpeed; k++)
            setAPA102Color(a->pos - k - a->len, 0, 0, 0, 0);

        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < NUMPIXELS + stepSpeed)
          a->pos += stepSpeed;
        else
          a->len -= stepSpeed;

        if (a->len <= 0)
        {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }

      animationStep++;
      animationStep %= maxAnim;
    }
  }

  sparkleState = !sparkleState;  // flip state
  if (sparkleState)
  {
    sparklePixel = random(NUMPIXELS);
    setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
  }
  else
    setAPA102Color(sparklePixel, 0, 0, 0, 0);
  
  if (needRefresh && canRefresh) {
      canRefresh =
      needRefresh = false;
      SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), refreshDone);
  }

}

struct topic {
  char    title[16];
  uint8_t rgb[3];
};

const topic topicList[] = 
{ { "login"  , { 200, 200, 200 } }  // white
, { "idea"   , { 255, 255,   0 } }  // yellow
, { "comment", {   0,   0, 255 } }  // blue
, { "outcome", { 255,   0,   0 } }  // green
, { "project", { 255,   0,   0 } }  // green
, { "status" , { 100, 255,   0 } }  // orange
, { "step"   , {   0,  75, 255 } }  // purple
, { "vote"   , {   0, 255,   0 } }  // red
, { "view"   , { 105, 255, 200 } }  // pink
};

const int   topics = sizeof(topicList)/sizeof(topicList[0]);


//Add a comet to the strip whenever initated by cloud event
int addComet(String command)
{
  int retVal = -1;

  for (int i = 0; i < topics; i++)
  {
    if (strcmp((const char*)command, topicList[i].title) == 0)
    {
      anim[animHead] = { topicList[i].rgb[0], topicList[i].rgb[1], topicList[i].rgb[2], 0, cometLen };
      Particle.publish(command);
      animHead++;
      animHead %= maxAnim;
      retVal = i;
      break;
    }
  }
  return retVal;
}

Hmm, when I flash that code to one of my Photons it doesn’t crash.
How do you make it crash?

What system version are you targeting?

BTW, you should declare your pxBuffer like this

uint32_t pxBuffer[1 + NUMPIXELS + NUMPIXELS/16 + 1]; // element 0 (leadin) ... LEDs ... 1 leadout clock per 2 LEDs (ceil)

the simple solution above does “work” and was only provided for clarity, but to have the strip update absolutely correct, you need to add these extra bytes.


Update:
I added some logging and some safe guard to the function and found the reason for the crash.

This shows that the animation code tries to access LEDs outside the allowed range.

0000013778 [app] ERROR: px out of range 0 <= -1 < 144
0000013778 [app] ERROR: px out of range 0 <= -2 < 144
0000013778 [app] ERROR: px out of range 0 <= -3 < 144
0000013778 [app] ERROR: px out of range 0 <= -4 < 144
0000013779 [app] ERROR: px out of range 0 <= -5 < 144
0000013779 [app] ERROR: px out of range 0 <= -6 < 144
0000013794 [app] ERROR: px out of range 0 <= 153 < 144
0000013794 [app] ERROR: px out of range 0 <= 152 < 144
0000013794 [app] ERROR: px out of range 0 <= 151 < 144
0000013794 [app] ERROR: px out of range 0 <= 150 < 144
0000013794 [app] ERROR: px out of range 0 <= 149 < 144
0000013795 [app] ERROR: px out of range 0 <= 148 < 144
0000013795 [app] ERROR: px out of range 0 <= 147 < 144
0000013795 [app] ERROR: px out of range 0 <= 146 < 144
0000013795 [app] ERROR: px out of range 0 <= 145 < 144
0000013796 [app] ERROR: px out of range 0 <= 144 < 144

But with the altered function that won’t happen anymore

uint32_t setAPA102Color(int px, uint8_t g, uint8_t r, uint8_t b, uint8_t brightness = 255) {
  uint32_t color = 0;
  
  brightness = map(brightness, 0, 255, 0, 31);  // only the bottom 5 bits available resuliting in 32 discrete levels

  // due to big endianness in revers order 
  color |= (brightness <<   0) | 0xE0;  //top 3 bits must be 111
  color |= (r          <<   8);
  color |= (g          <<  16);
  color |= (b          <<  24);

  if (0 <= px && px < NUMPIXELS) {
    return pxBuffer[px] = color;
  }
  
  Log.error("px out of range %d <= %d < %d", 0, px, NUMPIXELS);
  return -1;
}

BTW, the uint8_t brightness = 255 (originally = 31) was there on purpose to make the brightness parameter optional. If you don’t provide it full brightness will be assumed.

1 Like

I’m triggering the cloud event that is supposed to make the comet run:
Particle.function("led", addComet)

However, with the changes, it’s not crashing anymore. However, no animation is showing on my strip. Not the continuous random twinkling lights:

if (sparkleState)
  {
    sparklePixel = random(NUMPIXELS);
    setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
  }
  else
    setAPA102Color(sparklePixel, 0, 0, 0, 0);

Nor the comets when I trigger the cloud event.

My full code:

#define NUMPIXELS 144 // Number of LEDs in strip


//-------------------------------------------------------------------
// Here's how to control the LEDs from SPI pins (Hardware SPI):
//-------------------------------------------------------------------
// Hardware SPI is a little faster, but must be wired to specific pins
// (Core/Photon/P1/Electron = pin A5 for data, A3 for clock)

#define DATAPIN   A5
#define CLOCKPIN  A3
uint32_t pxBuffer[1 + NUMPIXELS + NUMPIXELS/16 + 1]; // element 0 (leadin) ... LEDs ... 1 leadout clock per 2 LEDs (ceil)

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  brightAnim[] = 
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 245, 240
, 240, 240, 240, 235, 230, 230, 230, 230, 225, 220
, 220, 220, 220, 215, 210, 210, 210, 210, 205, 200
, 200, 200, 200, 200, 195, 195, 190, 190, 190, 190
, 190, 195, 180, 180, 180, 175, 170, 165, 160, 155
, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105
, 100,  95,  90,  85,  80,  75,  70 
};

const int      cometLen = sizeof(brightAnim) / sizeof(brightAnim[0]);
const uint32_t msRefresh = 0;
const int stepSpeed = 10;
static int animationStep = 0;


//Set Color of pixel at position px, including brightness level
uint32_t setAPA102Color(int px, uint8_t g, uint8_t r, uint8_t b, uint8_t brightness = 255) {
  uint32_t color = 0;

  brightness = map(brightness, 0, 255, 0, 31);  // only the bottom 5 bits available resuliting in 32 discrete levels
  
  // due to big endianness in revers order 
  color |= (brightness <<   0) | 0xE0;  //top 3 bits must be 111
  color |= (r          <<   8);
  color |= (g          <<  16);
  color |= (b          <<  24);

  if (0 <= px && px < NUMPIXELS) {
    return pxBuffer[px] = color;
  }

  return -1;
}

bool needRefresh;
volatile bool canRefresh = true;

void refreshDone() {
  canRefresh = true;
}


void setup()
{
  memset(anim, 0, sizeof(anim));
  SPI.begin();
  SPI.setClockSpeed(8, MHZ);
  //SPI.setDataMode(0);
  SPI.setBitOrder(MSBFIRST);
  
  Particle.function("led", addComet);
}

void loop()
{
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  static bool sparkleState = false;
  static int  sparklePixel = 0;

  if (animHead != animTail)
  {
    ANIMATION_DATA *a;
    for (int animationStep = animTail; animationStep != animHead; ) 
    {
      a = &anim[animationStep];
      if (a->len)
      {
        int px;

        //Draw the Comet
        for (int p = a->len - 1; p >= 0; p--)
        {
          px = constrain(a->pos - p, 0, NUMPIXELS - 1);
          setAPA102Color(px, a->g, a->r, a->b, brightAnim[p]);
        }

        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0)
          for (int k = 0; k < stepSpeed; k++)
            setAPA102Color(a->pos - k - a->len, 0, 0, 0, 0);

        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < NUMPIXELS + stepSpeed)
          a->pos += stepSpeed;
        else
          a->len -= stepSpeed;

        if (a->len <= 0)
        {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }

      animationStep++;
      animationStep %= maxAnim;
    }
  }

  sparkleState = !sparkleState;  // flip state
  if (sparkleState)
  {
    sparklePixel = random(NUMPIXELS);
    setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
  }
  else
    setAPA102Color(sparklePixel, 0, 0, 0, 0);
  
  if (needRefresh && canRefresh) {
      canRefresh =
      needRefresh = false;
      SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), refreshDone);
  }

}

struct topic {
  char    title[16];
  uint8_t rgb[3];
};

const topic topicList[] = 
{ { "login"  , { 200, 200, 200 } }  // white
, { "idea"   , { 255, 255,   0 } }  // yellow
, { "comment", {   0,   0, 255 } }  // blue
, { "outcome", { 255,   0,   0 } }  // green
, { "project", { 255,   0,   0 } }  // green
, { "status" , { 100, 255,   0 } }  // orange
, { "step"   , {   0,  75, 255 } }  // purple
, { "vote"   , {   0, 255,   0 } }  // red
, { "view"   , { 105, 255, 200 } }  // pink
};

const int   topics = sizeof(topicList)/sizeof(topicList[0]);


//Add a comet to the strip whenever initated by cloud event
int addComet(String command)
{
  int retVal = -1;

  for (int i = 0; i < topics; i++)
  {
    if (strcmp((const char*)command, topicList[i].title) == 0)
    {
      anim[animHead] = { topicList[i].rgb[0], topicList[i].rgb[1], topicList[i].rgb[2], 0, cometLen };
      Particle.publish(command);
      animHead++;
      animHead %= maxAnim;
      retVal = i;
      break;
    }
  }
  return retVal;
}

You never set needRefresh = true, hence the condition to trigger the SPI.transfer() is never satisfied.

As frequently said before, you need to understand the code in order to solve such issues on your own one of these days.

Did you mean to leave this blank?
I know you said canRefresh is a flag to indicate whether an active transfer is being performed in the background...what would this be in my case? Is it ever supposed to be set to false?

That isn’t left blank. In C/C++ a code line ends at a semicolon. So the “unbroken” code line would read canRefresh = needRefresh = false; (read as: "Assign false to needRefresh and assign that to canRefresh").

And when you look in my code, canRefresh is initialised true and will always be set true in refreshDone() whenever a SPI.transfer() finished. So it only ever will be false for a few µs while the SPI.transfer() is ongoing.

Ah, okay, thanks.

My code still sin’t working properly.
Where does Log.error() display errors?

#define NUMPIXELS 144 // Number of LEDs in strip


//-------------------------------------------------------------------
// Here's how to control the LEDs from SPI pins (Hardware SPI):
//-------------------------------------------------------------------
// Hardware SPI is a little faster, but must be wired to specific pins
// (Core/Photon/P1/Electron = pin A5 for data, A3 for clock)

#define DATAPIN   A5
#define CLOCKPIN  A3
uint32_t pxBuffer[1 + NUMPIXELS + NUMPIXELS/16 + 1]; // element 0 (leadin) ... LEDs ... 1 leadout clock per 2 LEDs (ceil)

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  brightAnim[] = 
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 245, 240
, 240, 240, 240, 235, 230, 230, 230, 230, 225, 220
, 220, 220, 220, 215, 210, 210, 210, 210, 205, 200
, 200, 200, 200, 200, 195, 195, 190, 190, 190, 190
, 190, 195, 180, 180, 180, 175, 170, 165, 160, 155
, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105
, 100,  95,  90,  85,  80,  75,  70 
};

const int      cometLen = sizeof(brightAnim) / sizeof(brightAnim[0]);
const uint32_t msRefresh = 0;
const int stepSpeed = 10;
static int animationStep = 0;


//Set Color of pixel at position px, including brightness level
uint32_t setAPA102Color(int px, uint8_t g, uint8_t r, uint8_t b, uint8_t brightness = 255) {
  uint32_t color = 0;

  brightness = map(brightness, 0, 255, 0, 31);  // only the bottom 5 bits available resuliting in 32 discrete levels
  
  // due to big endianness in revers order 
  color |= (brightness <<   0) | 0xE0;  //top 3 bits must be 111
  color |= (r          <<   8);
  color |= (g          <<  16);
  color |= (b          <<  24);

  if (0 <= px && px < NUMPIXELS) {
    return pxBuffer[px] = color;
  }

  Log.error("px out of range %d <= %d < %d", 0, px, NUMPIXELS);
  return -1;
}

bool needRefresh;
volatile bool canRefresh = true;

void refreshDone() {
  canRefresh = true;
}


void setup()
{
  memset(anim, 0, sizeof(anim));
  SPI.begin();
  SPI.setClockSpeed(8, MHZ);
  //SPI.setDataMode(0);
  SPI.setBitOrder(MSBFIRST);
  
  Particle.function("led", addComet);
}

void loop()
{
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  static bool sparkleState = false;
  static int  sparklePixel = 0;

  if (animHead != animTail)
  {
    ANIMATION_DATA *a;
    for (int animationStep = animTail; animationStep != animHead; ) 
    {
      a = &anim[animationStep];
      if (a->len)
      {
        int px;

        //Draw the Comet
        for (int p = a->len - 1; p >= 0; p--)
        {
          px = constrain(a->pos - p, 0, NUMPIXELS - 1);
          setAPA102Color(px, a->g, a->r, a->b, brightAnim[p]);
        }

        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0)
          for (int k = 0; k < stepSpeed; k++)
            setAPA102Color(a->pos - k - a->len, 0, 0, 0, 0);

        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < NUMPIXELS + stepSpeed)
          a->pos += stepSpeed;
        else
          a->len -= stepSpeed;

        if (a->len <= 0)
        {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }

      animationStep++;
      animationStep %= maxAnim;
    }
  }

  sparkleState = !sparkleState;  // flip state
  if (sparkleState)
  {
    sparklePixel = random(NUMPIXELS);
    setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
  }
  else
    setAPA102Color(sparklePixel, 0, 0, 0, 0);
  
  if (needRefresh && canRefresh) {
      canRefresh =
      needRefresh = false;
      SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), refreshDone);
  }

}

struct topic {
  char    title[16];
  uint8_t rgb[3];
};

const topic topicList[] = 
{ { "login"  , { 200, 200, 200 } }  // white
, { "idea"   , { 255, 255,   0 } }  // yellow
, { "comment", {   0,   0, 255 } }  // blue
, { "outcome", { 255,   0,   0 } }  // green
, { "project", { 255,   0,   0 } }  // green
, { "status" , { 100, 255,   0 } }  // orange
, { "step"   , {   0,  75, 255 } }  // purple
, { "vote"   , {   0, 255,   0 } }  // red
, { "view"   , { 105, 255, 200 } }  // pink
};

const int   topics = sizeof(topicList)/sizeof(topicList[0]);


//Add a comet to the strip whenever initated by cloud event
int addComet(String command)
{
  int retVal = -1;

  for (int i = 0; i < topics; i++)
  {
    if (strcmp((const char*)command, topicList[i].title) == 0)
    {
      anim[animHead] = { topicList[i].rgb[0], topicList[i].rgb[1], topicList[i].rgb[2], 0, cometLen };
      Particle.publish(command);
      animHead++;
      animHead %= maxAnim;
      retVal = i;
      break;
    }
  }
  return retVal;
}

When I said that, where have you actually added that?

As I said earlier

Oops I sent you the wrong code.

I put it right before the condition to trigger the SPI.transfer().

  needRefresh = true;
  
  if (needRefresh && canRefresh) {
      canRefresh =
      needRefresh = false;
      SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), refreshDone);
  }

Also, the code is actually working. It seems that it takes a while for the data to be transferred down the entire strip? For example, when I flash the code over. The random pixels start flashing within just the first few LEDs of the strip only. Then, it slowly transfers down the strip so that the flashing pixels eventually randomly hit any pixel on the strip.

Maybe the strip is not being cleared when new code is being flashed. Is there an SPI function that clears all pixel data. Like strip.clear() does?

Also, would you have any idea when APA102 RGBW strips would be made available?

I have noticed the behaviour with the random sparkles too, but that is not due to the time the transfer takes but I’d rather put this down to the pseudo random numbers being produced by random(x).
I don’t know how fast your original animations were but I had the impression that I had to slow them down via msRefresh. But give it a try with NUMPIXELS 1000 - at that number you’ll definetly see the extra speed over doing the same with 1000 NeoPixels.

Also setting needRefresh unconditionally doesn’t seem to be the best choice.

I’d rather put one instance at the end of the if (animHead != animTail) block and another around the “sparkle” block which may want to be slowed down a bit too - full speed it looks more like fireworks than gentle sparkles IMO.

You mean as follows?

Also, yeah, I’m going to run this code on across A LOT of LEDs. Potentially around 5,000. So the ‘sparkle’ definitely looks less like fireworks.

#define NUMPIXELS 5184 // Number of LEDs in strip


//-------------------------------------------------------------------
// Here's how to control the LEDs from SPI pins (Hardware SPI):
//-------------------------------------------------------------------
// Hardware SPI is a little faster, but must be wired to specific pins
// (Core/Photon/P1/Electron = pin A5 for data, A3 for clock)

#define DATAPIN   A5
#define CLOCKPIN  A3
uint32_t pxBuffer[1 + NUMPIXELS + NUMPIXELS/16 + 1]; // element 0 (leadin) ... LEDs ... 1 leadout clock per 2 LEDs (ceil)

struct ANIMATION_DATA
{
  uint8_t g;
  uint8_t b;
  uint8_t r;
  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  brightAnim[] = 
{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
, 255, 255, 255, 255, 255, 255, 255, 255, 245, 240
, 240, 240, 240, 235, 230, 230, 230, 230, 225, 220
, 220, 220, 220, 215, 210, 210, 210, 210, 205, 200
, 200, 200, 200, 200, 195, 195, 190, 190, 190, 190
, 190, 195, 180, 180, 180, 175, 170, 165, 160, 155
, 150, 145, 140, 135, 130, 125, 120, 115, 110, 105
, 100,  95,  90,  85,  80,  75,  70 
};

const int      cometLen = sizeof(brightAnim) / sizeof(brightAnim[0]);
const uint32_t msRefresh = 10;
const int stepSpeed = 5;
static int animationStep = 0;


//Set Color of pixel at position px, including brightness level
uint32_t setAPA102Color(int px, uint8_t g, uint8_t b, uint8_t r, uint8_t brightness = 31) {
  uint32_t color = 0;

  brightness = map(brightness, 0, 255, 0, 31);  // only the bottom 5 bits available resuliting in 32 discrete levels
  
  // due to big endianness in revers order 
  color |= (brightness <<   0) | 0xE0;  //top 3 bits must be 111
  color |= (r          <<   8);
  color |= (g          <<  16);
  color |= (b          <<  24);

  if (0 <= px && px < NUMPIXELS) {
    return pxBuffer[px] = color;
  }

  return -1;
}

bool needRefresh = true;
volatile bool canRefresh = true;

void refreshDone() {
  canRefresh = true;
}


void setup()
{
  memset(anim, 0, sizeof(anim));
  SPI.begin();
  SPI.setClockSpeed(8, MHZ);
  //SPI.setDataMode(0);
  SPI.setBitOrder(MSBFIRST);
  
  Particle.function("led", addComet);
}

void loop()
{
  static uint32_t msDelay = 0;
  if (millis() - msDelay < msRefresh) return;
  msDelay = millis();
  
  static bool sparkleState = false;
  static int  sparklePixel = 0;
  
  if (animHead != animTail)
  {
    ANIMATION_DATA *a;
    for (int animationStep = animTail; animationStep != animHead; ) 
    {
      a = &anim[animationStep];
      if (a->len)
      {
        int px;

        //Draw the Comet
        for (int p = a->len - 1; p >= 0; p--)
        {
          px = constrain(a->pos - p, 0, NUMPIXELS - 1);
          setAPA102Color(px, a->g, a->r, a->b, brightAnim[p]);
        }

        //Erase the pixels behind comet as it moves forward  
        if (a->pos - a->len >= 0)
          for (int k = 0; k <= stepSpeed; k++)
            setAPA102Color(a->pos - k - a->len, 0, 0, 0, 0);

        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < NUMPIXELS + stepSpeed)
          a->pos += stepSpeed;
        else
          a->len -= stepSpeed;

        if (a->len <= 0)
        {
          *a = { 0, 0, 0, 0, 0 };
          animTail++;
          animTail %= maxAnim;
        }
      }

      animationStep++;
      animationStep %= maxAnim;
    }
    needRefresh = true;
  }

  sparkleState = !sparkleState;  // flip state
  if (sparkleState)
  {
    sparklePixel = random(NUMPIXELS);
    setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
    needRefresh = true;
  }
  else
  {
    setAPA102Color(sparklePixel, 0, 0, 0, 0);
    needRefresh = true;
  }
    
  
  if (needRefresh && canRefresh) {
      canRefresh =
      needRefresh = false;
      SPI.transfer(pxBuffer, NULL, sizeof(pxBuffer), refreshDone);
  }

}

struct topic {
  char    title[16];
  uint8_t rgb[3];
};

const topic topicList[] = 
{ { "login"  , { 200, 200, 200 } }  // white
, { "idea"   , { 255,   0, 255 } }  // yellow
, { "comment", {   0, 255,   0 } }  // blue
, { "outcome", { 255,   0,   0 } }  // green
, { "project", { 255,   0,   0 } }  // green
, { "status" , { 100,   0, 255 } }  // orange
, { "step"   , {   0, 255,  75 } }  // purple
, { "vote"   , {   0,   0, 255 } }  // red
, { "view"   , { 105, 200, 255 } }  // pink
};

const int   topics = sizeof(topicList)/sizeof(topicList[0]);


//Add a comet to the strip whenever initated by cloud event
int addComet(String command)
{
  int retVal = -1;

  for (int i = 0; i < topics; i++)
  {
    if (strcmp((const char*)command, topicList[i].title) == 0)
    {
      anim[animHead] = { topicList[i].rgb[0], topicList[i].rgb[1], topicList[i].rgb[2], 0, cometLen };
      Particle.publish(command);
      animHead++;
      animHead %= maxAnim;
      retVal = i;
      break;
    }
  }
  return retVal;
}

Yes, like that. In your current case - with sparkles every iteration of loop() - it doesn’t make a lot of difference, but if you had the sparkles only every X milliseconds it would avoid unnecessary transfers.
In that case I’d do something like this

  if (millis() - msLastSparkle >= msSparkleTime) {
    msLastSparkle = millis();
    sparkleState = !sparkleState;  // flip state
    if (sparkleState)
    {
      sparklePixel = random(NUMPIXELS);
      setAPA102Color(sparklePixel, random(255), random(255), random(255), 100);
    }
    else
    {
      setAPA102Color(sparklePixel, 0, 0, 0, 0);
    }
    needRefresh = true;
  }

BTW, I think I found the explanation for the gradual extent of the sparkles. It’s the AP102 protocol “ignoring” empty bytes.
You could add this to setup

  memset(pxBuffer, 0xE0, NUMPIXELS*4);