Comparing PROGMEM Data

Hello again,

I'm trying to minimize my RAM usage, but I'dont understand much about PROGMEM yet, since I never tried it before. But I have one particular function (below) that eats 374 bytes of RAM that I think I could use PROGMEM.

void name(char *command, int value) {
  int commandCode[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  char *commandName[] = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
  int commandSize = sizeof(commandCode) / sizeof(commandCode[0]);
  for (int i = 0; i < commandSize; i++) {
    if (atoi(command) == commandCode[i]) {
      command = commandName[i];
      break;
    }
  }
  Serial.print("Parameters: ");
  Serial.print(command);
  Serial.print(" = ");
  if (value == 1) Serial.println("On");
  else if (value == 0) Serial.println("Off");
}

I use this function to find an int value in the first array (commandCode), and print it's name equivalent from the second array (commandName).

I'm trying to store the arrays on the Flash Memory using the modified code above, but I don't know how to compare the first array (numbers) with the atoi(command) even using the info on the Arduino - PROGMEM page, it doesn't work.

void name(char *command, int value) {
  static const int commandCode[] PROGMEM = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  static const char commandName[][20] PROGMEM = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
  int commandSize = sizeof(commandCode) / sizeof(commandCode[0]);
  for (int i = 0; i < commandSize; i++) {
    if (atoi(command) == pgm_read_word(commandCode[i])) {
      strcpy_P(command, (char*)pgm_read_word(&(commandName[i])));
      break;
    }
  }
  Serial.print("Parameters: ");
  Serial.print(command);
  Serial.print(" = ");
  if (value == 1) Serial.println("On");
  else if (value == 0) Serial.println("Off");
}

When I parse command = 1 and value = 1 or command = 1 and value = 0 (for example) it print's:

Parameters: 1 = On
Parameters: 1 = Off

Can anyone point me what I'm doing wrong and how it should be done?

This will dramatically simplify your effort...
http://arduiniana.org/libraries/flash/

Tutorial : Gammon Forum : Electronics : Microprocessors : Putting constant data into program memory (PROGMEM)

Could you show your whole sketch, so we can try it ?
What is the "PRINTF" ?

Thanks Coding Badly, I will have a look at it.

Peter_n:
Tutorial : Gammon Forum : Electronics : Microprocessors : Putting constant data into program memory (PROGMEM)

Hi Peter_n, thanks for you answer, I saw that tutorial from Nick Gammon (I always search in his site when I have any difficults). I found that PROGMEM_readAnything template, but it didn't compile (see the errors bellow), and actually I would like to understand what I'm doing wrong.

snnipet.ino:4:34: error: 'T' does not name a type
snnipet.ino:4:38: error: ISO C++ forbids declaration of 'sce' with no type [-fpermissive]
snnipet.ino:4:43: error: 'T' has not been declared
snnipet.ino:5:1: error: 'T' does not name a type
snnipet.ino: In function 'void name(char*, int)':
snnipet.ino:28:60: error: no matching function for call to 'PROGMEM_getAnything(const int&)'
snnipet.ino:28:60: note: candidate is:
snnipet.ino:9:25: note: template<class T> T PROGMEM_getAnything(const T*)
snnipet.ino:9:25: note:   template argument deduction/substitution failed:
snnipet.ino:28:60: note:   mismatched types 'const T*' and 'int'
snnipet.ino:29:51: error: no matching function for call to 'PROGMEM_readAnything(const char [20], char*&)'
snnipet.ino:29:51: note: candidates are:
snnipet.ino:4:6: note: void PROGMEM_readAnything(const int*, int&)
snnipet.ino:4:6: note:   no known conversion for argument 1 from 'const char [20]' to 'const int*'
snnipet.ino:6:28: note: template<class T> void PROGMEM_readAnything(const T*, T&)
snnipet.ino:6:28: note:   template argument deduction/substitution failed:
snnipet.ino:29:51: note:   deduced conflicting types for parameter 'T' ('char' and 'char*')
Erro compilando.

Peter_n:
Could you show your whole sketch, so we can try it ?

I'm using this snnipet to test before integratting into my main sketch, if you wants to try.

char command[] = "1";
char value[] = "1";

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing");
  name(command, atoi(value));
}

void loop() {}

void name(char *command, int value) {
  static const int commandCode[] PROGMEM = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  static const char commandName[][20] PROGMEM = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
  int commandSize = sizeof(commandCode) / sizeof(commandCode[0]);
  for (int i = 0; i < commandSize; i++) {
    if (atoi(command) == pgm_read_word(commandCode[i])) {
      strcpy_P(command, (char*)pgm_read_word(&(commandName[i])));
      break;
    }
  }
  Serial.print("Parameters: ");
  Serial.print(command);
  Serial.print(" = ");
  if (value == 1) Serial.println("On");
  else if (value == 0) Serial.println("Off");
}

This compiles normally, but it print's the parsed command instead of the commandName (in this case "one"), I think it's because it didn't match correctly.

Initializing
Parameters: 1 = On

Peter_n:
What is the "PRINTF" ?

The PRINTF is just a macro to print to the Serial Monitor and a logfile in the SD Card with timestamp, I should have changed it for Serial.print before posting, I'm doing it right now, sorry.

I did not test your sketch yet, I'm using my tablet now.

You need to add code that shows that the number is not found.
The pgm_read_word() requires a pointer, use the '&' : &commandCode [ i ] ;
It is not good to strcpy_P a string that can be size 20 into an array that is only size 4. If your commandName size is 20, then make command[] also size 20.
What if 'value' is 3 ?

Peter_n:
I did not test your sketch yet, I'm using my tablet now.

You need to add code that shows that the number is not found.
The pgm_read_word() requires a pointer, use the '&' : &commandCode [ i ] ;
It is not good to strcpy_P a string that can be size 20 into an array that is only size 4. If your commandName size is 20, then make command[] also size 20.
What if 'value' is 3 ?

You are right, I made both arrays size 20, and tried to use the '&' but it crashes and prints:

InitInitInitInitInitInitInitInitInitInit

For ever and ever...

I return to the previous state and added a code to see if it's not found, and it isn't.

char command[] = "1";
char value[] = "1";

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing");
  name(command, atoi(value));
}

void loop() {}

void name(char *command, int value) {
  boolean found = false;
  static const int commandCode[] PROGMEM = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  static const char commandName[][20] PROGMEM = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"};
  int commandSize = sizeof(commandCode) / sizeof(commandCode[0]);
  for (int i = 0; i < commandSize; i++) {
    if (atoi(command) == pgm_read_word(commandCode[i])) {
      strcpy_P(command, (char*)pgm_read_word(&(commandName[i])));
      found = true;
      break;
    }
  }
  if (!found) Serial.println("no match");
  Serial.print("Parameters: ");
  Serial.print(command);
  Serial.print(" = ");
  if (value == 1) Serial.println("On");
  else if (value == 0) Serial.println("Off");
}

Prints:

Initializing
no match
Parameters: 1 = On

And about the value[], as I said, it's just a snnipet to test the function before implementing, once implemented I have previous check's to guarantee that the parsed value will be always 0 or 1, and the command will be in that array, that's why I don't even bother to actually test it here.

command[] has now size 2. You can not copy a string that is larger into command[]. Please make it size 20.
pgm_read_word requires a pointer. Please use the '&' : &CommandCode [ i ].
Now the CommandName [ i ] has extra '(' and ')', could you remove those ?

I tested your sketch with the notes that I wrote about.
In addition to my notes, the strcpy_P() does not need the pgm_read_word(). The ..._P functions use pointers to PROGMEM memory.

// Tested with Arduino 1.6.1 with Arduino Uno

#define COMMAND_SIZE 20

char command[COMMAND_SIZE];

// Both these arrays (inclusive the text) is located in PROGMEM
// Test it by declaring more elements.
const int commandCode[] PROGMEM =                {  1,     2,     3,       4,      5,      6,     7,       8,       9,      10 };
const char commandName[][COMMAND_SIZE] PROGMEM = { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten" };

void setup() {
  Serial.begin(115200);
  Serial.println("\nInitializing");
  
  Serial.print("Used ram : ");
  Serial.println( RAMEND + 1 - freeRam());
}

void loop() {
  // Value can be 0,1
  // cmd can be 1...10
  //    int value = random( 0, 2);
  //    int cmd = random( 1, 11);  
  int value = random( -1, 4);
  int cmd = random( -5, 15);
  
  itoa( cmd, command, 10);
  Serial.print("Value = ");
  Serial.print(value);
  Serial.print(", ");
  Serial.print("Command = ");
  Serial.print(command);
  Serial.print(", ");
  name(command, value);
  
  delay(1000);
}


void name(char *command, int value) {
  boolean found = false;

  int commandSize = sizeof(commandCode) / sizeof(commandCode[0]);
  for (int i = 0; i < commandSize; i++) {
    if ( atoi( command) == pgm_read_word( &commandCode[i])) {
      strcpy_P(command, commandName[i]);
      found = true;
      break;
    }
  }
  if (!found) {
    Serial.println("-> no match");
  }
  else {
    Serial.print("Parameters: ");
    Serial.print(command);
    Serial.print(" = ");
    if (value == 1) 
      Serial.print("On");
    else if (value == 0) 
      Serial.print("Off");
    else
      Serial.print( "undefined");
    Serial.println();
  }
}

int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

Are the values always incremental, like 0...10, or 100...110 ? Then the sketch could be written in a shorter way. If the text always represents a number, then the sketch could be simpler as well.

If you want any kind of number combined with any command text, then I would do this:
I would first convert the number into an integer, and make a function the searches for the text. Since I like 'struct', why not throw in a struct :stuck_out_tongue:

// Tested with Arduino 1.6.1 with Arduino Uno

#define COMMAND_SIZE 20

struct cmdSTRUCT {
  int code;
  char text[COMMAND_SIZE];
};

// This table is completely in PROGMEM
// Test it by declaring more elements.
const cmdSTRUCT table[] PROGMEM = {
  { 1,  "bolts" },
  { 2,  "nuts" },
  { 3,  "inch" },
  { 12, "cat" },
  { 5,  "temperature" },
  { 6,  "keyboard" },
  { 14, "fourty-six" },
  { 8,  "meter" },
  { 13, "say cheese" },
  { 10, "mouse" },
};

void setup() {
  Serial.begin(115200);
  Serial.println("\nInitializing");
  
  Serial.print("Used ram : ");
  Serial.println( RAMEND + 1 - freeRam());
}

void loop() {
  char buffer[COMMAND_SIZE];
  
  int value = random( -1, 3);     // 0 or 1
  int command = random( -2, 16);  // the table is searched for the number
  
  Serial.print("Value = ");
  Serial.print(value);
  Serial.print(", ");
  Serial.print("Command = ");
  Serial.print(command);
  Serial.print(", ");

  boolean found = name( command, buffer);

  if (!found) {
    Serial.println("-> no match");
  }
  else {
    Serial.print( "Parameters: ");
    Serial.print( buffer);
    Serial.print( " = ");
    if (value == 1) 
      Serial.print( "On");
    else if (value == 0) 
      Serial.print( "Off");
    else
      Serial.print( "undefined");
    Serial.println();
  }
  
  delay(1000);
}


// A function that finds the number in a table,
// and returns the text that belongs to that number (written to a pointer location).
// The return value indicates if the number was found.
boolean name(int cmd, char *pBuffer) {
  boolean fnd = false;

  int commandSize = sizeof(table) / sizeof(cmdSTRUCT);
  for (int i = 0; i < commandSize; i++) {
    if ( cmd == pgm_read_word( &table[i].code)) {
      strcpy_P(pBuffer, table[i].text);
      fnd = true;
      break;
    }
  }
  return( fnd);
}


int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

Peter_n:
I tested your sketch with the notes that I wrote about.
In addition to my notes, the strcpy_P() does not need the pgm_read_word(). The ..._P functions use pointers to PROGMEM memory.

Thanks Peter_n, I knew that should be something simple...

Both of your sketches works.

Only diference I saw is that if I declare the arrays inside the function it self I need to use the static keyword right?

I never used PROGMEM data inside a function.

A test with my second example (with the struct :stuck_out_tongue: ):
code : the code size according to the compiler.
ram : the dynamic memory according to the compiler.
used ram : according to the sketch.

Global declaration with const PROGMEM : code = 3612, ram = 288, used ram = 554
Global declaration with static const PROGMEM : code = 3612, ram = 288, used ram = 554
Inside function with const PROGMEM : code = 3692, ram = 508, used ram = 774
Inside function with static const PROGMEM : code = 3612, ram = 288, used ram = 554

The only faulty one is when it is declared inside a function without 'static'. The PROGMEM fails there.
Could you place the data outside the function ? To avoid any trouble.

For fun: Those number of the first test: 3612, 288, 554.
I can make them: 3060, 296, 554
By using this file:

# File : platform.local.txt
# Path : arduino \ hardware \ arduino \ avr
#        (next to platform.txt)

# These can be overridden in platform.local.txt
compiler.c.extra_flags=-fipa-pta -fira-loop-pressure -flto
compiler.c.elf.extra_flags=-fipa-pta -fira-loop-pressure -flto
compiler.S.extra_flags=
compiler.cpp.extra_flags=-fipa-pta -fira-loop-pressure -flto
compiler.ar.extra_flags=
compiler.objcopy.eep.extra_flags=
compiler.elf2hex.extra_flags=

Peter_n:
Global declaration with const PROGMEM : code = 3612, ram = 288, used ram = 554
Global declaration with static const PROGMEM : code = 3612, ram = 288, used ram = 554
Inside function with const PROGMEM : code = 3692, ram = 508, used ram = 774
Inside function with static const PROGMEM : code = 3612, ram = 288, used ram = 554

Yeah, that was exactly the same test I did, got the same results...
I think I will try to use the struct as you suggested, thanks again!!!