Guide to PROGMEM on ESP8266 and Arduino IDE

Intro

PROGMEM is a Arduino AVR feature that has been ported to ESP8266 toensure compatability with existing Arduino libraries, as well as, savingRAM. On the esp8266 declaring a string such asconstchar*xyz="thisisastring" will place this string in RAM, not flash. It ispossible to place a String into flash, and then load it into RAM whenit is needed. On an 8bit AVR this process is very simple. On the 32bitESP8266 there are conditions that must be met to read back from flash.

On the ESP8266 PROGMEM is a macro:

#define PROGMEM   ICACHE_RODATA_ATTR

ICACHE_RODATA_ATTR is defined by:

#define ICACHE_RODATA_ATTR  __attribute__((section(".irom.text")))

Which places the variable in the .irom.text section ie flash. Placing strings inflash requires using any of the methods above.

### Declare a global string to be stored in flash.
staticconstcharxyz[]PROGMEM="This is a string stored in flash";

Declare a flash string within code block.

For this you can use the PSTR macro. Which are all defined inpgmspace.h

#define PGM_P       const char *#define PGM_VOID_P  const void *#define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))

In practice:

voidmyfunction(void){PGM_Pxyz=PSTR("Store this string in flash");constchar*abc=PSTR("Also Store this string in flash");}

The two examples above will store these strings in flash. To retrieveand manipulate flash strings they must be read from flash in 4byte words.In the Arduino IDE for esp8266 there are several functions that can helpretrieve strings from flash that have been stored using PROGMEM. Both ofthe examples above returnconstchar*. However use of these pointers,without correct 32bit alignment you will cause a segmentation fault andthe ESP8266 will crash. You must read from the flash 32 bit aligned.

Functions to read back from PROGMEM

Which are all defined inpgmspace.h

intmemcmp_P(constvoid*buf1,PGM_VOID_Pbuf2P,size_tsize);void*memccpy_P(void*dest,PGM_VOID_Psrc,intc,size_tcount);void*memmem_P(constvoid*buf,size_tbufSize,PGM_VOID_PfindP,size_tfindPSize);void*memcpy_P(void*dest,PGM_VOID_Psrc,size_tcount);char*strncpy_P(char*dest,PGM_Psrc,size_tsize);char*strcpy_P(dest,src)char*strncat_P(char*dest,PGM_Psrc,size_tsize);char*strcat_P(dest,src)intstrncmp_P(constchar*str1,PGM_Pstr2P,size_tsize);intstrcmp_P(str1,str2P)intstrncasecmp_P(constchar*str1,PGM_Pstr2P,size_tsize);intstrcasecmp_P(str1,str2P)size_tstrnlen_P(PGM_Ps,size_tsize);size_tstrlen_P(strP)char*strstr_P(constchar*haystack,PGM_Pneedle);intprintf_P(PGM_PformatP,...);intsprintf_P(char*str,PGM_PformatP,...);intsnprintf_P(char*str,size_tstrSize,PGM_PformatP,...);intvsnprintf_P(char*str,size_tstrSize,PGM_PformatP,va_listap);

There are a lot of functions there but in reality they are_Pversions of standard c functions that are adapted to read from theesp8266 32bit aligned flash. All of them take aPGM_P which isessentially aconstchar*. Under the hood these functions all use, aprocess to ensure that 4 bytes are read, and the request byte is returned.

This works well when you have designed a function as above that isspecialised for dealing with PROGMEM pointers but there is no typechecking except againstconstchar*. This means that it is totallylegitimate, as far as the compiler is concerned, for you to pass it anyconstchar* string, which is obviously not true and will lead toundefined behaviour. This makes it impossible to create any overloadedfunctions that can use flash strings when they are defined asPGM_P.If you try you will get an ambiguous overload error asPGM_P ==constchar*.

Enter the __FlashStringHelper… This is a wrapper class that allows flashstrings to be used as a class, this means that type checking and functionoverloading can be used with flash strings. Most people will be familiar withtheF() macro and possibly the FPSTR() macro. These are defined inWString.h:

#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))#define F(string_literal) (FPSTR(PSTR(string_literal)))

SoFSPTR() takes a PROGMEM pointer to a string and casts it to this__FlashStringHelper class. Thus if you have defined a string asabovexyz you can useFPSTR() to convert it to__FlashStringHelper for passing into functions that take it.

staticconstcharxyz[]PROGMEM="This is a string stored in flash";Serial.println(FPSTR(xyz));

TheF() combines both of these methods to create an easy and quickway to store an inline string in flash, and return the type__FlashStringHelper. For example:

Serial.println(F("This is a string stored in flash"));

Although these two functions provide a similar function, they servedifferent roles.FPSTR() allows you to define a global flash stringand then use it in any function that takes__FlashStringHelper.F() allows you to define these flash strings in place, but you can’tuse them anywhere else. The consequence of this is sharing commonstrings is possible usingFPSTR() but notF().__FlashStringHelper is what the String class uses to overload itsconstructor:

String(constchar*cstr="");//constructorfromconstchar*String(constString&str);//copyconstructorString(const__FlashStringHelper*str);//constructorforflashstrings

This allows you to write:

Stringmystring(F("This string is stored in flash"));

How do I write a function to use __FlashStringHelper? Simples: cast the pointer back to a PGM_P and use the_P functions shown above. This an example implementation for String for the concat function.

unsigned char String::concat(const __FlashStringHelper * str) {    if (!str) return 0; // return if the pointer is void    int length = strlen_P((PGM_P)str); // cast it to PGM_P, which is basically const char *, and measure it using the _P version of strlen.    if (length == 0) return 1;    unsigned int newlen = len + length;    if (!reserve(newlen)) return 0; // create a buffer of the correct length    strcpy_P(buffer + len, (PGM_P)str); //copy the string in using strcpy_P    len = newlen;    return 1;}

How do I declare a global flash string and use it?

staticconstcharxyz[]PROGMEM="This is a string stored in flash. Len =%u";voidsetup(){Serial.begin(115200);Serial.println();Serial.println(FPSTR(xyz));//justprintsthestring,mustconvertittoFlashStringHelperfirstusingFPSTR().Serial.printf_P(xyz,strlen_P(xyz));//useprintfwithPROGMEMstring}

How do I use inline flash strings?

voidsetup(){Serial.begin(115200);Serial.println();Serial.println(F("This is an inline string"));//Serial.printf_P(PSTR("This is an inline string using printf%s"),"hello");}

How do I declare and use data in PROGMEM?

constsize_tlen_xyz=30;constuint8_txyz[]PROGMEM={0x53,0x61,0x79,0x20,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x74,0x6f,0x20,0x4d,0x79,0x20,0x4c,0x69,0x74,0x74,0x6c,0x65,0x20,0x46,0x72,0x69,0x65,0x6e,0x64,0x00};voidsetup(){Serial.begin(115200);Serial.println();uint8_t*buf=newuint8_t[len_xyz];if(buf){memcpy_P(buf,xyz,len_xyz);Serial.write(buf,len_xyz);//outputthebuffer.}}

How do I declare some data in PROGMEM, and retrieve one byte from it.

Declare the data as done previously, then usepgm_read_byte to getthe value back.

constsize_tlen_xyz=30;constuint8_txyz[]PROGMEM={0x53,0x61,0x79,0x20,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x74,0x6f,0x20,0x4d,0x79,0x20,0x4c,0x69,0x74,0x74,0x6c,0x65,0x20,0x46,0x72,0x69,0x65,0x6e,0x64,0x00};voidsetup(){Serial.begin(115200);Serial.println();for(inti=0;i<len_xyz;i++){uint8_tbyteval=pgm_read_byte(xyz+i);Serial.write(byteval);//outputthebuffer.}}

In summary

It is easy to store strings in flash usingPROGMEM andPSTR butyou have to create functions that specifically use the pointers theygenerate as they are basicallyconstchar*. On the other handFPSTR andF() give you a class that you can do implicitconversions from, very useful when overloading functions, and doingimplicit type conversions. It is worth adding that if you wish to storeanint,float or pointer these can be stored and read backdirectly as they are 4 bytes in size and therefor will be alwaysaligned!

Hope this helps.