Mega2560 crash loops until Serial.println is added

I’ve have encountered a seemingly strange situation involving an Arduino Mega2560 R3. I have a program that changes the color of an LED matrix on a button press. It seems that the code crashes and loops through setup repeatedly. I had what I thought was functioning code and then cleaned out all of my debugging Serial.println() statements only to find that the code would no longer run. Putting a single println back in the loop() made it work as expected. I am leery of that fix because it seems that it is hiding a bigger issue.

I searched for similar posts and found a few which all seem to allude to memory issues. I stripped down the code to close to the minimum and added a function for freeMemory checks. I can find nothing that says the Mega2560 is exhausting its 8192 bytes of SRAM.

The program includes the FastLED library to control a string of Neopixels. Reducing the size of the matrix does not seem to have any effect.

A couple of things seem to make the program run -

  1. add a Serial.println() in the loop function, or
  2. comment out one of the case statements in the loop function.

The code below exhibits the problem by repeatedly showing “Starting…” followed by the rest of the Serial output from the setup loop in the serial monitor. It just does that over and over again.

Starting…
Free Memory 1: 7694
0
Adding 48 LEDs
Setup complete
Starting…
Free Memory 1: 7694
0
Adding 48 LEDs
Setup complete
Starting…
Free Memory 1: 7694
0
Adding 48 LEDs
Setup complete
Starting…

I am compiling on Linux using the Arduino IDE downloaded from arduino.cc. I have tried versions 1.8.9, 1.8.10 and the latest nightly build. All have the same issue.

// XF 

#include <FastLED.h>

#define PIN 2
#define buttonPin 6

// Params for width and height
const uint8_t kMatrixWidth = 8;
const uint8_t kMatrixHeight = 6;

#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)

// button stuff
unsigned long currentMillis = 0;
unsigned long prevMillis = 0;
unsigned long prevButtonMillis = 0;

int buttonState = HIGH;
int lastButtonState = HIGH;
const unsigned long debounceInterval = 100;

// the animation mode currently being displayed by the LEDS
uint8_t aniMode = 0;
uint8_t isSolid = 0;

// The LEDs
CRGB leds[NUM_LEDS];

void setup() {
  // put your setup code here, to run once:
  delay(1000);
  Serial.begin(115200);
  Serial.println("Starting...");
  Serial.print("Free Memory 1: ");
  delay(2000);
  Serial.println(freeMemory());
  pinMode(buttonPin, INPUT);
  delay(2000);
  Serial.print("Adding ");
  Serial.print(NUM_LEDS);
  Serial.println(" LEDs");
  LEDS.addLeds<NEOPIXEL, PIN>(leds, NUM_LEDS);

  Serial.println("Setup complete");
  delay(2000);
}

//=======================================
void solid_color(CRGB color) {
  //Serial.println("running led_mode1 warm white");
  fill_solid(leds, NUM_LEDS, color);
  LEDS.show();
  isSolid = 1;
}

//=======================================
void readButton() {
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);
  //Serial.print("Reading is ");
  //Serial.println(reading);
  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    prevButtonMillis = millis();
  }

  if ((millis() - prevButtonMillis) > debounceInterval) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state

    if (reading != buttonState) {
      buttonState = reading;

      // only increase the if the new button state is HIGH
      if (buttonState == LOW) {
        aniMode++;
        isSolid = 0;
        if (aniMode > 6) {
          aniMode = 0;
        }
      }
    }
  }
  lastButtonState = reading;
  
}

int freeMemory() {
  extern int      __bss_end;
  extern uint8_t* __brkval;
  extern uint8_t  __heap_start;

  uint8_t top;

  Serial.println( (int) &top - ( __brkval == 0 ? (int) &__heap_start : (int) __brkval )) ;
}


void loop() {
  currentMillis = millis();
  readButton();
  // This Serial.println() is needed to make this script on Mega2560
  // Otherwise it goes into a boot loop
  //Serial.println("Works with this line uncommented");
  delay(1000);
  
  switch (aniMode) {
    case 0:
      if (isSolid == 1) break;
      solid_color(CRGB::Red);
      break;
    case 1:
      if (isSolid == 1) break;
      solid_color(CRGB::Orange);
      break;
    case 2:
      if (isSolid == 1) break;
      solid_color(CRGB::Yellow);
      break;
    case 3:
      if (isSolid == 1) break;
      solid_color(CRGB::Green);
      break;
    case 4:
      if (isSolid == 1) break;
      solid_color(CRGB::Blue);
      break;
    case 5:
      if (isSolid == 1) break;
      solid_color(CRGB::DarkBlue);
      break;
    case 6:
      if (isSolid == 1) break;
      solid_color(CRGB::DarkBlue);
      break;

  }
}
//END

I think this code is the problem

void solid_color(CRGB color) {
  //Serial.println("running led_mode1 warm white");
  fill_solid(leds, NUM_LEDS, color);
  LEDS.show();
  isSolid = 1;
}

You should have

FastLED.show();

if your serial moniter is telling you that the start function keeps repeating ... I would think that somehow your board is being reset.

is your power supply sufficient? could the LEDs be pulling enough power to reset your board?

Thank you for replying.

@blh64 - re: FastLED.show

The code I have (LEDS.show) is right out of the FastLED examples at FastLED/Noise.ino at master · FastLED/FastLED · GitHub

As a test I changed it from LEDS.show to FastLED.show and unfortunately it did not fix the issue. The setup loop still happens.

@taterking - re: power causing reset?

The LEDs are powered via a separate supply so the draw of the LEDs is not going through the Mega2560. The boot loop happens even when the LEDs are not connected to the board. For the board itself, I have powered the board both directly from USB and via a 9v supply. The setup loop happens with both.

Uncomment the Serial.println("Works with this line uncommented") in loop() and everything appears fine.

Look in the FastLED library file 'pixeltypes.h'. You'll see that colors like 'CRGB::Orange' are not CRGB objects (they are defined in an enum). But, you're trying to pass them to your solid_color() function as if they were.

Also, you promised the compiler that your freeMemory() function would return an int and then broke that promise.

The code within each case of the switch statement seems very cumbersome. Why not test for isSolid not equal to 1, or make isSolid boolean and test for false.

larscson:
Thank you for replying.

@blh64 - re: FastLED.show

The code I have (LEDS.show) is right out of the FastLED examples at FastLED/Noise.ino at master · FastLED/FastLED · GitHub

As a test I changed it from LEDS.show to FastLED.show and unfortunately it did not fix the issue. The setup loop still happens.

I peeked inside the FastLED.h file and LEDS is defined to be FastLED so they are the same.

gfvalvo:
Look in the FastLED library file 'pixeltypes.h'. You'll see that colors like 'CRGB::Orange' are not CRGB objects (they are defined in an enum). But, you're trying to pass them to your solid_color() function as if they were.

Also, you promised the compiler that your freeMemory() function would return an int and then broke that promise.

Thank you for putting another see of eyes on this.

You are correct, I broke the freeMemory as I was trying to narrow down the problem. It was originally returning the int and I changed it to print the val inside. I will revisit that. A question for you - by not specifcally returning an integer, would that be enough to crash the program as is happening in this case? (I only added freeMemory after the original crashing started in an effort to track down the issue.)

As for the CRGB color argument, I swear I have seen that syntax in the FastLED examples. I will see if I can confirm that.

larscson:
As for the CRGB color argument, I swear I have seen that syntax in the FastLED examples. I will see if I can confirm that.

I think I recall seeing odd behavior when misusing CRGB::color in the past. Can’t remember exactly what. Anyway, I’d correct that and your unnecessarily complex switch() statement at the same time. See below. I also fixed your freeMemory() function. But, since you only call it from setup(), it’s rather pointless. Other improvements / optimizations could be made once the crashing problem is fixed. For example, your 1000 ms delay will make the program annoyingly unresponsive to button pushes.

#include <FastLED.h>

#define PIN 2
#define buttonPin 6

// Params for width and height
const uint8_t kMatrixWidth = 8;
const uint8_t kMatrixHeight = 6;

#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)

// button stuff
unsigned long currentMillis = 0;
unsigned long prevMillis = 0;
unsigned long prevButtonMillis = 0;

int buttonState = HIGH;
int lastButtonState = HIGH;
const unsigned long debounceInterval = 100;

// the animation mode currently being displayed by the LEDS
uint8_t aniMode = 0;
uint8_t isSolid = 0;

// The LEDs
CRGB leds[NUM_LEDS];

void setup() {
  // put your setup code here, to run once:
  delay(1000);
  Serial.begin(115200);
  Serial.println("Starting...");
  Serial.print("Free Memory 1: ");
  delay(2000);
  freeMemory();
  pinMode(buttonPin, INPUT);
  delay(2000);
  Serial.print("Adding ");
  Serial.print(NUM_LEDS);
  Serial.println(" LEDs");
  LEDS.addLeds<NEOPIXEL, PIN>(leds, NUM_LEDS);

  Serial.println("Setup complete");
  delay(2000);
}

void readButton() {
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);
  //Serial.print("Reading is ");
  //Serial.println(reading);
  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    prevButtonMillis = millis();
  }

  if ((millis() - prevButtonMillis) > debounceInterval) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state

    if (reading != buttonState) {
      buttonState = reading;

      // only increase the if the new button state is HIGH
      if (buttonState == LOW) {
        aniMode++;
        isSolid = 0;
        if (aniMode > 6) {
          aniMode = 0;
        }
      }
    }
  }
  lastButtonState = reading;

}

void freeMemory() {
  extern int      __bss_end;
  extern uint8_t* __brkval;
  extern uint8_t  __heap_start;

  uint8_t top;

  Serial.println( (int) &top - ( __brkval == 0 ? (int) &__heap_start : (int) __brkval )) ;
}


void loop() {
  static const CRGB colors[] = {CRGB::Red, CRGB::Orange, CRGB::Yellow, CRGB::Green, CRGB::Blue, CRGB::DarkBlue, CRGB::DarkBlue};
  static const uint8_t numColors = sizeof(colors) / sizeof(colors[0]);

  currentMillis = millis();
  readButton();
  // This Serial.println() is needed to make this script on Mega2560
  // Otherwise it goes into a boot loop
  //Serial.println("Works with this line uncommented");
  delay(1000);

  if ((isSolid != 1) && (aniMode < numColors)) {
    fill_solid(leds, NUM_LEDS, colors[aniMode]);
    LEDS.show();
    isSolid = 1;
  }
}

larscson:
As for the CRGB color argument, I swear I have seen that syntax in the FastLED examples. I will see if I can confirm that.

Most definitely. There are many FastLED examples where they assign a color directly to one of the leds

led[0] = CRGB::Black;

where the led array is of type CRGB.

those predefined colors are uint32_t and the library has operators defined to all conversion/assignment so they get make into proper CRGB variables as needed.

Thank you all for your responses. I really appreciate it.

I have cleaned up the code instituting some of the recommended changes and removing the code I added for debugging such as the freeMemory function and the prints and delays.

Currently the code below installs and runs fine on an Adafruit Trinket M0. On the Mega2560 I get the crash loop. Similar to earlier in the thread, either commenting out “case 6” or uncommenting the Serial.println directly before the switch statement makes the code function on the Mega2560.

I’m hoping to learn what I may be doing incorrectly that is causing the Mega board to reset.

TIA

// XF 

#include <FastLED.h>

#define PIN 0
#define buttonPin 2

// Matrix information
const uint8_t kMatrixWidth = 30;
const uint8_t kMatrixHeight = 19;
const bool kMatrixSerpentineLayout=true;
#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)

unsigned long currentMillis = 0;
unsigned long prevMillis = 0;
unsigned long prevButtonMillis = 0;

int buttonState = HIGH;
int lastButtonState = HIGH;
const int debounceInterval = 100;

// the animation mode currently being displayed by the LEDS
uint8_t aniMode = 0;
bool isSolid = false;

// coordinates
static uint16_t x = random16();
static uint16_t y = random16();
static uint16_t z = 1;

CRGB color;
CRGBPalette16 currentPalette( PartyColors_p );

uint16_t speed = 20;
uint16_t scale = 30;

// This is the array that we keep our computed noise values in
uint8_t noise[MAX_DIMENSION][MAX_DIMENSION];

// The LEDs
CRGB leds[NUM_LEDS];


void setup() {
  // put your setup code here, to run once:
  delay(1000);
  Serial.begin(57600);
  pinMode(buttonPin, INPUT);
  Serial.println("Starting...");
  delay(1000);
  LEDS.addLeds<NEOPIXEL, PIN>(leds, NUM_LEDS);
  LEDS.setBrightness(192);
  Serial.println("Setup complete");
}

//=======================================
void XF_noise() {
  if ((currentMillis - prevMillis) >= 20) {
    prevMillis = millis();
  
    fillnoise8();
    
    for(int i = 0; i < kMatrixWidth; i++) {
      for(int j = 0; j < kMatrixHeight; j++) {
        // We use the value at the (i,j) coordinate in the noise
        // array for our brightness, and the flipped value from (j,i)
        // for our pixel's hue.
        uint16_t idx = noise[j][i];
        uint16_t bri = noise[i][j];
        leds[XY(i,j)] = ColorFromPalette(currentPalette, idx, bri);
      }
    }
    
    FastLED.show();
  }
}

//=======================================
void solid_color(CRGB color) {
  fill_solid(leds, NUM_LEDS, color);
  FastLED.show();
  isSolid = true; 
}

//=======================================
void readButton() {
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);
  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    prevButtonMillis = millis();
  }

  if ((millis() - prevButtonMillis) > debounceInterval) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == LOW) {
        aniMode++;
        isSolid = false;
        if (aniMode > 7) {
          aniMode = 0;
        }
      }
    }
  }
  lastButtonState = reading;
}

//=======================================
uint16_t XY( uint8_t x, uint8_t y) {
  uint16_t i;

  if ( kMatrixSerpentineLayout == false ){
    i = (y * kMatrixWidth) + x;
  }

  else if ( kMatrixSerpentineLayout == true ) {
    if( y & 0x01) {
      // Odd rows run backward
      uint8_t reverseX = (kMatrixWidth - 1) - x;
      i = (y * kMatrixWidth) + reverseX;
    } else {
      // Even (0,2,4...) rows run forward
      i = (y * kMatrixWidth) + x;
    }
  }

  return i;
}

//=======================================
// Fill the x/y array of 8-bit noise values using the inoise8 function.
void fillnoise8() {
  for(int i = 0; i < MAX_DIMENSION; i++) {
    uint16_t ioffset = scale * i;
    for(int j = 0; j < MAX_DIMENSION; j++) {
      uint16_t joffset = scale * j;
      noise[i][j] = inoise8(x + ioffset, y + joffset, z);     
    }
  }
  z += speed;
}

void loop() {
  currentMillis = millis();
  readButton();
  //Serial.println("Works with this line");
  
  switch (aniMode) {
    case 0:
      if (isSolid != true) { solid_color(CRGB::Red); }
      break;
    case 1:
      if (isSolid != true) { solid_color(CRGB::Blue); }
      break;
    case 2:
      if (isSolid != true) { solid_color(CRGB::Green); }
      break;
    case 3:
      if (isSolid != true) { solid_color(CRGB::Purple); }
      break;
    case 4:
      currentPalette = CloudColors_p;
      XF_noise();
      break;
    case 5:
      currentPalette = LavaColors_p;
      XF_noise();
      break;
    case 6:
      currentPalette = OceanColors_p;
      XF_noise();
      break;
/*    case 7:
      currentPalette = RainbowColors_p;
      XF_noise();
      break;
*/
    default:
      if (isSolid != true) { solid_color(CRGB::Black); }
      break;
  }
}
//END

What happened when you tried my code from Reply #8?

The only odd thing I see is this declaration

CRGB color;
//CRGBPalette16 currentPalette( PartyColors_p );
CRGBPalette16 currentPalette;

where you had '( PartyColors_p )' along with your variable declaration. Not sure what the compiler does with that...

gfvalvo:
What happened when you tried my code from Reply #8?

The code from reply 8 appears to work as expected as I did not experience any issues running it.

I appreciate the conciseness of your code. My C brain still has a ways to go and your input certainly helps.

As I expand the code to include animated modes rather than just solid. I am still interested in learning what is going wrong such that the board resets. Does anything in that code from reply 10 stand out as being problematic such that commenting out a case statement seems to make it work?

Are you connecting the led strip to digital pin 0? That is used by the serial interface to the USB port and may somehow be triggering a reset via the USB interface chip.

Your current code is more complex than what you originally posted. A better debugging strategy is to simplify things until you find the problem. Then, build it up testing along the way.

david_2018:
Are you connecting the led strip to digital pin 0? That is used by the serial interface to the USB port and may somehow be triggering a reset via the USB interface chip.

On the Mega2560 the LEDs are not connected to pin 0. Usually they use PIN 2. The reason that the code in reply 10 has PIN 0 is because that is what I used when I ran the code (successfully) on the Trinket M0.

I have tried a different combinations of pins for the button and LEDs on the Mega2560 always avoiding 0 and 1. Changing the pins does not seem to make a difference with respect to the reset loop.

As it stands...

I never did find a reason that the board resets/loops when the code contains a switch/case statement. The program functions without issue after replacing the switch/case with if/else statements.

I hate leaving things like this, so perhaps one day I will get to return and dig in even further.

Thanks to all who helped out.