Suggestions on reducing memory requirements using CRGB

With some help on this forum, I’ve got a small routine that flashes programmable LEDs with different patterns to work. I plan to merge this code with other code that makes use of a keypad to determine what messages / patterns get displayed as well as code that alternates individual lights in a specific order.

However, this code alone is pushing the limits of my card’s memory (see message below).
Any help on how to get this to work using less memory would be appreciated.

“Sketch uses 8878 bytes (27%) of program storage space. Maximum is 32256 bytes.
Global variables use 1599 bytes (78%) of dynamic memory, leaving 449 bytes for local variables. Maximum is 2048 bytes. Low memory available, stability problems may occur.”

And yes…I will be deleting the print statements used for debugging.

// Big thanks to david_2018 on Arduino forum for getting the pointer fun to work

#define NUM_ARRAYS  7
#define ARRAY_SIZE  50

//#include <HashMap.h>
#include <Arduino.h>
#include <FastLED.h>

#define DATA_PIN 7  //this is the data pin connected to the LED strip.  If using WS2801 you also need a clock pin
#define NUM_LEDS 50 //change this for the number of LEDs in the strip
#define COLOR_ORDER RGB

CRGB leds[NUM_LEDS];

const CRGB red    = CRGB (255, 0, 0);
const CRGB green  = CRGB (0, 255, 0);
const CRGB blue   = CRGB(0, 0, 255);
const CRGB orange = CRGB(255, 165, 0);
const CRGB white  = CRGB(255, 255, 255);
const CRGB off    = CRGB(0, 0, 0);

//define an enum so patterns can be referred to by their names
enum patterns {
  RedOnly_leds,
  GreenOnly_leds,
  BlueOnly_leds,
  OrangeOnly_leds,
  WhiteOnly_leds,
  RGBOW_leds,
  AllOff_leds,
  NUM_patterns //used to indicate the number of patterns
};

//this array is only needed if you want to print out the names corresponding to the pattern arrays
const char patternNames[][16] = {
  "RedOnly_leds",
  "GreenOnly_leds",
  "BlueOnly_leds",
  "OrangeOnly_leds",
  "WhiteOnly_leds",
  "RGBOW_leds",
  "AllOff_leds"
};

// Red, Green, Blue, Orange, White with alternating offs Sequence
CRGB patternArrays[NUM_patterns][ARRAY_SIZE] = {


  // only the RED bulbs (in same position as when all lit)
  //RedOnly_leds
  { red, off, off, off, off, off, off, off, off, off, off, off,
    red, off, off, off, off, off, off, off, off, off, off, off,
    red, off, off, off, off, off, off, off, off, off, off, off,
    red, off, off, off, off, off, off, off, off, off, off, off,
    red, off
  },

  // only the GREEN bulbs (in same position as when all lit)
  //GreenOnly_leds
  { off, off, green, off, off, off, off, off, off, off, off, off,
    off, off, green, off, off, off, off, off, off, off, off, off,
    off, off, green, off, off, off, off, off, off, off, off, off,
    off, off, green, off, off, off, off, off, off, off, off, off,
    off, green
  },

  // only the BLUE bulbs (in same position as when all lit)
  //BlueOnly_leds
  { off, off, off, off, blue, off, off, off, off, off, off, off,
    off, off, off, off, blue, off, off, off, off, off, off, off,
    off, off, off, off, blue, off, off, off, off, off, off, off,
    off, off, off, off, blue, off, off, off, off, off, off, off,
    off, off
  },

  // only the ORANGE bulbs (in same position as when all lit)
  //OrangeOnly_leds
  { off, off, off, off, off, off, orange, off, off, off, off, off,
    off, off, off, off, off, off, orange, off, off, off, off, off,
    off, off, off, off, off, off, orange, off, off, off, off, off,
    off, off, off, off, off, off, orange, off, off, off, off, off,
    off, off
  },

  // only the WHITE bulbs (in same position as when all lit)
  //WhiteOnly_leds
  { off, off, off, off, off, off, off, off, white, off, off, off,
    off, off, off, off, off, off, off, off, white, off, off, off,
    off, off, off, off, off, off, off, off, white, off, off, off,
    off, off, off, off, off, off, off, off, white, off, off, off,
    off, off
  },

  //RGBOW_leds
  { red, off, green, off, blue, off, orange, off, white, off, green, off,
    red, off, green, off, blue, off, orange, off, white, off, green, off,
    red, off, green, off, blue, off, orange, off, white, off, green, off,
    red, off, green, off, blue, off, orange, off, white, off, green, off,
    red, green
  },

  //AllOff_leds
  { off, off, off, off, off, off, off, off, off, off, off, off,
    off, off, off, off, off, off, off, off, off, off, off, off,
    off, off, off, off, off, off, off, off, off, off, off, off,
    off, off, off, off, off, off, off, off, off, off, off, off,
    off, off
  }
};

patterns i;

void setup( void )
{
  Serial.begin(9600);
  FastLED.addLeds<WS2811, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS); //setting up the FastLED
  //for( int i=0; i<NUM_ARRAYS; i++ )
  //{
  //    pArrayPtr = (int *)pgrArrayPtrs[i];
  //    flash_pattern( pArrayPtr );
  //}
}

void loop( void )
{
  // Set lights manually to RGBOW colors at the start
  memcpy (leds, patternArrays[RGBOW_leds], sizeof patternArrays[RGBOW_leds]);
  FastLED.show();
  delay(5000);
  //for (i = (patterns)0; i < NUM_patterns; i = (patterns)(i + 1)) {
    //cycles through all the patterns
    //the (pattern) type casting is needed because the compiler doesn't like incrementing an enum directly
    int user_input1 = 2;
    flash_pattern((patterns)user_input1);
    Serial.println( "got here" );
    Serial.println( patternNames[user_input1] );

 const char   user_input2 = OrangeOnly_leds;
    flash_pattern((patterns)user_input2);
    Serial.println( "got here" );
    Serial.println( patternNames[user_input2] );
    delay(4000);
  //}
}

void flash_pattern( patterns pPtr ) {  //flash the pattern passed into this function, then off with 4 seconds delay
  memcpy (leds, patternArrays[pPtr], sizeof patternArrays[pPtr] );
  FastLED.show();
}

Look into PROGMEM for array storage and F() for serial printing constant literals.

There are a couple of difficulties with storing your arrays in PROGMEM. The first is that the compiler doesn't want to store a constant of type CRGB into PROGMEM (probably because of the way it is declared in FastLED). The other is that you are using memcpy to copy the array into the leds array, which requires that the colors be stored as 24-bit integers. It is possible to use 24-bit integers with an arduino, but that introduces an additional problem because integers are stored least-significant-byte first, which means that the colors will be stored as BGR instead of RGB, so a direct copy will no longer work.

The method I've used is to declare a three-byte structure to hold the red, green, and blue values, then use that as the type for the arrays. That gives you the 24-bits per pixel, and puts the colors in the correct order so that memcpy can be used.

You can modify your code to use that technique, first you need to define the structure, then declare your colors a bit differently:

//const CRGB red    = CRGB (255, 0, 0);
//const CRGB green  = CRGB (0, 255, 0);
//const CRGB blue   = CRGB(0, 0, 255);
//const CRGB orange = CRGB(255, 165, 0);
//const CRGB white  = CRGB(255, 255, 255);
//const CRGB off    = CRGB(0, 0, 0);

struct myCRGB {
  byte _red;
  byte _green;
  byte _blue;
};

const myCRGB red    = {255, 0, 0};
const myCRGB green  = {0, 255, 0};
const myCRGB blue   = {0, 0, 255};
const myCRGB orange = {255, 165, 0};
const myCRGB white  = {255, 255, 255};
const myCRGB off    = {0, 0, 0};

The array is then declared as:

//CRGB patternArrays[NUM_patterns][ARRAY_SIZE] = {
const myCRGB patternArrays[NUM_patterns][ARRAY_SIZE] PROGMEM = {

You also need to change the memcpy command to memcpy_P to let the compiler know the source of the copy is stored in PROGMEM.

I just go with a Teensy 3.2 board. You get a more capable 32-bit ARM core running at a higher clock rate with way more resources. Plus PJRC has a DMA-based library that interfaces with FastLED. It pushes "Neopixel" signaling out of a UART port with barely any processor involvement and interrupts remain enabled.

David,

Thank you.
The Print F( improved things a bit:
WAS: Global variables use 1951 bytes (95%) of dynamic memory,
IS: Global variables use 1685 bytes (82%) of dynamic memory,

The memcpy_P changes and struct changes were made, but now other parts of the code are giving me errors that I think are related to these changes, but I’m not sure how to correct them. For example:

Testlayout5b_struct_memcpy_P:479:18: error: no match for ‘operator=’ (operand types are ‘CRGB’ and ‘const myCRGB’)

leds[lednum] = off;

See Attached File for complete source code

Testlayout5b_struct_memcpy_P.ino (16.6 KB)

Made a couple of little changes that should get it working for the moment, don’t have time tonight to go through your code thoroughly, do see a few things that seem a bit convoluted. Basically you can’t do a straight assignment from a structure to the CRGB of the leds array. There is a pre-defined CRGB::Black in FastLED that can be used instead of your “off” constant, I noted that in a comment in the code.

Testlayout5b_struct_memcpy_P_modified.ino (16.4 KB)

I’m out of my league here but it looks like it might be possible to “compress” the arrays.

For example, the white array could be

4 x 0 (off for 4 “steps”)
1 x 1 (on for 1)
11 x 0 (off for 11 “steps”)
etc.

Just a thought.

JohnRob:
I'm out of my league here but it looks like it might be possible to "compress" the arrays.

For example, the white array could be

4 x 0 (off for 4 "steps")
1 x 1 (on for 1)
11 x 0 (off for 11 "steps")
etc.

Just a thought.

With a limited number of colors that would work, assign each color a number and use that as an index into an array of RGB values. Does make retrieval a bit more complex and slower. Sixteen colors would only need four bits per pixel, so 50 leds could be stored in 25 bytes.

since thisCRGB patternArrays[NUM_patterns][ARRAY_SIZE] is taking up the bulk of you RAM (7 x 50 X 3 = 1050 bytes) and you are actually only using 6 different values

const myCRGB red    = {255, 0, 0};
const myCRGB green  = {0, 255, 0};
const myCRGB blue   = {0, 0, 255};
const myCRGB orange = {255, 165, 0};
const myCRGB white  = {255, 255, 255};
const myCRGB off    = {0, 0, 0};

to put in those 3 bytes, there is a lot wasted there, for the sake of being able to add more colors.
But actually really you should be creating a pattern like this using a loop of some kind rather than this way

{ red, off, off, off, off, off, off, off, off, off, off, off,
    red, off, off, off, off, off, off, off, off, off, off, off,
    red, off, off, off, off, off, off, off, off, off, off, off,
    red, off, off, off, off, off, off, off, off, off, off, off,
    red, off
  },

on the spot and don’t bother about the whole memcpy, the progmen etc.

for (uint8_t i=0; i<NUM_LEDS; i++) {
if (i%12) leds[i] = off;
else leds[i] = red;
}
FastLED.show();

Sketch uses 8878 bytes (27%) of program storage space. Maximum is 32256 bytes.

And with all your text and const data kept in flash you still have room for 400 patterns with color as 1 byte to 800 patterns with color as 1 nybble.

You can also use algorithms to make light patterns and effects like moving patterns. I’ve started to play with that, got 12mm string leds (50 per string until I cut or splice… you have same?) two days ago and had to 'speriment. Next thing I have in mind is morphing colors from one pattern to the next pattern, all the leds “at the same time” to get a flow look.

I haven’t gone to PROGMEM with this, algos don’t take much RAM.
It’s not the best effects but you might like how it works… or not.

#include <FastLED.h>

// LED LIGHTING SETUP
#define DATA_PIN  7
#define NUM_LEDS  50
#define BRIGHTNESS  128
#define LED_TYPE    WS2811
#define COLOR_ORDER RGB
CRGB leds[NUM_LEDS], tRGB;

byte idx;
int pattern;
const int patterns = 250;

// human eye sees motion at 24 FPS, TV is 30FPS
#define UPDATES_PER_SECOND  30
// leave cycles for more strips, Uno/Nano should run many with fast code


void setup()
{
  // LED LIGHTING SETUP
  FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
  FastLED.setBrightness(200);
}


void loop()
{
  static unsigned long tStart, tWait = 250;

  switch ( pattern )
  {
    case 0 :
      leds[ idx ].red = idx * 5;
      leds[ idx ].green = 250 - ( idx * 5 );
      leds[ idx ].blue = 0;
      break;

    case 50 :
      leds[ idx ].green = idx * 5;
      leds[ idx ].blue = 250 - ( idx * 5 );
      leds[ idx ].red = 0;
      break;

    case 100 :
      leds[ idx ].blue = idx * 5;
      leds[ idx ].red = 250 - ( idx * 5 );
      leds[ idx ].green = 0;
      break;

    case 150 :
      switch ( idx % 6 )
      {
        case 0 :
          leds[ idx ].red = 127;
          leds[ idx ].green = 0;
          leds[ idx ].blue = 0;
          break;
        case 1 :
          leds[ idx ].red = 63;
          leds[ idx ].green = 63;
          leds[ idx ].blue = 0;
          break;
        case 2 :
          leds[ idx ].red = 0;
          leds[ idx ].green = 127;
          leds[ idx ].blue = 0;
          break;
        case 3 :
          leds[ idx ].red = 0;
          leds[ idx ].green = 63;
          leds[ idx ].blue = 63;
          break;
        case 4 :
          leds[ idx ].red = 0;
          leds[ idx ].green = 0;
          leds[ idx ].blue = 127;
          break;
        case 5 :
          leds[ idx ].red = 63;
          leds[ idx ].green = 0;
          leds[ idx ].blue = 63;
          break;
      }
      break;

    case 200 :
      switch ( idx / 9 )
      {
        case 0 :
          leds[ idx ].red = 127;
          leds[ idx ].green = 0;
          leds[ idx ].blue = 0;
          break;
        case 1 :
          leds[ idx ].red = 63;
          leds[ idx ].green = 63;
          leds[ idx ].blue = 0;
          break;
        case 2 :
          leds[ idx ].red = 0;
          leds[ idx ].green = 127;
          leds[ idx ].blue = 0;
          break;
        case 3 :
          leds[ idx ].red = 0;
          leds[ idx ].green = 63;
          leds[ idx ].blue = 63;
          break;
        case 4 :
          leds[ idx ].red = 0;
          leds[ idx ].green = 0;
          leds[ idx ].blue = 127;
          break;
        case 5 :
          leds[ idx ].red = 63;
          leds[ idx ].green = 0;
          leds[ idx ].blue = 63;
          break;
      }
      break;

    default :
      if ( idx == 0 )
      {
        tRGB = leds[ NUM_LEDS - 1 ];
        leds[ NUM_LEDS - 1 ] = leds[ NUM_LEDS - 2 ];
      }
      else if ( idx == NUM_LEDS - 1 )
      {
        leds[ 0 ] = tRGB;
      }
      else
      {
        leds[ NUM_LEDS - idx - 1 ] = leds[ NUM_LEDS - idx - 2 ];
      }
  }

  if ( idx < NUM_LEDS - 1 )
  {
    idx++;
  }
  else
  {
    if ( pattern < 100 )
    {
      tWait = 180;
    }
    if ( pattern < 200 )
    {
      tWait = 120;
    }
    else
    {
      tWait = 240;
    }

    if ( millis() - tStart >= tWait )
    {
      FastLED.show();
      idx = 0;
      if ( pattern == patterns )
      {
        pattern = 150;
      }
      else
      {
        pattern++;
      }
      tStart = millis();
    }
  }
}

Your LED data arrays contain a lot of repeating values/patterns...
I’d be looking at calculating those values withban algorithm at runtime - to eliminate the arrays completely.

Wow!

Deva_Rishi, what a good idea. Very simple.

david_2018 I can't believe how low you got the memory down to.
What did you change? it wasn't obvious to me, but whatever it was it made a HUGE difference.

Dan

Practice, Experience and patience.
Works every time !

Just using the print( F( "this text stays in flash" )) vs print( "this text copies to RAM at startup" ) can save a lot of RAM.

Putting tables in PROGMEM can save even more.

Thank you all for the help, especially David_2018. It’s working great.
I’ve since added a 3x4 keypad and cleaned a few things up.
Last major change is to now replace the few hard coded values I still have in my code and use the keypad input to look up the arrays and send the correct color patter to flash_pattern and the correct item name to be spelled out in the function “type”

Still having some challenges properly using the pointers, and could use some help understanding the proper format. For Example:

I need use the 2 digit keypad returned value to select a color to flash and an item to spell out. Currently I have these hard coded until I can better understand how to use the pointers correctly. Areas I could use help with using the pointers are:

  1. How to pass the 2 digit returned value of keypad_value to the LED display
    display.showNumberDec(keypad_value, false); // Expect: ___keypad_total // Not working now

  2. How to use keypad_value to find the correct color array to pass to the function “flash_pattern”
    Array currently being used is: Array1[ITEM_ARRAY_SIZE] = { 1, 2, 3, 4, 5 };
    Tried using this, but it din’t work flash_pattern(Array2[keypad_value - 1]);

  3. How to pass the item selected from the keypad entry “(Array2[keypad_value - 1])” into the function “type” as in the line of code: type(item); to spell out the item corresponding to the keypad entry value.

Code attached.

Thanks in advance for helping me relearn the proper use of pointers with Arduino.

Testlayout5d1.ino (14.8 KB)

The name of an array is a pointer.

Well I’m certainly doing something wrong. For example, the following line works:
flash_pattern((patterns)user_input2); where user_input2 is a set fixed value.

But when I try to use the keypad returned value to get a pointer to the pattern in the array called Array2, as shown below:

flash_pattern(Array2[keypad_value - 1]);

I get teh following error message:
cannot convert ‘char*’ to ‘patterns’ for argument ‘1’ to ‘void flash_pattern(patterns)’

Function for flash_pattern is as follows:

void flash_pattern( patterns pPtr ) {  //flash the pattern passed into this function, then off with 4 seconds delay
  for (int j = 1; j <= flash_numloops; j++) {
    memcpy_P (leds, patternArrays[pPtr], sizeof patternArrays[pPtr] );
    FastLED.show();
    delay(flash_on_time);

    memcpy_P (leds, patternArrays[AllOff_leds], sizeof patternArrays[AllOff_leds]);
    FastLED.show();
    delay(flash_off_time);
  }
  // Reload xmass pattern but do not display.  Needed for spelling to work
  memcpy_P (leds, patternArrays[pPtr], sizeof patternArrays[pPtr] );

}

Try

flash_pattern((patterns)Array1[keypad_value - 1]);

Array2 is an array of pointers to character strings, not your array of numbers. Incidentally, unless you intend to put the numbers out of sequence, you really don't need Array1 anyway.

int  Array1[ITEM_ARRAY_SIZE] = { 1, 2, 3, 4, 5 };
char *Array2[ITEM_ARRAY_SIZE] = {"dog", "cat", "Horse", "Ant", "Firetruck"};

Have not had time to look at the rest of the code you posted last night, will look it over later.

Thank you David.

So what the code is supposed to do is:

  1. Get a 2 digit number from a keypad (wait for some switching to start the light show, whish is workign in my code)
  2. Use the 2 digit number from a keypad to look up a color pattern to flash the lights that are of that color using the function "flash_pattern"
  3. Then spell out the item selected using the "type" function.

So a specific example from the code attached above in a previous reply would be:

  1. Person enters 03 on the keypad.

  2. That would mean, from Array1, that the item color pattern to use is pattern 3
    Then from patternArrays the color to send to flash_pattern" would be 03 - 1 = OrangeOnly_leds
    (I subtract 1 to appropriately index the array which of course starts with a value of 0)
    So only the orange lights would flash on and off
    (Yeah, there's probably a less convoluted way to do that)

  3. Then, looking up value 03 - 1 in *Array2 (I subtract 1 to appropriately index the array which of course starts with a value of 0) and the word assigned to that index is "horse" so the characters "horse" would need to be sent from Array2 to the function "type" to type out the letters, one at a time, using the LED lights.

Does that make sense what I am trying to do?

Perhaps in a separate sketch that stores the arrays you print the contents of each in setup().
The code you will need to do that will give the lines to do this current project.

Don't forget pointer math, you can add offsets to any pointer and the result is still a pointer.

int arrayX = { 1,2,3,4 };

int x = *( arrayX + 3 ); // the *() gets the value at the pointer inside, it will be 4

The first value, 1, is at address of arrayX. The offset is 0, the int is at the address -- *(arrayX+0) is arrayX[0], same place in memory, all arrays start at 0 because the first value is at the array address. I hope this is clear.

The offset will be as many bytes as the variable type. Char pointer offsets are 1 byte, int pointer offsets are 2, etc.

I do get down to pointers and offsets occasionally, especially when addressing PROGMEM. It's more clear to me. Your mileage may vary.