AVR C: which pgm_read_* function to use for enum types in PROGMEM? - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: December 01, 2016, 12:47:44 am
content: 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: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: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 thepgm_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 usepgm_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 aboutsizeof(), but I feel hard-coding a mapping between enum size in bytes andpgm_read_*functions is a bit... yucky. I'm hoping that's not my only choice. Something defined at compile-time would be nice.

author: sleemanj
author_rank: Super Contributor
timestamp: December 01, 2016, 01:42:21 am
content: memcpy_P ?

author: HwAoRrDk
author_rank: Super Contributor
timestamp: December 01, 2016, 02:26:31 am
content: 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:my_enum_t elem;memcpy_P(&elem, &blah, sizeof(my_enum_t));

author: sleemanj
author_rank: Super Contributor
timestamp: December 01, 2016, 03:37:28 am
content: Quote from: HwAoRrDk on December 01, 2016, 02:26:31 amYes pretty sure that should work.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: December 01, 2016, 10:02:56 pm
content: That doesn't seem to work.I just end up with a random garbage value inelemthat'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, sosizeof(my_enum_t)in my case is actually 1 byte, and I would have been wrong to blindly go ahead and usepgm_read_word().

author: sleemanj
author_rank: Super Contributor
timestamp: December 01, 2016, 11:57:01 pm
content: Quote from: HwAoRrDk on December 01, 2016, 10:02:56 pmWorks 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: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, 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() { }OutputCode:Sizeof the enum is: 2 BytesFOOBARNARFZORTPOINTFNAGLE

author: HwAoRrDk
author_rank: Super Contributor
timestamp: December 02, 2016, 04:18:15 pm
content: 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 asconstorstatic. 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 addedconstand 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 swappedmemcpy_Pout for regularmemcpy. Suddenly it works!So my arrayisn'tbeing 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 missingconst.)Why?!?DoesPROGMEMonly work on global variables? My array is a local variable inside a function.

author: richardman
author_rank: Frequent Contributor
timestamp: December 02, 2016, 09:27:13 pm
content: Quote from: HwAoRrDk on December 02, 2016, 04:18:15 pmOK, 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:-)

author: sleemanj
author_rank: Super Contributor
timestamp: December 02, 2016, 10:06:28 pm
content: Quote from: HwAoRrDk on December 02, 2016, 04:18:15 pmYes 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.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: December 03, 2016, 12:50:07 am
content: Ahhhh, I see. Thanks guys for the explanation.I'll try specifying the local variable asstatic const, but if that doesn't work, I'll have to re-arrange to make the array global.

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: December 03, 2016, 01:53:57 am
content: Did you check for compiler warnings?Code:int main(void){my_enum_t blah[] PROGMEM = { FOO, BAR, BAR, FOO};avr-gcc...warning: '__progmem__' attribute ignoredBuild succeeded with1Warnings...Code:my_enum_t blah[] PROGMEM = { FOO, BAR, BAR, FOO};int main(void){avr-gcc... Build succeeded with0Warnings...

author: builder01
author_rank: Newbie
timestamp: July 26, 2021, 01:36:43 pm
content: 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!

author: ajb
author_rank: Super Contributor
timestamp: July 26, 2021, 04:47:16 pm
content: 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:enum musicalNotes{C5 = 425,Cs5 = 401,D5 = 379,Ds5 = 357,//and more...};That's equivalent toCode:#define C5 425#define Cs5 401//etcNeither one actually allocates anything, they just tell the compiler that when you type "C5" you mean "405". But if you do:Code: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: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: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.