Comprehensive Example Code Demonstrating using Arduino Flash Memory via PROGMEM
[Note: This page is only applicable to older arduino versions. For version 1.5 onwards see this page instead].
This post is a huge set of PROGMEM examples (done as unit tests) which you can freely copy and paste into your work.
The readership level is set at those who are already familiar with the arduino PROGMEM documentation here and the use of the F() macro and __FlashStringHelper*. Its designed as a reference on how to accomplish many different common tasks in PROGMEM , from standard string functions, to structures and arrays. The idea is to save you the “lets see how to make this work” time and communicate common pitfalls.
The code is well documented, so you should be able to find what you need quickly. There is data declarations out front, sets of unit tests in setup() and a simple unit testing framework at the end of the file.
NB: some of the examples have //JBYCDMUS after the line.
This stands for “just because you can, doesn’t mean you should” implying It works but is not recommended.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | //----------------------------------------------------------------------------------// // BUSYDUCKS.COM // // _ _ _ // // ( o)> ( o)< __ ( o)< // // ____\\ ____\\ __ (.. ) \\\_\\ // // ~~~~\_///__)~~\_///__)~~\_\\V__)~~\_____)~~~~~ // // // // Making you pro-duck-tive // // // // Author: Duckman Date: 20/3/13 Ver: 1.0 Licence: Creative Commons (by-sa) // // // // Demonstrates the use of PROGMEM. // // Compile with Arduino environment 1.02 or later // // // // Permision given to freely copy/paste "code snippets" into your own code. For // // other uses (e.g. derivative works) the Creative Commons Attribution Share- // // alike license applies (cite busyducks.com). This means comerical use is ok. // //----------------------------------------------------------------------------------// #include <environment.h> /* char, (1 byte) -127 to 128 also letters i.e. 'a' pgm_read_byte unsigned char, byte (1 byte) 0 to 255 pgm_read_byte int, short (2 bytes) -32,767 to 32,768 pgm_read_word unsigned int, unsigned short (2 bytes) 0 to 65,535 pgm_read_word long (4 bytes) -2,147,483,648 to 2,147,483,647 pgm_read_dword unsigned long (4 bytes) 0 to 4,294,967,295 pgm_read_dword For ATMEGA based arduino's double is the same as float float, double (4 bytes) -3.4028235E+38 to 3.4028235E+38 pgm_read_float */ //-------------------------------------------------------------------------------------------------------------- // Data structures for test //-------------------------------------------------------------------------------------------------------------- struct Person { int height; char *name; int age; Person *spouse; }; //-------------------------------------------------------------------------------------------------------------- // Test Data Sets //-------------------------------------------------------------------------------------------------------------- #define BYTE_TEST_PATTERN_DATA {0, 1, 2, 3, 4, 9, 15, 16, 21, 31, 32, 64, 100, 201, 212, 255, 7} #define BYTE_TEST_PATTERN_SIZE 17 #define MAX_STRING_SIZE 100 //-------------------------------------------------------------------------------------------------------------- // Test Data //-------------------------------------------------------------------------------------------------------------- //regular data const byte progMem_ucharArray[] PROGMEM = BYTE_TEST_PATTERN_DATA; const byte progMem_uchar PROGMEM = 130; const int progMem_int PROGMEM = -3021; const long progMem_long PROGMEM = 2000000001; const float progMem_float PROGMEM = 3.14159265359; const char progMem_string[] PROGMEM = "Snollygoster"; const char progMem_string2[] PROGMEM = "Problem Alchemist"; const char progMem_string3[] PROGMEM = "Lead Paradigm Architect"; const char progMem_emptyString[] PROGMEM = ""; const float progMem_floatArray[2][5] PROGMEM = {{0.2, 0.1, 0.5, 0.7, -3.2}, {0.4, 0.2, 1.0, 1.4, -6.4}}; PROGMEM const char *progMem_stringArray[] = { progMem_string, progMem_string2, progMem_string3 }; //structs //this line is a foward decleration, it allows person1 to create a valid pointer //to person2 even though person2 is not defined yet. extern const Person progMem_person2; const char _name[] PROGMEM = "Papa Smurf"; const Person progMem_person PROGMEM = {189, (char *)_name, 32, (Person*)&progMem_person2}; const char _name2[] PROGMEM = "Muma Smurf"; const Person progMem_person2 PROGMEM = {160, (char *)_name2, 41, (Person*)&progMem_person}; //-------------------------------------------------------------------------------------------------------------- // Stats //-------------------------------------------------------------------------------------------------------------- long passCount = 0; long failCount = 0; long countAtLastNameChange = 0; char currentTestName[64]; //-------------------------------------------------------------------------------------------------------------- // setup and loop //-------------------------------------------------------------------------------------------------------------- void setup() { byte localData[] = BYTE_TEST_PATTERN_DATA; char buffer[MAX_STRING_SIZE]; Serial.begin(9600); //JBYCDMUS = just because you can, doesn't mean you should Serial.println(F("---------------- single variable tests")); setTestName("single variable tests"); assert( progMem_uchar == 130 ); //JBYCDMUS assert( pgm_read_byte(&progMem_uchar) == 130 ); assert( progMem_int == -3021 ); //JBYCDMUS assert( progMem_long == 2000000001 ); //JBYCDMUS assert( (int)(pgm_read_float(&progMem_float)*10000) == 31415 ); Serial.println(F("---------------- array tests")); setTestName("array tests"); for(int i=0; i< BYTE_TEST_PATTERN_SIZE; i++) { assert( pgm_read_byte(&progMem_ucharArray[i]) == localData[i] ); } for(int i=0; i<5; i++) { //the easy way of accessing the array float a = pgm_read_float(&progMem_floatArray[0][i]); //another way of accessing the array same as [1][i] float b = pgm_read_float(((unsigned int)progMem_floatArray)+(5+i)*sizeof(float)); a = a * 2; //to check two floating points are "equal" assert (abs(a - b) < 0.0001 ); } Serial.println(F("---------------- struct tests")); setTestName("struct tests"); assert( progMem_person.height == 189 ); //JBYCDMUS assert( pgm_read_byte(&progMem_person.height) == 189 ); //to get a string use: (prog_char *)pgm_read_word(&progMem_person.name) assert( strcmp_P("Papa Smurf", (prog_char *)pgm_read_word(&progMem_person.name)) == 0 ); assert( pgm_read_byte(&progMem_person.age) == 32 ); assert( progMem_person.age == 32 ); //JBYCDMUS //get person1's spouse Person *p= (Person *)pgm_read_word(&progMem_person.spouse); assert( p == &progMem_person2 ); assert( pgm_read_byte(&p->age) == 41 ); //get person2's spouse p = (Person *)pgm_read_word(&progMem_person2.spouse); assert( p == &progMem_person ); assert( pgm_read_byte(&p->age) == 32 ); Serial.println(F("---------------- memory operation tests")); setTestName("memcmp [memory compare]"); //compares len bytes of the memory s1 and flash s2 byte data[] = BYTE_TEST_PATTERN_DATA; assert( memcmp_P(data, progMem_ucharArray, BYTE_TEST_PATTERN_SIZE) == 0 ); data[BYTE_TEST_PATTERN_SIZE-1] = 0; assert( memcmp_P(data, progMem_ucharArray, BYTE_TEST_PATTERN_SIZE) < 0 ); data[BYTE_TEST_PATTERN_SIZE-1] = 255; assert( memcmp_P(data, progMem_ucharArray, BYTE_TEST_PATTERN_SIZE) > 0 ); //copy len bytes from flash to SRAM memset (data, 0, BYTE_TEST_PATTERN_SIZE); assert( memcpy_P(data, progMem_ucharArray, BYTE_TEST_PATTERN_SIZE) > 0 ); assert( memcmp(data, localData, BYTE_TEST_PATTERN_SIZE) == 0 ); Serial.println(F("---------------- string function tests")); //---- strcmp & strncmp Compare two strings setTestName("strcmp [string compare]"); assert( strcmp_P("Snollygoster", progMem_string) == 0 ); assert( strcmp_P("sNollyGostEr", progMem_string) != 0 ); assert( strcmp_P("Bug", progMem_string) < 0 ); assert( strcmp_P("Zoo", progMem_string) > 0 ); setTestName("strncmp [string compare, first n chars]"); assert( strncmp_P("Snollyfoobar", progMem_string, 6) == 0 ); assert( strncmp_P("sNollyGostEr", progMem_string, 6) != 0 ); assert( strncmp_P("Bug", progMem_string, 3) < 0 ); assert( strncmp_P("Zoo", progMem_string, 3) > 0 ); //---- strcasecmp & strncasecmp, Compare two strings, ignoring case assert( strcasecmp_P("Snollygoster", progMem_string) == 0 ); assert( strcasecmp_P("sNollyGostEr", progMem_string) == 0 ); assert( strcasecmp_P("Bug", progMem_string) < 0 ); assert( strcasecmp_P("Zoo", progMem_string) > 0 ); setTestName("strncasecmp [string compare ignoring case]"); assert( strncasecmp_P("Snollyfoobar", progMem_string, 6) == 0 ); assert( strncasecmp_P("sNollYGostEr", progMem_string, 8) == 0 ); assert( strncasecmp_P("Bug", progMem_string, 3) < 0 ); assert( strncasecmp_P("Zoo", progMem_string, 3) > 0 ); //---- strcpy, strlcpy & strncpy: makes a copy of a string setTestName("strcpy [copy a string]"); clear(buffer, MAX_STRING_SIZE); //empty string buffer assert( strcpy_P(buffer, progMem_string) == buffer ); assert( strncmp(buffer, "Snollygoster", MAX_STRING_SIZE) == 0 ); setTestName("strlcpy [copy a string of maximim size, ensure null terminated result]"); char smallBuffer[12]; //not long enough to hold "Snollygoster" AND the final null terminator clear(buffer, MAX_STRING_SIZE); //empty string buffer clear(smallBuffer, 12); //empty string buffer assert( strlcpy_P(buffer, progMem_string, MAX_STRING_SIZE) == 12 ); assert( strlcpy_P(smallBuffer, progMem_string, 12) == 12 ); assert( strcmp(buffer, "Snollygoster") == 0 ); //This is the difference between strlcpy and strncpy, the whole string was not copied //to ensure a null terminator was present assert( strcmp(smallBuffer, "Snollygoste") == 0 ); setTestName("strncpy [copy a string of maximim size, null terminate iff space allows]"); clear(buffer, MAX_STRING_SIZE); //empty string buffer assert( strncpy_P(buffer, progMem_string, MAX_STRING_SIZE) == buffer ); assert( strcmp(buffer, "Snollygoster") == 0 ); //---- strcat, strlcat & strncat: Concatenate two strings setTestName("strcat [join two strings]"); strcpy(buffer, "Chief "); assert( strcat_P(buffer, progMem_string) == buffer ); assert( strcmp(buffer, "Chief Snollygoster") == 0 ); setTestName("strlcat [join two strings, limiting final size, ensure termination]"); strcpy(smallBuffer, "Chief "); //smallBuffer is only 12 characters //NOTE strlcat_P n = sizeof destination; while strncat_P n = number of chars to copy assert( strlcat_P(smallBuffer, progMem_string, 12) == 18 ); //12th character is the nul terminator assert( strcmp(smallBuffer, "Chief Snoll") == 0 ); strcpy(buffer, "Chief "); //smallBuffer is only 12 characters assert( strlcat_P(buffer, progMem_string, MAX_STRING_SIZE) == 18 ); assert( strcmp(buffer, "Chief Snollygoster") == 0 ); setTestName("strncat [join two strings, limiting final size]"); strcpy(buffer, "Chief "); //NOTE strncat_P n = number of chars to copy; while strlcat_P n = sizeof destination assert( strncat_P(buffer, progMem_string, 6) == buffer ); assert( strcmp(buffer, "Chief Snolly") == 0 ); //---- strlen & strnlen: Finds the length of the string (searches for the null terminator) setTestName("strlen [find the length of a string]"); assert( strlen_P( progMem_string) == 12 ); setTestName("strnlen [find the length of a string, limited to n]"); assert( strnlen_P( progMem_string, 6) == 6 ); assert( strnlen_P( progMem_string, MAX_STRING_SIZE) == 12 ); //---- strstr: searches s1 for the first occurrence of (the substring) s2 setTestName("strstr [find a string inside another string]"); //one item to find strcpy(buffer, "A Snollygoster sounds like something you would find in a handkerchief."); assert( strstr_P(buffer, progMem_string) == &buffer[2] ); assert( strstr_P("foobar", progMem_string) == NULL ); //what is defined to happen when searching an empty/null strings strcpy(buffer, "foobar"); assert( strstr_P(buffer, NULL) == NULL ); //this is why "if(strstr_P(s1, s2) != null){...}" is a bad idea!" assert( strstr_P(buffer, progMem_emptyString) == buffer ); Serial.println(F("---------------- done")); if(failCount > 0) { Serial.print(passCount); Serial.println(" test passed"); Serial.print(failCount); Serial.println(" test FAILED"); } else { Serial.print("All tests passed ("); Serial.print(passCount); Serial.println(" tests rum)"); } } void loop() { } void clear(char *buffer, int len) { memset(buffer, '\0', len); } //-------------------------------------------------------------------------------------------------------------- // Testing framework //-------------------------------------------------------------------------------------------------------------- void setTestName(const char *name) { strcpy(currentTestName, name); countAtLastNameChange = passCount+failCount; } void assert(bool isTrue) { if(isTrue) { pass(); } else { fail(); } } void pass() { passCount++; } void fail() { Serial.print("TEST FAILED: "); if(currentTestName != NULL) { int localTestNum = (passCount + failCount) - countAtLastNameChange + 1; Serial.print("("); Serial.print(currentTestName); Serial.print(" test "); Serial.print(localTestNum); Serial.print(")"); } Serial.println(""); failCount++; } |