How to load data from a const array to a array


#1

I tried this before but didn’t work.

for(unsigned int y=0;y!=64;y+=8){
for(unsigned int x=0;x!=128;x+=8){
t=sampleMAP[x+y];
arduboy.drawBitmap(x,y,sampletile+t,8,8,WHITE);
}}

I had to declare map it to RAM and then it works.

    unsigned char sampleMAP[48] = {
    0,0,0,0,6,9,2,0,
    0,0,2,3,2,0,10,0,
    0,2,2,0,5,2,2,0,
    0,0,2,4,2,0,11,0,
    0,0,0,0,7,8,2,15,
    0,0,0,0,0,0,0,0,};

    tile=0;
    for(int bgy=0;bgy<72;bgy+=12){
    for(int bgx=0;bgx<128;bgx+=16){
        
      arduboy.drawBitmap(bgx,bgy,background+sampleMAP[tile]*32,16,16,WHITE);
      tile++;//arduboy.display();
    }
    }

I tried doing this just now and trying to find out why I can’t load a const table to RAM or have a pointer to the table to get the data from.

const unsigned char WeaponNameList[] PROGMEM = {
'F','i','s','t',' ','a',' ','C','u','f','f',' ',
'W','o','o','d','e','n',' ','S','t','i','c','k',  
'O','a','k',' ','C','l','u','b',' ',' ',' ',' ',};

    for(i=0;i!=12;i++){
    WeaponName[i]=WeaponNameList[i];}

I can’t have all the tables to be mapped to RAM, I’ll quickly run out of RAM. So how do you mass read from PROGMEM and load the data to RAM by using a variable?


(Simon) #2
const uint8_t PROGMEM sampleMAP[48] = {
0,0,0,0,6,9,2,0,
0,0,2,3,2,0,10,0,
0,2,2,0,5,2,2,0,
0,0,2,4,2,0,11,0,
0,0,0,0,7,8,2,15,
0,0,0,0,0,0,0,0,};

tile=0;

for(int bgy=0;bgy<72;bgy+=12){
  for(int bgx=0;bgx<128;bgx+=16){
    uint8_t tileNo = pgm_read_byte(&sampleMAP[tile]);
    arduboy.drawBitmap(bgx,bgy,background+tileNo*32,16,16,WHITE);
    tile++;//arduboy.display();
  }
}

You might also want to look at Sprites class as it provides a lot of nice functions around drawing bitmaps with masks. There is an article here > https://drive.google.com/drive/folders/0B9TvPiLSoaEwLTJuckdWQWV1LW8 … In particular, volume 7, page 33.


#3

Thank you, I successfully retrieved the data from the table. I have a lot of reading to do in the magazine.


(Simon) #4

There are some really good articles in there. I think it is in Volume 5, 6 and 7 there are a series of articles talking about maps like your and how to store and compress them.


(Pharap) #5

As @filmote says, Sprites would be better:

const uint8_t PROGMEM sampleMap[] =
{
	0, 0, 0, 0, 6, 9, 2, 0,
	0, 0, 2, 3, 2, 0, 10, 0,
	0, 2, 2, 0, 5, 2, 2, 0,
	0, 0, 2, 4, 2, 0, 11, 0,
	0, 0, 0, 0, 7, 8, 2, 15,
	0, 0, 0, 0, 0, 0, 0, 0,
};

uint8_t tileIndex = 0;
for(uint8_t y = 0; y < 6; ++y)
{
	uint8_t drawY = (y * 12);
	
	for(uint8_t x = 0; x < 8; ++x)
	{
		uint8_t drawX = (x * 16);

		uint8_t tileId = pgm_read_byte(&sampleMap[tileIndex]);
		Sprites::drawOverwrite(drawX, drawY, background, tileId);
		++tile;
	}
}

All you’d have to do is add two bytes at the start of the background array specifying the width and height of the tiles.
(background isn’t the best name to be honest, I think tileImages or tilesheet would be better.)

You might also find that using 2D arrays for maps is easier:

const uint8_t PROGMEM sampleMAP[6][8] =
{
	{ 0, 0, 0, 0, 6, 9, 2, 0, },
	{ 0, 0, 2, 3, 2, 0, 10, 0, },
	{ 0, 2, 2, 0, 5, 2, 2, 0, },
	{ 0, 0, 2, 4, 2, 0, 11, 0, },
	{ 0, 0, 0, 0, 7, 8, 2, 15, },
	{ 0, 0, 0, 0, 0, 0, 0, 0, },
};

for(uint8_t y = 0; y < 6; ++y)
{
	uint8_t drawY = (y * 12);
	
	for(uint8_t x = 0; x < 8; ++x)
	{
		uint8_t drawX = (x * 16);

		uint8_t tileId = pgm_read_byte(&sampleMAP[y][x]);
		Sprites::drawOverwrite(drawX, drawY, background, tileId);
	}
}

But it depends if all your maps are going to be the same size or not.

There is a way to copy entire arrays from progmem into RAM, but often it’s not worth it.
If you only need a byte at a time,
then it’s often cheaper to just read a byte from progmem as-and-when you need it.

If you only need to print the strings then there’s a cheap way to do that, but it needs a bit of extra magic.

Thanks to this question I’ve finally got around to writing an example of this technique:

Don’t worry too much about how or why it works for now,
because that would require explaining several more advanced topics.
For now you only need to know how to use it.


#6

I should follow up on this, after learning the pgm_read_byte(); I was about to read and load data into the table.

void Battle(void){
arduboy.clear();
if(hardenemy==0){EnID=counter%HARDENEMYLISTSTART;}//example


if(hardenemy==1){c=counter%2;EnID=HARDENEMYLISTSTART+c;}
//for(i=0;i!=32;i++){
//MonsterImage[i]=pgm_read_byte(EasyEnemyGraphic+i+64);}
for(i=0;i!=8;i++){
EnemyName[i]=pgm_read_byte(EnID*8+EnemyNameList+i);}

  EnHP=pgm_read_byte(EnemyStatTable+EnID*6);
  EnMP=pgm_read_byte(EnemyStatTable+EnID*6+1);
  EnAtk=pgm_read_byte(EnemyStatTable+EnID*6+2);
  EnDef=pgm_read_byte(EnemyStatTable+EnID*6+3);
  ExpAward=pgm_read_byte(EnemyStatTable+EnID*6+4);
  GoldAward=pgm_read_byte(EnemyStatTable+EnID*6+5);
  arduboy.setCursor(0,0);
  arduboy.print("HP:");arduboy.print(HP);arduboy.print("/");arduboy.print(MaxHP);
  arduboy.setCursor(0,8);arduboy.print("EH:");arduboy.print(EnHP);
  arduboy.print("M:");arduboy.print(EnMP);
  arduboy.print("A:");arduboy.print(EnAtk);
  arduboy.print("D:");arduboy.print(EnDef);
  arduboy.print("E:");arduboy.print(ExpAward);
  arduboy.fillRect(48,16,32,32,WHITE);
  arduboy.drawRect(49,17,30,30,BLACK);
  if(hardenemy==0){arduboy.drawBitmap(56,24,EasyEnemyGraphic+EnID*32,16,16,BLACK);}
  if(hardenemy==1){arduboy.drawBitmap(52,20,HardEnemyGraphic+(EnID-HARDENEMYLISTSTART)*72,24,24,BLACK);}
//  background+sampleMAP[tile]*32
  arduboy.setCursor(0,48);for(i=0;i!=8;i++){arduboy.write(EnemyName[i]);}
  arduboy.print("\napproaches.");
WaitForButton(); 

I loved Mini Rogue so much, I finally decided to make a rogue type of game.

Practice2

Practice3

I will post my WIP when I get more further into developing this game.


(Simon) #7

Looking really good …

When printing, you should use the F macro to save RAM.

arduboy.print("\napproaches.");

becomes:

arduboy.print(F("\napproaches."));

#8

Whoa thanks. I compiled the code in Arduino, and it said I’ve used up 90% of available RAM. After going through all the print and adding the F macro, now I’m back down to the usual RAM 52%. I been compiling the project on ProjectABE and it doesn’t really tell you how much RAM is used up after compiling. Only the ROM size.


(Simon) #9

At 90% RAM usage, the Arduboy can become unstable especially if your code has an recursion or even deep, nested calls. 90% down to 52% is pretty impressive!

How are you traveling on your PROGMEM size?


(Pharap) #10

When you get chance to post the full code I’d like to havea go at reducing the memory usage.
I can already see some big saving opportunities in that snippet.

For now, if you don’t mind…

A few tips about for loops:

  • If your loop counter is an integer, aim to use < instead of != because it makes the intent clearer to the reader.
    Code is read more than it’s written, so it’s important to keep it clear, neat and tidy.

  • Don’t attempt to reuse the same variable for multiple loops, at best it doesn’t do anything,
    at worst it upsets the compiler’s optimiser because you’re artificially extending the lifetime of the variable.

  • Declare your loop variable as part of the for declaration, i.e. for(uint8_t i = 0; i < 8; ++i).

  • Make sure you’re using a locally declared loop variable, not a global one.
    Local variables are much cheaper than global variables.
    In the best case scenario they don’t even use any RAM,
    whereas globals have to use RAM by definition.

I have some other stuff I’d like to mention, but I’ll wait until the full code is available because that will make it easier to demonstrate.

In particular, I’d like to show how you can change EnemyName to use the code from the “FlashStringHelperDemo” that I published earlier. It should save both RAM and progmem.

For now, try changing

for(i=0;i!=8;i++){arduboy.write(EnemyName[i]);}

To

arduboy.write(EnemyName, 8);

#11

I have 11738 bytes left. Still few more feature to build to start building the game.

I tried it and it didn’t work. I tend to have every variable declared globally due to my habit working on Colecovision games using C. I will post the entire code soon.


(Stephane C) #12

Colecovision… The best version of Venture is on that console… A remake on the Arduboy could be interesting, I heard the enemy AI was particularly good for it’s time… Ok I am off subject. Sorry.


(Pharap) #13

Strange, it ought to do the same thing.

In this case it makes no difference whether you’re using C or C++, locals are cheaper than globals.

The only way C differs is that it requires local variables to be delcared at the start of a block scope whereas C++ permits them to be declared part way through a block scope.


(Pharap) #14

I’ve figured out the problem.
I’m going to make a PR to the Arduboy2 library to have it fixed.

Until then, this should work:

static_cast<Print &>(arduboy).write(name, sizeof(name));

If anyone is interested in what the proper solution is…

Essentially overriding Print::write(uint8_t) caused the other overloads of Print::write to be hidden by a rule called “name hiding”.
The solution is to add a using-declaration to bring the missing overloads into Arduboy2's scope.

As demonstrated by my PR:

The temporary solution I gave earlier works because it forces arduboy to be treated as a reference to a Print object,
thus bringing the formerly hidden write overloads into scope because the code is effectively dealing with the base class rather than the child class.

If any of that made sense to anyone, give yourself a biscuit,
this stuff is pretty advanced magic.