I have been writing some AVR code that involves storing some structs within program memory (using PROGMEM), but I was unsure as to the exact approach for retrieving and making use the struct's members, especially considering I would be storing strings, pointers to variables and pointers to functions. So I thought I would experiment with a test program and run it within the Atmel Studio simulator.
However, there is one thing I just cannot get to work: executing a function from a pointer within the PROGMEM-stored struct. I don't know whether it's something I'm doing wrong in the code, or whether it's the simulator acting incorrectly, or the compiler generating nonsensical assembly.
The code of my test program is as follows:
#include <stdio.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
typedef uint8_t (*foo_func_t)(const void *val);
typedef struct {
char title[10];
uint8_t count;
void *value;
foo_func_t func;
} foo_t;
static void blah(const foo_t *foo);
static uint8_t yadda(const void *val);
static uint8_t abc = 99;
static const foo_t xyz PROGMEM = {
"a string",
123,
&abc,
yadda
};
void blah(const foo_t *foo) {
uint8_t c, v, z;
char t[11];
foo_func_t *f;
c = pgm_read_byte(&foo->count); // Works
snprintf_P(t, 11, PSTR("%S\n"), foo->title); // Works
v = *((uint8_t *)pgm_read_ptr(&foo->value)); // Works
f = (foo_func_t *)pgm_read_ptr(&foo->func); // Seems to be okay
z = (*f)(&v /*pgm_read_ptr(&foo->value)*/); // Bad doggy, no biscuit!!
}
uint8_t yadda(const void *val) {
uint8_t tmp = *((uint8_t *)val);
return tmp + 10;
}
int main(void) {
blah(&xyz);
}
The problem comes when executing the line with
z = (*f)(&v) (ignore the commented-out
pgm_read_ptr(&foo->value) - that's the ultimate goal, to pass a variable pointer also stored in flash, but I thought I'd eliminate that from the equation). I believe the C code I have that retrieves the function pointer (
f = (foo_func_t *)pgm_read_ptr(&foo->func)) is probably correct, as
f gets the value 0x00E4, which appears to correspond to the address of
yadda(). But execution simply does
not pass to that function, and instead jumps off somewhere random, with the inevitable effect that the whole program just starts over.
While fiddling around in the debugger, I discovered something new to me, the 'Disassembler' pane, which is an actual interactive representation of the assembler code that can be stepped through (as opposed to the
avr-objdump output generated at compile time). When I step the disassembly onwards from the point of the offending line of C code, I see the following:
000000D0 LDD R24,Y+13 Load indirect with displacement
000000D1 LDD R25,Y+14 Load indirect with displacement
000000D2 MOVW R30,R24 Copy register pair
000000D3 LDD R18,Z+0 Load indirect with displacement
000000D4 LDD R19,Z+1 Load indirect with displacement
000000D5 MOVW R24,R28 Copy register pair
000000D6 ADIW R24,0x10 Add immediate to word
000000D7 MOVW R30,R18 Copy register pair
000000D8 ICALL Indirect call to (Z)
000000D9 STD Y+15,R24 Store indirect with displacement
Once it hits the
ICALL, that's where it jumps off somewhere random. I presume the caption is correct, and it jumps to a location corresponding to the Z register's value. Interestingly, just before the
MOVW R30,R18, the Z register actually
does contain the correct address: 0x00E4. But when that instruction executes, the Z reg ends up with the wrong value!
WTF is going on here!? What's at fault? My code, the simulator, the compiler?