[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.
//----------------------------------------------------------------------------------// // 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/* 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++; }