Author Topic: AVR C: which pgm_read_* function to use for enum types in PROGMEM?  (Read 4673 times)

0 Members and 1 Guest are viewing this topic.

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
This is probably just as much of a C question than a microcontroller one, but it concerns some code I'm writing for an Atmel AVR, so I figure here is as good a place as any to ask. :)

Say I have an enum defined like so:

Code: [Select]
typedef enum {
FOO, BAR /* etc... */
} my_enum_t;

And then somewhere in my code I want to have an array of this enum type, like so:

Code: [Select]
my_enum_t blah[] = { FOO, BAR, BAR, FOO /* etc... */ };

But this array is of non-trivial length, and I don't want it taking up valuable RAM, so I could choose to keep it in program space using the PROGMEM attribute. Then I can access its elements using one of the pgm_read_* functions. But which one? I don't know for sure what the byte size is of my enum type. I think I read somewhere that AVR GCC uses 16-bit int for enums by default, so I suppose I could use pgm_read_word(). But, what if I want to use the 'short-enums' compiler flag? Depending on my enum values, the enum type's size could then be any integer size! (The docs say "the enum type will be equivalent to the smallest integer type which has enough room".)

Is there a way to find out what size of int has been used for an enum (or all enums)? Of course, I know about sizeof(), but I feel hard-coding a mapping between enum size in bytes and pgm_read_* functions is a bit... yucky. I'm hoping that's not my only choice. Something defined at compile-time would be nice.
 

Offline sleemanj

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #1 on: December 01, 2016, 01:42:21 am »
memcpy_P ?
~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #2 on: December 01, 2016, 02:26:31 am »
memcpy_P? But isn't that for... oh, wait, I think I see what you're getting at.

So I'd do something like this?

Code: [Select]
my_enum_t elem;
memcpy_P(&elem, &blah[n], sizeof(my_enum_t));
 

Offline sleemanj

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #3 on: December 01, 2016, 03:37:28 am »
Code: [Select]
my_enum_t elem;
memcpy_P(&elem, &blah[n], sizeof(my_enum_t));

Yes pretty sure that should work.
~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #4 on: December 01, 2016, 10:02:56 pm »
That doesn't seem to work. :( I just end up with a random garbage value in elem that's not a valid member of the enumeration.

Oh, but I did find out that 'short-enums' is actually enabled by default with Atmel Studio 7, so sizeof(my_enum_t) in my case is actually 1 byte, and I would have been wrong to blindly go ahead and use pgm_read_word().
 

Offline sleemanj

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #5 on: December 01, 2016, 11:57:01 pm »
That doesn't seem to work. :( I just end up with a random garbage value in elem that's not a valid member of the enumeration.


Works for me (tested in Arduino IDE using avr-gcc 4.9.2).  Are you sure you're not doing something simple like overstepping array bounds or something?

Main compile opts
"/home/boffin/.arduino15/packages/arduino/tools/avr-gcc/4.9.2-atmel3.5.3-arduino2/bin/avr-g++" -c -g -Os -Wall -Wextra -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10613 -DARDUINO_AVR_NANO -DARDUINO_ARCH_AVR   "-I/usr/local/stow/arduino-1.6.13/hardware/arduino/avr/cores/arduino" "-I/usr/local/stow/arduino-1.6.13/hardware/arduino/avr/variants/eightanaloginputs" "/tmp/arduino_build_479767/sketch/sketch_dec02a.ino.cpp" -o "/tmp/arduino_build_479767/sketch/sketch_dec02a.ino.cpp.o"

Code (Arduino)
Code: [Select]
    typedef enum {
    FOO, BAR, NARF, ZORT, POINT, FNAGLE
    } my_enum_t;

    static const my_enum_t blah[] PROGMEM = { FOO, BAR, NARF, ZORT, POINT, FNAGLE };

    void setup() {
      Serial.begin(57600);
      Serial.print("Sizeof the enum is: ");
      Serial.print(sizeof( my_enum_t ) );
      Serial.println(" Bytes");
     
      for( uint8_t n = 0; n < sizeof(blah) / sizeof(my_enum_t); n++)
      {
        my_enum_t elem;
        memcpy_P(&elem, &blah[n], sizeof(my_enum_t));
        switch(elem)
        {
          case FOO:  Serial.println("FOO"); break;
          case BAR:  Serial.println("BAR"); break;
         
          case NARF:  Serial.println("NARF"); break;
          case POINT:  Serial.println("POINT"); break;
          case ZORT:  Serial.println("ZORT"); break;
          case FNAGLE:  Serial.println("FNAGLE"); break;
        }   
        delay(1000);
      }
    }

    void loop() { }


Output
Code: [Select]
Sizeof the enum is: 2 Bytes
FOO
BAR
NARF
ZORT
POINT
FNAGLE
~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #6 on: December 02, 2016, 04:18:15 pm »
Hmm, just tried that example myself and it works fine. I even altered it to copy into a struct member - as I was attempting to do in my own code - and still works fine.

Was sitting there trying to figure out what difference there was between my own code and yours, when it struck me that there was one difference: to do with how I was declaring the array. I hadn't declared it as const or static. So I commented both those out and the Arduino IDE compiler threw an error of "variable 'blah' must be const in order to be put into read-only section by means of '__attribute__((progmem))'". But... the compiler in Atmel Studio doesn't! ???

So I added const and tried my code again. No bueno. :(

Then something twigged. I had been debugging the code by using the simulator in AS7, and was observing the garbage value being copied in the 'Locals' window. But it was also showing the enum elements of the array by name... but... how? Surely if it's in program space, it can't?! What if the array wasn't actually residing in progmem after all? To test my theory, I swapped memcpy_P out for regular memcpy. Suddenly it works!

So my array isn't being put in progmem, even though I'm specifying that attribute perfectly correctly as far as I can tell. (And presumably also why the compiler didn't error when I was originally missing const.) Why?!? Does PROGMEM only work on global variables? My array is a local variable inside a function. :-//
 

Offline richardman

  • Frequent Contributor
  • **
  • Posts: 427
  • Country: us
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #7 on: December 02, 2016, 09:27:13 pm »
So my array isn't being put in progmem, even though I'm specifying that attribute perfectly correctly as far as I can tell. (And presumably also why the compiler didn't error when I was originally missing const.) Why?!? Does PROGMEM only work on global variables? My array is a local variable inside a function. :-//

OK, I don't know for sure how GCC/AVR does it without trying it, but generally speaking, if you just declare a LOCAL array inside a function, then it's allocated on the stack. In that case, it makes no sense to allocate it on "program memory" (flash). So if you try to tell that compiler that it's both a local on stack and in program memory, it may just do something random without saying anything.

You can declare the local array with the storage class "static", normally that says "allocate it in normal global data area", and if you couple it with the program memory attribute, then the compiler should be smart enough to do the expected thing.

It will take a couple paragraphs to explain the concepts of lifetime, linkage, and name scope, but they are all involved here:-)
// richard http://imagecraft.com/
JumpStart C++ for Cortex (compiler/IDE/debugger): the fastest easiest way to get productive on Cortex-M.
Smart.IO: phone App for embedded systems with no app or wireless coding
 

Offline sleemanj

  • Super Contributor
  • ***
  • Posts: 3024
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #8 on: December 02, 2016, 10:06:28 pm »
Does PROGMEM only work on global variables? My array is a local variable inside a function. :-//

Yes PROGMEM will work for const globals and should also work for static const locals.  But non static locals or non const anything(*) will not work.

(*) Some compiler versions implied const from PROGMEM, but I think more recently you have to do it explicitly.

~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1477
  • Country: gb
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #9 on: December 03, 2016, 12:50:07 am »
Ahhhh, I see. Thanks guys for the explanation.

I'll try specifying the local variable as static const, but if that doesn't work, I'll have to re-arrange to make the array global.
 

Offline Bruce Abbott

  • Frequent Contributor
  • **
  • Posts: 627
  • Country: nz
    • Bruce Abbott's R/C Models and Electronics
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #10 on: December 03, 2016, 01:53:57 am »
Did you check  for compiler warnings?

Code: [Select]
int main(void){
   my_enum_t blah[] PROGMEM = { FOO, BAR, BAR, FOO};
avr-gcc... warning: '__progmem__' attribute ignored
Build succeeded with 1 Warnings...


Code: [Select]
my_enum_t blah[] PROGMEM = { FOO, BAR, BAR, FOO};

int main(void){
avr-gcc... Build succeeded with 0 Warnings...
 

Offline builder01

  • Newbie
  • Posts: 6
  • Country: nl
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #11 on: July 26, 2021, 01:36:43 pm »
Hi,
Sorry to hijack this post.

How would I go about initializing a enum type with initialized values in PROGMEM.

I have a bunch of musical note frequencies that I want to map to names for readibility.
I was thinking something like this:
enum musicalNotes
   {
       C5      = 425,   
       Cs5     = 401, 
       D5      = 379,   
       Ds5     = 357,
      //and more...
};

Is it possible to initialize this directly within flash memory using PROGMEM?

I tried typedef enum{ C5, Cs5, D5, ... } musicalNotes;

and then PROGMEM const musicalNotes notes[] {C5 = 425, Cs5 = 401, ...}
But I get an error with "lvalue required as left operand of assignment".
Googling about it I found out that I can't initialize it in this way, only in the declaration of the enum type. But I'm not sure how to initialize it such that it's already within PROGMEM. Adding PROGMEM to something like:
PROGMEM const enum musicalNotes = {C5=425, ...}
...doesn't work.
How can I proceed?

Thanks in advance for your answer!
 

Offline ajb

  • Super Contributor
  • ***
  • Posts: 2604
  • Country: us
Re: AVR C: which pgm_read_* function to use for enum types in PROGMEM?
« Reply #12 on: July 26, 2021, 04:47:16 pm »
You've got some fundamental misunderstandings about enums here.  In C, all an enum does is assign names to literal constants.  It's equivalent to using #define, just has slightly nicer syntax. 

So when you do:
Code: [Select]
enum musicalNotes
   {
       C5      = 425,   
       Cs5     = 401,
       D5      = 379,   
       Ds5     = 357,
      //and more...
};

That's equivalent to
Code: [Select]
#define C5 425
#define Cs5 401
//etc

Neither one actually allocates anything, they just tell the compiler that when you type "C5" you mean "405".  But if you do:
Code: [Select]
enum
   {
       C5      = 425,   
       Cs5     = 401,
       D5      = 379,   
       Ds5     = 357,
      //and more...
} musicalNotes;

You've now both told the compiler about your names for all of these values, and directed the compiler to allocate a place to store one of those values called "musicalNotes".  However, there's only one place to store one of those values.

What you should be doing depends on what you're trying to achieve.  If you just want to be able to write "C5" in your code and have that turn into "425" when it gets compiled, then you just need to define the enum and don't need to allocate any place to store it.  All of the note names will just get translated into the integer values at compilation and it all ends up in program memory implicitly because those values are hardcoded into your code. 

If you need to be able to store notes to variables, or store a sequence of notes in progmem, then you need to both define the enum values and allocate storage for them.  You would then do something like:

Code: [Select]
enum musicalNotes
   {
       C5      = 425,   
       Cs5     = 401,
       D5      = 379,   
       Ds5     = 357,
      //and more...
};
//and then...
musicalNotes someTune[] = {C5, D5,C5, Bf3};
//or...
uint16_t someTune[] = {C5, D5,C5, Bf3};

You can assign the enum values to an integer type like uint16_t because, again, enum values are just fancy names for integer constants.  As long as the integer type has enough range to store the required values, it all works.  You can decorate 'someTune' with the PROGMEM attribute, or static, volatile, whatever you need.  The nice thing about this method is that you know exactly what size is getting allocated because you told the compiler, but that comes with the downside that if you add an enum value that's outside of the range for the type you used you're going to have problems. 

One other note (heh), with short enum names like you have here, you have more opportunity for collision with other names in the application.  Might not be an actual problem, but for example if you have a seven segment display you might also want to have an enum for segments or something and then you have two different values called "C1".  So you want to be careful with the scope of those enum definitions, or alternatively define them with a prefix like:
Code: [Select]
enum musicalNotes
   {
       NOTE_C5      = 425,   
       NOTE_Cs5     = 401,
       NOTE_D5      = 379,   
       NOTE_Ds5     = 357,
      //and more...
};

Even when you don't have a specific collision to avoid this sort of notation is common because it allows you to look at the value and immediately know what it means and also which part of the application it comes from.  Other languages have nicer syntax for enum-like things, where you would refer to "C5" as "musicalNotes.C5" or similar which avoids this whole problem entirely.  Newer languages have moved past the terseness of conventional C because it makes things like this easier to understand.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf