3.1 Const-in-program-memory Feature

For some devices, the MPLAB XC8 compiler can employ a const-in-progmem feature that automatically places any const-qualified objects with static storage duration into program memory rather than into RAM. When enabled, this feature affects where such objects are placed, how they are accessed, and the size and format of pointers that might indirectly access them.

Important: For some devices, this feature is enabled by default if no controlling option is specified, and it can adversely affect program operation.

Description

The 8-bit AVR devices fall into three groups with regard to how program Flash memory is mapped, those being:
  1. Devices that map their entire Flash memory into the data space (such as those in the tinyAVR® or AVR XMEGA® 3 families),
  2. Devices with configurable Flash mapping, which can map part of their Flash memory into the data space (such as those in the AVRDA family), and
  3. Devices that do not map any part of Flash memory into the data space.
Check the data sheet for your device to see if and how Flash memory is mapped into the data space. If you are not sure, the preprocessor macro __AVR_PROGMEM_IN_DATA_ADDRESS_SPACE__ is defined by the compiler for devices in group (1) or (2), provided the -mno-const-data-in-progmem has not been used. The macro __AVR_CONST_DATA_IN_CONFIG_MAPPED_PROGMEM__ is defined for devices in group (2), provided the -mno-const-data-in-config-mapped-progmem option has not been used. To see macros defined by the compiler, use the -E and -dM options with your project and check the definitions printed in the standard output stream.

Group (1) Devices

The const-in-progmem feature is not required and not used with any device from group (1), which map their program memory into the data space. Programs built for such devices have const-qualified objects with static storage duration placed into the mapped Flash region where they can be read from data memory. For these devices, this compiler feature is not necessary, and the placement and access of const-qualified objects is similar in both compilers.

Group (2) Devices

When building for devices in group (2), which map a block of program memory into the data space, the const-in-config-mapped-progmem feature can be used to control where const-qualified objects are placed and accessed. This feature is enabled by the -mconst-data-in-config-mapped-progmem option, or by default if no option is specified. When enabled, devices in group (2) are treated similarly to devices in group (1). All const-qualified objects with static storage duration are placed into a Flash region that is mapped by the runtime startup code, and which can be read from data memory. The size of this mapped Flash region, however, is limited to a single 32 KB block that is chosen by the linker.

This behavior can be disabled using the -mno-const-data-in-config-mapped-progmem option, so that const-qualified objects are stored in and read from non-mapped program memory, and the device is then treated similarly to those in group (3). And as is the case for other devices in group (3), the const-in-progmem feature can be disabled entirely using the -mno-const-data-in-progmem option, forcing the compiler to copy const-qualified data to RAM, where it can be read. In this case, the behavior is compatible with AVR GCC.

Group (3) Devices

When building for devices in group (3), which do not map program memory in the data space, the const-in-progmem feature can be used to control where const-qualified objects are placed and accessed. This feature is enabled by the -mconst-data-in-progmem option, or by default if no option is specified. It can be disabled by using the -mno-const-data-in-progmem option.

When the feature is disabled, the operation of MPLAB XC8 is largely compatible with that of AVR-GCC. Initial values for const-qualified objects are placed in program memory but are copied to a data memory location by the runtime startup code where they are accessed at runtime. The same is true for string literals. Additionally, data pointers not using any non-standard specifiers will indirectly access RAM objects, with any use of the const qualifier indicating only that the target is read-only. To be able to read program memory indirectly, the pointer must use a non-standard specifier, such as __memx or __flashn. For example:
const char * cp1;         // I can read objects in RAM
char * cp2;               // I can read and write objects in RAM
const __memx char * cp3;  // I can read objects in RAM or program memory
One consequence of this is that any function that takes a pointer argument (either const or non-const qualified target), for example, can only be passed the address of objects in the data memory space. This is particularly relevant for functions in the standard C library. An alternate function must be supplied and called if the action has to be performed using the address of an object in program memory.

When the const-in-progmem feature is enabled (the default action if no option has been specified), const-qualified objects are instead placed in and read from program memory. String literals are handled similarly. Such objects are read using alternate instruction sequences that are typically longer than the equivalent sequences that read from data memory, but less data memory is used by the program.

In addition, pointers to const-qualified types can indirectly read objects in either data or program memory, and a bit encoded into addresses assigned to such pointers determines which space is to be accessed. Essentially, these pointers act like their type had been specified with __memx in addition to the const qualifier. Code to dereference the pointer is larger, but because they can access either memory space, no duplication of pointers or functions is required to handle objects in each space.

Migration

When migrating code to MPLAB XC8, there are two alternatives for code running on devices considered as being in group (3) (including devices from group (2) that have disabled the mapping feature):
  • Disable the const-in-progmem feature and ensure legacy library routines that read from program memory are linked in with the project code.
  • Leave the const-in-progmem feature enabled and ensure that the project source code is not making incorrect assumptions about the location of const-qualified objects.
These two migration strategies are discussed below.

Disable the const-in-progmem Feature

To follow the first migration approach, disable this feature by deselecting the "Enables access of const variables directly from flash" checkbox in the “XC8 Global option” category in the MPLAB X IDE Project Properties dialog. Disabling this feature will ensure that const-qualified objects with static storage duration are copied to and accessed from data memory. Any pointer to a const-qualified type will read only from data memory.

AVR-GCC provides program-memory alternatives to some standard C library routines that take pointer arguments. These alternatives use a _P suffix and are written so that they will read from program memory, for example strlen_P(), specified in <avr/pgmspace.h>, which can be called to find the length of a string in program memory. These functions are not supplied with MPLAB XC8; however, you can download the AVR-libc to obtain the source files for these functions.

This Online Microchip Help web page documents the AVR-libc. It also contains a link to the AVR Libc web page where you can find a link to the download area. Download the required AVR-libc archive and unzip it to a local directory. You can include source files into your projects directly from this local directory or copy the required source files into your project directory and then add them to your project from that location. Note that some functions are written in assembly code.

Your program will need declarations from the <avr/pgmspace.h> header, which can be found in the downloaded library. Note that this header itself also includes other header files. You can provide the MPLAB XC8 preprocessor with the path to this header in the unzipped local directory, or alternatively the path to copies of this header in your project directory. The -I compiler option allows the path to be specified, and this can be specified in the “Include directories” field, found in the “Preprocessing and messages” options within the “XC8 Compiler” category of the Project Properties dialog. AVR-libc headers should be scanned first before the standard headers; however, this will be the case if you add the search path as described above.

On rare occasions, there might be conflicting information in code or headers provided by AVR-libc and those provided by the libraries shipped with MPLAB XC8. If the compiler sees a declaration from an AVR-libc header but links in the corresponding definition from the compiler-shipped library (or vice versa), the project might fail to build or execute correctly if there is a mismatch in that information. Where there is such a conflict, you could selectively copy those declarations required from AVR-libc headers and create your own header file from these.

For example, a programmer has decided to disable the const-in-progmem feature when migrating an AVR-GCC project. The "Enables access of const variables directly from flash" checkbox was unchecked in the Project Properties dialog (as shown in the figure below).
Figure 3-1. A project setup to build the program memory version of the string compare function (strcmp_P()) source file and disable the const-in-progmem feature (highlighted)
The original program performed a string compare between a RAM and program memory pointer, as shown below. As the strcmp_P() function call cannot be simply swapped to a call to strcmp() in the default standard C library, the source file (strcmp_P.S) for the strcmp_P function was copied from the ARV-libc archive into the project directory and added to the project's Source files folder in the MPLAB X IDE (as shown in the above figure). The source code contained in this file includes the header <macros.inc> (which itself includes <sectionname.h>). These headers were also copied to a subdirectory in the project directory and the path to that directory specified in the “Include directories” field of the Project Properties dialog. The prototype for the strcmp_P() function can be found in the <avr/pgmspace.h> header. This header was also copied to the subdirectory.
/* const-in-progmem Feature Disabled */

#include <string.h>
#include <pgmspace.h>

const char ro_ram_1[] = "another";           // I am in RAM
const char ro_ram_2[] = "string";            // So am I
const PROGMEM char ro_prog_1[] = "another";  // I am in program memory

volatile int diffs;

int main(void) {
    if (strcmp(ro_ram_1, ro_ram_2))          // compare RAM to RAM
        diffs++;
    if (strcmp_P(ro_ram_1, ro_prog_1))       // compare RAM to prog memory
        diffs++;
    while (1) {
    }
}

Leave the const-in-progmem Feature Enabled

To follow the second migration approach, ensure the const-in-progmem feature is enabled by selecting the "Enables access of const variables directly from flash" checkbox in the “XC8 Global option” category in the MPLAB X IDE Project Properties dialog. With this feature enabled, objects defined using the const qualifier and that have static storage duration will be placed in program memory and any pointer with a const-qualified target will be able to read from either data or program memory space.

Migrated projects using this feature typically fail when pointers to non-const objects are assigned addresses of objects in program memory. Code or linker options that expect any const-qualified object to be present in RAM will fail. Code that assumes a pointer has a certain address might fail. Direct access of const objects will work as expected.

The code shown below will not compile when this feature is enabled, due to a mismatching in types between the prototype for readChar() and the arguments of the last two calls.
char readChar(char * cp) {
  return * cp;
}

char foobar_in_ram = 0x33;
const char foobar_as_const[4] = { 3, 2, 4, 6 };

int main(void) {
  volatile char result;
    
  result = readChar(&foobar_in_ram);
  result = readChar(&foobar_as_const[2]);  // this won't work
  result = readChar("hi there");           // neither will this
}
The temptation is to cast the address of these calls so they match the prototype of the function, as follows:
  result = readChar((char *)&foobar_as_const[2]);     // No!
  result = readChar((char *)"hi there");              // No!
Such a change will indeed suppress the errors; however the code will fail when executing. If a pointer needs to be able to access program memory, ensure it is a pointer to a const-qualified type, that is, the prototype for readChar() needs to be char readChar(const char * cp).
The use of separate routines to access different memory spaces is no longer required. Always use the standard C string functions when the const-in-progmem feature is enabled. For example, you do not need to choose between calling strcmp() or strcmp_P() to compare strings; the standard strcmp() function provided in the standard library should be used to compare any string, regardless of where it is located.
/* const-in-progmem Feature Enabled */

#include <string.h>

char ro_ram_1[] = "another";                 // I am in RAM
char ro_ram_2[] = "string";                  // So am I
const PROGMEM char ro_prog_1[] = "another";  // I am in program memory

volatile int diffs;

int main(void) {
    if (strcmp(ro_ram_1, ro_ram_2))          // compare RAM to RAM
        diffs++;
    if (strcmp(ro_ram_1, ro_prog_1))         // compare RAM to prog memory
        diffs++;
    while (1) {
    }
}
The use of the non-standard (upper-case) %S printf-family conversion specifier provided by AVR-libc is also not required when the const-in-progmem feature is enabled. Use the standard %s specifier to print any string, for example:
    printf("These are the RAM characters: %s\n", ro_ram_1);
    printf("These are the ROM characters: %s\n", ro_prog_1);