c++arduinoesp8266neopixel

Neopixel sample code crashing when using a higher number of pixels


Context

I'm restarting a personal project that involved an ESP8266 and WS2812Bs (Neopixels).

It is worth noting that I do not have any Neopixels hooked up at the moment; I'm simply trying to get a feel for how fast I can update the pixels.

I'm running a very simple piece of example code taken from Adafruit's Neopixel GitHub repo. I've modified it slightly to make it more accurate to my use case and to remove comments (for the sake of posting here).

Details

The sample code:

#include <Adafruit_NeoPixel.h>

#define PIN 13        
#define NUMPIXELS 300 
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup()
{
  Serial.begin(115200);
  delay(1000);  
  pixels.begin();
}

void loop()
{
  Serial.println("Start");
  for (int i = 0; i < NUMPIXELS; i++)
  {
    Serial.println(i);
    pixels.setPixelColor(i, pixels.Color(0, 150, 0));
    pixels.show();
  }
  Serial.println("End");
}

It will crash before "End" is called. The loop only gets to ~227:

...
227

Soft WDT reset

ctx: cont 
sp: 3ffffd80 end: 3fffffd0 offset: 01b0

>>>stack>>>
3fffff30:  feefef00 feefeffe feefeffe 0000012c  
3fffff40:  3ffee798 00000003 3ffee798 40202a7c  
3fffff50:  3ffee798 3ffee768 3ffee798 40202bc5  
3fffff60:  3ffe894c 000000e3 3ffee798 40202cd7  
3fffff70:  3ffe8940 3ffee810 3ffee798 40202be0  
3fffff80:  3ffee798 3ffee768 0000012b 3ffee768  
3fffff90:  402014f2 3ffee768 000000e4 40202777  
3fffffa0:  feefeffe 00000000 3ffee7b4 3ffee7bc  
3fffffb0:  3fffdad0 00000000 3ffee7b4 40202ed4  
3fffffc0:  feefeffe feefeffe 3ffe85d8 40100739  
<<<stack<<<

 ets Jan  8 2013,rst cause:2, boot mode:(1,6)

Troubleshooting

This code will not crash if I reduce the number of pixels to 200 or add a delay(1) within the for loop.

Alternatively - removing the for loop and setting the LEDs by simply using the loop() seems to work.

#include <Adafruit_NeoPixel.h>

#define PIN 13        
#define NUMPIXELS 300 
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int i = 0;

void setup()
{
  Serial.begin(115200);
  delay(1000);  
  pixels.begin();
}

void loop()
{
    Serial.println(i);
    pixels.setPixelColor(i, pixels.Color(0, 150, 0)); 
    pixels.show();

  if (i == 299) {
    i = 0;
  } else {
    i = i + 1;
  }
}

So - the issue seems to ultimately depend on calling show() a certain number of times (227+) within a for loop inside of the loop() function.

Question

Many examples include the show within the for loop. I suspect that moving the show outside of the for loop is an adequate workaround; I did this in my original project with seemingly no issue.

But I'm still curious WHY this is happening. The fact that so many examples have the show() included in the for loop makes me think that this should work.

Does anybody know why setting ~300 LEDs in the above code would cause a crash, but 200 wouldn't?


Solution

  • The output from the board indicates the problem:

    Soft WDT reset
    

    The software watchdog timer is kicking in and resetting the board. See https://arduino-esp8266.readthedocs.io/en/latest/faq/a02-my-esp-crashes.html#watchdog.

    Embedded systems typically have a hardware and/or software watchdog. The watchdog will reset the system if it is not serviced within a predefined time period. This prevents the system becoming unresponsive if the software locks up or becomes overloaded.

    For the loop() code in question:

    void loop()
    {
      Serial.println("Start");
      for (int i = 0; i < NUMPIXELS; i++)
      {
        Serial.println(i);
        pixels.setPixelColor(i, pixels.Color(0, 150, 0));
        pixels.show();
      }
      Serial.println("End");
    }
    

    The implementation of pixels.show() uses a busy loop to implement the timing for writing to the LED. See https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.cpp#L205:

      // Data latch = 300+ microsecond pause in the output stream. Rather than
      // put a delay at the end of the function, the ending time is noted and
      // the function will simply hold off (if needed) on issuing the
      // subsequent round of data until the latch time has elapsed. This
      // allows the mainline code to start generating the next frame of data
      // rather than stalling for the latch.
      while(!canShow());
    

    227 iterations of the loop is enough of a busy loop to cause the watchdog timer to kick in.

    Calling pixels.show() fewer times or delay() (which calls yield() internally) allows the watchdog timer to be serviced.

    The simplest solution is to call pixels.show() once at the end of loop().

    So you need to remember to pat the puppy/dog - otherwise it bites.