[TUT] [C] GCC and the PROGMEM Attribute

Go To Last Post
204 posts / 0 new

Pages

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

First, thanks for the tutorial. It was helpful in understanding how things are supposed to be done.

Second... YUCK! Trying to define any data structure more complicated than a string or an array of simple data types reminds me of Fortran programs trying to implement 'data structures' with shared common areas and such. Does C++ permit operator overloading enough (ie including pointer dereference/etc) that one could use some C++ magic code to make the use of PROGMEM space invisible to the users?

I've got problems similar to everyone else; menus:

typedef const struct menu_item_ {
    unsigned char menu_key;
    menu_action_t menu_action;	/* function or menu pointer */
} menu_item_t;

const menu_item_t foo[] PROGMEM = {
  {'A', nextmenuA},
  {'B', nextmenuB},
  {0, "menu help string"}
};

And then code, for example, to draw a prompt (on a non-progmem implementation) that looks like:

menu_item_t *menu, *lastitem;
    drawmenu:
	lastitem = menu;
	while ((lastitem->menu_key & 0x7F) != 0)
	    lastitem++;  /* Find menu terminator to get type of draw */
	if (lastitem->menu_action != 0) {
	    char *p = (char *) lastitem->menu_action;
	    while (*p)
		printf("%c", *p++);
	} else {    
	    while (menu->menu_key != 0) {
		printf("%c, ", menu->menu_key & 0x7F);
		menu++;
	    }
	}
	printf("\r\n");
	return;				/* Done! */

and the code is looking really ugly as I try to insert pgm_read calls in the appropriate places...

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

So how come the definition of PSTR defines the string as

((const PROGMEM char *)(s))

instead of using the typedefs for prog_char:

((const prog_char *)(s))

It seems like the latter would allow C++ code to run different methods for progmem strings rather than ram strings (which would be a good thing.)

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

Thanks Dean! Your tutorials helped me a lot.

Nicu reSpawn.

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

Many thanks for this great tutorial, that helped me to optimize my code and understand better how to use flash to store data. I have one question though: is there a size limit in using flash to store static data ?
While I was switching/improving my strings to flash, it then suddenly did strange things (printing a long list of unreadable cars, as if the string wasn't terminated properly...). I had to go back to RAM for some parts of code.
Is there something I'm not considering ? Maybe a mistake somewhere else, but I don't see it right now.

I'm using an atmega128, and the summary is:
Program: 23934 bytes (18.3% Full)
(.text + .data + .bootloader)

Data: 642 bytes (15.7% Full)
(.data + .bss + .noinit)

Once more, thanks to avrfreaks folks !

Vincent.

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

There are two limits:

1) The hard limit of the amount of FLASH memory in the AVR - this is a physical limit
2) The 64KB limit due to GCC using 16-bit pointers - this can be worked around using special "far pointer" functions and macros

Your code is only 23KB long, so you're hitting neither. Does your code have any large RAM arrays which might be stepping on the stack or string if you're copying it into RAM before use?

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

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

Well, I don't copy anything myself to RAM. I just use large structure to store all states/transitions of the state machine driving the AVR. Something to do with mapping and 64k limit ? On the 64K limit, could you explain further the far pointer trick (is it in a tutorial I would have missed ?) ?

Thanks.
Vincent.

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

The avr-libc pgmspace.h module contains "far" versions of the existing methods - "pgm_read_byte_far()", for example. To use them on a string located over the 64KB barrier, you'd just OR the string's address (typecast as a long) with 0x10000 to create a psudo-pointer.

char StringLocatedOver64KB[] = "Test";

int main (void)
{
	char a = pgm_read_byte_far(0x10000 | (uint16_t)&StringLocatedOver64KB);
}

You'd need to write your own "far" versions of the other library routines which deal with FLASH memory, such as memcmp_P(), using the far accessor routines.

Alternatively, use Carlos' macro to get the far address of a variable in FLASH space instead:

#define GET_FAR_ADDRESS(var)                          \
({                                                    \
    uint_farptr_t tmp;                                \
                                                      \
    __asm__ __volatile__(                             \
                                                      \
            "ldi    %A0, lo8(%1)"           "\n\t"    \
            "ldi    %B0, hi8(%1)"           "\n\t"    \
            "ldi    %C0, hh8(%1)"           "\n\t"    \
            "clr    %D0"                    "\n\t"    \
        :                                             \
            "=d" (tmp)                                \
        :                                             \
            "p"  (&(var))                             \
    );                                                \
    tmp;                                              \
})

int main (void)
{
	char a = pgm_read_byte_far(GET_FAR_ADDRESS(StringLocatedOver64KB);
}

Which has the distinct advantage that it returns the correct address (albeit a long address) for any FLASH data whether it is over the 64KB boundary or not -- coupled with the far accessor routines and you have bulletproof code.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

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

Nice tutorial, but I'm still totally screwed.
I read also the avr-libc FAQ http://www.nongnu.org/avr-libc/u... but I'm still not getting how to make an array of structs into flash.

I want to have a struct of strings for a menu.
like
struct {
char z[2][20]
} mystruct

And I want to make an array of this structs for different languages.
mystruct myarray[n_languages]

So that i can access the strings by something like
this example:
array[French].z[0]
or
array[French].z[1]

And now I want to have my 2.5k of strings in flash using Progmem :lol:
How to do that?

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

Easy, just add the PROGMEM attribute to the structure itself:

struct {
char z[2][20]
} mystruct PROGMEM;

If you want an array of structures, use the PROGMEM on the array instead.

To access the structure elements you can either read out the structs from FLASH into a RAM structure using pgm_read_block (and then use the RAM strucuture as you would normally), or use the pgm_read_* macros on the structure elements individually as you use them.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

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

Thank you for your fast answer.
To clarify my understanding:

When using

struct {
char z[2][20]
} mystruct PROGMEM;

mystruct myarray[n_languages];

I will have the structs in Flash and the array in RAM?
Or do I have to use

struct {
char z[2][20]
} mystruct;

mystruct myarray[n_languages] PROGMEM;

?

And how do I fill my array of structs with strings when it resides in ROM?
In the moment I'm filling it during menu_init() function with this code:

menu_init{
strcpy(my_array[French].z[0], "Salut        ");
strcpy(my_array[French].z[1], "Test        ");
strcpy(my_array[English].z[1], "Hello        ");
.
.
.
}

I know I cannot do it this way anymore, because the ROM stuff is constant so I cannot write to it during runtime. But how to do it know in a smart way?

Regards,
Baldrian

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

Rule of thumb: when in doubt, add more PROGMEM. I think you'd end up with a PROGMEM array of structures, since you've got an actual array of structures, rather than an array of pointers to the structures (which *would* result in a SRAM array of pointers to PROGMEM space).

You cannot change the PROGMEM data at runtime (since it is embedded in the FLASH), but you can initialize it via standard C methods:

mystruct myarray[n_languages] PROGMEM = 
{
 {z: "Salut        "}, // Initializer for myarray[0]
 {z: "Test        "}, // Initializer for myarray[1]
 {z: "Hello        "},  // Initializer for myarray[2]
// etc.
};

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

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

Thanks alot man Very Happy I think I'm on the right way now.

The only thing is, that I cannot test out everything, because I have to wait for my hardware for 2 more weeks. Sad

I made the "readout the strings to my lcd buffer" routine like this:

// disp_buf is NOT in Flash. myarray is in flash.
for (i = 0; i < LCD_H; i++)
    strncpy_P(dispbuf.z[i], myarray[curlanguage].z[i], LCD_W);

It gives no warnings, which is good. I will see if it is working like expected.

Again, thanks a lot for your help.
Regards,
Baldrian

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
struct {
char z[menupoints][20]
} mystructX; 

mystructX myarrayX[n_languages] PROGMEM =
{
 {z: "Salut        "}, // Initializer for myarray[0]
 {z: "Test        "}, // Initializer for myarray[1]
 {z: "Hello        "},  // Initializer for myarray[2]
// etc.
};

Now I have this. I have different mystructX foe different menu sizes (so different menupoints values)

I'm building my menu on lcd by copying the myarrayX[language] to LCD buffer using strcpy_P.
This works fine, but now I have for every menu one function where this is done.
What I want to do is to have only one function and give the myarrayX as a parameter to the function. I think I have to realize this using a pointer, because the myarrayX are all different in size as I mentioned before.

I think I can give a pinter to myarrayX to the function and give the size (menupoints) also to it.
But I couldn't figure out how to do this. So, if someone has an idea about that, please share it with me.

Regards,
Baldrian

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

Baldrian,

Just a mad idea but to pack your strings how about using:

"Salut$Hello$Hola"

Then when you display(HELLO_ID, language) where language is 0=French, 1=English, 2=Spanish. You just first point to the right sting using "HELLO_ID" and then skip 'language' number of '$'s along the string to find the start point to display and then continue displaying characters until NUL or another '$' is encountered. Use something other than '$' if there's a chance it ever needs to be displayed.

In the code you present above, otherwise, you are wasting huge amounts of bytes for ' 's

Cliff

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

That means, delete the structs and just make a list of strings which all have a specific name like

char hello_id PROGMEM = "Salut$Hello$Hola" 

And then I will give some pointers (based on which menu items (strings) have to be displayed) to my menu() function.
Like:

 menu(&hello_ID)

Or better give a struct of pointers to all needed strings to the menu() functions.

I will try it.

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

No I was thinking of an array of strings such as:

char * strings[] = {
 "Salut$Hello$Hola",
 "Aurevoir$Bye$Adios"
 etc.
}

And possibly an enum or some defines:

typedef enum {
 HELLO_ID,
 GOODBYE_ID,
 etc.
} STRINGS;

or

#define HELLO_ID 1
#define GOODBYE_ID 2

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

I would do it a little differently in this case. (there are many ways to accomplish your goal) Since size is also of concern, I would create individual strings for each phrase in each language, then create an array of pointers (for each language) to the appropriate phrases. And finally create an array of pointers to each of the language arrays. Any other menu data is kept in its own array of structures. To select the current language to display a variable is loaded with the address of the desired language array. This pointer can be global, and then accessed by any display routines, or passed as an additional parameter to them.

typedef enum 
{
 HELLO_ID,
 GOODBYE_ID,
 etc.
} string_id_t;


typedef enum 
{
 ENGLISH_ID,
 FRENCH_ID,
 SPANISH_ID,
 etc.
} language_id_t;

// language-specific phrase strings
char hello_en[] PROGMEM = "Hello";
char goodbye_en[] PROGMEM = "Bye";

char hello_fr[] PROGMEM = "Salut";
char goodbye_fr[] PROGMEM = "Aurevoir";

char hello_es[] PROGMEM = "Hola";
char goodbye_es[] PROGMEM = "Adios";

etc.

// language-specific phrase arrays
const char *prompts_en[] PROGMEM = 
{
 hello_en, 
 goodbye_en,
 etc., 
 NULL
};

const char  *prompts_fr[] PROGMEM = 
{
 hello_fr, 
 goodbye_fr, 
 etc., 
 NULL
};

const char *prompts_es[] PROGMEM = 
{
 hello_es, 
 goodbye_es,
 etc., 
 NULL
};

etc.

// language array
const char **languages[] PROGMEM = 
{
 prompts_en,
 prompts_fr,
 prompts_es,
 etc., 
 NULL
};

// global var to hold the language pointer
const char **current_language = 
     pgm_read_word(&languages[ENGLISH_ID]);


// accessing individual strings
const char *phrase =
     pgm_read_word(¤t_language[string_id]);

while(ch = pgm_read_word(phrase++))
{
  // output code here
}

It may seem tedious, and it is to do by hand. However it can easily be managed with a small generator app that takes text files and generates the necessary data tables. This allows you to edit the resource strings for each language easily.

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

Quote:
Since size is also of concern, I would create individual strings for each phrase in each language,
I don't get it glitch, your way to do it is very clean but it does take more space than what clawson has above. You need N_LANGAUGES * 2 + (N_LANGAUGES - 1) * N_STRINGS * 2 bytes extra for all the pointer arrays.
/Lars

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

Yes Cliffs solution is, slightly, more dense than mine, but it requires more processing time, as you have to search the string for the correct entry.

My comment about size is aimed at the 2 dimensional array approach shown earlier. In which case it only takes more space if all the strings are close to the same length. As long as the average length of a string is more than 2 shorter than the longest string, you will be saving space with my method. (you still may if it is not, but it is not guaranteed)

Note that some of that overhead you show is required for Cliffs solution as well. The solution "as proposed" does not work in progmem.

char * strings[] = {
 "Salut$Hello$Hola",
 "Aurevoir$Bye$Adios"
 etc.
}

The above needs to be either stored as a 2 dimensional array (lots of wasted space, if the amalgamated strings differ in length greatly), or as an array of pointers (same as my method). Thus my method only adds one more level of pointers to the mix. The trade off here is a few hundred bytes, vs execution speed.

Working progmem versions of Cliffs Solution:
1) 2-dimensional Array

char strings[][20] PROGMEM = {
 "Salut$Hello$Hola",
 "Aurevoir$Bye$Adios"
 etc.
}

2) Array of pointers:

char hello_str[] PROGMEM = "Salut$Hello$Hola";
char goodbye_str[] PROGMEM = "Aurevoir$Bye$Adios";

const char *strings[] PROGMEM = {
 hello_str,
 goodbye_str,
 etc.
}

**for the given example strings, option 1 is more compact.

[edit]
one could reduce memory footprint of my method further, by storing all the string pointers in a 2d array.

typedef enum
{
 HELLO_ID,
 GOODBYE_ID,
 etc.
} string_id_t;


typedef enum
{
 ENGLISH_ID,
 FRENCH_ID,
 SPANISH_ID,
 etc.
} language_id_t;

// language-specific phrase strings
char hello_en[] PROGMEM = "Hello";
char goodbye_en[] PROGMEM = "Bye";

char hello_fr[] PROGMEM = "Salut";
char goodbye_fr[] PROGMEM = "Aurevoir";

char hello_es[] PROGMEM = "Hola";
char goodbye_es[] PROGMEM = "Adios";

etc.

// language-specific phrase arrays
const char *languages[N_LANGUAGES][N_WORDS] PROGMEM = {
 {
  hello_en,
  goodbye_en,
  etc.
 },{
  hello_fr,
  goodbye_fr,
  etc.
 },{
  hello_es,
  goodbye_es,
  etc.
 }
};

// global var to hold the language pointer
const char **current_language = languages[ENGLISH_ID];

// accessing individual strings
const char *phrase =
     pgm_read_word(¤t_language[string_id]);

while(ch = pgm_read_word(phrase++))
{
  // output code here
} 

Writing code is like having sex.... make one little mistake, and you're supporting it for life.

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

For the move to PROGMEM it was Glitch's (2) I was thinking of in fact.

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

Actually it was (2) I was thinking about when I compared the memory requirement (the size for the single pointer array is the reason for the - 1 in the expression).
/Lars

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

Hi,

How can allocate the table in a specific address?

Thanks

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

Yes (use a named section and set its address using --section-start to the linker) but the question is "why?"

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

Because I have to save a table in specific flash page. Then I will update this page only when the flash want to make a self update (once a month).

my table is defined as:

char myTable[8][256] PROGMEM = {....}

Then how can I change to ensure it is at the address 0x2000?

Thanks

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

Try:

char myTable[8][256] __attribute__ ((section (".mysection"))) = {....}

and in the Makefile add:

LDFLAGS += -Wl,--section-start=.mysection=0x2000

(note that is BYTE address 0x2000 which in Atmel code flash addressing is WORD address 0x1000)

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

Thanks for your tip. But then I can access this table with the PROGMEM?

Thanks again ;)

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

The parameters to pgm_read_byte()/pgm_read_word() don't need to be a variable name - it can just as easily be an absolute address. All you are doing here is calling a function that wraps an LPM instruction.

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

I'm also struggling with PROGMEM, in particular passing them to other functions... I can't see why the code below shouldn't work, I assume that the pointer is wrong, but I don't know why...

#include  // PROGMEM

const prog_char PROGMEM stringTest[] = "Test working?";

PGM_P PROGMEM stringTestTable[] = { stringTest };

char buffer[17];

void setup() {
	Serial.begin(9600);
}

void loop() {
	strcpy_P(buffer, (char*)pgm_read_word(&(stringTestTable[0])));
	Serial.print(buffer);
	Serial.print(" : ");
	
	strcpy_P(buffer, (char*)pgm_read_word(&(stringTest)));
	Serial.print(buffer);
	Serial.print(" : ");
	
	Print(stringTestTable[0]);
	Serial.print(" : ");
	
	Print(stringTest);
}

void Print(PGM_P str) {
	strcpy_P(buffer, (char*)pgm_read_word(&(str)));
	Serial.println(buffer);
}

It's arduino code, but I'm sure that won't bother you folks. I've tried const char* and _lots_ of variations instead of the PGM_P, can anyone help?

Thanks.

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

I was following this tutorial so I could create a COS table, as the COS function from math.h is taking up too much cpu time. But my program fails if I add in this statement:

cosresult = pgm_read_word(&costable[ICR1L]));

I have included pgmspace.h:

#include 

And I created the table in the first place with a little perl:

        open(TABLE, ">table.c");

	print TABLE "static unsigned int costable[] PROGMEM =\n";
	print TABLE "{\n";

        for ($i=0; $i <= 89; $i++) 
	{
		$cosr = (cos((90/256)*$i)*32765);
		printf TABLE "%.0f,\n", $cosr;
	}	
		$cosr = (cos((90/256)*90)*32765);
		printf TABLE "%.0f\n", $cosr;
 		print TABLE "};";

That way I'm taking advantage of the signed 16-bit space.

My variable were declared as so:

signed short cosresult;

static signed int costable[] PROGMEM =
{
32765,
30761,
24994,
16170,
5367,
-6092,
-16806,
-25464,
-31007,
-32757,
-30500,
-24512,
-15525,
-4639,
6814,
17433,
25921,
31237,
32732,
30223,
24017,
14873,
3910,
-7532,
-18052,
-26364,
-31451,
-32691,
-29931,
-23510,
-14213,
-3178,
8247,
18662,
26795,
31650,
32633,
29624,
22991,
13546,
2444,
-8957,
-19262,
-27212,
-31832,
-32558,
-29302,
-22461,
-12873,
-1709,
9663,
19853,
27615,
31998,
32468,
28965,
21920,
12192,
974,
-10364,
-20434,
-28004,
-32149,
-32361,
-28614,
-21367,
-11506,
-238,
11060,
21004,
28379,
32283,
32237,
28248,
20803,
10814,
-498,
-11750,
-21564,
-28740,
-32400,
-32097,
-27868,
-20229,
-10116,
1234,
12434,
22113,
29086,
32502,
31941
};

Anyone spot a reason why this would not work?

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

I was following this tutorial so I could create a COS table, as the COS function from math.h is taking up too much cpu time. But my program fails at runtime, even though it compiles, if I add in this statement:

cosresult = pgm_read_word(&costable[ICR1L]));

I have included pgmspace.h:

#include 

And I created the table in the first place with a little perl:

        open(TABLE, ">table.c");

	print TABLE "static unsigned int costable[] PROGMEM =\n";
	print TABLE "{\n";

        for ($i=0; $i <= 89; $i++) 
	{
		$cosr = (cos((90/256)*$i)*32765);
		printf TABLE "%.0f,\n", $cosr;
	}	
		$cosr = (cos((90/256)*90)*32765);
		printf TABLE "%.0f\n", $cosr;
 		print TABLE "};";

That way I'm taking advantage of the signed 16-bit space.

My variables were declared as so:

signed short cosresult;

static signed short costable[] PROGMEM =
{
32765,
30761,
24994,
16170,
5367,
-6092,
-16806,
-25464,
-31007,
-32757,
-30500,
-24512,
-15525,
-4639,
6814,
17433,
25921,
31237,
32732,
30223,
24017,
14873,
3910,
-7532,
-18052,
-26364,
-31451,
-32691,
-29931,
-23510,
-14213,
-3178,
8247,
18662,
26795,
31650,
32633,
29624,
22991,
13546,
2444,
-8957,
-19262,
-27212,
-31832,
-32558,
-29302,
-22461,
-12873,
-1709,
9663,
19853,
27615,
31998,
32468,
28965,
21920,
12192,
974,
-10364,
-20434,
-28004,
-32149,
-32361,
-28614,
-21367,
-11506,
-238,
11060,
21004,
28379,
32283,
32237,
28248,
20803,
10814,
-498,
-11750,
-21564,
-28740,
-32400,
-32097,
-27868,
-20229,
-10116,
1234,
12434,
22113,
29086,
32502,
31941
};

Anyone spot a reason why this would not work?

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

Quote:
But my program fails

What does that mean? If you run the code in the simulator do you see the expected cos() values being extracted from the array?

By the way I don't think you want 'static' on that array definition.

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

Ya I'm on linux here, and unfortunately my windoze partition is completely borked. We have simulavr, but I have not had the most luck with it previously, maybe it's time I did some research on that end. By fail, I mean that when I upload it into the AVR I get continuous resets like I'm blowing the stack, which is the reason why I moved this array out to progmem in the first place (only 512 bytes of ram on the atmega168 so I could put the entire array in ram, but 256 16-bit values would be the entire RAM and I'm pretty sure that would not work in any instance I can think of ;). Before trying progmem I was using signed 8-bit values in half my eeprom and accessing like this:

myphase = EEPROM_read(ICR1L);

But after reading up on EEPROM corruption, and the very fact I have so much more progmem space, I'd like to be using progmem instead.

So far as static, I think you're right, so I'm going to switch out static for const (which still gives me continuous resets after I changed it out).

I will definitely get after simulavr in the morning and find out if the data is at least pulling out.

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

Is the PROGMEM compatible with C++?
I got the following problem:

#include 
#include 
#include 
#include 
#include 


#include 

static const char FlashString[] PROGMEM = "Test"; 

int main()
{
	
	DDRC=0xFF;
	for(;;);
	return 0;
}

and get the warning that indeed shows what is happening:
"
../blink.c:27: warning: only initialized variables can be placed into program memory area
"
What the...?
This disagrees with all the theory I've ever read about it.
Now with all my C++ code big and fat, I have problems squeezing strings to PROGMEM desite few days of trials..
???
Compilation:

Build started 19.10.2008 at 02:58:58
avr-g++.exe -I"I:\ATMEGA\..\WinAVR-20080610\avr\include\avr"  -mmcu=atmega324p -Wall -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums  -DATMEGA324  -ffunction-sections  -fno-threadsafe-statics  -ansi  -fno-exceptions  
--pedantic -MD -MP -MT blink.o -MF dep/blink.o.d  -c  ../blink.c

../blink.c:27: warning: only initialized variables can be placed into program memory area
avr-g++.exe -mmcu=atmega324p -Wl,-Map=blink324.map blink.o    -lc -lm  -o blink324.elf
avr-objcopy -O ihex -R .eeprom  blink324.elf blink324.hex
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex blink324.elf blink324.eep || exit 0
avr-objdump -h -S blink324.elf > blink324.lss

AVR Memory Usage
----------------
Device: atmega324p

Program:     196 bytes (0.6% Full)
(.text + .data + .bootloader)

Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)


Build succeeded with 1 Warnings...

Using WinAVR-20080610

----------
OK sorry found the solution
// Following http://www.avrfreaks.net/index.p...
// in pgmspace.h change:

//ORIG # define PSTR(s) (__extension__({static char __c[] PROGMEM = (s); &__c[0];}))
//FIXED # define PSTR(s) (__extension__({static prog_char __c[] = (s); &__c[0];}))

This is silly trick that internal avrfreaks search engine ignores C++ while external google search can point you back inside the forum.

-------------
still there is something stinking:

	USART_puts("[A string from RAM]");
	USART_putstr_progmem(PSTR("{A string from FLASH}"));
	USART_putendl();
#define USART_putstrp(a) USART_putstr_progmem(PSTR(a))
#define USART_putsp(a) USART_puts_progmem(PSTR(a))
void USART_putstr_progmem(const char * data)
{
	while(pgm_read_byte(data))
	{
		USART_putc(pgm_read_byte(data++));
	}
}

void USART_puts_progmem(const char * data)
{
	USART_putstr_progmem(data);
	USART_putendl();
}

void USART_putstr(const char * data)
{
	for(uint16_t i=0; data[i]!=0; i++)
	{
		USART_putc(data[i]);
	}
}

void USART_puts(const char * data)
{
	USART_putstr(data);
	USART_putendl();
}

I will find the solution since we do not negotiate with terrorists. Still, the output from PROGMEM version is garbage. At least the compiler digested it.
----------
OK now the solution for C++:
changing

# define PSTR(s) (__extension__({static char __c[] PROGMEM = (s); &__c[0];}))

to

# define PSTR(s) (__extension__({static prog_char __c[] = (s); &__c[0];}))

masks the warning but also makes the code not work. strcpy_P, nothing will work.
So you have to live with the flood of warnings.
The only version of function arguments turned out to be this:

#define USART_putstrp(a) USART_putstr_progmem(PSTR(a))
#define USART_putsp(a) USART_puts_progmem(PSTR(a))
void USART_putstr_progmem(PGM_P data)
{
	while(pgm_read_byte(data))
	{
		USART_putc(pgm_read_byte(data++));
	}
}

void USART_puts_progmem(PGM_P data)
{
	USART_putstr_progmem(data);
	USART_putendl();
}

----
at the end, the only thing that is really missing, is sprintf_P that would take format string directly from PROGMEM area.
-----
here is the BUG:
http://www.mail-archive.com/gcc-...
it is not fixed even in the most recent
WinAVR-20081124rc3

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

I would like to verify that printf_P only changes the format argument to reside in program flash. And that if the format has %s, the following string pointer passed will be in RAM, and not program flash. The doxygen comments in the header seem to say only fmt is in program flash for print_P.

I would also like to ask about the var args functions vsnprintf_P/vsprintf_P, i assume since there is only one va_start() in the avr stdarg.h, that it doesnt care that its used with a program memory pointer or ram.

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

Correct - the only difference between printf and printf_P is that the format string of the second is stored in FLASH rather than ram. In the format string, you can use %s to denote a SRAM string argument, and %S to denote a FLASH string.

The va_args macros deal with parameters pass on the stack, which are pointers to data or (for the basic primatives) the data themselves. The variable arguments are stored in the SRAM stack, but the pointers may point to SRAM or FLASH memory -- the differentiation is how you use the pointer, not the pointer itself.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

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

Hi, i'm new with the avr microcontroller.

I'm using the usart and the string store int the flash memory.

I've made the next program, but it doesn't work good.

#include  
#include 
#include  

#define F_CPU 4000000
#define USART_BAUDRATE 9600
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

const char MenuItem1[] PROGMEM = "Menu Item 1";
const char MenuItem2[] PROGMEM = "Menu Item 2";
const char MenuItem3[] PROGMEM = "Menu Item 3";


char* MenuItemPointers[] PROGMEM = {MenuItem1, MenuItem2, MenuItem3};

//Funciones
//Funcion para un byte
void USART_Tx( unsigned char dato ) {

	while (!(UCSR0A & _BV(UDRE0)))
		/* nada */ ;
	UDR0 = dato;
}
//Funcion para mandar cadenas
void USART_TxString_P(const char *data)
	{
   while (pgm_read_byte(data) != 0x00)//Funcion obtiene dato señala *ptr
     USART_Tx(pgm_read_byte(data++));
	}

int main (void)
{	//Variables
	char indice;
	// Turn on the transmission and reception circuitry:
	UCSR0B |= (1 << RXEN0) | (1 << TXEN0);
	// Use 8-bit character sizes:
	//UCSR0C |= (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1);
	UCSR0C |= (1 << UCSZ00) | (1 << UCSZ01);
	// Load lower 8-bits of the baud rate value into the low byte
	// of the UBRR register:
	UBRR0L = BAUD_PRESCALE;
	// Load upper 8-bits of the baud rate value into the high byte
	// of the UBRR register:
	UBRR0H = (BAUD_PRESCALE >> 8);
	for (;;) // Loop forever
		{
		// Do nothing until data have been recieved and is ready
		// to be read from the UDR register:
		while ((UCSR0A & (1 << RXC0)) == 0) {};
		// Fetch the recieved byte value into the variable
		// called "ByteReceived":
   		indice = UDR0; // Fetch the recieved byte value into the variable "ByteReceived"
		USART_TxString_P((char*)pgm_read_word(&MenuItemPointers[indice])); 
		}
}

When i press '0',i get "ÿ.ÀHÀGÀFÀEÀDÀCÀBÀAÀ@À?À>À=À<À;À:À9À8À7À6À5À4À3À2À1À0ÀMenu Item 1"
When i press '1', i get "ÿ.ÀHÀGÀFÀEÀDÀCÀBÀAÀ@À?À>À=À<À;À:À9À8À7À6À5À4À3À2À1À0ÀMenu Item 1"
When i press '2', i get "2À1À0ÀMenu Item 1"

Can anyone help me?Does anyone know what is wrong?

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

If you press '0' what arrives at the AVR is not 0x00 but the ASCII code for character '0' which is 0x30. So it'll then try to read the 0x30th (48th) entry in your MenuItemPointers array (which doesn't exist). Try:

         indice = UDR0; // Fetch the recieved byte value into the variable "ByteReceived" 
         indice -= '0'; // convert ASCII to binary
      USART_TxString_P((char*)pgm_read_word(&MenuItemPointers[indice]));

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

Thanks, now it's work fine. I thought that i was sending the number not its code ascii.

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

That's the thing about UART - if you are just using it as a terminal (rather than sending/receiving files with binary content) that everything going out of the AVR probably needs to be converted from binary to ASCII (printf, itoa, etc.) and everything coming into the AVR probably needs to be converted from ASCII into binary (atoi, scanf, etc.)

Cliff

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

There was mentioning of a PDF. Anybody knows where I can find this PDF?
Thanks,
Leon

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

I removed it due to it being old and inaccurate after several revisions. Making new PDFs of all my tutorials is on my todo list, which never seems to be getting any shorter.

- Dean :twisted:

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

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

Hello,

I have used the PROGMEM attribute , but it is not working like it should be.

When I read a byte from flash I can see in the disassembler that the routine starts with the right values (right index , right pointer to the flashlocation), but the result is not right.

61: j = pgm_read_byte(EEPROMerrorCheckString[i]);
+00000180: 01F9 MOVW R30,R18 Copy register pair
+00000181: 5CEC SUBI R30,0xCC Subtract immediate
+00000182: 4FFF SBCI R31,0xFF Subtract immediate with carry
+00000183: 81E0 LDD R30,Z+0 Load indirect with displacement
+00000184: E0F0 LDI R31,0x00 Load immediate
+00000185: 91E4 LPM R30,Z Load program memory

I have no idee why the LDD R30,Z+0 and the LDI R31,0x00 are there. Theye are changing the right value store in the Z register cousing the result of the last instruction to be wrong.

Is there somebody who knows why this happens?

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

Just a guess since all pgm_read_xxxx functions need an address:

pgm_read_byte(&EEPROMerrorCheckString[i]); 

/Lars

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

I read a lot about this amazing pdf tutorial, can't wait to read it. Yes I also read jabius's comment.

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

JimW52 wrote:
Rev 2 :wink:

All references to prgm_read_word should be pgm_read_word.

Jim

yeah you're right bro.. :twisted:

ArcticSoul
Industrial Electronic Engineering, College Student

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

PROGMEM is such a code that i used in my dotmatriks project. :D

Thanks a lot for the tutorial Dean :)

ArcticSoul
Industrial Electronic Engineering, College Student

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

It is not easy to manipulate data in flash so I made some effort to simplify it.

Solution
I created a function readFlash() which can be used for all different kinds of data types and pointers.

Example
int i PROGMEM = 32767; // write data to program memory
int x = readFlash(i); // read data from program memory.

See here
http://www.arduino.cc/cgi-bin/ya...

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

Quote:

I created a function readFlash() which can be used for all different kinds of data types and pointers.

But just to be clear - that function overloading will only work in C++, not C. Those of us who program in C will just need to continue to pick the right pgm_read() function.

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

Hi,
frist of all thanks for the tutorial ;-)
let's see if I get it:

1)
it's the same to declare

const uint16_t prior0[10] PROGMEM = {100, 360, ..., 120};

and

const prog_uint16_t prior0[10] = {100, 360, ..., 120};

2)
I've got some constant arrays (like the one before) in the prog.mem.

const uint16_t prior0[10] PROGMEM = {100,...}
const uint16_t trans0[100] PROGMEM = {500, 456, ... };
const uint16_t obs0[30] PROGMEM = {500, 456, ... };
const uint16_t prior1[10] PROGMEM = {100,...}
...

this way I hope that all this data and the pointer table are in program space.
now how can I pass this arrays to a function? where I have to use the pgm_read()?

if not in prog space it will be something like:

in file dev.h:

void init(uint16_t pr[], uint16_t tr[], uint16_t ob[])
{
 ...
}

in file main.h:

//array declarations
...

void main(void)
{

init(prior0, trans0, obs0);
...
}

this is not the only way to handle this code, but it fits well in my project structure. I've got several of those arrays (12-15) and I have to pass 3 of them (selected by a comand) to some functions...
thanks again,
cheers!
bo.

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

1) yes, pgmspace.h just has this:

typedef uint16_t PROGMEM prog_uint16_t;

2) pointers whether they are to RAM or code flash are really just 16 bit numbers and could be passed around as such or you can use the universal get out clause which is "void *" ;-)

Pages