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).
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)
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.
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?
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.