String usage in IAR C/C++ Compiler for AVR
Technical Note 86174
Architectures:
AVR
Component:
compiler
Updated:
1/18/2016 11:59 AM
Introduction
This article goes into the aspects of the location (in the different memory areas) of strings.
Possible locations, and aspects thereof
When using the IAR C/C++ Compiler for AVR there are three (four) possible locations for storing strings:
- In an external ROM in the data space.
- In internal RAM.
- In flash.
In addition to this, there is also at least one hybrid that can be considered: - Copy strings on demand from flash to RAM.
Drawbacks of placement in flash memory (CODE space)
While the flash might sound as the most logical location for strings, it does not work very well with the Harvard Architecture of the AVR. A default pointer (pointer without any extension keyword) must be able to point to any object defined without an extension keyword. A string is a good example of such object. If we were to allow a char pointer (without an extension keyword) to point to flash, it would also imply that the RAM memory (and stack) would have to be in the flash memory space, which of course is impossible with the AVR since RAM only exists in data space.
A default pointer could be made to point to both flash and RAM, but this would require tagging the pointer so that it can be determined at runtime whether the pointer is actually is pointing to flash or data space. This would cost a lot both in code size and runtime overhead.
Placement in the data space
For an ISO/ANSI C program, this means that we are left with alternative 1 or alternative 2, that are memories in the data space. For many applications, you will not have an external ROM. The internal RAM always exists, but can be a scarce resource so it can be hard to put strings there.
Considerations
So with this background, where do we end up now? Is it not possible to use strings at all on the AVR? Of course it is, it just requires that we are a bit careful and consider the possibilities.
If you want to have an ISO/ANSI C compliant program, flash is not possible, you will either have to provide an external ROM, or use up RAM for your strings. If you are using RAM, you might need to keep strings short and few to be able to fit them all.
If you can relax ISO/ANSI C compliance, you can put strings in flash by defining flash char array variables. For strings that are to be passed to normal C library functions, they have to be in data space. To use flashed strings, you need to use alternative library routines that expects flash strings. We provide a few such alternative functions in the pgmspace.h header file. They are flash alternatives for some common C library functions with an extension _P. There are also __generic counterparts with the _G extension in the pgmspace.h header file. For your own code, you can always use the __flash keyword when passing the strings around between functions.
How to put ...
...strings in external ROM
This is the default if your derivative has an external bus and you are using the compiler from the command line. If you are using the IDE, be sure to untick the tick box "Place string literals and constants in initialized RAM" that can be found under the Code tab in Project>Options>C/C++ Compiler. The goal is to make strings and constants end up in a constant segment, such as NEAR_C, FAR_C or HUGE_C.
...strings in internal RAM
This is the default in the GUI and on all derivatives that does not have an external bus on the command line. To enable it manually from the command line, use command line option -y.
As a side note, this will also make the string writable, which can be considered a little bit of bad taste since doing this would sort of modify your program at runtime. Also, it would be a problem if the compiler allows string literals with the same text to share memory. Sometimes you can have a good reason for doing this though, then you need your string in RAM.
...strings into flash
The idea here is to override each string in the code so that it ends up in flash.
__flash char str1[] = "abcdef";
__flash char str2[] = "ghi";
__flash char __flash * pVar[] = { str1, str2 };
String literals cannot be automatically be put into flash in versions prior to 2.28A, but you can use a local static variable instead:
#include <pgmspace.h>
void f (int i)
{
static __flash char sl[] = "%d cookies\n";
printf_P(sl, i);
}
This does not result in any more code compared to if we allowed string literals in flash, it is just a little more typing.
Even if you are using 2.28A or later, you may prefer the above way of doing it, as what is going on is more explicit.
Additional information
String literals in flash (2.28A and later)
Use the command line keyword --string_literals_in_flash (introduced in 2.28A). It will direct string literals to a flash segment, either NEAR_F or FAR_F.
This also means that the type of the string literal object changes to reflect that the string reside in program space. As the type changes the following valid ISO/ANSI C program will no longer work:
#include <stdio.h>
void emit_time (int hour, int minute)
{
printf("%d:%d",
hour,
minute); // error -- type conflict
}
Adding a cast will shut the compiler up, but it will not work at runtime as the string is in program space, while printf() still thinks it gets a pointer to a string in data space:
#include <stdio.h>
void emit_time (int hour, int minute)
{
printf((char*) "%d:%d",
hour,
minute); // fails at runtime
}
As before, you have to use printf_P() to get it to work:
#include <pgmspace.h>
void emit_time (int hour, int minute)
{
printf_P("%d:%d",
hour,
minute);
}
With --string_literals_in_flash you can now create an array of strings in a single definition and have everything end up in flash. Here is how to do it on a -v1 chip (like 8515):
__flash char __flash *messages[5]=
{
"Message 1",
"Message 2",
"Message 3",
"Message 4"
};
If you are using a -v3 (like mega128) the string literals are placed in __farflash instead of __flash so you have to write:
__flash char __farflash *messages[5]=
{
"Message 1",
"Message 2",
"Message 3",
"Message 4"
};
Example of copying strings from flash to RAM
An alternative to using the flash strings directly from flash is to copy them to RAM as needed. A suitable sized RAM buffer is needed and a simple copy function can be employed. The following example code shows the idea:
__x_z char* string_access (char** handle, char __flash * p)
{
char* result = *handle;
char* d = result;
while (*d++ = *p++)
;
*handle = d;
return result;
}
To use it, you need to allocate a suitable sized buffer on the stack and provide a handle to each string_access() call:
void use_it (__flash char __flash * array[4])
{
char buffer[32];
char* bufptr = buffer;
char** handle = &bufptr;
static __flash char formatter[] = "%s:%s-%s:%s";
printf(string_access(handle, formatter),
string_access(handle, array[0]),
string_access(handle, array[1]),
string_access(handle, array[2]),
string_access(handle, array[3]));
}
This is fully reentrant and the buffer is deallocated when leaving the function.
All product names are trademarks or registered trademarks of their respective owners