Working with 3D arrays

Hello,

I am trying to send images to an addressable array of RGB LED’s.

I have two things I do not understand and have not figured out:

  1. How do you send a 3d array to a function and then return the new array after I manipulate it. For now an example that shows how to add 3 to each green value would be great.

  2. Where should the different array images be stored to save memory?

Thank you for any help.
Paul

Attached is a basic code that does not send data to the LED’s but just displays it to the debug window.

TestArray.ino (4.38 KB)

read this tutorial for help on how to store an array in program memory and then copy it out when needed.

How big is the array and which board? That will determine your options.

Note that images stored in PROGMEM can not be modified and saved (unless you use a boot loader that supports modifying it).

You can pass the array as an argument to the function. Not quite sure why you want to return it from the function. Are you planning to make a copy, modify the copy and return the copy?

I am having troubles retrieving back the data from Program Memory when created as a 3D array. The compiler accepts the 3D array but I cannot figure out how to read each value.

Is this possible?

// Testing out how to use program memory instead of RAM
// Mr. H
// Jan. 1, 2020
// 3D dimension array

// const int paul1 PROGMEM = {255, 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 255, 0, 0, 0};
const int paul1[8][3] PROGMEM =
{
{{111, 112, 113}, {211, 212, 213}, {311, 312, 313}, {411, 412, 413}, {511, 512, 513}, {611, 612, 613}, {711, 712, 713}, {811, 812, 813}},
{{211, 212, 213}, {221, 222, 223}, {321, 322, 323}, {421, 422, 423}, {521, 522, 523}, {621, 622, 623}, {721, 722, 723}, {821, 822, 823}},
{{311, 312, 313}, {231, 232, 233}, {331, 332, 333}, {431, 432, 433}, {531, 532, 533}, {631, 632, 633}, {731, 732, 733}, {831, 832, 833}},
{{411, 412, 413}, {241, 242, 243}, {341, 342, 343}, {441, 442, 443}, {541, 542, 543}, {641, 642, 643}, {741, 742, 743}, {841, 842, 843}},
{{511, 512, 513}, {251, 252, 253}, {351, 352, 353}, {451, 452, 453}, {551, 552, 553}, {651, 652, 653}, {751, 752, 753}, {851, 852, 853}},
};

void setup() {
Serial.begin(9600);
while (!Serial); // wait for serial port to connect. Needed for native USB

// // read back a 2-byte int
// for (int k = 0; k < sizeof(paul1)/2; k++) {
// Serial.println(pgm_read_word_near(paul1 + k));
// }
// Serial.println();
int R = 0;
int G = 0;
int B = 0;
int LEDn = 0;
for (int col = 0; col <= sizeof(paul1) / 2; col++) {
for (int row = 0; row <= 7; row++) {
R = pgm_read_word_near(paul1 + col);
G = pgm_read_word_near(paul1 + col);
B = pgm_read_word_near(paul1 + col);
Serial.print("R= “); Serial.print(R);
Serial.print(” G= “); Serial.print(G);
Serial.print(” B= "); Serial.println(B);
//leds[LEDn] = CRGB(G, R, B);// CRGB function is wrong
Serial.print("LED# "); Serial.println(LEDn);
LEDn ++;
}
}
}

void loop() {
// put your main code here, to run repeatedly:

}

I think you should start with figuring out how your for() loops will work. They currently do not iterate over your array.

Did you read the tutorial link in reply #1. You can not but multidimentional arrays in PROGMEM directly. Look at the array of strings example since that is an array of pointers to arrays of chars which is a multidimentional. You have to do it in steps.

What you really have is 40 sets of 3 each, so I don’t know why you need to make it a 3D array. If you really need it to be a 3D array, the initializer list needs to be grouped accordingly.

Also, if you are storing RGB tuples, it may be easier to just store long ints which can hold the RGB value as a single value (R2562566 + G *256 + B )

Better still, use “__uint24”, and save memory.

Or even better: use a meaningful struct for easy initialization and access:

[color=#434f54]// Struct to save one 24-bit RGB color value.[/color]
[color=#00979c]struct[/color] [b][color=#d35400]ColorRGB[/color][/b] [color=#000000]{[/color]
  [color=#00979c]uint8_t[/color] [color=#000000]r, g, b[color=#000000];[/color]
[color=#000000]}[/color][color=#000000];[/color]

[color=#434f54]// Wrapper to easily read colors from a PROGMEM LUT.[/color]
[color=#00979c]class[/color] [b][color=#d35400]Colors[/color][/b] [color=#000000]{[/color]
  [color=#00979c]public[/color][color=#434f54]:[/color]
    [b][color=#d35400]Colors[/color][/b][color=#000000]([/color][color=#00979c]const[/color] [b][color=#d35400]ColorRGB[/color][/b] [color=#434f54]*[/color][color=#000000]colors[/color][color=#000000])[/color] [color=#434f54]:[/color] [color=#000000]colors[/color][color=#000000]([/color][color=#000000]colors[/color][color=#000000])[/color] [color=#000000]{[/color][color=#000000]}[/color]  
    [b][color=#d35400]ColorRGB[/color][/b] [color=#00979c]operator[/color][color=#000000][[/color][color=#000000]][/color][color=#000000]([/color][b][color=#d35400]size_t[/color][/b] [color=#000000]i[/color][color=#000000])[/color] [color=#00979c]const[/color] [color=#000000]{[/color]
      [color=#5e6d03]return[/color] [color=#000000]{[/color]
        [color=#000000]pgm_read_byte[/color][color=#000000]([/color][color=#434f54]&[/color][color=#000000]colors[/color][color=#000000][[/color][color=#000000]i[/color][color=#000000]][/color][color=#434f54].[/color][color=#000000]r[/color][color=#000000])[/color][color=#434f54],[/color]
        [color=#000000]pgm_read_byte[/color][color=#000000]([/color][color=#434f54]&[/color][color=#000000]colors[/color][color=#000000][[/color][color=#000000]i[/color][color=#000000]][/color][color=#434f54].[/color][color=#000000]g[/color][color=#000000])[/color][color=#434f54],[/color]
        [color=#000000]pgm_read_byte[/color][color=#000000]([/color][color=#434f54]&[/color][color=#000000]colors[/color][color=#000000][[/color][color=#000000]i[/color][color=#000000]][/color][color=#434f54].[/color][color=#000000]b[/color][color=#000000])[/color][color=#434f54],[/color]
      [color=#000000]}[/color][color=#000000];[/color]
    [color=#000000]}[/color]
  [color=#00979c]private[/color][color=#434f54]:[/color]
    [color=#00979c]const[/color] [b][color=#d35400]ColorRGB[/color][/b] [color=#434f54]*[/color][color=#000000]colors[/color][color=#000000];[/color]
[color=#000000]}[/color][color=#000000];[/color]

[color=#434f54]// Color lookup table stored in program memory.[/color]
[color=#00979c]static[/color] [color=#00979c]const[/color] [b][color=#d35400]ColorRGB[/color][/b] [color=#000000]pgm_colors[/color][color=#000000][[/color][color=#000000]][/color] [color=#00979c]PROGMEM[/color] [color=#434f54]=[/color] [color=#000000]{[/color]
  [color=#000000]{[/color][color=#000000]0x00[/color][color=#434f54],[/color] [color=#000000]0x00[/color][color=#434f54],[/color] [color=#000000]0x00[/color][color=#000000]}[/color][color=#434f54],[/color] [color=#434f54]// black[/color]
  [color=#000000]{[/color][color=#000000]0xFF[/color][color=#434f54],[/color] [color=#000000]0x00[/color][color=#434f54],[/color] [color=#000000]0x00[/color][color=#000000]}[/color][color=#434f54],[/color] [color=#434f54]// red[/color]
  [color=#434f54]// ...[/color]
[color=#000000]}[/color][color=#000000];[/color]

[color=#434f54]// Array-like object to easily read colors[/color]
[b][color=#d35400]Colors[/color][/b] [color=#000000]colors[/color] [color=#434f54]=[/color] [color=#000000]pgm_colors[/color][color=#000000];[/color]

[color=#00979c]void[/color] [color=#5e6d03]setup[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]begin[/color][color=#000000]([/color][color=#000000]115200[/color][color=#000000])[/color][color=#000000];[/color]
  [color=#5e6d03]while[/color] [color=#000000]([/color][color=#434f54]![/color][b][color=#d35400]Serial[/color][/b][color=#000000])[/color][color=#000000];[/color]
  [b][color=#d35400]Serial[/color][/b][color=#434f54].[/color][color=#d35400]println[/color][color=#000000]([/color][color=#000000]colors[/color][color=#000000][[/color][color=#000000]1[/color][color=#000000]][/color][color=#434f54].[/color][color=#000000]r[/color][color=#434f54],[/color] [color=#00979c]HEX[/color][color=#000000])[/color][color=#000000];[/color] [color=#434f54]// FF[/color]
[color=#000000]}[/color]

[color=#00979c]void[/color] [color=#5e6d03]loop[/color][color=#000000]([/color][color=#000000])[/color] [color=#000000]{[/color][color=#000000]}[/color]

You could use access the progmem array directly, but the wrapper is easier to use, and you're less likely to forget to use pgm_read_byte when reading a value.

Pieter

TheMemberFormerlyKnownAsAWOL:
Better still, use “__uint24”, and save memory.

I’ve never used this. How is it in terms of execution speed when used in a numerically-intensive app?

econjack:
I've never used this. How is it in terms of execution speed when used in a numerically-intensive app?

On AVR, you'll save some memory and a few add/subtract instructions when compared to uint32_t, but it uses the same 32-bit multiplication and division routines as uint32_t: Compiler Explorer

It is not portable, and AFAIK, only AVR supports it. It wouldn't make sense to use it on an ARM anyway, since they operate on 32-bit words.

Thank you to everyone that commented. There is so much information and I am definitely outside my element.

I am trying to create a display with addressable RGB LED’s that are all in series. In my case 256 LED 8x32 (see attachment).

Program Goals:

  1. I wanted to save several images in memory
  2. Display one image at a time
  3. Create effects for the images
    3a. Rotate the image around so that it moves off one side of the display and starts reappearing on the other side. One column at a time at a fixed rate.
    3b. Rotate the image up and down (adjusting the rows)
    3c etc.

For these reasons I thought it would be best to store the images in program memory then copy into a array to make easy manipulation of the images location on the matrix. When I first started this project I used and array and no program memory. I could move my image around but had no memory for extra images and that is why I went to program memory.

Thanks for all the support. I will now work on learning some of the suggestions like incorporating the RGB into one variable (post #6).

Thank you,
Paul

I would use something like this:

#include <FastLED.h>

// POD RGB color struct
struct ColorRGB {
  uint8_t r, g, b;
};

// Wrapper to easily read pixels from a PROGMEM bitmap.
struct PGMBitmap {
  template <size_t Rows, size_t Cols>
  PGMBitmap(const ColorRGB (&colors)[Rows][Cols])
    : colors(&colors[0][0]), rows(Rows), cols(Cols) {}

  struct PGMBitmapRow {
    PGMBitmapRow(const ColorRGB *colors, size_t cols)
      : colors(colors), cols(cols) {}

    CRGB operator[](size_t col) const {
      if (col >= cols) // bounds check
        return CRGB::Black;
      return CRGB{
        pgm_read_byte(&colors[col].r),
        pgm_read_byte(&colors[col].g),
        pgm_read_byte(&colors[col].b),
      };
    }

    const ColorRGB * const colors;
    const size_t cols;
  };

  PGMBitmapRow operator[](size_t row) {
    if (row >= rows) // bounds check
      return {nullptr, 0};
    return {colors + row * cols, cols};
  }

  const ColorRGB * const colors;
  const size_t rows, cols;
};

// RGB bitmap stored in program memory. [rows][columns]
static const ColorRGB pgm_bitmap[][8] PROGMEM = {
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
  {{255, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 255}, {0, 0, 0}},
};

// 2D Array-like object to easily read colors
PGMBitmap bitmap = pgm_bitmap;

constexpr uint8_t LED_PIN  = 3;
constexpr auto COLOR_ORDER = GRB;
#define CHIPSET WS2811
constexpr uint8_t NUM_COLS = 32;
constexpr uint8_t NUM_ROWS = 8;
constexpr size_t NUM_LEDS  = NUM_COLS * NUM_ROWS;

CRGB leds[NUM_LEDS] = {};

uint16_t XY( uint8_t x, uint8_t y) {
  // TODO: Fix this function!
  // See https://github.com/FastLED/FastLED/blob/master/examples/XYMatrix/XYMatrix.ino
  return x + NUM_COLS * y;
}

void setup() {
  FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
}

void loop() {
  // Load the bitmap in the frame buffer
  for (uint8_t row = 0; row < bitmap.rows; ++row) {
    for (uint8_t col = 0; col < bitmap.cols; ++col) {
      leds[XY(col, row)] = bitmap[row][col];
    }
  }
  FastLED.show();
}

The ColorRGB datatype is used to efficiently save RGB values in a single variable, and can be used for PROGMEM arrays (unlike the FastLED::CRGB type).

The large PGMBitmap class handles reading a 2D array of ColorRGB from PROGMEM, and provides a simple interface so you can write bitmap[row][col], like you would with a normal 2D array.

The XY(x, y) function maps a point to the LED index, see the XYMatrix example of FastLED for more info, you’ll have to use their implementation, but I didn’t want to waste space posting it here.

I don’t have the hardware to test it, but if it doesn’t work, it should be very close.

TheMemberFormerlyKnownAsAWOL:
Better still, use "__uint24", and save memory.

One disadvantage of using __uint24 as opposed to a struct is that the integer is stored least-significant-byte first, so you cannot use memcpy_P for a direct copy to the CRGB array, unless you store the colors in reverse order.