Fixing the Adafruit CharlieWing Swirl Demo

See my previous post for which this is the continuation.



Out of the box, with the downloadable libraries mentioned in the Adafruit Tutorial for the CharlieWing, the FeatherWing microcontroller appears to lock up after about a minute; no more updates to the LEDs occurs.

 #include <Wire.h>  
 #include <Adafruit_GFX.h>  
 #include <Adafruit_IS31FL3731.h>  
   
 // If you're using the full breakout...  
 //Adafruit_IS31FL3731 ledmatrix = Adafruit_IS31FL3731();  
 // If you're using the FeatherWing version  
 Adafruit_IS31FL3731_Wing ledmatrix = Adafruit_IS31FL3731_Wing();  
   
 // The lookup table to make the brightness changes be more visible  
 uint8_t sweep[] = {1, 2, 3, 4, 6, 8, 10, 15, 20, 30, 40, 60, 60, 40, 30, 20, 15, 10, 8, 6, 4, 3, 2, 1};  
   
 int loopCount;  
   
 void setup() {  
  Serial.begin(250000);  
  Serial.println("ISSI swirl test");  
   
  // Begin: Added for debugging  
  ledmatrix.clear();  
  // End: Added for debugging  
    
  if (! ledmatrix.begin()) {  
   Serial.println("IS31 not found");  
   while (1);  
  }  
  Serial.println("IS31 found!");  
 }  
   
 void loop() {  
  // animate over all the pixels, and set the brightness from the sweep table  
  for (uint8_t incr = 0; incr < 24; incr++)  
   for (uint8_t x = 0; x < 16; x++)  
    for (uint8_t y = 0; y < 9; y++)  
     ledmatrix.drawPixel(x, y, sweep[(x+y+incr)%24]);  
  delay(40);  
 }  

My first thought was that the LEDs required more power than a single USB source could supply. This can be tested with software. I needed only to change the sweep[] array to reduce the power consumption of the CharlieWing.

 // The lookup table to make the brightness changes be more visible  
 //uint8_t sweep[] = {1, 2, 3, 4, 6, 8, 10, 15, 20, 30, 40, 60, 60, 40, 30, 20, 15, 10, 8, 6, 4, 3, 2, 1};  
 uint8_t sweep[] = {4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0};

Running with the modified sweep[] array produced the same result. The display freezes after about a minute. It seems power consumption is not at fault. Aside: this was the exact issue I had when using the Adafruit Neopixel Shield recently. Actually the CharlieWing only pulls about 40mA, so I didn't even need to run this test.

So, when exactly does it freeze? I'll print a counter that shows the number of iterations of the loop() function. Complete code:

 #include <Wire.h>  
 #include <Adafruit_GFX.h>  
 #include <Adafruit_IS31FL3731.h>  
   
 // If you're using the full breakout...  
 //Adafruit_IS31FL3731 ledmatrix = Adafruit_IS31FL3731();  
 // If you're using the FeatherWing version  
 Adafruit_IS31FL3731_Wing ledmatrix = Adafruit_IS31FL3731_Wing();  
   
 // The lookup table to make the brightness changes be more visible  
 //uint8_t sweep[] = {1, 2, 3, 4, 6, 8, 10, 15, 20, 30, 40, 60, 60, 40, 30, 20, 15, 10, 8, 6, 4, 3, 2, 1};  
 uint8_t sweep[] = {4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0};  
   
 int loopCount;  
   
 void setup() {  
  Serial.begin(250000);  
  Serial.println("ISSI swirl test");  
   
  if (! ledmatrix.begin()) {  
   Serial.println("IS31 not found");  
   while (1);  
  }  
  Serial.println("IS31 found!");  
 }  
   
 void loop() {  
  // animate over all the pixels, and set the brightness from the sweep table  
  for (uint8_t incr = 0; incr < 24; incr++)  
   for (uint8_t x = 0; x < 16; x++)  
    for (uint8_t y = 0; y < 9; y++)  
     ledmatrix.drawPixel(x, y, sweep[(x+y+incr)%24]);  
  delay(40);  
   
  Serial.print(loopCount++);  
  Serial.print(", ");  
  if (loopCount % 10 == 0) {  
   Serial.println();  
  }  
 }  

And the result:

 chksum 0x2d  
 csum 0x2d  
 v60000318  
 ~ld  
 RISSI swirl test  
 IS31 found!  
 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,   
 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,   
 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,   
 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,   
 40, 41, 42, 43, 44, 45, 46, 47, 48,   
 Soft WDT reset  
   
 ctx: cont   
 sp: 3ffef310 end: 3ffef5c0 offset: 01b0  
   
 >>>stack>>>  
 3ffef4c0: 00000000 00000001 3ffef52f 3ffee443   
 3ffef4d0: 3ffee445 00000000 00000000 4020120c   
 3ffef4e0: 00000002 00000001 00000004 40201300   
 3ffef4f0: 00000074 00000001 3ffee445 40201331   
 3ffef500: 00000074 00000001 3ffef52d 00000000   
 3ffef510: 0000002c 00000000 3ffee441 4020225c   
 3ffef520: 00000042 3ffee40c 3ffee430 40202284   
 3ffef530: 00000000 00000001 3ffee430 402028f2   
 3ffef540: 00000009 3ffee40c 00000000 40202980   
 3ffef550: 40101180 018d7f4b 3ffee4ac 00000000   
 3ffef560: 3ffee40c 00000008 00000000 402029d2   
 3ffef570: 3ffe8480 3ffee58c 402030bc 40202a8c   
 3ffef580: 00000018 3ffee408 3ffee568 3ffee58c   
 3ffef590: 00000017 00000000 0000000f 40202021   
 3ffef5a0: 3fffdad0 00000000 3ffee584 40203108   
 3ffef5b0: feefeffe feefeffe 3ffee5a0 40100718   
 <<<stack<<<  
 �88  �> 8 � >8888�8�8�� 8�� �8����>��88�� 8��>�>� 8>�>���88  8 �888>8�8�88  �> 8 � >8888� � ��  �� � ���� ��   � 8��>�>� 8>� ���88    �888>8�8� �� 8���>����8  

The LEDs freeze and, although the FeatherWing restarts, the display remains frozen. The only way I've found to get the display to update again is to unplug and replug the USB or hit the reset button on the FeatherWing. I tried adding a ledmatrix.clear() line into the loop() function but doesn't seem to work. Here is the code I'm using:

 // Begin: Added for debugging  
  ledmatrix.clear();  
  // End: Added for debugging

But being able to change the code so at least the display resumes when the watchdog timer restarts the microcontroller is like being able to repair your car after you crashed it. Can we try not crashing it in the first place? And this is why I like to create my own libraries for these things. If I wrote all the code I would stand a chance of fixing it. But I really should get better at troubleshooting code by others.

Divide and conquer! I'll create a minimal amount of code to get the display to do anything that's very simple then test just that. The tutorial page called Library Reference has what I need.

 #include <Adafruit_IS31FL3731.h>  
   
 Adafruit_IS31FL3731_Wing ledmatrix = Adafruit_IS31FL3731_Wing();  
   
 void setup() {  
   
  Serial.begin(250000);  
  Serial.println("setup()");  
   
  if (! ledmatrix.begin()) {  
   Serial.println("IS31 not found");  
   while (1);  
  }  
  Serial.println("IS31 found!");  
 }  
   
 void loop() {  
   
  // Runs without stopping.  
  Serial.println("loop()");  
  ledmatrix.drawPixel(0, 0, 255);  
  ledmatrix.drawPixel(1, 0, 0);  
  delay(50);  
  ledmatrix.drawPixel(0, 0, 0);  
  ledmatrix.drawPixel(1, 0, 255);  
  delay(50);  
   
 }  

OK, so this code is basically a 'blinky' example. And guess what? It doesn't lock up. I've run it for an hour. I can alternately upload both sketches and mine runs fine while Swirl locks up in less than a minute.

When comparing code between Adafruit's Swirl demo and my code there isn't a lot of difference. All the work is being accomplished by calls to ledmatrix.drawPixel(). So what is the difference? Well, about the only thing I can see is that Swirl has three embedded for loops which, combined, execute drawPixel() 24 * 16 * 9 = 3,456 times per execution of loop(). There's a 50 millisecond delay in the code, perhaps to provide time for the I2C bus to get data across from the FeatherWing to the CharlieWing. Pushing the delay up to 100ms doesn't fix it.

So it appears we're pushing data to the I2C bus faster than it can be transferred. Adding delays after each 3,456 calls doesn't solve the problem, I would assume because the microcontroller has a fixed buffer for the I2C bus and there's no bounds checking. Write too much to the buffer and you write data over executable code and bingo, watchdog reset. Perhaps we can put the delay inside one of the for loops as a kind of rate control?

 void loop() {  
  // animate over all the pixels, and set the brightness from the sweep table  
  for (uint8_t incr = 0; incr < 24; incr++) {  
   for (uint8_t x = 0; x < 16; x++) {  
    for (uint8_t y = 0; y < 9; y++) {  
     ledmatrix.drawPixel(x, y, sweep[(x+y+incr)%24]);  
    }  
    delay(1);  
   }  
  }  
 // delay(100);  
   
  Serial.print(loopCount++);  
  Serial.print(", ");  
  if (loopCount % 10 == 0) {  
   Serial.println();  
  }  
 }  

Now there's a 1ms delay for every 9 calls to drawPixel(). Each execution of loop() now entails 24 * 16 = 384ms total delay. In testing this has run flawlessly for 200 iterations. Obviously I'll need to run it overnight before I really believe it works. Before I do that, I'll go back to running delay() at the end of loop() and give it 384ms of delay. Whaddya know? It still crashes.

Now I'll move the delay out one more for loop to provide 1ms delay for every 16 * 9 = 144 calls to drawPixel(). This will entail 24ms total delay for every loop(). 200 iterations and still running. I still have Serial.print() lines in the code. These add delay. So I'll need to comment those out to give it a proper speed test.

The final timings still need some work but I think at this stage the hypothesis is proved. The Swirl demo for CharlieWing that comes from Adafruit can cause a reset condition such that all future calls to the CharlieWing fail. I'm guessing a buffer overflow is the root cause. The solution is to implement sufficient delays to ensure the I2C bus never saturates.

Going back to Adafruit's original code and making the minimum changes to ensure it works produces the listing below. Each modified line has a // NOTE: comment.

 #include <Wire.h>  
 #include <Adafruit_GFX.h>  
 #include <Adafruit_IS31FL3731.h>  
   
 // If you're using the full breakout...  
 //Adafruit_IS31FL3731 ledmatrix = Adafruit_IS31FL3731(); // NOTE: Commented out  
 // If you're using the FeatherWing version  
 Adafruit_IS31FL3731_Wing ledmatrix = Adafruit_IS31FL3731_Wing(); // NOTE: Uncommented  
   
   
 // The lookup table to make the brightness changes be more visible  
 uint8_t sweep[] = {1, 2, 3, 4, 6, 8, 10, 15, 20, 30, 40, 60, 60, 40, 30, 20, 15, 10, 8, 6, 4, 3, 2, 1};  
   
 void setup() {  
  Serial.begin(250000); // NOTE: Baud rate changed from 9600  
  Serial.println("ISSI swirl test");  
   
  if (! ledmatrix.begin()) {  
   Serial.println("IS31 not found");  
   while (1);  
  }  
  Serial.println("IS31 found!");  
 }  
   
 void loop() {  
  // animate over all the pixels, and set the brightness from the sweep table  
  for (uint8_t incr = 0; incr < 24; incr++) { // NOTE: Curly brace added  
   for (uint8_t x = 0; x < 16; x++)  
    for (uint8_t y = 0; y < 9; y++)  
     ledmatrix.drawPixel(x, y, sweep[(x+y+incr)%24]);  
   delay(1); // NOTE: delay added  
  } // NOTE: Curly brace added  
 // delay(20); // NOTE: Commented out  
 }  

That is all.

Comments