making an array of PROGMEM structs

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

Greetings,
I am trying to make an array of pointers to a struct in PROGMEM. To illustrate the problem I will show what I have been successful at.

#include "avr/eeprom.h"

// EEPROM variables
uint8_t EEMEM eep_armed = 1;
uint8_t EEMEM eep_arm_sta = 30;
uint8_t EEMEM eep_arm_ste = 10;
uint8_t EEMEM eep_reprtlev = 1;

// EEPROM Pointers (in RAM,  Yes, I should put them in ROM)
uint8_t * eepparam_ptr_ram[] ={
	&eep_armed,
	&eep_arm_sta,
	&eep_arm_ste,
	&eep_reprtlev
};

So, Now I can access these variables by name in the usual way or by the array index like so.

for (uint8_t i = 0 ; i<4 ; i++){
	temp = eeprom_read_byte(eepparam_ptr_ram[i]);
	some_function(temp);
}

=================
OK enough preliminary stuff. Now for the problem.
I want to do the same with a struct. Like so..

typedef struct  PROGMEM  {
	const uint8_t   mode;
	uint8_t       *eepval;
	const uint8_t   eepmin;
	const uint8_t   eepmax;
	const char  Text[];
} test_struct_t ;
//

const test_struct_t test0 = { (uint8_t)0 , (uint8_t*)&eep_armed		,(uint8_t)5,(uint8_t)100	,"Text 0"};
const test_struct_t test1 = { (uint8_t)10, (uint8_t*)&eep_arm_sta	,(uint8_t)8,(uint8_t)19	,"Text 1"};
const test_struct_t test2 = { (uint8_t)20, (uint8_t*)&eep_arm_ste	,(uint8_t)9,(uint8_t)40	,"Text 2"};
const test_struct_t test3 = { (uint8_t)30, (uint8_t*)&eep_reprtlev	,(uint8_t)0,(uint8_t)5		,"Text 3"};

I can get the data out in the usual way..

uint8_t temp;
// read a byte from ROM
temp = pgm_read_byte(&test0.mode) ;  
// Read text
rprintfProgStr(test2.Text);
// Read EEP via pointer in the struct
temp = eeprom_read_byte((uint8_t*) pgm_read_word(&test3.eepval));

But what I want is to be able to reference the structs via an array of pointers. (like the EEP example above. BUT .........

test_struct_t * teststuct_ptr_ram[] = {
	 test0,   /// errors on this line
	 test1,   /// errors on this line
	 test2,   /// errors on this line
	 test3   /// errors on this line
}

I get the following errors (4 times)

main.c:70: error: initializer element is not constant
main.c:70: error: (near initialization for 'teststuct_ptr_ram[0]')

So I can not get the pointer array made. After I get that done, then I will need to get the data out.

This does not give a compiler error, but I have not initialized the pointers and thus not tested it yet.

test_struct_t * teststuct_ptr_ram[4];
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
test_struct_t * teststuct_ptr_ram[] = { 
    test0,   /// errors on this line 
    test1,   /// errors on this line 
    test2,   /// errors on this line 
    test3   /// errors on this line 
}

teststruct_ptr_ram is an array of pointers to structs, but test0, test1, etc. are structs, not pointers to structs.

Regards,
Steve A.

The Board helps those that help themselves.

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

Steve,
Well I feel a bit silly, I should follow my own epp example at the top of my post.

test_struct_t * teststuct_ptr_ram[] = {
	 &test0,
	 &test1,
	 &test2,
	 &test3
}; 

No errors but I get warnings.

main.c:70: warning: initialization discards qualifiers from pointer target type

By typecasting explicitly I get a clean compile.

test_struct_t * teststuct_ptr_ram[] = {
	(test_struct_t*) &test0,
	(test_struct_t*) &test1,
	(test_struct_t*) &test2,
	(test_struct_t*) &test3
};  

Thanks
Kirk

Next I need to write some code to get the data out.

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

OK now, with Steve's help, I have an array of pointers that point to the head of each of the structs. I dug through the hex file to verify this. And I can get out the first byte of the struct with -

temp = pgm_read_byte(teststuct_ptr_ram[j]) ;

But this is not so useful. What I need is the correct syntax for getting the address of a member to give to pgm_read_XXX().

psuedocode temp = pgm_read_byte(teststruct_ptr_ram[j] (someoperator) membername)

Neither . nor -> gives the right answer which makes sense as a reference says

Quote:
-> returns the value of field in the structure pointed to by expr1.
. returns the value of field in the structure lvalue1
So both are returning values but I need addresses.
All I can think of is to make some sort of addition to the pointer using sizeof(member1)+sizeof(member2)... yuck

(Future note - I will be converting the pointer array to progmem at a later date. Are there any syntax snags there too?)

Thanks
(still testing)
Kirk

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

KirkCharles wrote:
I will be converting the pointer array to progmem at a later date.
The modified example code below has the array of pointers in Flash. First the pointer is read out and then the member is read using an offset from the pointer.
#include 
#include 
#include 

#if !defined(EEMEM)
#define EEMEM __attribute__((section(".eeprom")))
#endif

typedef struct
{
  const uint8_t mode;
  uint8_t       *eepval;
  const uint8_t eepmin;
  const uint8_t eepmax;
  const char    Text[];
} test_struct_t;
typedef test_struct_t PROGMEM prog_test_struct_t;

uint8_t eep_armed EEMEM;
uint8_t eep_arm_sta EEMEM;
uint8_t eep_arm_ste EEMEM;
uint8_t eep_reprtlev EEMEM;

const prog_test_struct_t test0 = { 0,  &eep_armed,    5,  100,  "Text 0"};
const prog_test_struct_t test1 = { 10, &eep_arm_sta,  8,  19,   "Text 1"};
const prog_test_struct_t test2 = { 20, &eep_arm_ste,  9,  40,   "Text 2"};
const prog_test_struct_t test3 = { 30, &eep_reprtlev, 0,  5,    "Text 3"}; 

prog_test_struct_t *teststruct_ptr[] =
{
  (prog_test_struct_t *)&test0,
  (prog_test_struct_t *)&test1,
  (prog_test_struct_t *)&test2,
  (prog_test_struct_t *)&test3
};

int j;
uint8_t temp;

int main(void)
{
  prog_test_struct_t *p;
  p = (prog_test_struct_t *)pgm_read_word(&teststruct_ptr[j]);
  temp = pgm_read_byte(&p->eepmin);
}

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

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

These should get you what you need.

while I haven't used these on an AVR project yet, I've used these macros at various times for going on 20+ years.

/*
 *	A few macros to calculate offsets of a structure member.
 *  OFFSETOF    calculates for a member within a structure/type
 *  OFFSETOFPTR calculates for a member within existing structure
 *		When using OFFSETOFPTR() you can pass in the pointer or
 *      for a structure with no pointer, just use "&" i.e. &datastruct 
 */
#ifndef OFFSETOF
#define OFFSETOF(type, member)  ((unsigned int)&(((type*)0)->member))
#endif

#ifndef SIZEOFMEMBER
#define SIZEOFMEMBER(type, member)  ((unsigned int)sizeof(((type*)0)->member))
#endif

#ifndef OFFSETOFPTR
#define OFFSETOFPTR(ptr, member)  ((void * )&((ptr)->member) - (void*)(ptr))
#endif

OFFSETOFPTR is kind of neat because you don't have to pass in a type.

As I haven't tried them yet with AVR code, they might need one additional layer of casting say if you need the final answer to be say a uint8_t

--- bill

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

Wow Don,
Thanks for taking so much time picking apart my code.

Quote:
First the pointer is read out and then the member is read using an offset from the pointer.
This advice unlocked the problem for me. I have done some testing and have some news and questions.
---------
News:
I can read the members of the struct! :-)
I still have the pointer array in RAM but have simplified the code. I almost understand what I did!
One key is to realize that the -> operator gives the value and we want the address. So putting & in front gives us the address back.

temp = pgm_read_byte( &teststuct_ptr_ram[j]->eepmin) ;
temp = pgm_read_byte(&(teststuct_ptr_ram[i]->eepmin));

Both of these work and I am confused why.
The -> op takes in a pointer and a member name. It gives back the value of the member.
-> is below & in precedence. So it seems to me that only the second example should work. (compiler bug?)
-----------
Questions: Don, You have layered the typedefs for the struct. The first is generic and the second is progmem specific. The generic one is never used. Do you do this for maintainability and readability?
You moved that EEMEM attribute. Is the end the preferred position for the attribute? (I know that gcc 4.3.2 is more picky about the position of the PROGMEM attribute)
-----------
Bill, Thanks for the macros, I will be digging into them in the next few days.

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

KirkCharles wrote:
-> is below & in precedence.
Huh?

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

KirkCharles wrote:
-> is below & in precedence. So it seems to me that only the second example should work.
Not in my book ("The C Programming Language" Kernighan & Ritchie, Second Edition). Check your reference again.
KirkCharles wrote:
The generic one is never used. Do you do this for maintainability and readability?
The "generic" one is used, of course, in defining the second. I did that primarily to illustrate that it could be done. However, it is useful to have more a generic typedef because they can be used in other situations. For example, if you wanted to, you could define a RAM-resident instance of the structure using the more generic typedef. Also, I have a vague recollection that it was necessary to do it like this in C++ code.
KirkCharles wrote:
You moved that EEMEM attribute.
I had no specific purpose for doing so. I normally write it as it is shown but there may be other variations that work equally well.

Don Kinzer
ZBasic Microcontrollers
http://www.zbasic.net

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

dkinzer wrote:
KirkCharles wrote:
-> is below & in precedence. So it seems to me that only the second example should work.
Not in my book ("The C Programming Language" Kernighan & Ritchie, Second Edition). Check your reference again.

Well once again I feel silly. I did not look carfully at the web reference I used. It was listed least to highest.
Thanks guys for all your help
Kirk

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

KirkCharles wrote:

Bill, Thanks for the macros, I will be digging into them in the next few days.

They are fairly straightforward.

The first two create a dummy pointer to a data structure at memory address 0.

The first then takes the address of the particular structure member.
Since the address of the data structure was zero, the address of the member is what the offset of the member is from the beginning of the structure. The zero pointer allows the compiler to essentially calculate this offset for us.

The second takes the size of the member from the dummy structure pointer.

The 3rd one, OFFSETOFPTR, works by doing the same type of math on a pointer to a data structure. It subtracts the address of the pointer from the address of a member in a data structure pointed to by the supplied pointer. It casts the pointers to void *
to ensure that the pointer math is handled as essentially numbers rather than in multiples of the size of the data structure.

--- bill