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.

 

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

 

Keep Reading

PreviousNext