Sketch crashing - memory leak?

This one is a total mystery to me. I have a strip of WS2812 around my Xmas tree and have programmed 10 different patterns into it. I have a “showcase” mode which cycles through all 10 patterns. Well, it is supposed to but it crashes after 1 cycle through them all. I had it working but I decided to change some things around and now it has been acting very strangely.

I don’t actually have it connected to the lights at the moment (I am at work) so I am running a whole bunch of Serial.print()s to see where in the sketch it is and at what point it crashes.
When I change around various things such as my Serial.print()s, adding in extra variables it changes the way it crashes. So far I have seen (on my serial monitor):
It stopping printing anything with no garbage and no further output to serial.
It printing a “null” character and stopping with no further output to serial.
It printing a funny ? within a diamond then resetting.
Typically the point at which it crashes is when currentPattern is either 0 or 1 on the second cycle of the “showcase”. This seems to be independent of the variable showcaseTime, although when I set that to 1000 it made it through 2 whole cycles of patterns before crashing. I have tried it at 2000, 3000 and 5000 and they all crash shortly after completing 1 cycle.
Actually there are cases where it doesn’t even complete 1 cycle. I just uncommented all my Serial.println(freeRam()) lines and it crashed on pattern 6. I will put the code in its current state below.

I don’t think I am running out of RAM because every time I call freeRam() it gives me the same value. I feel as though I am somehow corrupting memory somewhere somehow but I just can’t figure it out.

Also for anyone wondering how I get around the fact of using WS2812 with fastLED and an IR remote, I just put up with unresponsiveness of the remote and the fact I have to press a button several times to get a response.

Here is the start of my code. Since people don’t like downloading ino files (I never do either) and also complain when the full code isn’t posted, I have to split it into 3 posts.

#include <Arduino.h>
#include <FastLED.h>
#include "IRremote.h"

//function definitions
void checkRemoteControl();
void setInitialCondition();
void updatePattern();
void greenRed();
void whiteBlack();
void waveFade();
void waveConstant();
void rainbowFlow();
void fillRainbow();
void fillSingleColour();
void fullRainbow();
void singleSpiral();
void rainbowSpiral();
void translateIR();
void resetFillTime();
void resetForModeChange();
void updateRandom();
void updateTime(bool upPressed);
int freeRam();

const int NUM_LEDS = 123; 
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
const int GREEN_LED = 3;
const int RED_LED = 4;

bool onOff = true;
byte currentPattern = 0;
unsigned long patternLastUpdate;
unsigned long lastIRcodeTime;
unsigned long IRcodeDelay = 50;
bool canReceiveIRcode = true;
unsigned long patternUpdateTime[] = {1000, 1000, 100, 100, 100, 10, 10, 100, 100, 100};
bool greenOrRed = false;
byte brightnessLevels[] = {0,31,63,95,127,159,191,223,255};
byte systemBrightnessIndex = 5;
byte waveStartIndex = 0;
byte waveColour = 0;
byte rainbowHues[NUM_LEDS];
byte fillIndex = 0;
byte fillTo = NUM_LEDS + 1;
byte fillStartColour = 0;
bool fillReset = true;
byte fullRainbowIndex = 0;
bool spiralDirection = true;
byte spiralIndex = 0;
unsigned long spiralChangeDirectionTime = 0;
unsigned long spiralLastChange;
byte singleSpiralColour = 0;
byte rainbowSpiralColour = 0;
bool randomMode = true;
bool showcaseMode = true;
unsigned long showcaseTime = 2000;
unsigned long randomModeTime = showcaseTime;
unsigned long randomModeLastUpdate;

//IR remote stuff
int receiver = 11; // Signal Pin of IR receiver to Arduino Digital Pin 11
IRrecv irrecv(receiver);     // create instance of 'irrecv'
decode_results results;      // create instance of 'decode_results'

void setup()
{
  //serial only for debug
  Serial.begin(9600);
  delay(1000);
  Serial.println("start");
  delay(1000);
  
  FastLED.addLeds<LED_TYPE, 2, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.clear();
  FastLED.show();
  for(byte i = 0; i < NUM_LEDS; i++)
  {
    rainbowHues[i] = map(i,0,NUM_LEDS - 1,0,255);
  }
  irrecv.enableIRIn(); // Start the receiver
  pinMode(RED_LED,OUTPUT);
  pinMode(GREEN_LED,OUTPUT);
  delay(3000);
  
  patternLastUpdate = millis();
  spiralLastChange = millis();
  randomModeLastUpdate = millis();
  lastIRcodeTime = millis();
}

void loop() 
{
  checkRemoteControl();
  updateRandom();
  updatePattern();
}

void checkRemoteControl()
{
  if(millis() - lastIRcodeTime >= IRcodeDelay)
  {
    canReceiveIRcode = true;
    digitalWrite(GREEN_LED,LOW);
    digitalWrite(RED_LED,LOW);
  }
  //if a change in mode has been detected, clear led data
  if (irrecv.decode(&results) && canReceiveIRcode) // have we received an IR signal?
  {
    translateIR(); 
    irrecv.resume(); // receive the next value
  }
}

void updatePattern()
{
  if(onOff)
  {
    switch (currentPattern)
    {
      case 0:
        greenRed();
        break;
      case 1:
        whiteBlack();
        break;
      case 2:
        waveFade();
        break;
      case 3:
        waveConstant();
        break;
      case 4:
        rainbowFlow();
        break;
      case 5:
        fillRainbow();
        break;
      case 6:
        fillSingleColour();
        break;
      case 7:
        fullRainbow();
        break;
      case 8:
        singleSpiral();
        break;
      case 9:
        rainbowSpiral();
        break;
    }
  }
}

//switches alternating LEDs between green and red. how christmassy
void greenRed()
{
  Serial.print("0");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    greenOrRed = !greenOrRed;
    for(byte i = 0; i < NUM_LEDS; i++)
    {
      if(i%2 == greenOrRed)
      {
        leds[i] = CHSV(HUE_RED,255,brightnessLevels[systemBrightnessIndex]);
      }
      else
      {
        leds[i] = CHSV(HUE_GREEN,255,brightnessLevels[systemBrightnessIndex]);
      }
    }
    FastLED.show();
  }
}

void whiteBlack()
{
  Serial.print("1");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    greenOrRed = !greenOrRed;
    FastLED.clearData();
    for(byte i = 0; i < NUM_LEDS/2; i++)
    {
      leds[i*2+greenOrRed] = CRGB::White;
      leds[i*2+greenOrRed] %= brightnessLevels[systemBrightnessIndex];
    }
    FastLED.show();
  }
}

void waveFade()
{
  Serial.print("2");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    byte pattern[] = {9,9,9,4,3,2,1,0,1,2,3,4,9,9,9};
    byte j = waveStartIndex;
    byte b; //brightness
    //give a boost to the brightness since not many LEDs are on at once
    byte s = 0;
    if(systemBrightnessIndex != 0)
    {
      s = systemBrightnessIndex + 2;
    }
    if(s > 8)
    {
      s = 8;
    }
    for(byte i = 0; i < NUM_LEDS; i++)
    {
      b = s - pattern[j];
      if(b >= 9)
      {
        b = 0;
      }
      leds[i] = CHSV(waveColour,255,brightnessLevels[b]);
      j++;
      if(j == 15)
      {
        j = 0;
      }
    }
    waveColour++;
    if(waveStartIndex != 0)
    {
      waveStartIndex--;
    }
    else
    {
      waveStartIndex = 14;
    }
    FastLED.show();
  }
}

void waveConstant()
{
  Serial.print("3");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    byte pattern[] = {9,9,9,4,3,2,1,0,1,2,3,4,9,9,9};
    byte j = waveStartIndex;
    byte b; //brightness
    //give a boost to the brightness since not many LEDs are on at once
    byte s = 0;
    if(systemBrightnessIndex != 0)
    {
      s = systemBrightnessIndex + 2;
    }
    if(s > 8)
    {
      s = 8;
    }
    for(byte i = 0; i < NUM_LEDS; i++)
    {
      b = s - pattern[j];
      if(b >= 9)
      {
        b = 0;
      }
      leds[i] = CHSV(map(i,0,NUM_LEDS-1,0,255),255,brightnessLevels[b]);
      j++;
      if(j == 15)
      {
        j = 0;
      }
    }
    if(waveStartIndex != 0)
    {
      waveStartIndex--;
    }
    else
    {
      waveStartIndex = 14;
    }
    FastLED.show();
  }
}
void rainbowFlow()
{
  Serial.print("4");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    for(byte i = 0; i < NUM_LEDS; i++)
    {
      rainbowHues[i] -= 2;
      leds[i] = CHSV(rainbowHues[i],255,brightnessLevels[systemBrightnessIndex]);
    }
    FastLED.show();
  }
}

void fillRainbow()
{
  Serial.print("5");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {  
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    if(fillReset)
    {
      fillReset = false;
      FastLED.clearData();
      fillTo = NUM_LEDS + 1;
      fillStartColour -= 16;
      patternUpdateTime[currentPattern] = 10;
    }
    else
    {
      leds[fillIndex] = CHSV(map(fillTo,0,NUM_LEDS-1,0,255)+fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
      if(fillIndex < 3){
        leds[0] = CHSV(map(fillTo,0,NUM_LEDS-1,0,255)+fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
      }
      else
      {
        leds[fillIndex - 3] = CRGB::Black;
      }
      fillIndex++;
      if(fillIndex == fillTo)
      {
        fillIndex = 0;
        if(fillTo > 3)
        {
          fillTo -= 3;
        }
        else
        {
          leds[0] = CHSV(map(fillTo,0,NUM_LEDS-1,0,255)+fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
          leds[1] = CHSV(map(fillTo,0,NUM_LEDS-1,0,255)+fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
          leds[2] = CHSV(map(fillTo,0,NUM_LEDS-1,0,255)+fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
          fillReset = true;
          patternUpdateTime[currentPattern] = 3000;
        }
      }
    }
    FastLED.show();
  }
}

void fillSingleColour()
{
  Serial.print("6");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {  
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    if(fillReset)
    {
      fillReset = false;
      FastLED.clearData();
      fillTo = NUM_LEDS + 1;
      fillStartColour -= 32;
      patternUpdateTime[currentPattern] = 10;
    }
    else
    {
      leds[fillIndex] = CHSV(fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
      if(fillIndex < 3){
        leds[0] = CHSV(fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
      }
      else
      {
        leds[fillIndex - 3] = CRGB::Black;
      }
      fillIndex++;
      if(fillIndex == fillTo)
      {
        fillIndex = 0;
        if(fillTo > 3)
        {
          fillTo -= 3;
        }
        else
        {
          leds[0] = CHSV(fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
          leds[1] = CHSV(fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
          leds[2] = CHSV(fillStartColour,255,brightnessLevels[systemBrightnessIndex]);
          fillReset = true;
          patternUpdateTime[currentPattern] = 3000;
        }
      }
    }
    FastLED.show();
  }
}

void fullRainbow()
{
  Serial.print("7");
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    fill_solid(leds, NUM_LEDS, CHSV(fullRainbowIndex, 255, brightnessLevels[systemBrightnessIndex]));
    fullRainbowIndex++;
    FastLED.show();
  }
}

void singleSpiral()
{
  Serial.print("8");
  if(millis() - spiralLastChange >= spiralChangeDirectionTime)
  {
    randomSeed(analogRead(A0));
    spiralChangeDirectionTime = random(5,11);
    spiralChangeDirectionTime *= 1000;
    spiralDirection = !spiralDirection;
    singleSpiralColour = random(256);
    spiralLastChange = millis();
  }
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    for(byte i = 0; i < NUM_LEDS; i++)
    {
      if(i%4 == spiralIndex)
      {
        leds[i] = CHSV(singleSpiralColour, 255, brightnessLevels[systemBrightnessIndex]);
      }
      else
      {
        leds[i] = CRGB::Black;
      }
    }
    if(spiralDirection)
    {
      spiralIndex++;
      if(spiralIndex == 4)
      {
        spiralIndex = 0;
      }
    }
    else
    {
      if(spiralIndex != 0)
      {
        spiralIndex--;
      }
      else
      {
        spiralIndex = 3;
      }
    }
    FastLED.show();
  }
}

void rainbowSpiral()
{
  Serial.print("9");
  if(millis() - spiralLastChange >= spiralChangeDirectionTime)
  {
    randomSeed(analogRead(A1));
    spiralChangeDirectionTime = random(5,11);
    spiralChangeDirectionTime *= 1000;
    spiralDirection = !spiralDirection;
    spiralLastChange = millis();
  }
  if(millis() - patternLastUpdate >= patternUpdateTime[currentPattern])
  {
    Serial.println("in");
    Serial.println(millis());
    Serial.println(freeRam());
    patternLastUpdate = millis();
    rainbowSpiralColour += 2;
    for(byte i = 0; i < NUM_LEDS; i++)
    {
      if(i%4 == spiralIndex)
      {
        leds[i] = CHSV(map(i,0,NUM_LEDS-1,0,255) + rainbowSpiralColour, 255, brightnessLevels[systemBrightnessIndex]);
      }
      else
      {
        leds[i] = CRGB::Black;
      }
    }
    if(spiralDirection)
    {
      spiralIndex++;
      if(spiralIndex == 4)
      {
        spiralIndex = 0;
      }
    }
    else
    {
      if(spiralIndex != 0)
      {
        spiralIndex--;
      }
      else
      {
        spiralIndex = 3;
      }
    }
    FastLED.show();
  }
}
// takes action based on IR code received
void translateIR() 
{
  switch(results.value)
  {
    case 0xFFA25D://POWER
      onOff = !onOff;
      if(!onOff)
      {
        FastLED.clear();
        FastLED.show();
      }
      break;
    case 0xFFE21D://FUNC/STOP
      randomMode = true;
      showcaseMode = false;
      resetFillTime();
      randomSeed(analogRead(A2));
      currentPattern = random(10);
      randomModeLastUpdate = millis();
      randomModeTime = random(6,13) * 1000;
      randomModeTime *= 10;
      FastLED.clear();
      FastLED.show();
      waveStartIndex = 0;
      fillTo = NUM_LEDS + 1;
      fillReset = true;
      fillStartColour = 0;
      break;
    case 0xFF629D://VOL+
      break;
    case 0xFF22DD://REWIND
      updateTime(true);
      digitalWrite(GREEN_LED,HIGH);
      break;
    case 0xFF02FD://PAUSE
      break;
    case 0xFFC23D://FAST FORWARD
      updateTime(false);
      digitalWrite(GREEN_LED,HIGH);
      break;
    case 0xFFE01F://DOWN
      if(systemBrightnessIndex != 0)
      {
        systemBrightnessIndex--;
      }
      digitalWrite(GREEN_LED,HIGH);
      break;
    case 0xFFA857://VOL-
      break;
    case 0xFF906F://UP
      if(systemBrightnessIndex != 8)
      {
        systemBrightnessIndex++;
      }
      digitalWrite(GREEN_LED,HIGH);
      break;
    case 0xFF9867://EQ
      break;
    case 0xFFB04F://ST/REPT      
      showcaseMode = true;
      randomMode = true;
      currentPattern = 255;
      randomModeLastUpdate = millis();
      break;
    case 0xFF6897://0
      resetFillTime();
      currentPattern = 0;
      resetForModeChange();
      break;
    case 0xFF30CF://1
      resetFillTime();
      currentPattern = 1;
      resetForModeChange();
      break;
    case 0xFF18E7://2
      resetFillTime();
      currentPattern = 2;
      resetForModeChange();
      waveStartIndex = 0;
      break;
    case 0xFF7A85://3
      resetFillTime();
      currentPattern = 3;
      resetForModeChange();
      waveStartIndex = 0;
      break;
    case 0xFF10EF://4
      resetFillTime();
      currentPattern = 4;
      resetForModeChange();
      fillTo = NUM_LEDS + 1;
      fillReset = true;
      fillStartColour = 0;
      break;
    case 0xFF38C7://5
      resetFillTime();
      currentPattern = 5;
      resetForModeChange();
      fillTo = NUM_LEDS + 1;
      fillReset = true;
      fillStartColour = 0;
      break;
    case 0xFF5AA5://6
      resetFillTime();
      currentPattern = 6;
      resetForModeChange();
      break;
    case 0xFF42BD://7
      resetFillTime();
      currentPattern = 7;
      resetForModeChange();
      break;
    case 0xFF4AB5://8
      resetFillTime();
      currentPattern = 8;
      resetForModeChange();
      break;
    case 0xFF52AD://9
      resetFillTime();
      currentPattern = 9;
      resetForModeChange();
      break;
    case 0xFFFFFFFF://REPEAT
      break;  
  }
  lastIRcodeTime = millis();
  canReceiveIRcode = false;
  if(!digitalRead(GREEN_LED))
  {
    digitalWrite(RED_LED,HIGH);
  }
}

void resetFillTime()
{
  if(currentPattern == 4)
  {
    patternUpdateTime[4] = 10;
  }
  if(currentPattern == 5)
  {
    patternUpdateTime[5] = 10;
  }
}

void resetForModeChange()
{
  randomMode = false;
  showcaseMode = false;
  digitalWrite(GREEN_LED,HIGH);
  patternLastUpdate = millis();
}

void updateRandom()
{
  if(randomMode && (millis() - randomModeLastUpdate >= randomModeTime))
  {
    if(currentPattern == 6 || currentPattern == 5)
    {
      patternUpdateTime[currentPattern] = 10;
    }
    if(!showcaseMode)
    {
      byte r = currentPattern;
      randomSeed(analogRead(A4));
      while(r == currentPattern)
      {
        currentPattern = random(10);
      }
      randomModeLastUpdate = millis();
      randomModeTime = random(6,13)*1000;
      randomModeTime *= 10;
      FastLED.clearData();
    }
    else //showcase mode
    {
      randomModeTime = showcaseTime;
      if(currentPattern < 9)
      {
        currentPattern++;
      }
      else
      {
        currentPattern = 0;
      }
      // if(currentPattern == 6 || currentPattern == 5)
      // {
      //   randomModeTime = 20000;
      // }
      Serial.println(currentPattern);
      randomModeLastUpdate = millis();
    }
    if(currentPattern == 6 || currentPattern == 5)
    {
      fillReset = true;
    }
  }
}

void updateTime(bool upPressed)
{
  if(!randomMode)
  {
    if(upPressed)
    {
      if(currentPattern == 0 || currentPattern == 1)
      {
        patternUpdateTime[currentPattern] += 1000;
        if(patternUpdateTime[currentPattern] > 5000)
        {
          patternUpdateTime[currentPattern] = 5000;
        }
      }
      else if(currentPattern == 6 || currentPattern == 5)
      {
        //do nothing
      }
      else
      {
        patternUpdateTime[currentPattern] += 50;
        if(patternUpdateTime[currentPattern] > 300)
        {
          patternUpdateTime[currentPattern] = 300;
        }
      }
    }
    else
    {
      if(currentPattern == 0 || currentPattern == 1)
      {
        patternUpdateTime[currentPattern] -= 1000;
        if(patternUpdateTime[currentPattern] < 1000)
        {
          patternUpdateTime[currentPattern] = 1000;
        }
      }
      else
      {
        patternUpdateTime[currentPattern] -= 50;
        if(patternUpdateTime[currentPattern] < 100)
        {
          patternUpdateTime[currentPattern] = 100;
        }
      }
    }
  }
}

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

For long programs please add your .ino file as an attachment. I am always wary of joining parts of a program in case I introduce another error.

Two things are most common for causing crashes - using the String class (I don't think it is in your program) and writing beyond the bounds of an array.

I am not familiar with the FastLED library.

...R

which IRremote.h are you using ? i can't compile your code. Also now that there is an issue in a sketch that uses the FASTLED library, with possible memory leak, why use that one if you are using a WS2812 ? People complain about "String" usage, but if you see what FASTLED does to your memory and how part of your object is separately declared i think using the Adafruit neopixel is much preferred if your chip is supported !
Anyway, i doubt you are having any actual memory issues unless you are writing beyond the size of any Array...
that is why i was wondering about this

fillTo = NUM_LEDS + 1;

what is fillTo used for ? i could only find a reference here in this map

CHSV(map(fillTo,0,NUM_LEDS-1,0,255)+fillStartColour,255,brightnessLevels[systemBrightnessIndex]);

I don't use the FASTLED very often but here i suspect might be your issue... really i'm guessing i don't know how the map function works, but i know there is no error check on assigning beyond the size of the Array.

Actually i had another look, i think this line is causing your problemif(fillIndex == fillTo)
in combination with fillTo = NUM_LEDS + 1;
i would say fillTo = NUM_LEDS;  // never beyond the end and
if(fillIndex >= fillTo) do not just reset it when it is spot on.

Deva, you are right that my usage of NUM_LEDS + 1 is an error. I was originally filling up the led strip one at a time, but that was slow so I ended up starting alternately at index 0 and then index 1. However I think that will only end up with me getting a slightly wrong colour.

However, this doesn't fix the problem because this is not the point at which I end up crashing. It crashes while it is waiting. For example my output might look something like:

00000000000000000000000in
0000000000000000000000in
000000000000000000000in
000000000000000000001
11111111111111111111in
1111111111111111111in
111111(crash here)

IRremote.h is the one by Rafi Khan. I am using PIO in VScode.

Robin, you are correct that I am not using String, and the crash does not happen when I am writing to an array.

I ended up changing WS2812 to WS2811 and it seems to work. So it looks like it was something to do with FastLED.

My wife will be pleased to have the xmas lights back (even though we are now past the actual date).

Thanks both for responding.

Actually come to think of it there was one point where I try to write to an address outside of the leds array due to the NUM_LEDS + 1 thing. Anyway, it works now by changing all the NUM_LEDS +1 to NUM_LEDS and the WS2812 to WS2811 (WS2812B also works too).