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

Okay, I just received my DotStar LED strip.
I can’t tell which wire is the clock and which is the Data. It’s not labeled.

Which is which?

And is this correct setup with photon microcontroller:
5v => VIN
GND => GND
Clock => A3
Data => A5

Nothing underneath either?
The strips I have have the CLK near the Vcc raild and MOSI near GND.

However, you can just try. swapping CLK and MOSI won’t damage the LEDs you will just not get the light pattern you expect and hence know it’s wrong :wink:

I’m trying to translate this comet code to my dotstar LED strip.

Is there a ‘setcolordimmed’ function for DotStar?

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

SYSTEM_MODE(AUTOMATIC)

const int PIXEL_COUNT = 1728;
const int PIXEL_PIN   = D1;
const int 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  whiteAnim[] = 
{ 255, 200, 175, 160, 155, 150, 145, 140, 135, 130
, 125, 120, 115, 110, 105, 100,  95,  90,  85,  80
,  75,  70,  65,  60,  55,  50,  45,  40,  35,  30
,  25,  20,  15,  10,   5,   0,   0,   0,   0,   0
,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0
,   0,   0,   0,   0,   0,   0,   0 
};
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(whiteAnim) / sizeof(whiteAnim[0]);
const uint32_t msRefresh = 0;
const int stepSpeed = 10;
static int animationStep = 0;

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

  strip.begin();
  strip.show(); // 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, PIXEL_COUNT - 1);
          strip.setColorDimmed(px, a->g, a->r, a->b, whiteAnim[p], brightAnim[p]);
        }

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

        //Make comet jump forward stepSpeed pixels at a time to make comet run seemingly faster
        if (a->pos < PIXEL_COUNT + 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(PIXEL_COUNT);
    strip.setPixelColor(sparklePixel, random(255), random(255), random(255), 100);
  }
  else
    strip.setPixelColor(sparklePixel, 0, 0, 0, 0);

  strip.show();
}

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]);

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 }; // purple
      Particle.publish(command);
      animHead++;
      animHead %= maxAnim;
      retVal = i;
      break;
    }
  }
  return retVal;
}

The bottom 5 bits in the leading byte in the 32 bit parameter you send to the LEDs is the brightness of the individual LED (32 possible levels).
So you can set the brightness per LED in your pixel buffer.

Sorry, I don’t know what that means.

What do you mean by ‘the bottom 5 bits’?
Right now, I’m setting the color of an individual LED with ‘strip.setPixelColor(px, a->g, a->r, a->b)’.

And that does nothing else then combining the RGB components into on 32bit number and placing that integer in a pixel buffer at position px.
Just like this

#define LEDCOUNT 300
uint32_t pxBuffer[LEDCOUNT];
// actually for the APA102 protocoll you need some extra bytes for leadin/leadout
//uint32_t pxBuffer[1 + LEDCOUNT + LEDCOUNT/16 + 1]; // element 0 (leadin) ... LEDs ... 1 leadout clock per 2 LEDs (ceil)

bool needRefresh;
volatile bool canRefresh = true;

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

  pxBuffer[px] = color;

  return color;
}

void refreshDone() {
  canRefresh = true;
}

void setup() {
  SPI.begin();
  SPI.setClockSpeed(8, MHZ);
  //SPI.setDataMode(0);
  SPI.setBitOrder(MSBFIRST);
}

void loop() {
  // setup your pixels 

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

This is all you need to work with APA102 strips - actually it's already a bit more to also illustrate use of DMA and guard against messing up data while still being sent out in the background.

1 Like

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.