Adding LED arrays inside setup

Hi,

I am trying to add about 400 LEDs to my project. This gives me a low memory warning, and it turns out that the program does not work with that many LEDs (using Arduino Nano).

To add the LED`s I created globally arrays and added them in the setup function. Since the arrays where global they ate a lot of memory. So I tried to create the arrays inside the setup function and then add them to FastLED like so (for simplicity I only add some array in this example) :

void setup()
{ 
      CRGB leds1[10];
      CRGB leds2[14];
      CRGB leds3[11];
      CRGB leds4[30];

      FastLED.addLeds<NEOPIXEL, 2>(leds1, 10);
      FastLED.addLeds<NEOPIXEL, 3>(leds2, 14);
      FastLED.addLeds<NEOPIXEL, 3>(leds3, 11);
      FastLED.addLeds<NEOPIXEL, 4>(leds4, 30);
}

In the loop function I access them like so:

        //i = LED strip number, led_num = specific LED on strip
        FastLED[i].leds()[led_num] = CRGB::White;
        FastLED.show();

This seems to work fine as long as I only add 3 arrays. If I add more than that the LED strips shows all kind of colors. Anyone know what the problem could be, or have any other solutions to the problem?

Thanks for any help and guidance!
Each LED strip must be controlled by it`s own data pin.

What type of strip? Ws2811? Ws2812? Why did you post only some of your code?

The only question we don't need to ask is "did you read the forum guide before you posted?"

Thanks for your reply, and sorry for missing information. It is WS2812B LED`s.
Code:

#include <FastLED.h>

#define STEP_1_PIN 2
#define STEP_2_PIN 3
#define STEP_3_PIN 4
#define STEP_4_PIN 5
#define STEP_5_PIN 6
#define STEP_6_PIN 7
#define STEP_7_PIN 8
#define STEP_8_PIN 9
#define STEP_9_PIN 10
#define STEP_10_PIN 11
#define STEP_11_PIN 12
#define STEP_12_PIN A0
#define STEP_13_PIN A1
#define STEP_14_PIN A2
#define STEP_15_PIN A3
#define LIGHT_SENSOR_PIN A4

#define STEP_1_NUM_LEDS 22
#define STEP_2_NUM_LEDS 23
#define STEP_3_NUM_LEDS 24
#define STEP_4_NUM_LEDS 24
#define STEP_5_NUM_LEDS 27
#define STEP_6_NUM_LEDS 32
#define STEP_7_NUM_LEDS 26
#define STEP_8_NUM_LEDS 24
#define STEP_9_NUM_LEDS 26
#define STEP_10_NUM_LEDS 32
#define STEP_11_NUM_LEDS 28
#define STEP_12_NUM_LEDS 24
#define STEP_13_NUM_LEDS 23
#define STEP_14_NUM_LEDS 23
#define STEP_15_NUM_LEDS 22
#define NUM_STEPS 4

//CRGB* steps[15];

void initLeds()
{
  for(int i = 0; i < NUM_STEPS; i++)
  { 
    for(int j = 0; j < FastLED[i].size(); j++)
    {
        FastLED[i].leds()[j] = CRGB::Black;
        FastLED.show();
    }
  }

}

void setup()
{ 
      CRGB leds1[STEP_1_NUM_LEDS];
      CRGB leds2[STEP_2_NUM_LEDS];
      CRGB leds3[STEP_3_NUM_LEDS];
      CRGB leds4[STEP_4_NUM_LEDS];
      CRGB leds5[STEP_5_NUM_LEDS];
      CRGB leds6[STEP_6_NUM_LEDS];
      CRGB leds7[STEP_7_NUM_LEDS];
      CRGB leds8[STEP_8_NUM_LEDS];
      CRGB leds9[STEP_9_NUM_LEDS];
      CRGB leds10[STEP_10_NUM_LEDS];
      CRGB leds11[STEP_11_NUM_LEDS];
      CRGB leds12[STEP_12_NUM_LEDS];
      CRGB leds13[STEP_13_NUM_LEDS];
      CRGB leds14[STEP_14_NUM_LEDS];
      CRGB leds15[STEP_15_NUM_LEDS];

      Serial.begin(9600);
      FastLED.addLeds<NEOPIXEL, STEP_1_PIN>(leds1, STEP_1_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_2_PIN>(leds2, STEP_2_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_3_PIN>(leds3, STEP_3_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_4_PIN>(leds4, STEP_4_NUM_LEDS);/*
      FastLED.addLeds<NEOPIXEL, STEP_5_PIN>(leds5, STEP_5_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_6_PIN>(leds6, STEP_6_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_7_PIN>(leds7, STEP_7_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_8_PIN>(leds8, STEP_8_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_9_PIN>(leds9, STEP_9_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_10_PIN>(leds10, STEP_10_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_11_PIN>(leds11, STEP_11_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_12_PIN>(leds12, STEP_12_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_13_PIN>(leds13, STEP_13_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_14_PIN>(leds14, STEP_14_NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, STEP_15_PIN>(leds15, STEP_15_NUM_LEDS);*/
     //initLeds();
}

void enableBlinkLights(int step_num, float color = CRGB::White)
{  
    step_num -= 1;
    Serial.print("Step: ");
    Serial.println((step_num+1));
    Serial.print("Num leds: ");
    Serial.println(FastLED[step_num].size());
    Serial.println("#######################################");

    for(int led = 0; led < FastLED[step_num].size(); led++)
    { 
        FastLED[step_num].leds()[led] = color;
        FastLED.show();
        FastLED[step_num].leds()[led] = CRGB::Black;
        FastLED.show();
        delay(1);
        FastLED[step_num].leds()[led] = color;
        FastLED.show();
        delay(1);
        Serial.print("Setting LED: ");
        Serial.println(led);
    }
    Serial.println("#######################################");
}

void disableLights(int step_num)
{
    step_num -= 1;

    for(int led = (FastLED[step_num].size() - 1); led >= 0; led--)
    { 
        FastLED[step_num].leds()[led] = CRGB::Black;
        FastLED.show();
        delay(10);
    }
}

void loop()
{
    enableBlinkLights(1);
    enableBlinkLights(2);
    enableBlinkLights(3);
    enableBlinkLights(4);
    delay(2000);/*
    disableLights(4);
    disableLights(3);
    disableLights(2);
    disableLights(1);*/
}
FastLED[i].leds()[j]

Did you see this in example code somewhere? Please post a link to that. I'm not sure how you can address FastLED as an array.

The lifetime of the arrays ends when the setup ends. After that, accessing the LEDs is undefined behavior. At that point, the FastLED library is just reading from (and probably writing to) memory that is no longer valid.

Make the arrays global. They still take up memory, regardless of where you declare them. If you add them to the setup, the compiler just can't predict the memory usage at compile time.

Pieter

PaulRB:

FastLED[i].leds()[j]

Did you see this in example code somewhere? Please post a link to that. I'm not sure how you can address FastLED as an array.

I just looked in the documentation: http://fastled.io/docs/3.1/class_c_l_e_d_controller.html#a5e86c45c89ebfb7b21f9045790302399

As far as I understand the array operator on the FastLED class return a pointer to the CLEDController?

CLEDController & CFastLED::operator[] ( int x ) 
Get a reference to a registered controller.

Returns
a reference to the Nth controller
Definition at line 73 of file FastLED.cpp.

PieterP:
The lifetime of the arrays ends when the setup ends. After that, accessing the LEDs is undefined behavior. At that point, the FastLED library is just reading from (and probably writing to) memory that is no longer valid.

Make the arrays global. They still take up memory, regardless of where you declare them. If you add them to the setup, the compiler just can't predict the memory usage at compile time.

Pieter

Oh, I see. So there is no way around the memory problem?

Nickless:
Oh, I see. So there is no way around the memory problem?

Not really. You can optimize the rest of your code, and maybe save some bytes here and there. If that's not enough, your best option is to get a board with more RAM.

For a start, add the F() macro around your string literals.

The low memory warning on its own is not a problem per se. If you don't use the heap (or use it responsibly), and if you limit the number of nested function calls, you should be fine.

The problem is that you can't really know how much memory you're going to use beforehand. The compiler only reports the static memory usage, which contains objects that will live for the entire duration of the program, as well as string literals (if you don't save them in progmem), virtual function tables, buffers for the UART, etc.

Each time you call a function, a stack frame is added to the stack, this uses some memory. If you exit a function, the stack frame is removed from the stack again, so it can be reused for the next function. If you call a function inside of another function, the stack keeps on growing. If the stack grows larger than the amount of memory you have left, you get a stack overflow.
How far it grows depends on what functions you're calling, and you can't know that at compile time, because it depends on run-time conditions.

Heap memory usage is even harder to predict.

PieterP:
Not really. You can optimize the rest of your code, and maybe save some bytes here and there. If that's not enough, your best option is to get a board with more RAM.

For a start, add the F() macro around your string literals.

The low memory warning on its own is not a problem per se. If you don't use the heap (or use it responsibly), and if you limit the number of nested function calls, you should be fine.

The problem is that you can't really know how much memory you're going to use beforehand. The compiler only reports the static memory usage, which contains objects that will live for the entire duration of the program, as well as string literals (if you don't save them in progmem), virtual function tables, buffers for the UART, etc.

Each time you call a function, a stack frame is added to the stack, this uses some memory. If you exit a function, the stack frame is removed from the stack again, so it can be reused for the next function. If you call a function inside of another function, the stack keeps on growing. If the stack grows larger than the amount of memory you have left, you get a stack overflow.
How far it grows depends on what functions you're calling, and you can't know that at compile time, because it depends on run-time conditions.

Heap memory usage is even harder to predict.

Okay, I will see what I can do. Thanks for your help! :smiley:

A big chunk of memory is used by the virtual function tables of the FastLED library. There's also significant memory usage from the guard variables for the local static controllers in the addLEDs functions.
The problem is that the library authors chose to use the pin number as a template parameter (probably for runtime performance reasons or compile-time checks). This creates a new class for each pin, so the compiler has to create a vtable for every one of these classes, which is extremely expensive.

Here's an overview of the static memory usage of your sketch (minus the string literals):

00000157 b Serial
00000096 b leds6
00000096 b leds10
00000084 b leds11
00000081 b leds5
00000078 b leds9
00000078 b leds7
00000072 b leds8
00000072 b leds4
00000072 b leds3
00000072 b leds12
00000069 b leds2
00000069 b leds14
00000069 b leds13
00000066 b leds15
00000066 b leds1
00000018 d vtable for NEOPIXEL<(unsigned char)9>
00000018 d vtable for NEOPIXEL<(unsigned char)8>
00000018 d vtable for NEOPIXEL<(unsigned char)7>
00000018 d vtable for NEOPIXEL<(unsigned char)6>
00000018 d vtable for NEOPIXEL<(unsigned char)5>
00000018 d vtable for NEOPIXEL<(unsigned char)4>
00000018 d vtable for NEOPIXEL<(unsigned char)3>
00000018 d vtable for NEOPIXEL<(unsigned char)2>
00000018 d vtable for NEOPIXEL<(unsigned char)17>
00000018 d vtable for NEOPIXEL<(unsigned char)16>
00000018 d vtable for NEOPIXEL<(unsigned char)15>
00000018 d vtable for NEOPIXEL<(unsigned char)14>
00000018 d vtable for NEOPIXEL<(unsigned char)12>
00000018 d vtable for NEOPIXEL<(unsigned char)11>
00000018 d vtable for NEOPIXEL<(unsigned char)10>
00000018 d vtable for CPixelLEDController<(EOrder)66, 1, 4294967295ul>
00000018 d vtable for ClocklessController<(unsigned char)9, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)8, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)7, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)6, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)5, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)4, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)3, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)2, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)17, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)16, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)15, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)14, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)12, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)11, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for ClocklessController<(unsigned char)10, 4, 10, 6, (EOrder)66, 0, false, 10>
00000018 d vtable for HardwareSerial
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)9>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)8>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)7>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)6>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)5>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)4>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)3>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)2>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)17>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)16>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)15>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)14>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)12>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)11>(CRGB*, int, int)::c
00000017 b CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)10>(CRGB*, int, int)::c
00000016 d vtable for CLEDController
00000013 b FastLED
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)9>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)8>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)7>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)6>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)5>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)4>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)3>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)2>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)17>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)16>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)15>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)14>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)12>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)11>(CRGB*, int, int)::c
00000008 b guard variable for CLEDController& CFastLED::addLeds<NEOPIXEL, (unsigned char)10>(CRGB*, int, int)::c
00000004 b CFastLED::countFPS(int)::lastframe
00000004 b lastshow
00000004 b timer0_overflow_count
00000004 b timer0_millis
00000002 b CFastLED::countFPS(int)::br
00000002 b CLEDController::m_pTail
00000002 b CLEDController::m_pHead
00000001 b PixelController<(EOrder)66, 1, 4294967295ul>::init_binary_dithering()::R
00000001 b gTimeErrorAccum256ths
00000001 b timer0_fract

This is the output of avr-nm -Crtd --size-sort /tmp/arduino_build_352891/sketch.ino.elf | grep -i ' [dbv] '.
The first column is the size in bytes.

So looking at the memory usage, it seems more efficient to chain all your small LED strips together, and drive them from one or two pins, instead of connecting each strip to a different pin.

PieterP:
So looking at the memory usage, it seems more efficient to chain all your small LED strips together, and drive them from one or two pins, instead of connecting each strip to a different pin.

Thanks, I will try to connect them and check out the result :slight_smile:

Have a look at the FastLED documentation on using multiple strips:

Multiple Controller Examples

Paying particular attention to the section at the bottom "Managing your own output". That shows a couple of examples of using a single LED buffer for multiple strips. You will still have overhead for each strip (appears to be 43 bytes per strip from my testing), but sufficiently small to fit on a nano.