Pointer to structure in FLASH, no memcopy.

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

I've read through the posts, I've written some samples and I have not been able to get a pointer to a member of a structure in an array of structures in FLASH to work the same as if it were in RAM. I am clearly missing something about the macros for PGM_VOID_P and PGM_P or something.

I have been able to locate strings in FLASH and send them just fine. I suppose I could restructure this to look like to parallel arrays but that's what structures are for right? All of the examples in the posts perform a memcpy. I do not really need a copy of the member string, I would just like to send it to the uart from FLASH one character at a time. Eventually it will be a command interpreter.

Compiled with WinAVR install of avrgcc.

This bit works fine:


/* Example demonstrating reading a structure in RAM and executing a function pointer */

#include 
#include 
#include "string.h"
.
.
// ... prototypes for functions LcdCntl etc.
.
.

// typedef for the command structure
typedef struct 
  {
  char *cmmnd_strng;
  void (* foo)(void);
  }cmmnd_strc;

// Declare and populate an array of structures
static cmmnd_strc cmmnd_tbl [] =
  {
    { "Lcdoff",		LcdCntl },
    { "LedOn",		LedCntl },
    { "setbaud",	ChngBaud },
  };
.
.
void tx_string( char *ptr ) // Send a string in  flash to the UART ************
{
unsigned char temp;
	temp = *ptr;
	while ( temp != '\0') {
		temp = *ptr;
		while ( !(UCSRA & (1 << UDRE))) ; // wait for tx buffer to empty
		UDR = temp;
		ptr++;
	}
}


void tx_string_P( PGM_P ptr ) // Send a string in  flash to the UART **********
{
unsigned char temp;
	temp = pgm_read_byte(ptr);
	while ( temp != '\0') {
		temp = pgm_read_byte(ptr);
		while ( !(UCSRA & (1 << UDRE))) ; // wait for tx buffer to empty
		UDR = temp;
		ptr++;
	}
}

// Uart init .. init uart trimmed from post
  
int main(void) //**************************************************************
{
cmmnd_strc *p_strc = cmmnd_tbl;		// Creates a pointer of type cmmnd_strc

	USART_Init(BAUDRATE9600);			// Init UART for the tx_string routine
	tx_string("\r\nStart, struct in RAM\r\n");

	// De-reference an element in the structure and Print the text in the table.
	tx_string(p_strc->cmmnd_strng);
	p_strc->foo();
	
	tx_string("\r\n:End\r\n");

	while(1);
	return (0);
} 

Now move the array of structures to FLASH, this does not work

typedef struct 
  {
  char *cmmnd_strng;
  void (* foo)(void);
  }cmmnd_strc;

// Declare and populate an array of structures
static cmmnd_strc cmmnd_tbl [] PROGMEM =
  {
    { "Lcdoff",		LcdCntl },
    { "LedOn",		LedCntl },
    { "setbaud",	ChngBaud },
  };
  
// ...uart init and tx_string functions trimmed from post

int main(void) //**************************************************************
{
PGM_VOID_P p_strc = cmmnd_tbl;		// Creates a pointer of type cmd_st

	USART_Init(BAUDRATE9600);			// Init UART for the tx_string routine
	tx_string("\r\nStart, struct in FLASH:\r\n");

	// De-reference an element in the structure and Print the text in the table.
	tx_string_P(p_strc->cmmnd_strng);	

	tx_string("\r\n:End\r\n");

	while(1);
	return (0);
} 

I appologize in advance if this is something plebian but I am down to poking sticks at it and not getting anywhere. Thanks for any pointers.

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

Look at the avr-libc documentation , especially FAQ 14 should be interesting for you.

Regards,

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

Thank you, I did go over it earlier but does this mean that the only way to reference a structure in FLASH is by copying it to a buffer RAM first? It would be faster to make parallel arrays. Right? Arrays are linear and can be referenced directly. Why can't a structure be referenced the same way in FLASH that it is in RAM?

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

feralbeagle wrote:
Thank you, I did go over it earlier but does this mean that the only way to reference a structure in FLASH is by copying it to a buffer RAM first? It would be faster to make parallel arrays. Right? Arrays are linear and can be referenced directly. Why can't a structure be referenced the same way in FLASH that it is in RAM?

An AVR is using HARVARD Architecture , meaning that it has separate addres/data busses for FLASH & RAM.

FLASH bascially is designed to be read only by the "ProgramCounter".
Atmel has given a few instructions to read data from flash ,LPM i think its called ???.
So in order to access tha FLASH from a program you have to use that one.
Thats what avr-libc does wit those xxx_P routines.

So you either have to memcopy_P the entire struct to ram , or to read the FLASH struct bytewise , and send that to the uart.

NOTE !!!!

Afaik you can not be sure that the struct is stored in flash/eeprom/ram , in the 100% same way you have defined it in AVR-GCC.

Meaning you cant just start from "Byte 1" in the struct and send bytes one at a time. As the fields could be reversed in the physical memory layout.

So you might want to reference data in the struct , via the same struct definition.

/Bingo

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

feralbeagle wrote:
.. does this mean that the only way to reference a structure in FLASH is by copying it to a buffer RAM first?

Hi.

No, but it cannot be done directly.
Here's a similar example :

char Testchar;

// first put your info strings into flash
char Str1[] PROGMEM="Lcdoff";
char Str2[] PROGMEM="Ledon";
char Str3[] PROGMEM="Setbaud";

typedef struct 
  { 
   PGM_P cmmnd_strng; 
   int num; 
  }cmmnd_strc; 


// Declare and populate an array of structures
// inserting flash addresses of existing PROGMEM strings 
cmmnd_strc cmmnd_tbl[] PROGMEM = 
  { 
    { Str1,1}, 
    { Str2,2}, 
    { Str3,3}, 
  }; 

//********************
int main(void)
{
 // to get a single char from info string you have to do two steps :
// - get a structure field flash address ( e.g. required string address)
// - use this address to read required char

 Testchar=pgm_read_byte(pgm_read_word(&cmmnd_tbl[2].cmmnd_strng)+2);
//.........

Asm preview shows :

0000002a :
  2a:	44 00 01 00 3e 00 02 00 36 00 03 00                 D...>...6...

00000036 :
  36:	53 65 74 62 61 75 64 00                             Setbaud.

0000003e :
  3e:	4c 65 64 6f 6e 00                                   Ledon.

00000044 :
  44:	4c 63 64 6f 66 66 00                                Lcdoff.

0000004b :
  4b:	06 07 08 09 00      

Address of cmmnd_tbl[2].cmmnd_strng is 0x0036; it's "Setbaud" start address. This address - incremented with char index - is used in
pgm_read_byte to read char ( 't' )

HTH
Best regards Jurek S.

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

JerzySzczesiul wrote:

I should clear some mistakes in comments :

 // to get a single char from info string you have to do two steps :
// - get a structure field flash address ( e.g. required string address)
// - use this address to read required char

should rather be :

// - use a structure field flash address to read it's content ,
//  which is an appropriate info string flash address
// - use info string flash address to read chars from the string

and

Address of cmmnd_tbl[2].cmmnd_strng is 0x0036; it's "Setbaud" start address. This address - incremented with char index - is used in
pgm_read_byte to read char ( 't' )

should be :

Content of cmmnd_tbl[2].cmmnd_strng is 0x0036; it's "Setbaud" string address. This address - incremented with char index - is used in
pgm_read_byte to read char ( 't' )

Best regards Jurek S.


[/code]

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

Ah, it makes sense. Thank you all very much. This is exactly what I was looking for. I'll be using a different approach unless I have a lot of RAM left over. -Bryan

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

Jurek,

Thank you so much for this detailed explanation. I stuck you code example in with the serial routines and it works great. This is exactly what I needed. There were a few points that were eluding me and this cleared it up very well.