PROGMEM using structs and arrays

Go To Last Post
16 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

I am trying to write a menu system for a Nokia 3310 display using structs and arrays.
I need to cut down on my RAM usage as i don't have any left, so I am using the PROGMEM command to put the strings in Flash, unfortunatley the code I am using does not seem to compile:

 
typedef struct menu_entry
{
	void (*select)(void);  // routine to call when selected
	char name[15];      // name to display for this entry
	struct menu *next_menu;
} Menu_entry;

typedef struct menu
{
	unsigned int num_entries;      //  total # of entries in menu
	unsigned int menu_level;
	const struct menu *previous;
	menu_entry_t entry[4];
	char header[15];                   //  top displayed entry
	char footer[15];
} Menu;


const char text1[] PROGMEM = " text1 ";
const char text2[] PROGMEM = " text2 ";
const char text3[] PROGMEM = " text3 ";
const char text4[] PROGMEM = " text4 ";
const char text5[] PROGMEM = " text5 ";
const char text6[] PROGMEM = " text6 ";


//############################################################

Menu Main_Menu = {
	
	.num_entries = 0,
	.menu_level = 0,
	.previous = NULL,
	.header = text1,
	.footer = text2,
	
};
//############################################################

Menu menu2 = {
	
	.entry =
	{
		{	
			.name = text3,
			.next_menu = &menu3,
		},
		{	
			.name = text4,
			.next_menu = &menu4,
		},
	},
	.num_entries = 2,
	.menu_level = 1,
	.previous = &Main_menu,
	.header = text5,
	.footer = text6,
	
};

wherever i use the " .XXXXXX = textX" the comiler complains.

Any Ideas?

Thanks

JT

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi!

It looks like an incompatible types conversion.
(You are trying to initialize an array with a pointer)
Depending on what you want to do with these strings, you could

1) use pointers instead of arrays in the struct.

2) Initialize the arrays with the strcpy_P(target,source) function.

___(°)^(°)___
A(VR)staroth

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why not place the entire menu system in PROGMEM instead of just the strings? (Assuming, of cource, that the menu hierarchy never changes...)

In any event, you hould be able to get rid of the compiler complaints about ".XXXXX =textX" by changing "XXXXX" from type "char[]" to type "PGM_P".

Otherwise, change the "XXXXX" from type "char[]" to type "char*" and then change the assignment to ".XXXXX = (char*)textX". This is a bit of a hack, but it will work perfectly. And you'll be able to extend this solution so that you'll be able to create pointers to any arbitrary type of structure in program memory. (Ie. you'll be able to place all of your menus in PROGMEM and create pointers between them.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi lfmorrison,

I actually want to put the whole menu system into PROGMEM, but have no idea actually on how to implemet something like that, have been reading FAQ 14....
As this is the first time I am using PROGMEM, i will see if i can just get the strings into PROGMEM.

I have used your first suggestion, seems to be working now..

How would I go about palcing the whole meun system into PROGMEM?

Thanks

JT

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
typedef struct menu_entry 
{ 
   void (*select)(void);  // routine to call when selected 
   char* name;      // name to display for this entry 
   struct menu *next_menu; 
} Menu_entry; 

typedef struct menu 
{ 
   unsigned int num_entries;      //  total # of entries in menu 
   unsigned int menu_level; 
   const struct menu *previous; 
   menu_entry_t entry[4]; 
   char* header;                   //  top displayed entry 
   char* footer; 
} Menu; 

Menu Settings_Menu PROGMEM = {
//...
//...
//...
};

char Text1[] PROGMEM = "Open File";
char Text2[] PROGMEM = "Main Menu";
char Text3[] PROGMEM = "This is a footer";
char Text4[] PROGMEM = "Settings";

Menu MainMenu PROGMEM = {
   num_entries = 2, 
   menu_level = 0, 
   previous = NULL, 
   entry = {
      {
         select = OpenFile, 
         name = (char*)Text1, 
         next_menu = NULL
      },
      {
         select = Settings,
         name = (char*)Text4,
         next_menu = (Menu*)Settings_Menu
      }
   }
   header = (char*)Text2,
   footer = (char*)Text3
};

That will place the entire Main Menu and Settings Menu in PROGMEM.

Now, you'll have to use pgm_read_XX to read the various members of the menu.

void DrawMenu(Menu* menu)
{
   // Draw the header
   lcd_place_string_P((char*)pgm_read_word(menu->header));
   // Draw the menu options
   for(int x = 0; x < pgm_read_word(menu->num_entries); x++)
      lcd_place_string_P((char*)pgm_read_word(menu->entry[x]));
   // Draw the footer
   lcd_place_string_P((char*)pgm_read_word(menu->footer));
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks,
One last question, how does one now read a function pointer out of PROGMEM,
as:

pgm_read_word(current_menu->entry[0].select());

won't work

Thanks

JT

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Try something like

typedef void (*fn_ptr) (void);
<...>
(fn_ptr)pgm_read_word(current_menu->entry[0].select) ();
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Something like this would work when a variable index is used:

    ((fn_ptr)pgm_read_word(¤t_menu->entry[i].select))();

Actually that '&' is needed in the other examples above also since an address must be given to the pgm_read... functions.
/Lars

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

oops... right, Lars!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks Guys,

I basically have it sort of working, after pulling out most of my hair :)

Just two last problems:

Why cant' i do this any more when struct is in PROGMEM

current_menu = &Main_Screen;

and

extern menu_t PROGMEM Main_Screen;
extern menu_t PROGMEM Main_menu;

Thanks for the help sofar

JT

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What does the compiler's complaint look like? In the first case, I'd guess it's an error about incompatible pointer types, right?

current_menu = &Main_Screen;

In this little snippet of code, we see a pointer to a menu_t called current_menu. We also have a menu_t instance called Main_Screen which lives in PROGMEM.

When you take the address of Main_Screen, you end up with a pointer that GCC thinks is incompatible with the pointer-type of current_menu. Somehow, the __attribute__((section(".progmem"))) bit in the declaration of Main_Screen is the cause of this incompatibility. But, you and I both know that the assignment is perfectly valid. So, we can re-assure the compiler that it really is OK to make the assignment by doing this:

current_menu = (menu_t*)&Main_Screen;

As for the other problem:
What does the compiler's complaint look like? Try switching the order of the words in the declaration:

extern menu_t Main_Screen PROGMEM;

[edit]Cliff's questions are valid: is Main_Screen declared as a menu_t that lives in PROGMEM? Is current_menu declared as a menu_t* ?[/edit]

Last Edited: Mon. Sep 19, 2005 - 12:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

When you say "can't do this" what do you mean? - it generates some kind of compiler error?

Just looking at it simplistically. If Main_screen is of type "menu_t PROGMEM" then "current_menu" is going to need to have been declared as a pointer (*) to that type to not get compiler warnings. I don't actually see your declaration of current_menu in any of the foregoing above.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

lfmorrison, your suggestion worked,
as the compiler warning were the __attribute__((section,
and now by reasuring the compiler with (menu_t*) the compiler is now very happy,
with respect to the :

extern menu_t Main_Screen PROGMEM;

that error magically disapeared.

The PROGMEM works great now, I have saved almost 900 Bytes by doing this,
which is great, I think that I will use this every where I can from now on.

One last thing, is if I do this:

if (pgm_read_word(¤t_menu->entry[i].next_menu) != NULL)
{
....
}

The Compiler says:
warning: comparison between pointer and integer
and when I create a pointer called *nothing and make it = to NULL, then replace the NULL in the if statement with *nothing, it compiles with no errors, but the programme bombs out when it reaches that routine.

I still have not figured that out

Other than that, the menu system is fully functional

Thanks

JT

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

NULL is defined as a pointer to a nonexistant void. In an AVR, the void which NULL points to lives at location 0x0000.

pgm_read_word() is a macro that can only ever return a 16-bit integer.

It just so happens that on most AVRs, pointers and 16-bit integers both occupy the same amount of storage space. And it also just so happens that a pointer to location 0xABCD and an integer containing the value 0xABCD both contain the exact same bit pattern of 1's and 0's.

From a pedantic standpoint, it is impossible to talk about comparing a pointer to an integer, because they aren't really the same type of data. One's a number. The other is an address. But, if the compiler is second-guessing your decisions even though you really do know what you're doing, then you can tell the compiler to go ahead and do the comparison anyways by typecasting one of them into the same type as the other.

So the following code should work:

if((menu_t*)pgm_read_word(¤t_menu->entry[i].next_menu) != NULL)
{
...
}

On the other hand, doing something like:

int* something;
something = NULL;
if(pgm_read_word(¤t_menu->entry[i].next_menu) != *something)
{
...
}

Will certainly not work. Think about what that code is going to do. NULL is defined as a pointer to 0x0000. Address 0x0000 in an AVR is actually the first entry in the GPR file. So, your comparison will be between two integers: the 16-bit value stored in PROGMEM at &current_menu->entry[i].next_menu, and whatever value happens to be stored in r0:r1. You normally have absolutely no way of knowing exactly what value that' going to be. It should come as no surprise that this doesn't work the way you might want it to.

Last Edited: Thu. Sep 22, 2005 - 05:53 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks,
It worked.......
I must admit, that my C skills are a little on the "poor" side
But, I have learnt a lot :wink:

Thanks for all the help

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello All of this thread.

This thread is rather old, but I still hope to find some of the contributers.

First of all I am fresh in C and AVRGCC so if I miss something....

I managed to get most of the suff working but I have still a problem with the .previous/next_menu mechanism
Where do I place the declaration of the next_menu
Before the previous then .previous can't find it
After then .next_menu does.

How can this be solved?

Thanks.

With best regards,

Frans.