SRAM Memory Issues

Hi,
I'm having issues in optimising for the max SRam usage. Currently I'm hitting the ceiling and I've worked out what is causing the most usage but I don't know how to improve it. I'm using a nano so it has 2048 bytes of which I want to keep under 80% to reduce chance of runtime issues.

I've coupled together a few examples from FastLED libraries and modified a few to suit what I'm after. I'm using 4x 100 long strips all with individual LED arrays to individually address them.

I've noticed that when I reduce NUM_STRIPS it drops by 300bytes each increment. I would have thought this should only be 100bytes as it is only adding 100 more LEDs? Maybe a struct?
So this is taking up 1200bytes or 60% of my total, I would have though it would only take 400bytes? Is it possible to be able to reduce this by either moving to PROGMEM or recode so it's more optimised?

Note: I've commented out fire() as it was pushing it over.

Thanks for your help.

#include "FastLED.h"
#define LED_PIN0 5
#define LED_PIN1 6
#define LED_PIN2 7
#define LED_PIN3 8

const byte NUM_STRIPS = 4;
const byte NUM_LEDS = 100;
#define COLOR_ORDER GRB
#define CHIPSET WS2812B
#define BRIGHTNESS 100
#define GRAVITY -1   // Downward (negative) acceleration of gravity in m/s^2
#define h0 1.6       // Starting height, in meters, of the ball (strip length)
#define NUM_BALLS 8  // Number of bouncing balls you want (recommend < 7, but 20 is fun in its own way)

//BUTTON
const byte Button = 2;
byte ButtonActive = LOW;
byte ButtonHold;
byte ButtonLong = 0;
byte ButtonShort = 0;

//Bounce
float h[NUM_BALLS];                        // An array of heights
float vImpact0 = sqrt(-2 * GRAVITY * h0);  // Impact velocity of the ball when it hits the ground if "dropped" from the top of the strip
float vImpact[NUM_BALLS];                  // As time goes on the impact velocity will change, so make an array to store those values
float tCycle[NUM_BALLS];                   // The time since the last time the ball struck the ground
int pos[NUM_BALLS];                        // The integer position of the dot on the strip (LED index)
long tLast[NUM_BALLS];                     // The clock time of the last ground strike
float COR[NUM_BALLS];

//Fire
const byte COOLING = 70;
const byte SPARKING = 150;
bool gReverseDirection = false;

CRGB leds[NUM_STRIPS][NUM_LEDS];

struct colorstruct {
  byte color;
  byte sat;
  byte val;
};

byte mode; 
byte snakemode = 0;


unsigned long lastUpdate = 0;  // for millis() when last update occoured
unsigned long lastUpdate2 = 0;

uint8_t gHue = 0;  // rotating "base color" used by many of the patterns

void setup() {
  Serial.begin(9600);
  delay(2000);  // sanity check delay - allows reprogramming if accidently blowing power w/leds
  FastLED.addLeds<CHIPSET, LED_PIN0, COLOR_ORDER>(leds[0], NUM_LEDS);
  FastLED.addLeds<CHIPSET, LED_PIN1, COLOR_ORDER>(leds[1], NUM_LEDS);
  FastLED.addLeds<CHIPSET, LED_PIN2, COLOR_ORDER>(leds[2], NUM_LEDS);
  FastLED.addLeds<CHIPSET, LED_PIN3, COLOR_ORDER>(leds[3], NUM_LEDS);
  LEDS.setBrightness(BRIGHTNESS);

  pinMode(Button, INPUT_PULLUP);

  //Bounce
  for (int i = 0; i < (NUM_BALLS); i++) {  // Initialize variables
    tLast[i] = millis();
    h[i] = h0;
    pos[i] = 0;             // Balls start on the ground
    vImpact[i] = vImpact0;  // And "pop" up at vImpact0
    tCycle[i] = 0;
    COR[i] = 0.90 - float(i) / pow(NUM_BALLS, 2);
  }

  mode = 0; 
}

void loop() {

  static int animation = 0, lastReading;
  int reading = digitalRead(Button);
  if (lastReading == HIGH && reading == LOW) {
    animation++;                        // change animation number
    if (animation > 10) animation = 0;  // wrap round if too big
    FastLED.clear();
    FastLED.show();
    delay(50);  // debounce delay
  }
  lastReading = reading;  // save for next time

  updateanimation(animation);
}


void updateanimation(int ani) {
  switch (ani) {
    case 0:
      rainbow(30);
      break;
    case 1:
      drip(random(200, 2000));
      break;
    case 2:
      confetti(30, 300, 150, 200, 255);  // Fade speed, New light Speed, Colour, Saturation, Brightness
      break;
    case 3:
      lightning(random(10, 2000), 5, 0);  //Frequency between, speed (number added each update), pixel update rate
      break;
    case 4:
      snake(30);
      break;
    case 5:
      confetti(0, 200, 50, 150, 255);
      break;
    case 6:
      bounce();
      break;
    case 7:
      rainbow(30);
      addGlitter(80);
      break;
    case 8:
      juggle();
      break;
    case 9:
      bpm();
      break;
    case 10:
      //fire(20);
      break;
  }
}

void rainbow(int wait) {
  FastLED.show();
  if (millis() - lastUpdate >= wait) {
    gHue++;
    lastUpdate = millis();
  }
  for (int i = 0; i < NUM_STRIPS; i++) {
    fill_rainbow(leds[i], NUM_LEDS, gHue, 7);
  }
}

void drip(int freq) {
  static unsigned long lastUpdate;
  static int dripmode;
  int waitstart = 700;
  static int wait;
  int maxspeed = 20;
  static byte nextstep;

  if (nextstep < 0) {
    wait = waitstart;
    dripmode = random(0, 4);
    nextstep = NUM_LEDS - 1;
  }

  int step = nextstep;
  static int locked;
  static int freqnext;

  if (locked == 0) {
    switch (dripmode) {
      case 0:
        if (millis() - lastUpdate >= wait) {
          leds[0][step] = CHSV(uint8_t(150), 155, 150);
          wait = (max(wait - sqrt(wait), maxspeed)) / 1.8;
          lastUpdate = millis();
          FastLED.show();
          nextstep -= 1;
          if (nextstep < 0) {
            leds[0][0] = CRGB::Black;
            FastLED.show();
          }
          leds[0][step] = CRGB::Black;
        }
        break;
      case 1:
        if (millis() - lastUpdate >= wait) {
          leds[1][step] = CHSV(uint8_t(150), 155, 150);
          wait = (max(wait - sqrt(wait), maxspeed)) / 1.8;
          lastUpdate = millis();
          FastLED.show();
          nextstep -= 1;
          if (nextstep < 0) {
            leds[1][0] = CRGB::Black;
            FastLED.show();
          }
          leds[1][step] = CRGB::Black;
        }
        break;
      case 2:
        if (millis() - lastUpdate >= wait) {
          leds[2][step] = CHSV(uint8_t(150), 155, 150);
          wait = (max(wait - sqrt(wait), maxspeed)) / 1.8;
          lastUpdate = millis();
          FastLED.show();
          nextstep -= 1;
          if (nextstep < 0) {
            leds[2][0] = CRGB::Black;
            FastLED.show();
          }
          leds[2][step] = CRGB::Black;
        }
        break;
      case 3:
        if (millis() - lastUpdate >= wait) {
          leds[3][step] = CHSV(uint8_t(150), 155, 150);
          wait = (max(wait - sqrt(wait), maxspeed)) / 1.8;
          lastUpdate = millis();
          FastLED.show();
          nextstep -= 1;
          if (nextstep < 0) {
            leds[3][0] = CRGB::Black;
            FastLED.show();
          }
          leds[3][step] = CRGB::Black;
        }
        break;
    }
  }
  if (nextstep <= 0) {
    locked = 1;
    freqnext = freq;
  }
  if (locked == 1) {
    if (millis() - lastUpdate >= freqnext) {
      locked = 0;
    }
  }
}

void confetti(int wait, int wait2, int color, int sat, int val) {
  if (millis() - lastUpdate >= wait) {
    fadeToBlackBy(leds[0], NUM_LEDS, 5);
    fadeToBlackBy(leds[1], NUM_LEDS, 5);
    fadeToBlackBy(leds[2], NUM_LEDS, 5);
    fadeToBlackBy(leds[3], NUM_LEDS, 5);
    lastUpdate = millis();
  }
  if (millis() - lastUpdate2 >= wait2) {
    int pos0 = random16(NUM_LEDS);
    int pos1 = random16(NUM_LEDS);
    int pos2 = random16(NUM_LEDS);
    int pos3 = random16(NUM_LEDS);
    leds[0][pos0] += CHSV(color, sat, val);
    leds[1][pos1] += CHSV(color, sat, val);
    leds[2][pos2] += CHSV(color, sat, val);
    leds[3][pos3] += CHSV(color, sat, val);
    lastUpdate2 = millis();
  }
  FastLED.show();
}

void lightning(int freq, int speed, int wait) {
  static int lightmode;
  int colormode;
  static colorstruct color;
  static int nextstep;
  static int locked;
  static int freqnext;

  if (nextstep <= 0) {
    FastLED.clear();
    FastLED.show();
    lightmode = random(0, 4);
    colormode = random8(0, 100);
    nextstep = NUM_LEDS - 1;
    if (colormode < 55) {
      color = { 202, 0, 255 };  //White
    } else if (colormode < 70) {
      color = { 160, 150, 255 };  //Blue
    } else if (colormode < 85) {
      color = { 64, 150, 255 };  //Yellow
    } else if (colormode < 100) {
      color = { 210, 150, 255 };  //Violet
    }
  }

  if (locked == 0) {
    switch (lightmode) {
      case 0:
        if (millis() - lastUpdate >= wait) {
          for (int i = 0; i < speed; i++) {
            leds[0][nextstep] = CHSV(color.color, color.sat, color.val);
            nextstep -= 1;
          }
          lastUpdate = millis();
          FastLED.show();
        }
        break;
      case 1:
        if (millis() - lastUpdate >= wait) {
          for (int i = 0; i < speed; i++) {
            leds[1][nextstep] = CHSV(color.color, color.sat, color.val);
            nextstep -= 1;
          }
          lastUpdate = millis();
          FastLED.show();
        }
        break;
      case 2:
        if (millis() - lastUpdate >= wait) {
          for (int i = 0; i < speed; i++) {
            leds[2][nextstep] = CHSV(color.color, color.sat, color.val);
            nextstep -= 1;
          }
          lastUpdate = millis();
          FastLED.show();
        }
        break;
      case 3:
        if (millis() - lastUpdate >= wait) {
          for (int i = 0; i < speed; i++) {
            leds[3][nextstep] = CHSV(color.color, color.sat, color.val);
            nextstep -= 1;
          }
          lastUpdate = millis();
          FastLED.show();
        }
        break;
    }
  }
  if (nextstep <= 0) {
    locked = 1;
    freqnext = freq;
  }
  if (locked == 1) {
    if (millis() - lastUpdate >= freqnext) {
      locked = 0;
    }
  }
}

void snakedirection(int wait, int fade, int direction, int stripno) {
  static int nextstep;
  switch (direction) {
    case 0:
      if (millis() - lastUpdate >= wait) {
        leds[stripno][nextstep] = CHSV(uint8_t(110), 255, 250);
        for (int i = 0; i < NUM_STRIPS; i++) {
          fadeToBlackBy(leds[i], NUM_LEDS, fade);
        }
        FastLED.show();
        nextstep -= 1;
        lastUpdate = millis();
        if (nextstep < 0) {
          direction = 1;
          nextstep = 0;
          snakemode += 1;
          if (snakemode > 23) {
            snakemode = 0;
          }
        }
      }
      break;
    case 1:
      if (millis() - lastUpdate >= wait) {
        leds[stripno][nextstep] = CHSV(uint8_t(110), 255, 250);
        for (int i = 0; i < NUM_STRIPS; i++) {
          fadeToBlackBy(leds[i], NUM_LEDS, fade);
        }
        FastLED.show();
        nextstep += 1;
        lastUpdate = millis();
        if (nextstep >= NUM_LEDS) {
          direction = 0;
          nextstep = 99;
          snakemode += 1;
          if (snakemode > 23) {
            snakemode = 0;
          }
        }
      }
  }
}

void snake(int speed) {
  //go down one strip then up the next
  switch (snakemode) {
    case 0:
      snakedirection(speed, 110, 1, 0);
      break;
    case 1:
      snakedirection(speed, 95, 0, 1);
      break;
    case 2:
      snakedirection(speed, 80, 1, 2);
      break;
    case 3:
      snakedirection(speed, 70, 0, 3);
      break;
    case 4:
      snakedirection(speed, 60, 1, 2);
      break;
    case 5:
      snakedirection(speed, 50, 0, 1);
      break;
    case 6:
      snakedirection(speed, 40, 1, 0);
      break;
    case 7:
      snakedirection(speed, 32, 0, 1);
      break;
    case 8:
      snakedirection(speed, 25, 1, 2);
      break;
    case 9:
      snakedirection(speed, 20, 0, 3);
      break;
    case 10:
      snakedirection(speed, 15, 1, 2);
      break;
    case 11:
      snakedirection(speed, 10, 0, 1);
      break;
    case 12:
      snakedirection(speed, 10, 1, 0);
      break;
    case 13:
      snakedirection(speed, 15, 0, 1);
      break;
    case 14:
      snakedirection(speed, 20, 1, 2);
      break;
    case 15:
      snakedirection(speed, 25, 0, 3);
      break;
    case 16:
      snakedirection(speed, 32, 1, 2);
      break;
    case 17:
      snakedirection(speed, 40, 0, 1);
      break;
    case 18:
      snakedirection(speed, 50, 1, 0);
      break;
    case 19:
      snakedirection(speed, 60, 0, 1);
      break;
    case 20:
      snakedirection(speed, 70, 1, 2);
      break;
    case 21:
      snakedirection(speed, 80, 0, 3);
      break;
    case 22:
      snakedirection(speed, 95, 1, 2);
      break;
    case 23:
      snakedirection(speed, 110, 0, 1);
      break;
  }
}

void bounce() {
  for (int i = 0; i < NUM_BALLS; i++) {
    tCycle[i] = millis() - tLast[i];  // Calculate the time since the last time the ball was on the ground
    // A little kinematics equation calculates positon as a function of time, acceleration (gravity) and intial velocity
    h[i] = 0.5 * GRAVITY * pow(tCycle[i] / 1000, 2.0) + vImpact[i] * tCycle[i] / 1000;

    if (h[i] < 0) {
      h[i] = 0;                          // If the ball crossed the threshold of the "ground," put it back on the ground
      vImpact[i] = COR[i] * vImpact[i];  // and recalculate its new upward velocity as it's old velocity * COR
      tLast[i] = millis();

      if (vImpact[i] < 0.01) vImpact[i] = vImpact0;  // If the ball is barely moving, "pop" it back up at vImpact0
    }
    pos[i] = round(h[i] * (NUM_LEDS - 1) / h0);  // Map "h" to a "pos" integer index position on the LED strip
  }

  leds[0][pos[0]] = CHSV(uint8_t(0 * 40), 255, 255);
  leds[0][pos[4]] = CHSV(uint8_t(4 * 40), 255, 255);
  leds[1][pos[1]] = CHSV(uint8_t(1 * 40), 255, 255);
  leds[1][pos[5]] = CHSV(uint8_t(5 * 40), 255, 255);
  leds[2][pos[2]] = CHSV(uint8_t(2 * 40), 255, 255);
  leds[2][pos[6]] = CHSV(uint8_t(6 * 40), 255, 255);
  leds[3][pos[3]] = CHSV(uint8_t(3 * 40), 255, 255);
  leds[3][pos[7]] = CHSV(uint8_t(7 * 40), 255, 255);

  FastLED.show();

  for (int x = 0; x < NUM_STRIPS; x++) {
    for (int i = 0; i < NUM_BALLS; i++) {
      leds[x][pos[i]] = CRGB::Black;
    }
  }
}

void addGlitter(fract8 chanceOfGlitter) {
  if (random8() < chanceOfGlitter) {
    for (int i = 0; i < NUM_STRIPS; i++) {
      leds[i][random16(NUM_LEDS)] += CRGB::White;
    }
  }
  FastLED.show();
}

void juggle() {
  // eight colored dots, weaving in and out of sync with each other
  for (int i = 0; i < NUM_STRIPS; i++) {
    fadeToBlackBy(leds[i], NUM_LEDS, 20);
  }
  byte dothue = 0;
  for (int i = 0; i < 8; i++) {
    for (int a = 0; a < NUM_STRIPS; a++) {
      leds[a][beatsin16(i + 7, 0, NUM_LEDS - 1)] |= CHSV(dothue, 200, 255);
      dothue += 32;
    }
  }
  FastLED.show();
}

void bpm() {
  // colored stripes pulsing at a defined Beats-Per-Minute (BPM)
  uint8_t BeatsPerMinute = 62;
  CRGBPalette16 palette = PartyColors_p;
  uint8_t beat = beatsin8(BeatsPerMinute, 64, 255);
  for (int i = 0; i < NUM_LEDS; i++) {  //9948
    for (int a = 0; a < NUM_STRIPS; a++) {
      leds[a][i] = ColorFromPalette(palette, gHue + (i * a), beat - gHue + (i * 10));
    }
  }
  FastLED.show();
}

void fire(int wait) {
  // Array of temperature readings at each simulation cell
  static byte heat0[NUM_LEDS];
  static byte heat1[NUM_LEDS];
  static byte heat2[NUM_LEDS];
  static byte heat3[NUM_LEDS];
  //static unsigned long lastUpdate;
  if (millis() - lastUpdate >= wait) {
    // Step 1.  Cool down every cell a little
    for (int i = 0; i < NUM_LEDS; i++) {
      heat0[i] = qsub8(heat0[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
      heat1[i] = qsub8(heat1[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
      heat2[i] = qsub8(heat2[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
      heat3[i] = qsub8(heat3[i], random8(0, ((COOLING * 10) / NUM_LEDS) + 2));
    }

    // Step 2.  Heat from each cell drifts 'up' and diffuses a little
    for (int k = NUM_LEDS - 1; k >= 2; k--) {
      heat0[k] = (heat0[k - 1] + heat0[k - 2] + heat0[k - 2]) / 3;
      heat1[k] = (heat1[k - 1] + heat1[k - 2] + heat1[k - 2]) / 3;
      heat2[k] = (heat2[k - 1] + heat2[k - 2] + heat2[k - 2]) / 3;
      heat3[k] = (heat3[k - 1] + heat3[k - 2] + heat3[k - 2]) / 3;
    }

    // Step 3.  Randomly ignite new 'sparks' of heat near the bottom
    if (random8() < SPARKING) {
      int y = random8(7);
      heat0[y] = qadd8(heat0[y], random8(160, 255));
      heat1[y] = qadd8(heat1[y], random8(160, 255));
      heat2[y] = qadd8(heat2[y], random8(160, 255));
      heat3[y] = qadd8(heat3[y], random8(160, 255));
    }

    // Step 4.  Map from heat cells to LED colors
    for (int j = 0; j < NUM_LEDS; j++) {
      CRGB color0 = HeatColor(heat0[j]);
      CRGB color1 = HeatColor(heat1[j]);
      CRGB color2 = HeatColor(heat2[j]);
      CRGB color3 = HeatColor(heat3[j]);
      int pixelnumber;
      if (gReverseDirection) {
        pixelnumber = (NUM_LEDS - 1) - j;
      } else {
        pixelnumber = j;
      }
        leds[0][pixelnumber] = color0;
        leds[1][pixelnumber] = color1;
        leds[2][pixelnumber] = color2;
        leds[3][pixelnumber] = color3;
    }

    lastUpdate = millis();
  }
  FastLED.show();
}

PROGMEM is not suitable for such use, nor EEPROM. Keep in mind also that the lifetime of PROGMEM is about 10k cycles and 100k for EEPROM.
You have to optimize your code or move to a bigger MCU.

1 Like

...is only four bytes but it is const. Every little bit helps.

Do you really need 32 bit color data for each LED? (hint: the answer is "no")

1 Like

One LED has three bytes of storage for red, green, and blue, right?

1 Like

CRGB is a struct that has a byte for each of red, blue and green, so 3 bytes per pixel (LED).

1 Like

Of course RGB has three bytes per pixel, when staring at it for a while you lose clarity! There is a way to change it to CHSV but that still has three bytes (hue, sat, val).
It looks like I will have to optimise more, cut out animations or get a larger microprocessor.
Thanks for your help everyone.

It looks like there are some minor savings using byte rather than int here and there, but it's not likely to help enough. Your idea of getting better hardware seems like a very good one.

Yeah I've just gone through it and saved about 11 bytes by changing from int to byte haha. Unfortunately not close to enough.
Seeing as I'm using a nano I can swap out to a 'nano every' with triple the ram compared to the basic nano. I didn't realise they existed until just now when I started searching for a higher capacity board.
Another good thing is it will fit into my header on my soldered board and inside my 3d printed case.
https://store.arduino.cc/usa/nano-every