Accessing Int Arrays from Programme Memory

Hi, I'm trying to store a large number of ints within arrays in Programme Memory. I've produced some example code to demonstrate the issue that I'm having.
The code works fine when I try to access the data within the arrays using a number (not stored within a variable). Or when I access the data using a variable that was declared outside of any functions and is not changed within a function. However when accessing the data with a variable that is changed within a function it returns incorrect data.
I think this is an issue surrounding pointers, but I'm baffled as to what I'm doing wrong.

const int ARRAY_0[] PROGMEM = {0,1,2,3,4,5}; //0
const int ARRAY_1[] PROGMEM = {0,1,2,3,4,5}; //1
const int ARRAY_2[] PROGMEM = {0,1,2,3,4,5}; //2
const int ARRAY_3[] PROGMEM = {0,1,2,3,4,5}; //3
const int ARRAY_4[] PROGMEM = {0,1,2,3,4,5}; //4
const int ARRAY_5[] PROGMEM = {0,1,2,3,4,5}; //5
const int ARRAY_6[] PROGMEM = {0,1,2,3,4,5}; //6

const int *const ARRAY_TABLE[] PROGMEM = {ARRAY_0,ARRAY_1,ARRAY_2,ARRAY_3,ARRAY_4,ARRAY_5,ARRAY_6};

int selected_array = 0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  for (int i = 0; i < 6;i++){
    int value = (int)pgm_read_word(&(ARRAY_TABLE[0])[i]);
    Serial.print(value);
    Serial.print(",");
  }
  Serial.println(" ");
  
  for (int i = 0; i < 6;i++){
    int value = (int)pgm_read_word(&(ARRAY_TABLE[selected_array])[i]);
    Serial.print(value);
    Serial.print(",");
  }
  Serial.println(" ");
  
  //selected_array = 0;
  for (int i = 0; i < 6;i++){
    int value = (int)pgm_read_word(&(ARRAY_TABLE[selected_array])[i]);
    Serial.print(value);
    Serial.print(",");
  }

  delay(10000);
}

Writes the following to the Serial port, as expected.
0,1,2,3,4,5,
0,1,2,3,4,5,
0,1,2,3,4,5,
However, if I uncomment the change in value (from 0 to 0) of selected_array...

const int ARRAY_0[] PROGMEM = {0,1,2,3,4,5}; //0
const int ARRAY_1[] PROGMEM = {0,1,2,3,4,5}; //1
const int ARRAY_2[] PROGMEM = {0,1,2,3,4,5}; //2
const int ARRAY_3[] PROGMEM = {0,1,2,3,4,5}; //3
const int ARRAY_4[] PROGMEM = {0,1,2,3,4,5}; //4
const int ARRAY_5[] PROGMEM = {0,1,2,3,4,5}; //5
const int ARRAY_6[] PROGMEM = {0,1,2,3,4,5}; //6

const int *const ARRAY_TABLE[] PROGMEM = {ARRAY_0,ARRAY_1,ARRAY_2,ARRAY_3,ARRAY_4,ARRAY_5,ARRAY_6};

int selected_array = 0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  for (int i = 0; i < 6;i++){
    int value = (int)pgm_read_word(&(ARRAY_TABLE[0])[i]);
    Serial.print(value);
    Serial.print(",");
  }
  Serial.println(" ");
  
  for (int i = 0; i < 6;i++){
    int value = (int)pgm_read_word(&(ARRAY_TABLE[selected_array])[i]);
    Serial.print(value);
    Serial.print(",");
  }
  Serial.println(" ");
   
  selected_array = 0;
  for (int i = 0; i < 6;i++){
    int value = (int)pgm_read_word(&(ARRAY_TABLE[selected_array])[i]);
    Serial.print(value);
    Serial.print(",");
  }

  delay(10000);
}

Writes the following to the Serial port:
0,1,2,3,4,5,
-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,

Notice how both the second and third loops return erroneous data, when the variable was changed between the two.

Any help with this will be much appreciated - thank you, in advanced.

The first case works because the compiler optimizes the access to ARRAY_TABLE away if it can see that selected_array is a compile-time constant.

ARRAY_TABLE[selected_array] tells the compiler to read the array element from RAM, which won't work, because it's in flash.
You need to use pgm_read_ptr first, and then use that pointer to read the values using pgm_read_word.

It's probably easier (and more efficient) to use a 2D array instead of an array of pointers to other arrays.

Pieter

Sorry, I should have mentioned I'm using a Mega 2560.

It's probably easier (and more efficient) to use a 2D array instead of an array of pointers to other arrays.

Probably ? For sure it is. you save the space for the pointers, and you only need to call pgm_read_word() (which was all you did, but anything retrieved to PROGMEM needs to be done using the special functions.

This works:

      int *ptr = pgm_read_ptr(&(ARRAY_TABLE[selected_array]));
      int value = (int) pgm_read_word(&ptr[i]);

Note: You can use 'ptr+i' in place of '&ptr[i]'

That's all very useful, thank you so much for your replies. I've been struggling with this one for a while.

The reason why I didn't go for a 2D array is because the individual arrays (ARRAY_0, ARRAY_1, etc.) vary in size significantly in the actual code I'm working on. Most have only 1 int and others have up to 50, and there's 255 arrays in total.

Thanks again.

. Most have only 1 int and others have up to 50, and there's 255 arrays in total.

And they need to be part of the one main array ? You will have to be quite careful about not overshooting the boundary, how do you plan to do that ?

tibbsy:
That's all very useful, thank you so much for your replies. I've been struggling with this one for a while.

The reason why I didn't go for a 2D array is because the individual arrays (ARRAY_0, ARRAY_1, etc.) vary in size significantly in the actual code I'm working on. Most have only 1 int and others have up to 50, and there's 255 arrays in total.

Thanks again.

One thing to be very careful of - the pgm_read_* functions can only access the first 64 KB of PROGMEM. If you end up with more than that (and remember that other libraries may also use PROGMEM), somewhere the code will be reading garbage rather than the values you want.

It's possible to work around the limit, but requires some gymnastics to do so.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.