| Author |
Message |
|
|
Posted: Apr 26, 2006 - 08:15 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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:
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):
Code:
#include <avr/pgmspace.h>
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:
Code:
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:
Code:
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:
Code:
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:
Code:
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:
Code:
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:
Code:
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:
Code:
USART_TxString_P(PSTR("FLASH STRING"));
Or:
Code:
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:
Code:
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:
Code:
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.
Code:
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:
Code:
#include <avr/io.h>
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?
Code:
#include <avr/io.h>
#include <avr/pgmspace.h>
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.
Code:
#include <avr/io.h>
#include <avr/pgmspace.h>
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!
Code:
#include <avr/io.h>
#include <avr/pgmspace.h>
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:
Code:
#include <avr/io.h>
#include <avr/pgmspace.h>
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
Last edited by abcminiuser on Feb 04, 2012 - 02:21 PM; edited 20 times in total
|
| |
|
|
|
|
|
Posted: Apr 26, 2006 - 09:49 AM |
|


Joined: Jan 03, 2006
Posts: 4417
Location: Hemel Hemsptead, UK
|
|
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...
A minor correction: should
Code:
strncmp("RAM STRING, PSTR("FLASH STRING"));
really be
Code:
strncmp(RAM STRING, PSTR("FLASH STRING"));
(Oh, and 'solely' rather than 'souley')
Neil |
_________________ Neil Barnes
www.nailed-barnacle.co.uk
|
| |
|
|
|
|
|
Posted: Apr 26, 2006 - 09:54 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Fixed, and fixed. For the former, I was missing the closing quote on the RAM string. Thanks for the comments (and great story ).
- Dean  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 10:58 AM |
|


Joined: Jun 01, 2004
Posts: 351
Location: Brisbane, Australia
|
|
Still not quite right.
Code:
strncmp("RAM STRING", PSTR("FLASH STRING"));
should be
Code:
strncmp_P("RAM STRING", PSTR("FLASH STRING"));
Jim |
|
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 11:06 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 11:18 AM |
|


Joined: Jun 01, 2004
Posts: 351
Location: Brisbane, Australia
|
|
Don't feel too bad, it took me two goes to get the correction right.
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 |
|
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 11:30 AM |
|


Joined: Jun 27, 2005
Posts: 3412
Location: St.Petersburg, Russia
|
|
| 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.
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 12:37 PM |
|

Joined: Aug 29, 2002
Posts: 786
Location: Muenster, Germany
|
|
|
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  |
|
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 12:50 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Fixed. Honestly, I should start putting revision numbers on all my written works . Thanks for the comment.
- Dean :twisted
POST REV: 1.0 |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 01:07 PM |
|

Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada
|
|
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.
Code:
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.
Code:
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] |
Last edited by lfmorrison on Apr 27, 2006 - 01:13 PM; edited 2 times in total
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 01:12 PM |
|


Joined: Jun 01, 2004
Posts: 351
Location: Brisbane, Australia
|
|
Rev 2
All references to prgm_read_word should be pgm_read_word.
Jim |
|
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 01:32 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
|
JimW52 wrote:
Rev 2
All references to prgm_read_word should be pgm_read_word.
Jim
Fixed. Sigh .
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 27, 2006 - 06:16 PM |
|


Joined: Nov 14, 2005
Posts: 169
Location: Philadelphia, USA
|
|
| 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! |
|
|
| |
|
|
|
|
|
Posted: Apr 28, 2006 - 06:18 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Cheers! I thought it was high time the tutorials forum was used for its intended purpose!
- Dean  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 28, 2006 - 10:00 AM |
|


Joined: Mar 12, 2004
Posts: 1174
Location: Linköping, Sweden
|
|
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
Code:
USART_TxString(&MenuItemPointers[EnteredNum]);
should be
Code:
USART_TxString(MenuItemPointers[EnteredNum]);
and
Code:
USART_TxString_P(&MenuItemPointers[EnteredNum]);
should be
Code:
USART_TxString_P(MenuItemPointers[EnteredNum]);
/Lars |
|
|
| |
|
|
|
|
|
Posted: Apr 28, 2006 - 10:09 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
You sure? USART_TxString is expecting an address for it's pointer. When I tested out the code:
Code:
uint8_t Data[5] = "Test";
USART_TxString(Data[1]);
Produced a warning while:
Code:
uint8_t Data[5] = "Test";
USART_TxString(&Data[1]);
Did not. What am I missing?
- Dean
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 28, 2006 - 11:14 AM |
|

Joined: May 19, 2004
Posts: 6
Location: Pordenone, Italy
|
|
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 |
|
|
| |
|
|
|
|
|
Posted: Apr 28, 2006 - 11:24 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 28, 2006 - 12:56 PM |
|

Joined: May 19, 2004
Posts: 6
Location: Pordenone, Italy
|
|
It's a good idea to put tutorials + notes from posts in one pdf file!
Another errata:
Code:
void USART_TxString_P(const char *data)
{
while (pgm_read_byte(*FlashData) != 0x00)
USART_Tx(pgm_read_byte(*FlashData++));
}
Corrige:
Code:
void USART_TxString_P(const char *data)
{
while (pgm_read_byte(data) != 0x00)
USART_Tx(pgm_read_byte(data++));
}
Patrik |
|
|
| |
|
|
|
|
|
Posted: Jul 18, 2006 - 07:51 PM |
|

Joined: Sep 04, 2004
Posts: 103
Location: Los Angeles
|
|
| minor typo shouldnt #include <avr/pgmspace.h.h>? be #include <avr/pgmspace.h> |
|
|
| |
|
|
|
|
|
Posted: Jul 20, 2006 - 10:27 PM |
|

Joined: May 28, 2006
Posts: 50
Location: vancouver
|
|
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? |
|
|
| |
|
|
|
|
|
Posted: Jul 20, 2006 - 11:55 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Oct 13, 2006 - 02:27 AM |
|

Joined: Aug 27, 2006
Posts: 11
|
|
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. |
|
|
| |
|
|
|
|
|
Posted: Oct 13, 2006 - 07:00 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Oct 13, 2006 - 11:07 AM |
|

Joined: Aug 27, 2006
Posts: 11
|
|
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.
Code:
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++));
|
|
|
| |
|
|
|
|
|
Posted: Oct 17, 2006 - 07:20 AM |
|

Joined: May 13, 2005
Posts: 50
|
|
Why did you say that
Code:
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..
Code:
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) |
|
|
| |
|
|
|
|
|
Posted: Oct 17, 2006 - 07:25 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Sorry, I meant that individual string can only then be used once. If you make the string a global declaration:
Code:
uint8_t SomeString PROGMEM = "Some String";
Then you can use it multiple times throughout your program:
Code:
// 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:
Code:
// 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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Oct 17, 2006 - 04:33 PM |
|

Joined: May 13, 2005
Posts: 50
|
|
|
abcminiuser wrote:
Sorry, I meant that individual string can only then be used once. If you make the string a global declaration:
Code:
uint8_t SomeString PROGMEM = "Some String";
Then you can use it multiple times throughout your program:
Code:
// 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:
Code:
// 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
aahh i see i see.. thanks |
|
|
| |
|
|
|
|
|
Posted: Dec 07, 2006 - 01:40 PM |
|


Joined: Apr 25, 2004
Posts: 3809
Location: Denmark
|
|
|
|
|
|
|
Posted: Jan 16, 2007 - 10:30 PM |
|

Joined: Dec 14, 2006
Posts: 2
|
|
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
Code:
pgm_read_word(&LCD_SegTable[4])
be
Code:
pgm_read_word(LCD_SegTable[4])
? |
|
|
| |
|
|
|
|
|
Posted: Jan 17, 2007 - 01:17 AM |
|

Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada
|
|
|
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
Code:
pgm_read_word(&LCD_SegTable[4])
be
Code:
pgm_read_word(LCD_SegTable[4])
?
No. |
|
|
| |
|
|
|
|
|
Posted: Jan 17, 2007 - 08:20 PM |
|

Joined: Dec 14, 2006
Posts: 2
|
|
| Oh I see, it's because LCD_SegTable contains ints (not int pointers), while MenuItemPointers contains char pointers. |
|
|
| |
|
|
|
|
|
Posted: Apr 27, 2007 - 08:25 AM |
|


Joined: Apr 30, 2004
Posts: 43
Location: Sweden
|
|
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. |
|
|
| |
|
|
|
|
|
Posted: Apr 27, 2007 - 08:41 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Apr 27, 2007 - 09:23 AM |
|


Joined: Apr 30, 2004
Posts: 43
Location: Sweden
|
|
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.  |
|
|
| |
|
|
|
|
|
Posted: Apr 29, 2007 - 04:13 AM |
|


Joined: Feb 14, 2007
Posts: 1858
Location: San Diego California
|
|
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!
|
| |
|
|
|
|
|
Posted: Apr 29, 2007 - 09:01 PM |
|


Joined: Oct 15, 2004
Posts: 69
Location: Australia
|
|
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!)
|
| |
|
|
|
|
|
Posted: Apr 30, 2007 - 08:32 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Thanks AllN, no problem! Glad it helped.
Paul: You're dead right, I didn't catch that on my proof-reading. Fixed.
- Dean  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jul 06, 2007 - 06:50 PM |
|

Joined: Nov 01, 2006
Posts: 18
Location: San Jose, CA, USA
|
|
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
Code:
const unsigned char PROGMEM font[128][5]
Why, then, does
Code:
tempChar0 = pgm_read_byte(font[cTemp][0]);
not work but
Code:
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! |
|
|
| |
|
|
|
|
|
Posted: Jul 06, 2007 - 07:07 PM |
|


Joined: Feb 14, 2007
Posts: 1858
Location: San Diego California
|
|
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!
|
| |
|
|
|
|
|
Posted: Jul 07, 2007 - 01:15 AM |
|


Joined: Aug 29, 2006
Posts: 229
Location: Kingston, Jamaica
|
|
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-
Code:
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 |
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2007 - 10:12 AM |
|

Joined: Jul 06, 2007
Posts: 3
|
|
| Explaine pls. how can I access fields of structure located in program memory ? |
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2007 - 01:55 PM |
|

Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada
|
|
|
Code:
#include <avr/pgmspace.h>
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);
}
|
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2007 - 02:54 PM |
|

Joined: Jul 06, 2007
Posts: 3
|
|
|
Code:
#include <avr/pgmspace.h>
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:
Code:
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 ? |
|
|
| |
|
|
|
|
|
Posted: Jul 09, 2007 - 03:37 PM |
|

Joined: Dec 08, 2004
Posts: 4719
Location: Nova Scotia, Canada
|
|
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.
Code:
typedef void (*function_pointer)();
int main(void)
{
function_pointer Proc = (function_pointer)pgm_read_word(&foo.Procedure);
Proc();
}
- Luke |
|
|
| |
|
|
|
|
|
Posted: Jul 31, 2007 - 11:26 PM |
|

Joined: Dec 02, 2004
Posts: 284
Location: San Diego, CA
|
|
Dean, I just wanted to say thanks for writing this tutorial... It just helped me out a lot!!!!
- James |
|
|
| |
|
|
|
|
|
Posted: Aug 16, 2007 - 03:05 AM |
|


Joined: Aug 10, 2007
Posts: 37
Location: Brisbane, Australia
|
|
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 |
|
|
| |
|
|
|
|
|
Posted: Aug 16, 2007 - 05:03 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Oct 07, 2007 - 05:59 PM |
|

Joined: Jun 04, 2007
Posts: 1075
Location: Cincinnati, Ohio - USA
|
|
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 |
|
|
| |
|
|
|
|
|
Posted: Oct 07, 2007 - 11:52 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Oct 09, 2007 - 04:04 AM |
|

Joined: Jun 04, 2007
Posts: 1075
Location: Cincinnati, Ohio - USA
|
|
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.  |
|
|
| |
|
|
|
|
|
Posted: Oct 23, 2007 - 01:52 AM |
|

Joined: Jun 19, 2002
Posts: 957
Location: SF Bay area
|
|
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:
Code:
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:
Code:
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... |
|
|
| |
|
|
|
|
|
Posted: Dec 14, 2007 - 10:25 AM |
|

Joined: Jun 19, 2002
Posts: 957
Location: SF Bay area
|
|
So how come the definition of PSTR defines the string as
Code:
((const PROGMEM char *)(s))
instead of using the typedefs for prog_char:
Code:
((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.) |
|
|
| |
|
|
|
|
|
Posted: Dec 20, 2007 - 11:27 PM |
|

Joined: Jun 10, 2007
Posts: 62
Location: Romania, Oradea
|
|
| Thanks Dean! Your tutorials helped me a lot. |
|
|
| |
|
|
|
|
|
Posted: Jul 17, 2008 - 04:20 PM |
|

Joined: Apr 16, 2008
Posts: 43
|
|
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. |
|
|
| |
|
|
|
|
|
Posted: Jul 17, 2008 - 04:30 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jul 17, 2008 - 05:15 PM |
|

Joined: Apr 16, 2008
Posts: 43
|
|
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. |
|
|
| |
|
|
|
|
|
Posted: Jul 17, 2008 - 06:03 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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.
Code:
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:
Code:
#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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jul 30, 2008 - 05:06 PM |
|


Joined: Jul 10, 2008
Posts: 174
Location: Ruhrpott, Germany
|
|
Nice tutorial, but I'm still totally screwed.
I read also the avr-libc FAQ http://www.nongnu.org/avr-libc/user-man ... _rom_array 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
How to do that? |
_________________ http://xkcd.com/221/
|
| |
|
|
|
|
|
Posted: Jul 30, 2008 - 05:28 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Easy, just add the PROGMEM attribute to the structure itself:
Code:
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jul 30, 2008 - 05:43 PM |
|


Joined: Jul 10, 2008
Posts: 174
Location: Ruhrpott, Germany
|
|
Thank you for your fast answer.
To clarify my understanding:
When using
Code:
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
Code:
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:
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 |
|
|
| |
|
|
|
|
|
Posted: Jul 31, 2008 - 01:42 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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:
Code:
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Jul 31, 2008 - 01:38 PM |
|


Joined: Jul 10, 2008
Posts: 174
Location: Ruhrpott, Germany
|
|
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:
Code:
// 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 |
|
|
| |
|
|
|
|
|
Posted: Aug 09, 2008 - 11:08 AM |
|


Joined: Jul 10, 2008
Posts: 174
Location: Ruhrpott, Germany
|
|
|
Code:
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 |
|
|
| |
|
|
|
|
|
Posted: Aug 09, 2008 - 04:04 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
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 |
_________________
|
| |
|
|
|
|
|
Posted: Aug 10, 2008 - 12:47 PM |
|


Joined: Jul 10, 2008
Posts: 174
Location: Ruhrpott, Germany
|
|
That means, delete the structs and just make a list of strings which all have a specific name like
Code:
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:
Code:
menu(&hello_ID)
Or better give a struct of pointers to all needed strings to the menu() functions.
I will try it. |
_________________ http://xkcd.com/221/
|
| |
|
|
|
|
|
Posted: Aug 10, 2008 - 03:28 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
No I was thinking of an array of strings such as:
Code:
char * strings[] = {
"Salut$Hello$Hola",
"Aurevoir$Bye$Adios"
etc.
}
And possibly an enum or some defines:
Code:
typedef enum {
HELLO_ID,
GOODBYE_ID,
etc.
} STRINGS;
or
#define HELLO_ID 1
#define GOODBYE_ID 2
|
_________________
|
| |
|
|
|
|
|
Posted: Aug 10, 2008 - 08:10 PM |
|


Joined: Jan 12, 2002
Posts: 7829
Location: Canada
|
|
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.
Code:
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. |
|
|
| |
|
|
|
|
|
Posted: Aug 10, 2008 - 11:10 PM |
|


Joined: Mar 12, 2004
Posts: 1174
Location: Linköping, Sweden
|
|
|
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 |
|
|
| |
|
|
|
|
|
Posted: Aug 10, 2008 - 11:32 PM |
|


Joined: Jan 12, 2002
Posts: 7829
Location: Canada
|
|
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.
Code:
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
Code:
char strings[][20] PROGMEM = {
"Salut$Hello$Hola",
"Aurevoir$Bye$Adios"
etc.
}
2) Array of pointers:
Code:
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.
Code:
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
}
|
|
|
| |
|
|
|
|
|
Posted: Aug 11, 2008 - 11:28 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
| For the move to PROGMEM it was Glitch's (2) I was thinking of in fact. |
_________________
|
| |
|
|
|
|
|
Posted: Aug 11, 2008 - 09:17 PM |
|


Joined: Mar 12, 2004
Posts: 1174
Location: Linköping, Sweden
|
|
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 |
|
|
| |
|
|
|
|
|
Posted: Aug 20, 2008 - 11:43 AM |
|

Joined: Mar 03, 2008
Posts: 26
|
|
Hi,
How can allocate the table in a specific address?
Thanks |
|
|
| |
|
|
|
|
|
Posted: Aug 20, 2008 - 12:43 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
| Yes (use a named section and set its address using --section-start to the linker) but the question is "why?" |
_________________
|
| |
|
|
|
|
|
Posted: Aug 20, 2008 - 12:58 PM |
|

Joined: Mar 03, 2008
Posts: 26
|
|
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 |
|
|
| |
|
|
|
|
|
Posted: Aug 20, 2008 - 01:04 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
Try:
Code:
char myTable[8][256] __attribute__ ((section (".mysection"))) = {....}
and in the Makefile add:
Code:
LDFLAGS += -Wl,--section-start=.mysection=0x2000
(note that is BYTE address 0x2000 which in Atmel code flash addressing is WORD address 0x1000) |
_________________
|
| |
|
|
|
|
|
Posted: Aug 21, 2008 - 12:52 PM |
|

Joined: Mar 03, 2008
Posts: 26
|
|
Thanks for your tip. But then I can access this table with the PROGMEM?
Thanks again  |
|
|
| |
|
|
|
|
|
Posted: Aug 21, 2008 - 01:05 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
| 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. |
_________________
|
| |
|
|
|
|
|
Posted: Aug 25, 2008 - 12:07 PM |
|

Joined: Aug 25, 2008
Posts: 1
|
|
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...
Code:
#include <avr/pgmspace.h> // 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. |
|
|
| |
|
|
|
|
|
Posted: Sep 24, 2008 - 10:32 PM |
|

Joined: Mar 17, 2007
Posts: 25
Location: Austin, TX
|
|
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:
Code:
cosresult = pgm_read_word(&costable[ICR1L]));
I have included pgmspace.h:
Code:
#include <avr/pgmspace.h>
And I created the table in the first place with a little perl:
Code:
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:
Code:
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? |
|
|
| |
|
|
|
|
|
Posted: Sep 24, 2008 - 10:35 PM |
|

Joined: Mar 17, 2007
Posts: 25
Location: Austin, TX
|
|
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:
Code:
cosresult = pgm_read_word(&costable[ICR1L]));
I have included pgmspace.h:
Code:
#include <avr/pgmspace.h>
And I created the table in the first place with a little perl:
Code:
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:
Code:
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? |
|
|
| |
|
|
|
|
|
Posted: Sep 25, 2008 - 10:01 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
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. |
_________________
|
| |
|
|
|
|
|
Posted: Sep 25, 2008 - 10:16 AM |
|

Joined: Mar 17, 2007
Posts: 25
Location: Austin, TX
|
|
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:
Code:
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. |
|
|
| |
|
|
|
|
|
Posted: Oct 19, 2008 - 01:59 AM |
|


Joined: Jan 30, 2008
Posts: 168
Location: Wroclaw, Poland
|
|
Is the PROGMEM compatible with C++?
I got the following problem:
Code:
#include <avr/io.h>
#include <avr/fuse.h>
#include <util/delay.h>
#include <compat/deprecated.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
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:
Code:
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.php?name ... nitialized
// in pgmspace.h change:
Code:
//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:
Code:
USART_puts("[A string from RAM]");
USART_putstr_progmem(PSTR("{A string from FLASH}"));
USART_putendl();
Code:
#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
Code:
# define PSTR(s) (__extension__({static char __c[] PROGMEM = (s); &__c[0];}))
to
Code:
# 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:
Code:
#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-bugs@gc ... 11903.html
it is not fixed even in the most recent
WinAVR-20081124rc3 |
|
|
| |
|
|
|
|
|
Posted: Dec 04, 2008 - 09:29 PM |
|

Joined: Nov 06, 2008
Posts: 4
|
|
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. |
|
|
| |
|
|
|
|
|
Posted: Dec 05, 2008 - 09:18 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Mar 22, 2009 - 07:21 PM |
|

Joined: Jul 19, 2006
Posts: 2
|
|
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.
Code:
#include <stdlib.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#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? |
|
|
| |
|
|
|
|
|
Posted: Mar 22, 2009 - 07:52 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
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:
Code:
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]));
|
_________________
|
| |
|
|
|
|
|
Posted: Mar 22, 2009 - 08:42 PM |
|

Joined: Jul 19, 2006
Posts: 2
|
|
| Thanks, now it's work fine. I thought that i was sending the number not its code ascii. |
|
|
| |
|
|
|
|
|
Posted: Mar 23, 2009 - 09:24 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
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 |
_________________
|
| |
|
|
|
|
|
Posted: Mar 26, 2009 - 10:01 AM |
|

Joined: Sep 22, 2008
Posts: 22
|
|
There was mentioning of a PDF. Anybody knows where I can find this PDF?
Thanks,
Leon |
|
|
| |
|
|
|
|
|
Posted: Mar 26, 2009 - 09:27 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
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  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Mar 27, 2009 - 09:52 AM |
|

Joined: Mar 26, 2009
Posts: 1
|
|
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? |
|
|
| |
|
|
|
|
|
Posted: Mar 30, 2009 - 11:57 AM |
|


Joined: Mar 12, 2004
Posts: 1174
Location: Linköping, Sweden
|
|
Just a guess since all pgm_read_xxxx functions need an address:
Code:
pgm_read_byte(&EEPROMerrorCheckString[i]);
/Lars |
|
|
| |
|
|
|
|
|
Posted: Apr 10, 2009 - 10:09 AM |
|

Joined: Apr 10, 2009
Posts: 1
|
|
| I read a lot about this amazing pdf tutorial, can't wait to read it. Yes I also read jabius's comment. |
|
|
| |
|
|
|
|
|
Posted: Apr 21, 2009 - 07:03 AM |
|

Joined: Jan 01, 2009
Posts: 37
Location: Home SoPCo., State Polytechnic of Jakarta
|
|
|
JimW52 wrote:
Rev 2
All references to prgm_read_word should be pgm_read_word.
Jim
yeah you're right bro..  |
|
|
| |
|
|
|
|
|
Posted: Apr 24, 2009 - 08:33 AM |
|

Joined: Jan 01, 2009
Posts: 37
Location: Home SoPCo., State Polytechnic of Jakarta
|
|
PROGMEM is such a code that i used in my dotmatriks project.
Thanks a lot for the tutorial Dean  |
_________________ ArcticSoul
Industrial Electronic Engineering, College Student
|
| |
|
|
|
|
|
Posted: Jun 17, 2009 - 08:48 AM |
|

Joined: Jun 17, 2009
Posts: 2
|
|
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/yabb2/YaB ... 1245052549 |
|
|
| |
|
|
|
|
|
Posted: Jun 17, 2009 - 09:11 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
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. |
_________________
|
| |
|
|
|
|
|
Posted: Jun 22, 2009 - 09:21 PM |
|

Joined: May 19, 2008
Posts: 17
|
|
Hi,
frist of all thanks for the tutorial
let's see if I get it:
1)
it's the same to declare
Code:
const uint16_t prior0[10] PROGMEM = {100, 360, ..., 120};
and
Code:
const prog_uint16_t prior0[10] = {100, 360, ..., 120};
2)
I've got some constant arrays (like the one before) in the prog.mem.
Code:
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:
Code:
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. |
|
|
| |
|
|
|
|
|
Posted: Jun 23, 2009 - 09:45 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
1) yes, pgmspace.h just has this:
Code:
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 *"  |
_________________
|
| |
|
|
|
|
|
Posted: Jul 13, 2009 - 08:56 PM |
|

Joined: Nov 01, 2003
Posts: 127
Location: Greece
|
|
First of all this is a great tutorial. Thanks.
I am a little bit confused though. I am having a project in which I intend to use a menu to interface the uC.
Should I use progmem or eemem. Which one is better for LCD, menu
and a Keyboard? |
|
|
| |
|
|
|
|
|
Posted: Jul 13, 2009 - 09:49 PM |
|

Joined: Nov 17, 2004
Posts: 13847
Location: Vancouver, BC
|
|
| AVRs by in large don't have much eemem, so unless you needs are small, there is probably not enough room in eemem. If you do fit in eemem, then it is up to you which you use, but I think that most would choose progmem. Reading from eemem takes longer than reading from progmem. |
_________________ Regards,
Steve A.
The Board helps those that help themselves.
|
| |
|
|
|
|
|
Posted: Jul 14, 2009 - 12:45 PM |
|

Joined: Nov 01, 2003
Posts: 127
Location: Greece
|
|
| thanks for the clarification. |
|
|
| |
|
|
|
|
|
Posted: Dec 11, 2009 - 02:24 AM |
|

Joined: Dec 09, 2009
Posts: 11
|
|
Hey guys,
Is it possible to implement PROGMEM for AVR32 ?
Regards, |
|
|
| |
|
|
|
|
|
Posted: Jan 18, 2010 - 03:03 PM |
|

Joined: Sep 12, 2007
Posts: 35
Location: Atlanta, GA USA
|
|
Thanks so much for this tutorial. It explains stuff that other authors miss.
May I suggest that you emphasize that PROGMEM assignments MUST be global. It took me quite a while to figure out the warning (which I think should be an error as it breaks all use of the constant)
Thanks
Kirk |
|
|
| |
|
|
|
|
|
Posted: May 17, 2010 - 08:19 PM |
|

Joined: May 12, 2010
Posts: 7
Location: Concord, CA, 94519
|
|
Utmost puzzling javascript:emoticon(' ')
So I followed this tutorial to put some strings into Program memory and then read them with pgm_read_byte(). While the string is reads correctly (when optimization is not at -Os) I can't explain what I see in the debugger!
The .lss file shows that the string is put in program memory right after the interrupt vector table, at address 0x7c. When I single step with the disassembler I can see that the LPM instruction via the Z pointer reads from address 0x7c.
HOWEVER when I bring up a memory window or scroll the disassembler to 0x7c there is no string. Instead I find the string at address 0x38, apparently right in the middle of the vector table, and I find that address 0x7c is inhabited by code. When I add a break point at 0x00007b the debugger stops there, so I'm clearly executing from there.
javascript:emoticon(' ')
What is going on? Why does it appear that my string in memory is not where the .lss file says it should be and why am I executing code where a strings should be. What magic is this ?
Gary |
|
|
| |
|
|
|
|
|
Posted: May 17, 2010 - 08:57 PM |
|


Joined: Jul 23, 2001
Posts: 2439
Location: Osnabrueck, Germany
|
|
| You are mixing up byte and word addresses. The simulator/disassembler presents you everything related to the flash with word addresses, the GNU tools use byte addresses. So 0x7c in the .lss file is 0x3e in the simulator/disassembler. |
_________________ Stefan Ernst
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 08:47 PM |
|

Joined: Jun 23, 2010
Posts: 15
|
|
| What about trying to store data in the bootloader section using PROGMEM? Is there a way to change PROGMEM's start location. I can recall strings and variables just fine from FLASH at 0x00000, but not from the bootloader section. |
|
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 09:56 PM |
|


Joined: Jul 23, 2001
Posts: 2439
Location: Osnabrueck, Germany
|
|
|
chrislego88 wrote:
What about trying to store data in the bootloader section using PROGMEM? Is there a way to change PROGMEM's start location. I can recall strings and variables just fine from FLASH at 0x00000, but not from the bootloader section.
If you write a bootloader then you move the whole Flash stuff by relocating the .text section. This also moves the PROGMEM section. So what is you actual problem?
Or don't you write a bootloader but only want to place some data in the bootloader section (whyever)? Then simply don't use the PROGMEM section. Use your own section and place it there. |
_________________ Stefan Ernst
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 10:17 PM |
|

Joined: Jun 23, 2010
Posts: 15
|
|
I did relocate the .text section to my boot section. The problem that I am having is trying to recall data from the boot section.
For example,
Code:
I run that and it complies but I can never get buf to the init string. So I am not sure why memcpy_P cannot recall the data in FLASH.
const char init[] PROGMEM = "Boot Version";
const char flash[] PROGMEM = "FLASH";
PGM_P table[2] PROGMEM = { init,flash };
int main() {
char buf[32];
PGM_P p;
memcpy_P(&p,&(table[0]),sizeof(PGM_P));
strcpy_P(buf,p);
}
The code complies but I cannot get buf to hold any information. I am not really sure why. I always get the value of p to be 0xFFFF and then it crashes on strcpy_P command. |
Last edited by chrislego88 on Jun 25, 2010 - 10:34 PM; edited 1 time in total
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 10:33 PM |
|


Joined: Jul 23, 2001
Posts: 2439
Location: Osnabrueck, Germany
|
|
| I don't see any problem. So please be more specific about the context. What mcu? Are you aware that PROGMEM does not work beyond 64k (at least not out-of-the-box)? |
_________________ Stefan Ernst
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 10:50 PM |
|

Joined: Jun 23, 2010
Posts: 15
|
|
I have a Atxmega128a1 and the boot section starts at 0x020000.
I know that it doesn't work past 64k but my boot section is only 8k, so if I move the .text section to the boot section address I shouldn't have any problems right? |
|
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 10:53 PM |
|


Joined: Jul 23, 2001
Posts: 2439
Location: Osnabrueck, Germany
|
|
|
chrislego88 wrote:
I have a Atxmega128a1 and the boot section starts at 0x020000.
I know that it doesn't work past 64k but my boot section is only 8k, so if I move the .text section to the boot section address I shouldn't have any problems right?
So you don't think that 0x020000 is beyond 0x010000 (64k)?
BTW: I am not familiar with the XMegas. Has an Atxmega128 more than 128k Flash? If not, how then can the boot section start at 0x020000?
EDIT: Forget this. Meanwhile I took a look into the data sheet.  |
_________________ Stefan Ernst
Last edited by sternst on Jun 25, 2010 - 11:11 PM; edited 1 time in total
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 11:07 PM |
|

Joined: Jun 23, 2010
Posts: 15
|
|
No it is beyond 64K but the PROGMEM doesn't travel with the .text section and have it start at 0x020000
If it doesn't then how can I store those strings in my boot section and still be able to recall them? |
|
|
| |
|
|
|
|
|
Posted: Jun 25, 2010 - 11:41 PM |
|


Joined: Jul 23, 2001
Posts: 2439
Location: Osnabrueck, Germany
|
|
|
Quote:
but the PROGMEM doesn't travel with the .text section and have it start at 0x020000
Why do you think it does not travel? Your code does not work because PROGMEN has traveled beyond 0x010000.
Quote:
how can I store those strings in my boot section and still be able to recall them?
The first thing you can do is to read at least the thread you are posting in. The 64k border was already a topic here. |
_________________ Stefan Ernst
|
| |
|
|
|
|
|
Posted: Jun 28, 2010 - 10:14 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
Search out Carlos Lamas (I may have spelt the wrong?) utilities called morepgmspace.h
EDIT: take a look at this thread:
http://www.avrfreaks.net/index.php?name ... p;p=599080
It may not be the best thread about morepgmspace.h but it includes a post by Carlos which should allow you to easily find his other posts. |
_________________
|
| |
|
|
|
|
|
Posted: Jun 28, 2010 - 04:55 PM |
|

Joined: Jun 23, 2010
Posts: 15
|
|
|
clawson wrote:
Search out Carlos Lamas (I may have spelt the wrong?) utilities called morepgmspace.h
EDIT: take a look at this thread:
http://www.avrfreaks.net/index.php?name ... p;p=599080
It may not be the best thread about morepgmspace.h but it includes a post by Carlos which should allow you to easily find his other posts.
Thanks a lot those seem to work like a charm. |
|
|
| |
|
|
|
|
|
Posted: Jul 17, 2010 - 06:56 PM |
|

Joined: Jul 16, 2008
Posts: 3
|
|
| Thanks a lot for the explanation, it has been very usefull for me!! |
|
|
| |
|
|
|
|
|
Posted: Sep 07, 2010 - 01:26 AM |
|

Joined: Dec 09, 2008
Posts: 1
|
|
Thanks for explaining this Dean, ENORMOUSLY helpful.
About the eemem question above (in case your still following the thread) - That portion of memory can tolerate 100000 writes as opposed to 10000 in flash, so ultimately flash seems to me best for constant strings or at strings that rarely change. |
|
|
| |
|
|
|
|
|
Posted: Sep 07, 2010 - 02:38 AM |
|

Joined: Aug 26, 2008
Posts: 66
Location: Montreal, Canada
|
|
Hello, first of all, thanks for the tutorial!
I didn't read the whole thread but did do some quick search in it.
I was wondering if there was an easy way to directly load a resource file into a program memory destination.
for instance, I have the file res.txt with 800 bytes in it. I would like to copy its contents into program memory at address 0x1000, is there any special instructions in avr studio / avr-gcc to do so? |
|
|
| |
|
|
|
|
|
Posted: Sep 24, 2010 - 10:13 PM |
|

Joined: Sep 24, 2010
Posts: 6
|
|
|
Handi3 wrote:
I have the file res.txt with 800 bytes in it. I would like to copy its contents into program memory at address 0x1000, is there any special instructions in avr studio / avr-gcc to do so?
I've found this bin2hex perl script to be really useful for this.
See: http://www.chami.com/tips/delphi/052098d.html
This script encodes any data file (binary or text) into a header suitable for perl, C/C++ or Pascal. So its great for including images, binary data or plain text.
To get C compatible output invoke the script like this:
Code:
perl bin2hex.pl res.txt 1 > res.h
Then you just include the header file into your program.
Code:
#include "res.h"
Since I had a number of files to convert I wrapped bin2hex in this bash script:
Code:
#!/bin/sh
for file in input/*; do
newfile=$(echo $file | sed 's|input/||')
echo "Converting $file to $array PROGMEM"
perl bin2hex.pl $file 1 | sed -e "s/bin_data\[\]/$newfile\[\] PROGMEM/" -e "s/\}/, 0\}/" > output/$newfile.h
done
[edit]The text that starts with "perl" is all one line. Only "done" comes after on a line by itself[/edit]
This converts all of the files in the input/ directory into the output/ directory.
It also changes the name of the array to the name of the file (without extension).
Additionally it adds the PROGMEM attribute to each array, and a NULL termination since I want to deal with the data as a string.
As far as locating the string at a specific address in program memory: Are you sure this is necessary?
Once the header is included you can use the progmem access functions to retreive the data by name.
Hope that helps...
johnea
p.s. I attached the bin2hex.pl script for completeness. Although you'll need to rename bin2hex.txt to bin2hex.pl |
|
|
| |
|
|
|
|
|
Posted: Sep 29, 2010 - 12:28 AM |
|

Joined: Sep 29, 2010
Posts: 1
|
|
Hello,
Super Tutorial... but???
Is it possible to use signed char or signed int with the whole PROGMEM buisness???
Regards |
|
|
| |
|
|
|
|
|
Posted: Sep 29, 2010 - 12:29 AM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Sure, just use the pgm_read_byte() and pgm_read_word() functions, and cast the result to the appropriate type.
- Dean  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Nov 16, 2010 - 09:34 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
Moderator note: @Decoder, I have spit the fault diagnosis of your program off from this thread and placed it in GCC forum as:
Problem using PROGMEM
THIS thread is not for diagnosing every last program that just happens to use PROGMEM but is for discussing the article in the first post.
Moderator |
_________________
|
| |
|
|
|
|
|
Posted: Feb 04, 2011 - 12:32 AM |
|


Joined: Mar 25, 2008
Posts: 18
Location: Greece, Athens
|
|
Allo! As always a great tutorial Dean!
I was wondering if i could use PROGMEN with some EEMEM data. I have a huge array with EEPROM data pointers and it seems pity to overload my RAM with these.
Wil something like this work?
Code:
uint8_t EEMEM NonVolatileString[10] PROGMEN;
|
|
|
| |
|
|
|
|
|
Posted: Feb 04, 2011 - 12:11 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
The EEPROM equivalent of "PROGMEM" is "EEMEM". You'd use:
Code:
#include <avr/eeprom.h>
...
uint8_t EEMEM NonVolatileString[10];
there's a separate tutorial about the use of EEPROM in GCC in this forum.
(BTW it's PROGMEM not PROGMEN - it's short for PROGram MEMory) |
_________________
|
| |
|
|
|
|
|
Posted: Mar 04, 2011 - 08:29 PM |
|

Joined: Feb 28, 2011
Posts: 14
|
|
I have a quick question, if anyone here can help. I have a program where I have a large configuration struct that houses several different data types. Right now, I have two copies of this struct in EEPROM, which correspond to different configurations. I would like store defaults for each configuration in program memory, so a function like pgm_read_block (analogous to the eeprom_read_block I already use) would be very handy. I saw it mentioned earlier in the thread, but can't find it in pgmspace.h included with WinAVR-20100110. Is it an extension that I could find elsewhere, or will I have to write it myself?
Thanks. |
|
|
| |
|
|
|
|
|
Posted: Mar 04, 2011 - 08:33 PM |
|


Joined: Jan 23, 2004
Posts: 9831
Location: Trondheim, Norway
|
|
Use memcpy_P(), which works just like the regular memcpy() function, but uses a source located in FLASH memory space.
- Dean  |
_________________ Atmel Studio 6.1 is now released, grab it here.
Report AS6/ASF bugs here.
|
| |
|
|
|
|
|
Posted: Mar 04, 2011 - 08:51 PM |
|

Joined: Feb 28, 2011
Posts: 14
|
|
| Awesome, that works great. Thanks Dean. |
|
|
| |
|
|
|
|
|
Posted: May 13, 2011 - 06:29 PM |
|


Joined: Mar 02, 2010
Posts: 103
Location: Firenze, Italy
|
|
This tutorial was written time ago, but I find it still very useful *today*. So thank you very much, Dean  |
|
|
| |
|
|
|
|
|
Posted: Jul 21, 2011 - 11:18 AM |
|

Joined: Oct 20, 2010
Posts: 14
Location: Poland Warsaw
|
|
Really great tutorial Dean! thanks very much
I thinking about what is the best solution for language strings. Example: I have device with lcd display. The device supports 3 languages. There's about ~50 strings for each language (various length).
My idea is (I don't know if its correct) to create 3 different language strings for each text in progmem, sth like:
Code:
const char txt1_en[] PROGMEM = "text 1 in english";
const char txt1_de[] PROGMEM = "text 1 in german";
const char txt1_pl[] PROGMEM = "text 1 in polish (different length)";
// ...
Additionally 1 table with pointers which point to currently using language strings, e.g.
Code:
// english default
char * LanguagePnts[] = {txt1_en, txt2_en, txt3_en}; // ... etc.
Now if user change language in run-time, the pointers should change, and point to elements of diffrent language strings in progmem.
Code:
// if user change lang to PL:
LanguagePnts[0] = txt1_pl;
LanguagePnts[1] = txt2_pl;
LanguagePnts[2] = txt3_pl;
//etc.
LCD 'write string' function (progmem version) should be language independent, e.g.
Code:
LCD_WriteString_P(LanguagePnts[0]); //txt1
LCD_WriteString_P(LanguagePnts[1]); //txt2
LCD_WriteString_P(LanguagePnts[2]); //txt3
//etc.
I don't know if it is efficiently solution, or even correct one. I'm still beginner and I'll be gratefull to everyone for help.
greetings! |
|
|
| |
|
|
|
|
|
Posted: Jul 21, 2011 - 12:08 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
Quote:
I don't know if it is efficiently solution, or even correct one.
Looks pretty good to me - obviously the array of pointers itself has to be in RAM not PROGMEM if you plan to change language at run time. But you could keep the initialisers for each language themselves in PROGMEM and do something like a memcpy_P() to copy an entire set of language pointers into place in one go. |
_________________
|
| |
|
|
|
|
|
Posted: Jul 21, 2011 - 12:24 PM |
|

Joined: Oct 20, 2010
Posts: 14
Location: Poland Warsaw
|
|
| [quote="clawson"]
Quote:
But you could keep the initialisers for each language themselves in PROGMEM and do something like a memcpy_P() to copy an entire set of language pointers into place in one go.
great advice, I'll try this, thank you!
edit: I saved over 1.5kB ram only on language strings! |
|
|
| |
|
|
|
|
|
Posted: Aug 10, 2011 - 12:28 PM |
|

Joined: Mar 26, 2010
Posts: 15
|
|
I found that this example (from this thread) works for me, whereas the example in the HOWTO does not work for me.
eg.
Code:
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
};
I am using avr-gcc 4.3.5 to compile a cpp sourcecode (inherited from my Arduino environment). The example in the HOWTO leaves me with mismatching typeconversion errors or section type conflicts when I use "const". |
|
|
| |
|
|
|
|
|
Posted: Aug 11, 2011 - 08:32 PM |
|

Joined: Dec 17, 2009
Posts: 4
|
|
Questions:
1. Can we change the flash contents by changing the content of the variable using PROGMEM?
2. If question 1 is Yes, then correct me if I'm wrong, the flash sector has to be erased, then store data to it. So how does it work? |
|
|
| |
|
|
|
|
|
Posted: Aug 11, 2011 - 09:04 PM |
|

Joined: Dec 17, 2009
Posts: 4
|
|
Please help!
I have a Atmega M1284P. Data changeable by user and it's used to store in EEPROM, but now decide to store in flash. Here is my Flash memory organize:
App Region: 0 - 0xFFFF
Data Region: 0x10000 to 0x1DFFF
Boot Region: 0x1E000 to 0x1FFFF
I provide 2 commands to user READ_Data and WRITE_Data with 3 parameters: U16_t ui16Address, U16_t ui16Len, U8_t * pui8Buffer
The READ_Data function has no problem, I can use pgm_read_byte_far( 0x10000 + ui16Addr) where ui16Addr is from 0 to 0xDFFF (used to be the EEPROM address).
For the WRITE_Data function, should I assign Data Region Address of the Flash to be 0x1000 to a PROMEM variable? How can I do it?
When initialize, the data is used to read to RAM data structure variables. To save RAM memory, so now I benefit to use Flash instead. |
|
|
| |
|
|
|
|
|
Posted: Aug 11, 2011 - 09:28 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
| Check out morepgmspace.h by carlos llamas |
_________________
|
| |
|
|
|
|
|
Posted: Aug 12, 2011 - 08:39 PM |
|

Joined: Dec 17, 2009
Posts: 4
|
|
| I don't think we can change the contents of the flash using these two utilities pgmspace.h and morepgmspace.h. We have to use boot_page_erase, boot_page_fill, boot_page_write in <avr/boot.h> |
|
|
| |
|
|
|
|
|
Posted: Aug 12, 2011 - 09:19 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
Quote:
I don't think we can change the contents of the flash using these two utilities pgmspace.h and morepgmspace.h. We have to use boot_page_erase, boot_page_fill, boot_page_write in <avr/boot.h>
Correct - this thread is about LPM not SPM. |
_________________
|
| |
|
|
|
|
|
Posted: Aug 14, 2011 - 12:21 AM |
|

Joined: Jun 19, 2002
Posts: 957
Location: SF Bay area
|
|
| Is there my any chance some sort of "test suite" for the pgmspace.h functions and definitions? I'm working on a compatibility shim for non-AVR processors and prospect of testing everything is daunting... |
|
|
| |
|
|
|
|
|
Posted: Aug 15, 2011 - 10:52 AM |
|

Joined: Aug 05, 2011
Posts: 18
|
|
At first thank you all for this good explanation.
I have compilation error:
Code:
variable ‘__c’ must be const in order to be put into read-only section by means of ‘__attribute__((progmem))’
I've googled it and the only thing I have found at the moment is that this error is fixed in AVRLibC 1.7.1.
But upgrading from my 1.6.8 to 1.7.1 changed nothing.
How could it be fixed?
P.S.
Please tell me if I should start new thread. |
|
|
| |
|
|
|
|
|
Posted: Aug 15, 2011 - 10:59 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
Quote:
Please tell me if I should start new thread.
You should - this thread is only for making improvements/corrections to the original article. Start a thread in GCC. Say whether you are using C or C++ in it. |
_________________
|
| |
|
|
|
|
|
Posted: Aug 15, 2011 - 05:08 PM |
|

Joined: Dec 17, 2009
Posts: 4
|
|
Thank you Clawson,
SPM doesn't support or disabled in Application Section, it works only in Bootloader Section. So there is NO WAY to change data in the flash when an app is running, user has to jump to BL mode to write data, then jump back to an app section. How do I read the program counter (PC) to save to EEPROM, so I could jump back to the application? |
|
|
| |
|
|
|
|
|
Posted: Aug 15, 2011 - 05:15 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
Quote:
SPM doesn't support or disabled in Application Section, it works only in Bootloader Section. So there is NO WAY to change data in the flash when an app is running, user has to jump to BL mode to write data, then jump back to an app section. How do I read the program counter (PC) to save to EEPROM, so I could jump back to the application?
It's easy to write code that updates the flash at run time. As you say it just needs to use SPM and it has to be located in the bootloader section which is why <avr/boot.h> has the BOOTLOADER_SECTION macro. But this is not the correct thread to be discussing this.
NOTE:
This thread is ONLY for discussing the original article about PROGMEM so I'm going to lock this thread now. If anyone has anything to add to correct/improve the original article then PM js, plons or clawson and ask one of us to temporarily unlock this thread to add your contribution. |
_________________
|
| |
|
|
|
|
|
Posted: May 15, 2012 - 08:09 AM |
|


Joined: Dec 21, 2006
Posts: 1483
Location: Saar-Lor-Lux
|
|
|
abcminiuser wrote:
Code:
#include <avr/pgmspace.h>
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};
Right definition of MenuItemPointers is:
Code:
const char * const MenuItemPointers[] PROGMEM = ...
or otherwise:
Code:
error: variable 'MenuItemPointers' must be const in order to be put into read-only section by means of '__attribute__((progmem))'
warning: initialization discards 'const' qualifier from pointer target type [enabled by default]
warning: initialization discards 'const' qualifier from pointer target type [enabled by default]
warning: initialization discards 'const' qualifier from pointer target type [enabled by default]
Quote:
"const" modifier
const is a qualifier |
|
|
| |
|
|
|
|
|
Posted: Jul 22, 2012 - 01:33 AM |
|

Joined: Jul 22, 2012
Posts: 19
|
|
Hi,
at first, thanks for the great tutorial, the explanations and all the comments. I've tried to read and understand everything, but...
I am using AVR STUDIO and the ATmega 162. Within a large program, I would like to strip down my SRAM usage and so I have defined a sort of terminal emulation field in the following way, now:
Code:
const char TERMSPEC_ANSI[13][7] PROGMEM = {
{ 27, 91, '2', 'J', 0, 0, 0 },
{ 27, 91, 'A', 0, 0, 0, 3 },
{ 27, 91, 'B', 0, 0, 0, 3 },
{ 27, 91, 'C', 0, 0, 0, 3 },
{ 27, 91, 'D', 0, 0, 0, 3 },
{ 27, 91, 'H', 0, 0, 0, 3 },
{ 27, 91, 'H', 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 },
{ 27, 91, '7', 'm', 0, 0, 0 },
};
Compiling runs well, but then I try to read out a character at row position y and column position x at run-time, using:
Code:
c = pgm_read_byte( TERMSPEC_ANSI[ y ][ x ] );
But I receive an unexpected character for c . When debugging the memory location, I see that:
(1) The FLASH memory is now (different from the SRAM version) organised as WORDs, not BYTEs. So every address in memory holds two bytes.
(2) The start address of my field TERMSPEC_ANSI (shown in the tooltips) is way off the memory location where my field is now located within the FLASH.
Any suggestions? Any help how to address and retrieve the correct values?
Thanks.
Peter |
|
|
| |
|
|
|
|
|
Posted: Jul 22, 2012 - 04:00 AM |
|


Joined: Mar 28, 2001
Posts: 20387
Location: Sydney, Australia (Gum trees, Koalas and Kangaroos, No Edelweiss)
|
|
| Can you try
Code:
c = pgm_read_byte( & TERMSPEC_ANSI[ y ][ x ] );
& sign added. Don't ask me why  |
_________________ John Samperi
Ampertronics Pty. Ltd.
www.ampertronics.com.au
* Electronic Design * Custom Products * Contract Assembly
|
| |
|
|
|
|
|
Posted: Jul 22, 2012 - 08:46 AM |
|


Joined: Dec 06, 2007
Posts: 2512
Location: Redmond, WA USA
|
|
| pgm_read_byte needs an address to an array element, not the array element itself. |
_________________ Larry
Those afraid to embrace the future will quickly fade into the past. - larryvc
|
| |
|
|
|
|
|
Posted: Jul 22, 2012 - 01:30 PM |
|

Joined: Jul 22, 2012
Posts: 19
|
|
|
larryvc wrote:
pgm_read_byte needs an address to an array element, not the array element itself.
Thanks, John and Larry. In the end it was kinda obvious
The two questions in the last part of my recent posting will be concerned elsewhere, right? It rather has to do with the handling and displaying of FLASH locations within AVR STUDIO, not with the PROGMEM attribute itself.
Thanks.
Peter |
|
|
| |
|
|
|
|
|
Posted: Jul 22, 2012 - 06:16 PM |
|


Joined: Dec 06, 2007
Posts: 2512
Location: Redmond, WA USA
|
|
| I think that it would be better if you asked that in a new thread in one of the Studio forums, whichever Studio you are using. |
_________________ Larry
Those afraid to embrace the future will quickly fade into the past. - larryvc
|
| |
|
|
|
|
|
Posted: Aug 06, 2012 - 01:30 PM |
|

Joined: Aug 27, 2007
Posts: 143
Location: Budapest, Hungary
|
|
I have a question: I have many files in my project and I want to make visible a flash based (PROGMEM) const array as a global variable.
in globals.c I have:
Code:
const char global_progmem_const [] PROGMEM = "This is a global PROGMEM const string, visible from all modules!";
in globals.h:
Code:
extern const char global_progmem_const [] PROGMEM;
Is this the correct approach? |
|
|
| |
|
|
|
|
|
Posted: Sep 18, 2012 - 08:52 AM |
|

Joined: Dec 08, 2005
Posts: 12
|
|
Hi @ all,
First I want to thanks for this usefull tutorial, although I figured some of it out myself, with already existing code in my project.
I have now hit a problem with the pointertables:
This:
Code:
const char string_normal[] PROGMEM = "normal";
const char* table [NUMBER_OF_LANGUAGES][2] = {
{settings00,string_normal},
{settings10,settingsSpeed11}
};
works perfectly fine (although the pointer table is not in flash itself, which isn't the point here)
But when I implement a pointer to the string, which is then inserted in the pointer table:
Code:
const char string_normal[] PROGMEM = "normal";
const char* settings01 = string_normal;
const char* table [NUMBER_OF_LANGUAGES][2] = {
{settings00,settings01},
{settings10,settings11}
};
I get an compiling error:
initializer element is not constant
Whats the correct approach here? I tried every combination of const and PROGMEM attributes but always get this error.
For clarification: I could of course use the first mentioned alternative, but i want to create the pointer tables with a preprocessor macro, because there are a lot of them and it would make implementations of new languages easier. |
|
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 05:05 PM |
|

Joined: Dec 27, 2009
Posts: 6
Location: Paese TREVISO (IT)
|
|
excellent, I would put in flash at a 0x1f00 address a bytes of value 5 by the compiler and then read it:
# define fas 0x1f00 / / 5
.
.
pgm_read_byte (fas);
how can I tell the compiler (gcc) to put at 0x1f00 a byte value of 5?
thanks |
|
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 05:29 PM |
|


Joined: Jul 05, 2007
Posts: 962
Location: Greece
|
|
What is the need of the byte value being written in that specific location?
You should use uint8_t fas PROGMEM = 5;
and then just read it using pgm_read_byte (&fas);
Alex |
_________________ "For every effect there is a root cause. Find and address the root cause rather than try to fix the effect, as there is no end to the latter."
Author Unknown
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 05:35 PM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
If there really was some reason you needed something at 0x1F00 then you can do:
Code:
const uint8_t foo = 5 __attribute__((section(".myvar")));
then pass this to the linker:
Code:
-Wl,-section-start=.myvar=0x1F00
and finally:
Code:
var = pgm_read_byte((uint8_t *)0x1F00);
But like Alex says, why would you do this? Why not use PROGMEM properly as explained in the original article of this thread? If you do start specifying absolute addresses like 0x1F00 you may conflict with what the linker tries to do automatically - how do you know it wasn't already planning to place code or flash data at that location? |
_________________
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 05:47 PM |
|


Joined: Jul 05, 2007
Posts: 962
Location: Greece
|
|
By the way I like to use const with PROGMEM and I forgot it in my example, I think it is even required in new gcc versions
Code:
const uint8_t fas PROGMEM = 5;
Alex |
_________________ "For every effect there is a root cause. Find and address the root cause rather than try to fix the effect, as there is no end to the latter."
Author Unknown
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 06:17 PM |
|

Joined: Dec 27, 2009
Posts: 6
Location: Paese TREVISO (IT)
|
|
| I wish that the customer can easily find some constants before program firmware with isp |
|
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 06:18 PM |
|


Joined: Jul 05, 2007
Posts: 962
Location: Greece
|
|
How about copying them to eeprom, they can read them from there.
Alex |
_________________ "For every effect there is a root cause. Find and address the root cause rather than try to fix the effect, as there is no end to the latter."
Author Unknown
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 06:22 PM |
|

Joined: Dec 27, 2009
Posts: 6
Location: Paese TREVISO (IT)
|
|
| I'm afraid that the compiler can change the position of my constant every time I change the firmware ..sig! |
|
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 06:24 PM |
|

Joined: Dec 27, 2009
Posts: 6
Location: Paese TREVISO (IT)
|
|
| mmm, eprom? YES , can be a possible solution... i must think... |
|
|
| |
|
|
|
|
|
Posted: Dec 13, 2012 - 06:54 PM |
|

Joined: Dec 27, 2009
Posts: 6
Location: Paese TREVISO (IT)
|
|
I saw that the compiler always puts constant from address 0x26 (ATmega8), then fine by me, I did as you told me you and alexan_e
thankss! |
|
|
| |
|
|
|
|
|
Posted: Dec 14, 2012 - 09:28 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
Quote:
I wish that the customer can easily find some constants before program firmware with isp
But ISP doesn't allow subsections of the flash to be changed/replaced?!? I'd go with Alex's idea of keeping config value in EEPROM which the customer *can* program separately.
Quote:
I saw that the compiler always puts constant from address 0x26 (ATmega8), then fine by me,
It's not the compiler that did that, it's the linker. This is because the linker is given a "linker script" that tells it how to lay things out in flash. That file is ..\AVRToolchain\avr\lib\ldscripts\avr4.x which contains:
Code:
/* Internal text space or external memory. */
.text :
{
*(.vectors)
KEEP(*(.vectors))
/* For data that needs to reside in the lower 64k of progmem. */
*(.progmem.gcc*)
*(.progmem*)
. = ALIGN(2);
This says that anything in a section called .vectors will be placed at the very start of flash - that includes the reset jump and the interrupt vector table - this is then followed by anything in .progmem* sections which means anything where you use PROGMEM which is similar to __attribute__((section(".progmem")). The reset+vectors on a mega8 are clearly 0x26 bytes long. |
_________________
|
| |
|
|
|
|
|
Posted: Dec 21, 2012 - 05:09 PM |
|

Joined: Nov 06, 2009
Posts: 44
|
|
|
lammelm wrote:
I have a question: I have many files in my project and I want to make visible a flash based (PROGMEM) const array as a global variable.
in globals.c I have:
Code:
const char global_progmem_const [] PROGMEM = "This is a global PROGMEM const string, visible from all modules!";
in globals.h:
Code:
extern const char global_progmem_const [] PROGMEM;
Is this the correct approach?
Can anyone confirm that this code is the correct way to use global progmem vars in multiple source files? |
|
|
| |
|
|
|
|
|
Posted: Dec 21, 2012 - 07:37 PM |
|


Joined: Jun 15, 2008
Posts: 1762
Location: North Carolina USA
|
|
| The PROGMEM attribute in the .h file means nothing to gcc, the symbol is just a hex offset from the start of a particular linker section and the code that refers to it still has to use the method appropriate to the hardware location of that section. Some compilers may do that automatically, but not gcc. For easier maintenance you can define both the attribute and the appropriate programmic references in the .h file and include that in all the c files.
Code:
/* httpd string storage is in RAM by default. Other storage can be defined here */
#define HTTPD_STRING_TYPE PROGMEM_TYPE
#define PROGMEM_TYPE 1
#define EEPROM_TYPE 2
#if HTTPD_STRING_TYPE==PROGMEM_TYPE
#define HTTPD_STRING_ATTR PROGMEM
/* These will fail if the server strings are above 64K in program flash */
#define httpd_memcpy memcpy_P
#define httpd_strcpy strcpy_P
#define httpd_strcmp strcmp_P
#define httpd_strncmp strncmp_P
#define httpd_strlen strlen_P
#define httpd_snprintf snprintf_P
#elif HTTPD_STRING_TYPE==EEPROM_TYPE
#define HTTPD_STRING_ATTR EEPROM
#define httpd_memcpy memcpy_E
#define httpd_strcpy strcpy_E
#define httpd_strcmp strcmp_E
#define httpd_strncmp strncmp_E
#define httpd_strlen strlen_E
#define httpd_snprintf snprintf_E
#else
#define httpd_memcpy memcpy
#define httpd_strcpy strcpy
#define httpd_strcmp strcmp
#define httpd_strncmp strncmp
#define httpd_strlen strlen
#define httpd_snprintf snprintf
#endif
extern const char httpd_http[];
...
The .c file that allocates the variable would have
Code:
const char httpd_http[] HTTPD_STRING_ATTR = "HTTPfoo1.0 ";
static unsigned short
generate_status(void *sstr)
{
httpd_memcpy(uip_appdata, httpd_http, sizeof(httpd_http)-1);
and the other references would just need
Code:
int foo = httpd_strlen(httpd_http);
|
|
|
| |
|
|
|
|
|
Posted: Jan 27, 2013 - 08:52 PM |
|

Joined: Jan 27, 2013
Posts: 1
|
|
|
|
|
|
|
Posted: Jan 28, 2013 - 09:29 AM |
|


Joined: Jul 18, 2005
Posts: 62345
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
Quote:
Could anyone comment on the efficacy of this solution?
Surely it'd be better to extend the implementation of the prinln method maybe using the static const modifier as an indicator as to when PSTR() is used.
EDIT: Looked at Print.h in Arduino and first the method is "println" not "prinln" but also note this:
Code:
<snip>
size_t println(const __FlashStringHelper *);
size_t println(const String &s);
size_t println(const char[]);
<snip>
I don't know what "__FlashStringHelper" is but it sure looks like an attempt here to overload the println() method so it can handle flash strings anyway.
EDIT2: OK I do know what _FlashStringHelper is now:
Code:
WString.h:#define F(string_literal) (reinterpret_cast<__FlashStringHelper *>(PSTR(string_literal)))
So I would suggest that:
Code:
Serial.printfln(F("hello"));
probably already does what you are trying to achieve. |
_________________
|
| |
|
|
|
|
|