|
| 1 | +Guide to PROGMEM on ESP8266 and Arduino IDE |
| 2 | +=========================================== |
| 3 | + |
| 4 | +Intro |
| 5 | +----- |
| 6 | + |
| 7 | +PROGMEM is a Arduino AVR feature that has been ported to ESP8266 to |
| 8 | +ensure compatability with existing Arduino libraries, as well as, saving |
| 9 | +RAM. On the esp8266 declaring a string such as ``const char * xyz = |
| 10 | +"this is a string"`` will place this string in RAM, not flash. It is |
| 11 | +possible to place a String into flash, and then load it into RAM when |
| 12 | +it is needed. On an 8bit AVR this process is very simple. On the 32bit |
| 13 | +ESP8266 there are conditions that must be met to read back from flash. |
| 14 | + |
| 15 | +On the ESP8266 PROGMEM is a macro: |
| 16 | + |
| 17 | +.. code:: cpp |
| 18 | +
|
| 19 | + #define PROGMEM ICACHE_RODATA_ATTR |
| 20 | +
|
| 21 | +``ICACHE_RODATA_ATTR`` is defined by: |
| 22 | + |
| 23 | +.. code:: cpp |
| 24 | +
|
| 25 | + #define ICACHE_RODATA_ATTR __attribute__((section(".irom.text"))) |
| 26 | +
|
| 27 | +Which places the variable in the .irom.text section ie flash. Placing strings in |
| 28 | +flash requires using any of the methods above. |
| 29 | + |
| 30 | +| ### Declare a global string to be stored in flash. |
| 31 | +
|
| 32 | +.. code:: cpp |
| 33 | +
|
| 34 | + static const char xyz[] PROGMEM = "This is a string stored in flash"; |
| 35 | +
|
| 36 | +Declare a flash string within code block. |
| 37 | +----------------------------------------- |
| 38 | + |
| 39 | +For this you can use the PSTR macro. Which are all defined in |
| 40 | +`pgmspace.h <https://github.com/esp8266/Arduino/blob/master/cores/esp8266/pgmspace.h>`__ |
| 41 | + |
| 42 | +.. code:: cpp |
| 43 | +
|
| 44 | + #define PGM_P const char * |
| 45 | + #define PGM_VOID_P const void * |
| 46 | + #define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];})) |
| 47 | +
|
| 48 | +In practice: |
| 49 | + |
| 50 | +.. code:: cpp |
| 51 | +
|
| 52 | + void myfunction(void) { |
| 53 | + PGM_P xyz = PSTR("Store this string in flash"); |
| 54 | + const char * abc = PSTR("Also Store this string in flash"); |
| 55 | + } |
| 56 | +
|
| 57 | +The two examples above will store these strings in flash. To retrieve |
| 58 | +and manipulate flash strings they must be read from flash in 4byte words. |
| 59 | +In the Arduino IDE for esp8266 there are several functions that can help |
| 60 | +retrieve strings from flash that have been stored using PROGMEM. Both of |
| 61 | +the examples above return ``const char *``. However use of these pointers, |
| 62 | +without correct 32bit alignment you will cause a segmentation fault and |
| 63 | +the ESP8266 will crash. You must read from the flash 32 bit aligned. |
| 64 | + |
| 65 | +Functions to read back from PROGMEM |
| 66 | +----------------------------------- |
| 67 | + |
| 68 | +Which are all defined in |
| 69 | +`pgmspace.h <https://github.com/esp8266/Arduino/blob/master/cores/esp8266/pgmspace.h>`__ |
| 70 | + |
| 71 | +.. code:: cpp |
| 72 | +
|
| 73 | + int memcmp_P(const void* buf1, PGM_VOID_P buf2P, size_t size); |
| 74 | + void* memccpy_P(void* dest, PGM_VOID_P src, int c, size_t count); |
| 75 | + void* memmem_P(const void* buf, size_t bufSize, PGM_VOID_P findP, size_t findPSize); |
| 76 | + void* memcpy_P(void* dest, PGM_VOID_P src, size_t count); |
| 77 | + char* strncpy_P(char* dest, PGM_P src, size_t size); |
| 78 | + char* strcpy_P(dest, src) |
| 79 | + char* strncat_P(char* dest, PGM_P src, size_t size); |
| 80 | + char* strcat_P(dest, src) |
| 81 | + int strncmp_P(const char* str1, PGM_P str2P, size_t size); |
| 82 | + int strcmp_P(str1, str2P) |
| 83 | + int strncasecmp_P(const char* str1, PGM_P str2P, size_t size); |
| 84 | + int strcasecmp_P(str1, str2P) |
| 85 | + size_t strnlen_P(PGM_P s, size_t size); |
| 86 | + size_t strlen_P(strP) |
| 87 | + char* strstr_P(const char* haystack, PGM_P needle); |
| 88 | + int printf_P(PGM_P formatP, ...); |
| 89 | + int sprintf_P(char *str, PGM_P formatP, ...); |
| 90 | + int snprintf_P(char *str, size_t strSize, PGM_P formatP, ...); |
| 91 | + int vsnprintf_P(char *str, size_t strSize, PGM_P formatP, va_list ap); |
| 92 | +
|
| 93 | +There are a lot of functions there but in reality they are ``_P`` |
| 94 | +versions of standard c functions that are adapted to read from the |
| 95 | +esp8266 32bit aligned flash. All of them take a ``PGM_P`` which is |
| 96 | +essentially a ``const char *``. Under the hood these functions all use, a |
| 97 | +process to ensure that 4 bytes are read, and the request byte is returned. |
| 98 | + |
| 99 | +This works well when you have designed a function as above that is |
| 100 | +specialised for dealing with PROGMEM pointers but there is no type |
| 101 | +checking except against ``const char *``. This means that it is totally |
| 102 | +legitimate, as far as the compiler is concerned, for you to pass it any |
| 103 | +``const char *`` string, which is obviously not true and will lead to |
| 104 | +undefined behaviour. This makes it impossible to create any overloaded |
| 105 | +functions that can use flash strings when they are defined as ``PGM_P``. |
| 106 | +If you try you will get an ambiguous overload error as ``PGM_P`` == |
| 107 | +``const char *``. |
| 108 | + |
| 109 | +Enter the \_\_FlashStringHelper... This is a wrapper class that allows flash |
| 110 | +strings to be used as a class, this means that type checking and function |
| 111 | +overloading can be used with flash strings. Most people will be familiar with |
| 112 | +the ``F()`` macro and possibly the FPSTR() macro. These are defined in `WString.h <https://github.com/esp8266/Arduino/blob/master/cores/esp8266/WString.h#L37>`__: |
| 113 | + |
| 114 | +.. code:: cpp |
| 115 | +
|
| 116 | + #define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer)) |
| 117 | + #define F(string_literal) (FPSTR(PSTR(string_literal))) |
| 118 | +
|
| 119 | +So ``FSPTR()`` takes a PROGMEM pointer to a string and casts it to this |
| 120 | +``__FlashStringHelper`` class. Thus if you have defined a string as |
| 121 | +above ``xyz`` you can use ``FPSTR()`` to convert it to |
| 122 | +``__FlashStringHelper`` for passing into functions that take it. |
| 123 | + |
| 124 | +.. code:: cpp |
| 125 | +
|
| 126 | + static const char xyz[] PROGMEM = "This is a string stored in flash"; |
| 127 | + Serial.println(FPSTR(xyz)); |
| 128 | +
|
| 129 | +The ``F()`` combines both of these methods to create an easy and quick |
| 130 | +way to store an inline string in flash, and return the type |
| 131 | +``__FlashStringHelper``. For example: |
| 132 | + |
| 133 | +.. code:: cpp |
| 134 | +
|
| 135 | + Serial.println(F("This is a string stored in flash")); |
| 136 | +
|
| 137 | +Although these two functions provide a similar function, they serve |
| 138 | +different roles. ``FPSTR()`` allows you to define a global flash string |
| 139 | +and then use it in any function that takes ``__FlashStringHelper``. |
| 140 | +``F()`` allows you to define these flash strings in place, but you can't |
| 141 | +use them anywhere else. The consequence of this is sharing common |
| 142 | +strings is possible using ``FPSTR()`` but not ``F()``. |
| 143 | +``__FlashStringHelper`` is what the String class uses to overload its |
| 144 | +constructor: |
| 145 | + |
| 146 | +.. code:: cpp |
| 147 | +
|
| 148 | + String(const char *cstr = ""); // constructor from const char * |
| 149 | + String(const String &str); // copy constructor |
| 150 | + String(const __FlashStringHelper *str); // constructor for flash strings |
| 151 | +
|
| 152 | +This allows you to write: |
| 153 | + |
| 154 | +.. code:: cpp |
| 155 | +
|
| 156 | + String mystring(F("This string is stored in flash")); |
| 157 | +
|
| 158 | +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. |
| 159 | + |
| 160 | +.. code:: cpp |
| 161 | +
|
| 162 | + unsigned char String::concat(const __FlashStringHelper * str) { |
| 163 | + if (!str) return 0; // return if the pointer is void |
| 164 | + 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. |
| 165 | + if (length == 0) return 1; |
| 166 | + unsigned int newlen = len + length; |
| 167 | + if (!reserve(newlen)) return 0; // create a buffer of the correct length |
| 168 | + strcpy_P(buffer + len, (PGM_P)str); //copy the string in using strcpy_P |
| 169 | + len = newlen; |
| 170 | + return 1; |
| 171 | + } |
| 172 | +
|
| 173 | +How do I declare a global flash string and use it? |
| 174 | +-------------------------------------------------- |
| 175 | + |
| 176 | +.. code:: cpp |
| 177 | +
|
| 178 | + static const char xyz[] PROGMEM = "This is a string stored in flash. Len = %u"; |
| 179 | +
|
| 180 | + void setup() { |
| 181 | + Serial.begin(115200); Serial.println(); |
| 182 | + Serial.println( FPSTR(xyz) ); // just prints the string, must convert it to FlashStringHelper first using FPSTR(). |
| 183 | + Serial.printf_P( xyz, strlen_P(xyz)); // use printf with PROGMEM string |
| 184 | + } |
| 185 | +
|
| 186 | +How do I use inline flash strings? |
| 187 | +---------------------------------- |
| 188 | + |
| 189 | +.. code:: cpp |
| 190 | +
|
| 191 | + void setup() { |
| 192 | + Serial.begin(115200); Serial.println(); |
| 193 | + Serial.println( F("This is an inline string")); // |
| 194 | + Serial.printf_P( PSTR("This is an inline string using printf %s"), "hello"); |
| 195 | + } |
| 196 | +
|
| 197 | +How do I declare and use data in PROGMEM? |
| 198 | +----------------------------------------- |
| 199 | + |
| 200 | +.. code:: cpp |
| 201 | +
|
| 202 | + const size_t len_xyz = 30; |
| 203 | + const uint8_t xyz[] PROGMEM = { |
| 204 | + 0x53, 0x61, 0x79, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, |
| 205 | + 0x74, 0x6f, 0x20, 0x4d, 0x79, 0x20, 0x4c, 0x69, 0x74, 0x74, |
| 206 | + 0x6c, 0x65, 0x20, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x00}; |
| 207 | +
|
| 208 | + void setup() { |
| 209 | + Serial.begin(115200); Serial.println(); |
| 210 | + uint8_t * buf = new uint8_t[len_xyz]; |
| 211 | + if (buf) { |
| 212 | + memcpy_P(buf, xyz, len_xyz); |
| 213 | + Serial.write(buf, len_xyz); // output the buffer. |
| 214 | + } |
| 215 | + } |
| 216 | +
|
| 217 | +How do I declare some data in PROGMEM, and retrieve one byte from it. |
| 218 | +--------------------------------------------------------------------- |
| 219 | + |
| 220 | +Declare the data as done previously, then use ``pgm_read_byte`` to get |
| 221 | +the value back. |
| 222 | + |
| 223 | +.. code:: cpp |
| 224 | +
|
| 225 | + const size_t len_xyz = 30; |
| 226 | + const uint8_t xyz[] PROGMEM = { |
| 227 | + 0x53, 0x61, 0x79, 0x20, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, |
| 228 | + 0x74, 0x6f, 0x20, 0x4d, 0x79, 0x20, 0x4c, 0x69, 0x74, 0x74, |
| 229 | + 0x6c, 0x65, 0x20, 0x46, 0x72, 0x69, 0x65, 0x6e, 0x64, 0x00 |
| 230 | + }; |
| 231 | +
|
| 232 | + void setup() { |
| 233 | + Serial.begin(115200); Serial.println(); |
| 234 | + for (int i = 0; i < len_xyz; i++) { |
| 235 | + uint8_t byteval = pgm_read_byte(xyz + i); |
| 236 | + Serial.write(byteval); // output the buffer. |
| 237 | + } |
| 238 | + } |
| 239 | +
|
| 240 | +In summary |
| 241 | +---------- |
| 242 | + |
| 243 | +It is easy to store strings in flash using ``PROGMEM`` and ``PSTR`` but |
| 244 | +you have to create functions that specifically use the pointers they |
| 245 | +generate as they are basically ``const char *``. On the other hand |
| 246 | +``FPSTR`` and ``F()`` give you a class that you can do implicit |
| 247 | +conversions from, very useful when overloading functions, and doing |
| 248 | +implicit type conversions. It is worth adding that if you wish to store |
| 249 | +an ``int``, ``float`` or pointer these can be stored and read back |
| 250 | +directly as they are 4 bytes in size and therefor will be always |
| 251 | +aligned! |
| 252 | + |
| 253 | +Hope this helps. |
0 commit comments