0

I'm working on a project that will eventually scale to 864 LEDs on WS2815 LED strips (three rows of 288 LEDs). I'm currently running it on an Arduino Uno with a single test strip of 144 LEDs.

FastLED stores its data in 3-byte structs, so I obviously cannot simply hold all of the data in SRAM and work with it directly (at least not without upgrading to a controller with more memory). I'm going to use a Raspberry Pi to send the Arduino data, one section at a time. To further save memory, I want to implement an RGB color lookup table in flash, then instead of

CRGB pixels[PIXEL_COUNT];

taking up over 2KiB of memory 800+ values, or

CRGB pixels[PIXEL_BUFFER_COUNT];

with a buffer of 288 RGB values consuming most of my working memory (864 bytes), I would have

CRGB *pixels[PIXEL_BUFFER_COUNT];

Where each pointer would reference a location in a PROGMEM 256-entry table of CRGB values. This is a more manageable 576 bytes in SRAM.

I realize FastLED does not work this way. There's a lot of work ahead of me if I do this, but before I get started, my question:


Will I run into issues with reading the actual data from flash memory while sending to the LED strips? As currently implemented in FastLED, RGB data is in SRAM, but each pointer dereference will require reading from flash. Is the subsequent delay enough that it will cause an issue with sending data to the strips?

1

On an Arduino Uno, or other AVR-based Arduino, reading a byte from RAM takes 2 CPU cycles. Reading it from flash takes 3 cycles. So flash vs. RAM is not such a high overhead. However, if you store a LUT in flash, and the color indexes in RAM, then each pixel would require:

  • 2 cycles for reading the color index from RAM
  • 3 × 3 cycles for reading the RGB values from flash.

That's a total of 11 cycles, vs. 6 cycles it would take to read the RGB values from RAM, as is done in the stock FastLED library.

It sounds bad, but it could be that the precise timing is not such a big issue. I don't know the WS2815, but if they are similar to the WS2812, then you don't need to be anywhere as precise with the timings as the datasheet suggests. C.f. the blog post NeoPixels Revealed: How to (not need to) generate precisely timed signals.

Edit: there will be a few more cycles needed for the pointer arithmetic. I estimate something around 7 cycles. At low level, the code for getting a color should look roughly like this:

uint8_t *pixel = ...;
uint8_t index = *pixel;            // read from RAM: 2 cycles
uint8_t pgm_p;                     // pointer to flash
pgm_p = &pgm_lut + 3 * index;      // pointer arithmetic: ≥ 7 cycles
struct CRGB color;
color.r = pgm_read_byte(pgm_p++);  // read from flash: 3 cycles
color.g = pgm_read_byte(pgm_p++);  // read from flash: 3 cycles
color.b = pgm_read_byte(pgm_p);    // read from flash: 3 cycles
  • The WS2815 is basically a 12V WS2812B. Thank you for the detailed breakdown. – Jon Harper Sep 28 at 16:20
  • Your edit is also very helpful. With the NeoPixels Revealed article and some buffering, I might be able to pull this off. Since my LEDs will be displaying a gradient of the palette to an extent, I can read the nearest CRGB values into SRAM and just move the pointer ahead as needed when I find an index I don't have locally. – Jon Harper Sep 28 at 19:47
1

Because flash is on a different data bus to SRAM there is extra shuffling around needed internally in the CPU to get the data to where it needs to be. This uses a special "LPM" instruction (Load Program Memory), which takes an extra clock cycle to operate. On top of that the only way of using it is through the Z "index" register - there's no kind of immediate or offset loads. So it can be a little clunky to work with. All that is masked by the provided C functions, of course.

The upshot is that yes, it is somewhat slower reading from flash. How much slower, though, is impossible to say. Best case scenario: each access will take 1 more clock cycle. Worst case: well, it's impossible to know. That somewhat depends on both how you implement your reading of the CLUT and how the compiler batches things together.

I would suggest that you will need to employ the use of an oscilloscope and just try it out. Implement your changes, then manually adjust the delays and timing to get the output waveform just right.

  • Exactly what I needed to know. Thanks. – Jon Harper Sep 28 at 16:11
  • Can always use a '1284P based board with 16K SRAM, lots of room to store patterns, etc. for quick access. – CrossRoads Sep 30 at 0:32
  • @CrossRoads I’ve looked at larger boards but am enjoying the challenge of this. Storing my data in 32bit blocks is also (probably, I have not benchmarked) speeding things up. – Jon Harper Oct 8 at 8:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.