Progmem and the reading thereof


(Kea Oliver) #1

Im storing a 2d array of bytes in progmem because its pretty huge (4kb) If i want to read from this 2d array can someone give me an example of how to do this?

I understand I need pgm_read_byte(address) But im not sure what the address would be?

(storing the array in progmem because the rest of the game isnt likely to take up too much space and because I want more detail to my map than the bit array i used before)


(Pharap) #2
constexpr uint8_t arrayWidth = 10;
constexpr uint8_t arrayHeight = 5;

uint8_t arrayOfBytes[arrayHeight][arrayWidth] PROGMEM =
{
	// ...
};

uint8_t readArrayByte(uint8_t x, uint8_t y)
{
	return pgm_read_byte(&arrayOfBytes[y][x]);
}

(Strictly speaking arrayWidth and arrayHeight should be size_t, but size_t is 16 bits on the Arduboy, so using uint8_t should produce less code. I’m not completely sure though, I’ve never actually tested - it’s possible that it doesn’t make a difference because it’s constexpr or because it has to get promoted anyway.)


(Kea Oliver) #3

Oh that easy? Nice one. Just as an aside question, say I have converted a bitmap image into an array, this array was produced by going left to right along each “row” of pixels on the image and assigned them a byte (so a 2x2 pixel image might look like {0, 0, 0, 0}) If i want to convert this to a 2d array I should just be able to do arrayofbytes[2,2] right?


(Pharap) #4

So basically you’re generating your maps by drawing them in an image editor and then treating each colour as a different tile type?

If so, a 2x2 image (when converted) would look like:

constexpr uint8_t arrayWidth = 2;
constexpr uint8_t arrayHeight = 2;

uint8_t arrayOfBytes[arrayHeight][arrayWidth] PROGMEM =
{
	{ 0, 0 },
	{ 0, 0 },
};

(Assuming the image was all one colour, and that colour corresponded to 0.)

You don’t have to use constexpr variables,
but it’s easier to do that than to extract the dimensions from the array (especially a 2D array).

I’ve got two more bits of advice…


Firstly,
I recommend using an enum class (‘scoped enumeration’) for map tiles instead of using number types.
It makes the code more readable.

E.g.

enum class Tile
{
	Empty,
	Tree,
};

constexpr uint8_t mapWidth = 2;
constexpr uint8_t mapHeight = 2;

Tile mapData[mapHeight][mapWidth] PROGMEM =
{
	{ Tile::Empty, Tile::Empty },
	{ Tile::Empty, Tile::Tree },
};

Tile readMapTile(uint8_t x, uint8_t y)
{
	return static_cast<Tile>(pgm_read_byte(&mapData[y][x]));
}

Then your code becomes more readable, because you can write things like:

bool isSolid(Tile tile)
{
	switch(tile)
	{
		case Tile::Empty:
			return false;
		case Tile::Tree:
			return true;
		default:
			return true;
	}
}

And in case you haven’t encountered a switch statement before, think of it like this:

bool isSolid(Tile tile)
{
	if(tile == Tile::Empty)
		return false;
	else if(tile == Tile::Tree)
		return true;
	else
		return true;
}

Secondly, something more advanced…

If you’ve only got 16 tiles or less then you can pack more than one tile per byte.
You’d need to do some bitshifting to pack and extract the data.

It’s possible to do this as well as using an enum class for the tile types,
but you’d need to know how.

I can explain it if you want, but if you don’t plan to do this then I don’t want to overload you with info.


And if you haven’t stumbled upon it yet, you might find this useful:


(Kea Oliver) #5

Thanks for the useful advice, I know I could squeeze more data into each byte but Im doubting if i NEED to for this particular application. If I did I would probably just draw the map in bits using the function i used in my roguelike wip and then modify what it displays based on when on the array a tile is drawn, would squeeze the map well below 1kb but im being lazy for reasons of getting more done.


(Pharap) #6

Depending on how big your game gets you might have to eventually.
That progmem runs out really quickly.


If you do end up packing your bits and using your map code, make sure to replace your #defines with constexpr variables.
Also, try using MapStorage = unsigned char; instead of #define MAP_STORAGE unsigned char.

Macros are evil and should be avoided whenever possible.