Problem referencing a position on an array constant

While implementing Bitsy-Converter, I ran into a strange behavior, where acessing a position in an array using constants works okay, while using variables returns zero.

This is the code that’s causing the problem; specifically, the problem happens at the loop commented as “Fill the background with the tiles”:


#include <Arduboy.h>

Arduboy arduboy;

void setup() {
  // put your setup code here, to run once:
  arduboy.begin();
  arduboy.clear();
  arduboy.print("Hello World");
  arduboy.display();
  
  Serial.begin(9600);
}

typedef struct Room {
    uint8_t tileMap[16][16];
} Room;

const Room PROGMEM rooms[] = {

  // Room 0
  {{
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
    { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
  }}

};

const uint8_t FRAME_COUNT = 5;


enum ImageOffsets {
  ofs_BLANK = 0,
  ofs_TIL_a = 1,
  ofs_SPR_A = 2,
  ofs_SPR_a = 3,
  ofs_ITM_0 = 4
};

const uint8_t PROGMEM images[][8] = { 

  // BLANK: index 0, offset 0, 1 frame(s)
  { B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000 },
  // TIL_a: index 1, offset 1, 1 frame(s)
  { B11111111, B10000001, B10000001, B10011001, B10011001, B10000001, B10000001, B11111111 },
  // SPR_A: index 2, offset 2, 1 frame(s)
  { B00100000, B00010000, B11111000, B00111111, B00111111, B11111000, B00010000, B00100000 },
  // SPR_a: index 3, offset 3, 1 frame(s)
  { B00000000, B00111100, B11111000, B01111100, B01100000, B11100000, B00010000, B00001100 },
  // ITM_0: index 4, offset 4, 1 frame(s)
  { B00000000, B00010000, B00111000, B01001000, B01001000, B00111000, B00000000, B00000000 } 
};;

void loop() {
  // put your main code here, to run repeatedly:
  if (!arduboy.nextFrame()) return;

  // Run twice every second.
  if (arduboy.everyXFrames(30))
  {
    arduboy.clear();
    arduboy.setCursor(0, 56);
    arduboy.print("Hello World");
    
    // Fill the background with the tiles
    for (uint8_t ty = 0; ty != 7; ty++) {
      for (uint8_t tx = 0; tx != 16; tx++) {
        //uint8_t tn = rooms[0].tileMap[ty][tx];
        uint8_t tn = rooms[0].tileMap[ty][tx];
        
        Serial.print("ty = ");
        Serial.print(ty);
        Serial.print(" tx = ");
        Serial.print(tx);
        Serial.print(" tn = ");
        Serial.print(tn);
        Serial.print(" direct = ");
        Serial.print(rooms[0].tileMap[1][1]);
        Serial.println();
        
        arduboy.drawBitmap(tx * 8, ty * 8, images[tn], 8, 8, WHITE);
      }
    }
    
	  arduboy.display();
  }
}

The problem here is that if I tx and ty are both 1, the expression rooms[0].tileMap[ty][tx] is returning zero, while acessing the same position using constant values (rooms[0].tileMap[1][1]), the expression returns the correct value, which should be 1.

Is there anything being declared wrong, to cause this discrepancy?

Your array is stored in PROGMEM. So accessing it using constants causes the compiler to automatically replace the value with that in the constant array (since it will never change at run time). However, when accessing them with variables the values are stored in PROGMEM and not RAM. You can’t access PROGMEM by simply indexing into the array. You have to use one of the pgm_read_*(address) functions. Keep in mind the address supplied to pgm_read is not an index into the array but a pointer to a memory location. This is why it’s typical to convert all data to a single byte array (example array[y][x] would instead be array[y * width + x], or however you want to encode the array.

For more information read up on Arduino’s PROGMEM as well as pgmspace.h which defines the pgm_read_* functions needed for dynamically accessing PROGMEM.

1 Like

Many thanks; that did the trick. :+1:


#include <Arduboy.h>

Arduboy arduboy;

void setup() {
  // put your setup code here, to run once:
  arduboy.begin();
  arduboy.clear();
  arduboy.print("Hello World");
  arduboy.display();
  
  Serial.begin(9600);
}

typedef struct Room {
    uint8_t tileMap[16 * 16];
} Room;

const Room PROGMEM rooms[] = {

  // Room 0
  {{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  }}

};

const uint8_t FRAME_COUNT = 5;


enum ImageOffsets {
  ofs_BLANK = 0,
  ofs_TIL_a = 1,
  ofs_SPR_A = 2,
  ofs_SPR_a = 3,
  ofs_ITM_0 = 4
};

uint8_t ty;
uint8_t tx;
uint8_t tn;


const uint8_t PROGMEM images[][8] = { 

  // BLANK: index 0, offset 0, 1 frame(s)
  { B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000, B00000000 },
  // TIL_a: index 1, offset 1, 1 frame(s)
  { B11111111, B10000001, B10000001, B10011001, B10011001, B10000001, B10000001, B11111111 },
  // SPR_A: index 2, offset 2, 1 frame(s)
  { B00100000, B00010000, B11111000, B00111111, B00111111, B11111000, B00010000, B00100000 },
  // SPR_a: index 3, offset 3, 1 frame(s)
  { B00000000, B00111100, B11111000, B01111100, B01100000, B11100000, B00010000, B00001100 },
  // ITM_0: index 4, offset 4, 1 frame(s)
  { B00000000, B00010000, B00111000, B01001000, B01001000, B00111000, B00000000, B00000000 } 
};;

void loop() {
  // put your main code here, to run repeatedly:
  if (!arduboy.nextFrame()) return;

  // Run twice every second.
  if (arduboy.everyXFrames(30))
  {
    arduboy.clear();
    arduboy.setCursor(0, 56);
    arduboy.print("Hello World");
    
    // Fill the background with the tiles
    for (ty = 0; ty != 7; ty++) {
      for (tx = 0; tx != 16; tx++) {
        tn = pgm_read_byte(rooms[0].tileMap + ty * 16 + tx);
        
        if (ty == 1) {
          Serial.print("ty = ");
          Serial.print(ty);
          Serial.print(" tx = ");
          Serial.print(tx);
          Serial.print(" tn = ");
          Serial.print(tn);
          Serial.println();
        }
        
        arduboy.drawBitmap(tx * 8, ty * 8, images[tn], 8, 8, WHITE);
      }
    }

	  arduboy.display();
  }
}
1 Like

Actually, it’s completely possible to read an array of structs from progmem as long as the struct is ‘trivial’ (i.e. its memory layout is such that it can be compied as a sequence of bytes).

Instead of using pgm_read_* you just use memcpy_P.

But in @haroldo-ok’s case it would be possible to just do:

const uint8_t value = pgm_read_byte(&room[index].tileMap[y][x]);

This works because the precedence of & is 3 and the precedence of . and [] is 2.

That’s unnecessary, for regular arrays [y][x] will be translated to [y * width + x] anyway.
There are some cases where flat arrays can be easier to work with (e.g. when an array is dynamically allocated), but in most cases 2D arrays are fine and incur no penalty.


The Arduboy library is no longer maintained,
you should switch to using Arduboy2 instead.

For the record, this typedef is completely superflous in C++.

In case you don’t know, wrapping string literals in F() when feeding them to a print or println function stores them in progmem instead of RAM.

2 Likes