PROGMEM Headaches...

I'm using PROGMEM to store string tables. I recently "upgraded" the application from v1.0.5 to 1.6.5, which, of course, required source code changes. Now, I'm getting a warning on every line that uses PROGMEM. Here is a typical use, which is pretty much copied from the PROGMEM reference page:

const char StringTbl0[] PROGMEM = "Ready              ";
const char StringTbl1[] PROGMEM = "PDBDown         ";
const char StringTbl2[] PROGMEM = "Loosen             ";
const char StringTbl3[] PROGMEM = "Tighten            ";
const char StringTbl4[] PROGMEM = "Config Mode     ";
const char StringTbl5[] PROGMEM = "Manual Mode    ";
const char StringTbl6[] PROGMEM = "Update Mode    ";

PGM_P const StringTbl[] PROGMEM = {
    StringTbl0,
    StringTbl1,
    StringTbl2,
    StringTbl3,
    StringTbl4,
    StringTbl5,
    StringTbl6,
};

The above generates 14 warnning, one for EVERY line that either contains PROGMEM, or references one of the arrays:

SuperPDB.ino:239: warning: only initialized variables can be placed into program memory area

Why? How do I get rid of the warnings?

Regards, Ray L.

RayLivingston: Why? How do I get rid of the warnings?

I think "PGM_P" is deprecated now.

You'd perhaps better use:

const char* const StringTbl[] PROGMEM = {

instead. Do you see any warnings with that?

With that change, I still get 7 errors, one for each line declaring a string, and one for the line declaring the array of pointers.

Regards, Ray L.

Hmmmm..... This is odd.... I decided to make a stripped-down test program, basically containing just the above code. It compiles fine, with no warnings. The same lines in my full program generates warnings...

Regards, Ray L.

This is quite befuddling, since there is ANOTHER PROGMEM page that says to use this syntax, which still generates the same warnings:

prog_char StringTbl0[] PROGMEM = "Ready              ";
prog_char StringTbl1[] PROGMEM = "PDBDown         ";
prog_char StringTbl2[] PROGMEM = "Loosen             ";
prog_char StringTbl3[] PROGMEM = "Tighten            ";
prog_char StringTbl4[] PROGMEM = "Config Mode     ";
prog_char StringTbl5[] PROGMEM = "Manual Mode    ";
prog_char StringTbl6[] PROGMEM = "Update Mode    ";

PROGMEM const char *StringTbl[] PROGMEM = {
    StringTbl0,
    StringTbl1,
    StringTbl2,
    StringTbl3,
    StringTbl4,
    StringTbl5,
    StringTbl6,
};

Regards, Ray L.

RayLivingston: This is quite befuddling, since there is ANOTHER PROGMEM page that says to use this syntax, which still generates the same warnings:

prog_char StringTbl0[] PROGMEM = "Ready              ";
prog_char StringTbl1[] PROGMEM = "PDBDown         ";
prog_char StringTbl2[] PROGMEM = "Loosen             ";
prog_char StringTbl3[] PROGMEM = "Tighten            ";
prog_char StringTbl4[] PROGMEM = "Config Mode     ";
prog_char StringTbl5[] PROGMEM = "Manual Mode    ";
prog_char StringTbl6[] PROGMEM = "Update Mode    ";

PROGMEM const char *StringTbl[] PROGMEM = { StringTbl0, StringTbl1, StringTbl2, StringTbl3, StringTbl4, StringTbl5, StringTbl6, };




Regards,
Ray L.

That syntax is definitely out of date with the new version. prog_char went out with 1.0.6 as far as I know.

Delta_G: That syntax is definitely out of date with the new version. prog_char went out with 1.0.6 as far as I know.

Then where is the correct syntax for 1.6.x?? This was all working just fine under 1.0.5. When I updated to 1.6.x I had to change the code to get it to compile, and the current syntax did get it to compile, and work, but I want to get rid of all the warnings.

Another odd thing: I had some debugging code, which included a couple of functions for logging state changes. I #ifdef'd those functions out, and ended up with link errors, because those functions were no longer defined. Not compile errors, mind you, but LINK errors! I''m baffled as to how it did NOT generate compile errors! And no, they are not declared extern.

I'm currently running v1.6.5. Is this a buggy version??

Regards, Ray L.

RayLivingston: Then where is the correct syntax for 1.6.x?? This was all working just fine under 1.0.5.

I had the same problem. You know what I did? I compiled under 1.0.6 and uploaded and called it a day. Project works and I'm satisfied.

RayLivingston: but I want to get rid of all the warnings.

Good luck. The Arduino core is full of them. Doesn't seem like anyone over there is too concerned with warnings. Once they get a compile they call it good code.

The thing about any latest version is that it is new. You run into contributed libraries that haven't been updated and new bugs that weren't expected. IMHO, if there isn't some feature in the newest version that you absolutely need, then stick with the stable version that you know works.

Well, this has certainly been a bizarre and random experience! Here is the code that works:

const char StringTbl0[] PROGMEM = "Ready           ";
const char StringTbl1[] PROGMEM = "PDBDown         ";
const char StringTbl2[] PROGMEM = "Loosen          ";
const char StringTbl3[] PROGMEM = "Tighten         ";
const char StringTbl4[] PROGMEM = "Config Mode     ";
const char StringTbl5[] PROGMEM = "Manual Mode     ";
const char StringTbl6[] PROGMEM = "Update Mode     ";

PROGMEM const char * const StringTbl[] = {
 StringTbl0,
 StringTbl1,
 StringTbl2,
 StringTbl3,
 StringTbl4,
 StringTbl5,
 StringTbl6,
};

Even odder, after fixing this, i started getting compile errors in library code i haven't touched in almost two years! Everywhere I had a Serial.write of a hex literal (i.e. - "Serial.write(0xa5);") I was getting errors about an ambiguous argument type. I had to add type casts to the argument of every such call. That code has worked perfectly for years! Even odder, it was throwing errors in "system" libraries I don't even use, like Tone, and several others!

One more bizarre thing:Before I started trying to clear up the warnings, the compiler was NOT giving me the RAM usage at the end of the build - only FLASH usage. Now the RAM usage display is back!

There's still bugs in that there compiler....

But, it now compiles with no errors or warnings, so I am back in business.

Regards, Ray L.

Delta_G: I had the same problem. You know what I did? I compiled under 1.0.6 and uploaded and called it a day. Project works and I'm satisfied.

That's exactly what I've done for the last 18 months. But I was forced to "upgrade" a while back - I forget why. But it is now working, and I get no warnings.

Regards, Ray L.

Thread: [Resolved] PROGMEM in Arduino IDE 1.6.0

I just spent an hour getting an old PROGMEM + offset example to work with my current 1.6.1

Hey, I’d rather use offsets than addresses when the offsets are shorter. There could be many…

This works in 1.6.1 and please, more than cosmetic improvements are welcome! This is WIP.

#include <avr/io.h>
#include <avr/pgmspace.h>

#define NAMES 12
const char namesTable[] PROGMEM =
{
  "MARK\0FRANCIS\0JOHN\0ANDREW\0ROBERT\0SEAN\0HAYES\0LUDWIG\0HARRISON\0GRAND\0SALTER\0BOND\0"
};

const char namesOffset[] PROGMEM =
{
  0, 5, 13, 18, 25, 32, 37, 43, 50, 59, 65, 72
};

const char *nT; // will point into namesTable, add offsets to point to names

const char userCommands[] PROGMEM = {
  "RAM\0MSG\0\\END\0TEST"
};

#define  FIRSTNAMES    6
#define  LASTNAMES     6
const char *firstName;
const char *lastName;



void  printProgstring( const char *FM )
{
  byte fb;
  do
  {
    fb = pgm_read_byte( FM++ );
    if ( fb )  Serial.print((char) fb );
  }
  while ( fb );
}

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

void setup(void)
{
  Serial.begin( 115200 );
  Serial.print( "\n Free RAM = " );
  Serial.println( freeRam());
  Serial.println();

  byte i, j, k;

  for ( i = 0; i < FIRSTNAMES; i++ )
  {
    j = pgm_read_byte( namesOffset + i );
    printProgstring( namesTable + j );
    Serial.print( F( " " ));
    j = pgm_read_byte( namesOffset + FIRSTNAMES + i );
    printProgstring( namesTable + j );
    Serial.println();
  }

  Serial.println( "\n" );

  char c;
  j = 0;
  for ( i = 0; i < 4; i++ )
  {
    while ( c = pgm_read_byte( userCommands + j++ ))
    {
      if ( c ) Serial.print( c );
    }
    Serial.println( );
  }
}

void loop(void)
{
}

http://www.gammon.com.au/progmem

As for the warnings and errors. Compilers get upgraded. Suddenly code that used to compile doesn’t. Even in libraries other people wrote.

I understand what you're trying to do there, but i think you'll find maintaining that string to be a major PITA. Anytime you add, delete, move or modify an entry, the offsets for all following entries will change. You'd be much better off simply having printProgstring "walk" the string, finding the nulls, and counting them. That way, the only argument printProgstring needs is the INDEX of the string to print, rather than it's offset. When you add/delete/move/modify strings, you don't have to re-calculate all those offsets. Use an enum to define meaningful names for the indices.

Regards, Ray L.

RayLivingston: I understand what you're trying to do there, but i think you'll find maintaining that string to be a major PITA. Anytime you add, delete, move or modify an entry, the offsets for all following entries will change. You'd be much better off simply having printProgstring "walk" the string, finding the nulls, and counting them. That way, the only argument printProgstring needs is the INDEX of the string to print, rather than it's offset. When you add/delete/move/modify strings, you don't have to re-calculate all those offsets. Use an enum to define meaningful names for the indices.

Regards, Ray L.

Sure. How do you manage that at compile time? I keep the table in flash because I don't want it eating up RAM.

Even more... add? edit? move? modify? These are in flash and they're only as examples of text. Perhaps I should have used IT-type prompts and labels instead? Data that changes would be better off stored on SD.

It's easier for me to make a sketch that generates the source data for whatever table I use. I run it and use ctrl-C copy from serial monitor and ctrl-V to paste to the IDE.

GoForSmoke: Sure. How do you manage that at compile time? I keep the table in flash because I don't want it eating up RAM.

There is no table, only the long string with embedded nulls. The caller tells you to print String #3. You start at the beginning of the string, and find the second null, then extract characters until the next null.

Regards, Ray L.

Okay, thanks. If I need to trade cycles for flash that would be a way to do it.

It’s a table of strings as stored. Each one starts at an address and ends with a NULL.

The offsets are 1 byte per string (limited table size) instead of 2 byte pointers. Bigger table, pointers.

Ray's idea is perfectly practical, even for larger strings. And the overhead of reading flash is only one extra clock cycle per byte compared to RAM.

RayLivingston:
Why? How do I get rid of the warnings?

Regards,
Ray L.

How about this:

void setup (void)
{
    uint8_t n;
    uint8_t x;

    const char *StringTbl[] = {
        PSTR("Ready          "),
        PSTR("PDBDown        "),
        PSTR("Loosen         "),
        PSTR("Tighten        "),
        PSTR("Config Mode    "),
        PSTR("Manual Mode    "),
        PSTR("Update Mode    "),
    };

    Serial.begin (115200);

    n = (sizeof (StringTbl) / sizeof (*StringTbl));

    for (x = 0; x < n; x++) {
        Serial.print ("String ");
        Serial.print (x);
        Serial.print (": ");
        Serial.println_P (StringTbl[x]);
    }
}

void loop (void)
{
    // nothing
}

(don’t know what the excess spaces in the strings are for, but I left them… FWIW).

Krupski: (don't know what the excess spaces in the strings are for, but I left them..... FWIW).

A prompt with extra spaces will clear whatever might be showing after on an LCD or ANSI terminal. It's a convenience trick and all of those array elements will be 12 chars whether ' ' or zeros.