[TUT] [C] GCC and the PROGMEM Attribute

Last post
185 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

For an updated version of this tutorial in PDF format, please see this page of my website.

PART I - PROGMEM BASICS

The PROGMEM attribute is always a source of confusion for those beginning with AVR-GCC. The PROGMEM attribute is a powerful one and holds the potential to save a lot of RAM, which is something of a limited commodity on many AVRs. Before you can use the PROGMEM attribute, you must first understand what it does and why it is useful.

When strings are used in a program, they are commonly "hard-coded" into the firmware source code:

LCD_puts("Hardcoded String");

While this seems the most logical way of using strings, it is *not* the most optimal. Common sense and intuition would dictate that the compiler would store the string in the program memory, and read it out byte-by-byte inside the LCD_puts routine. But this is not what happens.

Because the LCD_puts routine (or other string routines) are designed to work with strings in RAM, the compiler is forced to read out the entire string constant from program memory into RAM, and then pass the string's RAM pointer to the routine (in this case LCD_puts). It certainly works but the RAM wastage adds up to significant amounts with each string. Why?

Initial variable values and strings are copied out from program memory into RAM as part of the C startup routines, which execute before your main() function. Those startup routines give your globals their initial values, as well as ensure that all strings are inside RAM so they can be passed to your desired string handling routines. As more strings are added to your program, more data must be copied to RAM at startup and the more RAM is used up by holding static (unchanging) data.

The solution to the problem is forcing strings to stay in program memory and only be read out as they are needed. This is not a simple task and requires string routines specifically designed to handle strings kept solely inside the program memory space.

Using the "const" modifier on your string variables is a natural solution - but a wrong one. A "const" variable can only not be modified by the program code, but it does not explicitly prevent the variable value from being copied out to the AVRs RAM on startup. Some compilers implicitly do this for you, but GCC needs to be explicitly told that the contents must live in program memory for the entire duration of the program's execution.

Entering pgmspace.h

The AVR-LibC library contains a header file, avr/pgmspace.h, which contains all the interfacing information needed to allow you to specify data which is to be kept inside the AVR's flash memory. To use the pgmspace.h functions, you need to include the header at the start of your C file(s):

#include 

To force a string into program memory, we can now use the "PROGMEM" attribute modifier on our string constants. An example of a global string which is stored into program memory and not copied out at execution time is:

char FlashString[] PROGMEM = "This is a string held completely in flash memory.";

Although it is not an absolute requirement, we can remind ourselves - and possibly prevent bugs further down the track - that the string cannot be changed at all during execution by declaring it as a constant:

const char FlashString[] PROGMEM = "This is a string held completely in flash memory.";

Now that the PROGMEM string has a "const" modifier, we cannot try to modify it in our code. The pgmspace.h header also exposes a neat little macro, PSTR, which by some GCC magic allows you to create inline strings:

LCD_puts(PSTR("Program Memory String"));

This stops you from having to clutter your program up with hundreds of variables which hold one-time-used strings. The downside to using the PSTR macro rather than a PROGMEM modified string variable is that you can only use the PSTR string once.

However, we now have a problem - now all your code doesn't work! What's wrong?

The problem is that your string functions are expecting the string to be inside RAM. When you pass a string to a routine, you are in fact just passing a pointer to the start of the string in RAM. The string handling routine then just loads the bytes of the string one-by-one, starting from the pointer's location. But our PROGMEM strings are not in RAM, so the pointer to them is invalid.

A pointer to a string stored in PROGMEM returns the address in flash memory at which the string is stored. It's still a valid pointer, it's just pointing to a different memory space. To use PROGMEM strings in our application, we need to make our string routines PROGMEM-pointer aware.

Again, pgmspace.h comes to our rescue. It contains several functions and macros which deal with PROGMEM-based strings. First, you have all your standard string routines (memcpy, strcmp, etc.) with a "_P" postfix denoting that the function deals with the FLASH memory space. For example, to compare a RAM-based string with a PROGMEM-based string, you would use the strcmp_P function:

strcmp_P("RAM STRING", PSTR("FLASH STRING"));

For a full list of the avaliable string functions, check out the AVRLibC documentation which is installed with your WinAVR installation.

But what if you have your own string function, like a USART-transmitting routine? Lets look at a typical example:

void USART_TxString(const char *data)
{
	while (*data != '\0')
	  USART_Tx(*data++);
}

This relies on the routine USART_Tx, which for the purposes of this example we will assume to be predefined as a function which transmits a single passed character through the AVR's USART. Now, how do we change our routine to use a PROGMEM string?

pgmspace.h exposes a macro which is important for PROGMEM-aware routines; pgm_read_byte. This macro takes a PROGMEM pointer as its argument, and returns the byte located at that pointer value. To mark that our new routine deals in PROGMEM strings, let's append a "_P" to it's name just like the other routines in pgmspace.h:

void USART_TxString_P(const char *data)
{
	while (pgm_read_byte(data) != 0x00)
	  USART_Tx(pgm_read_byte(data++));
}

Now we have our PROGMEM-aware routine, we can use PROGMEM-attributes strings:

USART_TxString_P(PSTR("FLASH STRING"));

Or:

const char TestFlashStr[] PROGMEM = "FLASH STRING";
USART_TxString_P(TestFlashStr);

This should give you a basic idea of how to store strings and keep them in flash memory space. What follows is the second half of this tutorial, for more advanced uses of the PROGMEM attribute.

PART II - More advanced uses of PROGMEM

Ok, so by now you should be able to create and use your own simple strings stored in program memory. But that's not the end of the PROGMEM road - there's still plenty more to learn! In this second tutorial section, I'll cover two slightly more advanced techniques using the PROGMEM attribute. The first part will lead on to the second, so please read both.

Storing data arrays in program memory

It's important to realize that all static data in your program can be kept in program memory without being read out into RAM. While strings are by far the most common reason for using the PROGMEM attribute, you can also store arrays too.

This is especially the case when you are dealing with font arrays for a LCD, or the like. Consider the following (trimmed) code for an AVRButterfly LCD font table:

static unsigned int LCD_SegTable[] PROGMEM =
{
    0xEAA8,     // '*'
    0x2A80,     // '+'
    0x4000,     // ','
    0x0A00,     // '-'
    0x0A51,     // '.' Degree sign
    0x4008,     // '/'
}

Because we've added the PROGMEM attribute, the table is now stored firmly in flash memory. The overall savings for a table this size is negligible, but in a practical application the data to be stored may be many times the size shown here - and without the PROGMEM attribute, all that will be copied into RAM.

First thing to notice here is that the table data is of the type unsigned int. This means that our data is two bytes long (for the AVR), and so out prgm_read_byte macro won't suffice for this instance.

Thankfully, another macro exists; pgm_read_word. A word for the AVR is two bytes long - the same size as an int. Because of this fact, we can now make use of it to read out our table data. If we wanted to grab the fifth element of the array, the following extract would complete this purpose:

pgm_read_word(&LCD_SegTable[4])

Note that we are taking the address of the fourth element of LCD_SegTable, which is an address in flash and not RAM due to the table having the PROGMEM attribute.

PROGMEM Pointers

It's hard to remember that there is a layer of abstraction between flash memory access - unlike RAM variables we can't just reference the contents at will without macros to manage the separate memory space - macros such as prgm_read_byte. But when it all really starts confusing is when you have to deal with pointers to PROGMEM strings which are also held in PROGMEM.

Why could this possibly be useful you ask? Well, i'll start as usual with a short example.

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

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

Here we have three strings containing menu function names. We also have an array which points to each of these three items. Let's use our pretend function USART_TxString from part I of this tutorial. Say we want to print out the menu item corresponding with a number the user presses, which we'll assume is returned by an imaginary function named USART_GetNum. Our (pseudo)code might look like this:

#include 

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

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

void main (void)
{
   while (1) // Eternal Loop
   {
      char EnteredNum = USART_GetNum();

      USART_TxString(MenuItemPointers[EnteredNum]);
   }
}

Those confident with the basics of C will see no problems with this - it's all non-PROGMEM data and so it all can be interacted with in the manner of which we are accustomed. But what happens if the menu item strings are placed in PROGMEM?

#include 
#include 

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

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

void main (void)
{
   while (1) // Eternal Loop
   {
      char EnteredNum = USART_GetNum();

      USART_TxString_P(MenuItemPointers[EnteredNum]);
   }
}

Easy! We add the PROGMEM attribute to our strings and then substitute the RAM-aware USART_TxString routine with the PROGMEM-aware one we delt with in part I, USART_TxString_P. I've also added in the "const" modifier as explained in part I, since it's good practice. But what if we want to save a final part of RAM and store our pointer table in PROGMEM also? This is where it gets slightly more complicated.

Let's try to modify our program to put the pointer array into PROGMEM and see if it works.

#include 
#include 

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};

void main (void)
{
   while (1) // Eternal Loop
   {
      char EnteredNum = USART_GetNum();

      USART_TxString_P(MenuItemPointers[EnteredNum]);
   }
}

Hmm, no luck. The reason is simple - although the pointer table contains valid pointers to strings in PROGMEM, now that it is also in PROGMEM we need to read it via the prgm_read_x macros.

First, it's important to know how pointers in GCC are stored.

Pointers in GCC are usually two bytes long - 16 bits. A 16-bit pointer is the smallest sized integer capable of holding the address of any byte within 64kb of memory. Because of this, our "uint8_t*" typed pointer table's elements are all 16-bits long. The data the pointer is addressing is an unsigned character of 8 bits, but the pointer itself is 16 bits wide.

Ok, so now we know our pointer size. How can we read our 16-bit pointer out of PROGMEM? With the pgm_read_word macro of course!

#include 
#include 

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};

void main (void)
{
   while (1) // Eternal Loop
   {
      char EnteredNum = USART_GetNum();

      USART_TxString_P(pgm_read_word(&MenuItemPointers[EnteredNum]));
   }
}

Almost there! GCC will probably give you warnings with code like this, but will most likely generate the correct code. The problem is the typecast; pgm_read_word is returning a word-sized value while USART_TxString_P is expecting (if you look back at the parameters for the function in part I) a "char" pointer. While the sizes of the two data types are the same (remember, the pointer is 16-bits!) to the compiler it looks like we are trying to do the code equivalent of shoving a square peg in a round hole.

To instruct the compiler that we really do want to use that 16-bit return value of pgm_read_word as a pointer to a const char in flash memory, we need to typecast it to a pointer in the form of char*. Our final program will now look like this:

#include 
#include 

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};

void main (void)
{
   while (1) // Eternal Loop
   {
      char EnteredNum = USART_GetNum();

      USART_TxString_P((char*)pgm_read_word(&MenuItemPointers[EnteredNum]));
   }
}

I recommend looking through the AVR-lib-c documentation (included with the installation of WinAVR in the \docs directory) in the AVR/PgmSpace.h header documentation section for information about the other PROGMEM related library functions avaliable. Other functions (such as pgm_read_dword(), which reads four bytes (double that of a word for the AVR architecture) behave in a similar manner to the functions detailed in this tutorial and might be of use for your end-application.

And so ends the second part of this tutorial. Feedback and corrections welcome!

For an updated version of this tutorial in PDF format, please see this page of my website.

- Dean :twisted:

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

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

This is a very useful technique since it's easy to write code in which the stack bounces off the variables - and the real gotcha is that all those 'printf' statements you put in to try and trace it make it worse; they're eating the ram space. Indeed, my Coupe ECU monitor had to have a complete rewrite to cope with a change of language from English to Italian; the Italian was a couple of dozen characters longer and that was enough to break it.

Very embarrassing... :o

A minor correction: should

strncmp("RAM STRING, PSTR("FLASH STRING"));

really be

strncmp(RAM STRING, PSTR("FLASH STRING"));

(Oh, and 'solely' rather than 'souley')

Neil

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

Fixed, and fixed. For the former, I was missing the closing quote on the RAM string. Thanks for the comments (and great story :)).

- Dean :twisted:

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

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

Still not quite right.

strncmp("RAM STRING", PSTR("FLASH STRING"));

should be

strncmp_P("RAM STRING", PSTR("FLASH STRING"));

Jim

Your message here - reasonable rates.

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

Darnit! Fixed. Guess i'm not one to teach after all ;).

Is it worth me posting more advanced techniques like PROGMEM arrays of pointers to PROGMEM strings, or even creating a GCC EEMEM tutorial?

- Dean :twisted:

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

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

Don't feel too bad, it took me two goes to get the correction right. :D

As for the other tutorials - yes go ahead. There will always be someone here to pick up the odd typo.

Keep up the good work.

Jim

Your message here - reasonable rates.

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

Thanks for explaining this Dean! I've been lazy to dig up all this information myself and now I found this tutorial and it's all clear. Great job!

The Dark Boxes are coming.

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

Quote:
It's hard to remember that there is a layer of abstraction between flash memory access - unlike RAM variables we can't just manipulate the contents at will.

Well, the layer of abstraction is not that we can't manipulate the contents (flash data can't be manipulated at all), but that we can't use C's normal way or referencing them.

But maybe this is again splitting hairs ... but then it would be newbie's hair, which is much more worth than mine, 'cause it will be gone within some years :lol:

Einstein was right: "Two things are unlimited: the universe and the human stupidity. But i'm not quite sure about the former..."

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

Fixed. Honestly, I should start putting revision numbers on all my written works :D. Thanks for the comment.

- Dean :twisted

POST REV: 1.0

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

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

It may be worth adding a discussion of the pro's and cons of the two techniques that are available for tables of PROGMEM strings.

You can create a table of pointers, and each pointer points to an individual string, as discussed in the tutorial above.

eg.

char String1[] PROGMEM = "String 1";
char String2[] PROGMEM = "String 2";
char String3[] PROGMEM = "String 3";
char String4[] PROGMEM = "String 4";

PGM_P StringTable[NUMBER_OF_STRINGS] PROGMEM = {
String1,
String2,
String3,
String4
};

Alternatively, you can create a unified two-dimensional array, which is entirely contained in PROGMEM. Each entry in the top-level of the array is a fixed-length array of characters.

eg.

char StringTable[NUMBER_OF_STRINGS][FIXED_STRING_LENGTH] PROGMEM = {
{"String 1"},
{"String 2"},
{"String 3"},
{"String 4"}
};

There are pro's and cons for each approach.
The major drawback of the two-dimensional array versus the table-of-pointers-to-strings, is that the "inner" dimension of the array has to be large enough to accommodate the largest individual string, even if none of the other strings need that much space. In the table-of-pointers approach, each individual string only reserves exactly enough memory to accommodate itself.

The major drawback of the table-of-pointers approach is two-fold: First, you have these extra two bytes associated with each and every string, where the pointers physically reside. The two-dimensional array approach doesn't have to literally store these pointers.

Second, you have to dereference PROGMEM twice in order to access the contents of an individual string: Once to extract the start-address of the string you want from the "top-level" table, and a second time to get at the individual character you're interested in within the target string. With a two-dimensional table, the compiler can compute the starting-addresses for the indiviudal strings deterministically, so you only need to dereference PROGMEM once, at the point where you're actually extracting an individual character from the strings.

The trade-off in storage requirement can be decided rather easily: If most of the individual strings that you want to place in the table are within one or two characters in length, your best bet is to use the two-dimensional table. In that case, the wasted space involved in padding the ends of the shorter strings will be smaller than the space that would be wasted by adding an extra two bytes for pointers to each-and-every string in a look-up table.

If you have a string set where there is a wide discrepancy between the longest and shortest strings, and most strings would have more than one or two bytes of NULL padding at the end to match the length of the longest string, then you'd be better off using the table-of-pointers approach.

[edit 1] Inserted code examples [/edit]
[edit 2] Fixed code tags [/edit]

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

Rev 2 :wink:

All references to prgm_read_word should be pgm_read_word.

Jim

Your message here - reasonable rates.

  • 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

Fixed. Sigh :roll:.

LFMorrison: Excellent points. I'm used to dealing with odd-ball sized strings and I hate the idea of padding, so I usually opt for this approach.

- Dean :twisted:

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

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

People might not want to fill this up with lots of "thanks" posts, but I want Dean and all those that helped to know that the time and attention that went into this are VERY MUCH APPRECIATED!! Stuff like this is great and exactly what I needed!

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

Cheers! I thought it was high time the tutorials forum was used for its intended purpose!

- Dean :twisted:

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

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

In the examples where the pointer array is still in RAM (if anyone cares about them in this context) you should not have the '&' operator, it will not work. So

      USART_TxString(&MenuItemPointers[EnteredNum]); 

should be

      USART_TxString(MenuItemPointers[EnteredNum]);  

and

      USART_TxString_P(&MenuItemPointers[EnteredNum]); 

should be

      USART_TxString_P(MenuItemPointers[EnteredNum]); 

/Lars

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

You sure? USART_TxString is expecting an address for it's pointer. When I tested out the code:

uint8_t Data[5] = "Test";
	USART_TxString(Data[1]);

Produced a warning while:

	uint8_t Data[5] = "Test";
	USART_TxString(&Data[1]);

Did not. What am I missing?

- Dean :twisted:

PS: Thanks for the feedback guys - it may look like i'm grumbing about all the errors, but I'm secretly pleased that there are people reading it in detail ;)

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

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

I agree with Lajon.
@abcminiuser: in your last post you confuse Data (the string) and MenuItemPointers (the pointers to the strings)!

    USART_TxString() and USART_TxString_P() expects an unsigned char*.
    MenuItemPointers[x] is an unsigned char*.
So you need to pass the menu item (as stated by Lajon) without the '&'.
If you use &MenuItemPointers[x] you will pass the address of the pointer to the string; this is wrong.

Many thanks for your tutorial Dean!

Patrik

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

Whoops! You're right Patrick and Lars, sorry. I was indeed confusing the string array with the pointer array. Fixed, and thanks.

Should I combine the two parts at the top so that it's easier to read? I could also attach it in PDF form, if required.

- Dean :twisted:

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

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

It's a good idea to put tutorials + notes from posts in one pdf file!

Another errata:

void USART_TxString_P(const char *data)
{
   while (pgm_read_byte(*FlashData) != 0x00)
     USART_Tx(pgm_read_byte(*FlashData++));
}

Corrige:

void USART_TxString_P(const char *data)
{
   while (pgm_read_byte(data) != 0x00)
     USART_Tx(pgm_read_byte(data++));
}

Patrik

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

minor typo shouldnt #include ? be #include

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

Great tutorial, very well written.

One question:

Quote:
Because of this fact, we can now make use of it to read out our table data. If we wanted to grab the fourth element of the array, the following extract would complete this purpose:

Code:
pgm_read_word(&LCD_SegTable[4])


Wouldn't this be the fifth element of the array?

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

Now I know why Smokey's book had so much erratta! If I can't even get a couple of pages right, I shudder to think of what a full book would contain!

You are of course correct. I was using conventional numbering rather than the (true!) zero-indexed way of counting. Fixed.

- Dean :twisted:

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

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

I recently dealt with the same error as rotamat brought up with USART_TxString_P(). pgm_read_byte() takes a pointer, which is pointed out in your explanation (that's how I figured it out). Dereferencing a pointer as a parameter to this function will give you some very funny output. :)

Might be worth updating this to save some people who don't understand pointers well some trouble, I didn't bother reading the unofficial errata in the rest of the thread. I saw a .pdf there so I instantly trusted it... bad habit.

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

GlingON,

I just looked it over again and can't spot the error you speak of - was it due to the PDF being out-dated compared to the thread? I just updated the PDF with the latest content so any errors in it that were corrected in my post should be fixed.

Cheers,
- Dean :twisted:

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

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

Yeah, it was the .pdf. I was looking at that since it's nicer to look at than the forums. Sorry, I should have specified more clearly.

That said I'm still seeing the error in the post and in the .pdf. Let me point it out. It is near the end of part 1.

void USART_TxString_P(const char *data)
{
   while (pgm_read_byte(*data) != 0x00)
     USART_Tx(pgm_read_byte(*data++));
}

The body of the function should read:

while (pgm_read_byte(data) != 0x00)
     USART_Tx(pgm_read_byte(data++));
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Why did you say that

LCD_puts(PSTR("Program Memory String"));

only works once??

The documentation say's absolutly nothing about that..

and i managed to use PSTR() a millions times with printf_P()

i used it many times like so..

printf_P(PSTR("Hello world"));
printf_P(PSTR("How are you doing"));

litterally about 100 times, with no issues..

what exactly did you mean by you can only use it once?

I was using a basic hitachi interface LCD (4x20)

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

Sorry, I meant that individual string can only then be used once. If you make the string a global declaration:

uint8_t SomeString PROGMEM = "Some String";

Then you can use it multiple times throughout your program:

// In some function
printf_P(SomeString);

// In some other function
printf_P(SomeString);

When using PSTR, that string is only available in the instance where it is used - it's unavailable to other statements in your program. You'd have to make duplicates of the string:

// In some function
printf_P(PSTR("Some String"));

// In some other function
printf_P(PSTR("Some String"));

And waste flash (unless the appropriate GCC optimiser flags are set).

Sorry for the confusion.

- Dean :twisted:

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

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

abcminiuser wrote:
Sorry, I meant that individual string can only then be used once. If you make the string a global declaration:

uint8_t SomeString PROGMEM = "Some String";

Then you can use it multiple times throughout your program:

// In some function
printf_P(SomeString);

// In some other function
printf_P(SomeString);

When using PSTR, that string is only available in the instance where it is used - it's unavailable to other statements in your program. You'd have to make duplicates of the string:

// In some function
printf_P(PSTR("Some String"));

// In some other function
printf_P(PSTR("Some String"));

And waste flash (unless the appropriate GCC optimiser flags are set).

Sorry for the confusion.

- Dean :twisted:

aahh i see i see.. thanks

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

I think this thread might be of value here

PROGMEM using structs and arrays
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=31622

/Bingo

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

Awesome guide! After reading it a few times, I think I have a much better understanding about accessing program data in AVR.

Based on what Lars was saying, should

pgm_read_word(&LCD_SegTable[4])

be

pgm_read_word(LCD_SegTable[4])

?

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

Sleekas wrote:
Awesome guide! After reading it a few times, I think I have a much better understanding about accessing program data in AVR.

Based on what Lars was saying, should

pgm_read_word(&LCD_SegTable[4])

be

pgm_read_word(LCD_SegTable[4])

?


No.

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

Oh I see, it's because LCD_SegTable contains ints (not int pointers), while MenuItemPointers contains char pointers.

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

A fan-blody-tastic tutorial. *cheers*
Three things come to mind.
1. In the pdf there is an intendation issue on page 3
2. You say nothing about the cost in instructions to get a byte/word from progmem contra ram. Im gessing it's a 5:1 ratio or something.
3. Could be nice to know what the pgm_read_byte actualy does.

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

Cheers d99mhl!

1) Fixed the indentation issue - I assume you were referring to the code with the while loop body non-indented?

2) I should probably add in a footnote. Cost varies depending on the data's flash location and AVR architecture, but yes it's about 5:1 per constant. You're trading a small amount of execution time and flash space for a a (usually) large amount of precious RAM space.

3) Again, depends on the data location and AVR chosen. You can check out the code inside the pgmspace.h header, if you're so inclined. Basically it just loads the data's flash address into the high pointer register pairs (I think the Z pointer, r30/31, is required) then executes a series of LPM instructions. Each LPM loads one byte of flash data, and with the post-pointer-increment feature data of several bytes like a word just has several of these instructions in series.

- Dean :twisted:

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

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

1. Yes, no worries.
2. Agreed, mostly it's an intresting question when doing somthing verry timeing critical.
3. Will do. Remember "Magic is just a technology that we have yet to understand" and I don't like too much magic in my code. ;)

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

Hi Dean
Yet another great Tut! But this one I can’t thank you enough for. As it is saving me I’ll bet a week of research and experiment time.
Thanks again,
John
You’re the greatest!!

Resistance is futile…… You will be compiled!

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

The strncmp_P() call is missing the 'n' parameter.
Prototype in pgmspace.h =
extern int strncmp_P(const char *, PGM_P, size_t) __ATTR_PURE__;
Or just use strcmp_P()
Cheers, Paul

I have coded for about 2 dozen architectures, and they all have their place.
Don't get too religious about them (but AVR is excellent!)

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

Thanks AllN, no problem! Glad it helped.

Paul: You're dead right, I didn't catch that on my proof-reading. Fixed.

- Dean :twisted:

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

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

Very nice tutorial, thanks to you all.

Gentlemen, I have a quick question: As I understand it, all arrays/structs are passed as pointers when used as a function argument. I am using a font set declared as

 const unsigned char PROGMEM font[128][5] 

Why, then, does

tempChar0 = pgm_read_byte(font[cTemp][0]);

not work but

tempChar0 = pgm_read_byte(&font[cTemp][0]);

does? It seems to me that pgm_read_byte() would be getting a pointer to a pointer. Am I misunderstanding something?

Thanks!

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

Because &font[cTemp][0] is the address of the unsigned char and is a pointer while the 1st is not.
Only because the author of pgm_read_byte wrote it to receive a pointer not the unsigned char it self?

Edited:
And it's the best practice!

Resistance is futile…… You will be compiled!

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

Yes arrays are passed by reference but you aren't passing an array, you are passing one elemental element of that array. If on the other hand you tried-

tempChar0 = pgm_read_byte(font[cTemp]);

you'd find it worked, since you are passing the 5 element array (it's still an element in the larger array but it is an array itself).
Of course you could only do that to get the first byte of each character so not that usefull..

Edward

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

Explaine pls. how can I access fields of structure located in program memory ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
#include 
struct MyStruct
{
   char x;
   char y;
   int z;
};

struct MyStruct foo PROGMEM = {'a', 'b', 312};

int main(void)
{
    int MyInt = pgm_read_word(&foo.z);
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
#include  
struct MyStruct 
{ 
   char x; 
   char y; 
   int z;
   void (*Procedure)();
}; 

struct MyStruct foo PROGMEM =
 {'a', 'b', 312, Read_Procedure}; 

int main(void) 
{ 
    int MyInt = pgm_read_word(&foo.z); 
}

If structure is located in RAM I can call read procedure:

int main(void) 
{ 
    int MyInt = pgm_read_word(&foo.z);
    
    foo.Read_Procedure();
}

Tell me please how can I call read procedure if I locate structure in program memory ?

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

Note that this is off the top of my head so there may be some inconsistent syntax; I have done things similar to this before, but I don't have the source code in front of me as I write this.

typedef void (*function_pointer)();

int main(void)
{
   function_pointer Proc = (function_pointer)pgm_read_word(&foo.Procedure);
   Proc();
}

- Luke

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

Dean, I just wanted to say thanks for writing this tutorial... It just helped me out a lot!!!!

- James

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

Hi, Dean, Thanks for the tute.
You say, in two places:

Quote:
pgm_read_byte. This macro takes a PROGMEM pointer as its argument, and returns the string located at that pointer value.

It should say something like:
Quote:
pgm_read_byte. This macro takes a PROGMEM pointer as its argument, and returns one byte located at that pointer value.

Also maybe add pgm_read_dword() beside pgm_read_word(), something like. "Similarly, for 4-byte values, we can use prgm_read_dword()."

-- Alf

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

Thanks Alf, I fixed up the one incorrect reference to strings I could find, and added in a footnote about looking at the other avaliable functions. I didn't want to derail the paragraph about pgm_read_word with information about pgm_read_dword as the former was discussing reading out pointers from program space (hard enough as it is) so I went for the footnote.

Cheers!
- Dean :twisted:

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

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

Hi Dean,

Is there any reason that this correction was never made to the tutorial:

Quote:

void USART_TxString_P(const char *data)
{
while (pgm_read_byte(*data) != 0x00)
USART_Tx(pgm_read_byte(*data++));
}

The body of the function should read:

Code:
while (pgm_read_byte(data) != 0x00)
USART_Tx(pgm_read_byte(data++));

Very nice tutorial overall though!

Thanks

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

Quote:

Is there any reason that this correction was never made to the tutorial:

Yes, an extreamly good one - I forgot! It's now fixed, thanks!

- Dean :twisted:

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

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

Thanks Dean,

I was working on the IAR to GCC Converter when I found your tutorial which has been a lot of help. I'm nearly finished with the flash part, then I just need to tackle the EEPROM stuff. :)

Pages