Can I put class/struct objects in PROGMEM?

Go To Last Post
19 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First, thanks for the warm welcome (I know you would if given the chance. ;) ) Second, apologies if this is the wrong section. I'm working with an Arduino (and tool chain) but this does seem like a compiler question.

 

Actually, I know the answer to this question. :D I'm pretty sure I've got class objects in PROGMEM. I just can't seem to get them out.

 

// fun with PROGMEM

#include <avr/pgmspace.h>

#define line {Serial.print(l++);Serial.print(" ");}
typedef unsigned const PROGMEM int prog_uint16_t;

int iRAM=11;
struct A {
public:
  prog_uint16_t    a;
  A(int x):a(x){};
  const prog_uint16_t PROGMEM * getAaddr() const { return &a; }
};

prog_uint16_t PROGMEM  iFLASH=22;

A const PROGMEM a(33);


void setup() {
  int l=1;
  Serial.begin(115200);
  delay(2000); //alow some time to open serial console
  
  line; Serial.print(F("single  int/RAM value    ")); Serial.println(iRAM);
  line; Serial.print(F("single  int/RAM address  ")); Serial.println((int)&iRAM);
  Serial.println("");
  line; Serial.print(F("single  int/PROGMEM value/RAM   ")); Serial.println(iFLASH);
  line; Serial.print(F("single  int/PROGMEM address     ")); Serial.println((int)&iFLASH);
  line; Serial.print(F("single  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word(&iFLASH));
  Serial.println("");
  line; Serial.print(F("class  int/PROGMEM value/RAM   ")); Serial.println(a.a);
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a.a);
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)a.getAaddr());
  line; Serial.print(F("class  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word((const int PROGMEM *) &a.a));
  line; Serial.print(F("class  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word(a.getAaddr()));

}

void loop() {

}

Here's the result. Compiled and run using the 1.5.8 version of the IDE the results I get (in the console window) are:

1 single  int/RAM value    11
2 single  int/RAM address  256

3 single  int/PROGMEM value/RAM   22
4 single  int/PROGMEM address     415
5 single  int/PROGMEM value/Flash 22

6 class  int/PROGMEM value/RAM   17967
7 class  int/PROGMEM address     417
8 class  int/PROGMEM address     417
9 class  int/PROGMEM value/Flash 0
10 class  int/PROGMEM value/Flash 0

Lines 1 and two are just for reference (and give me some idea where a RAM address might wind up.) Line 3 was unexpected. I did not think I could directly access PROGMEM variables directly, but perhaps if it is close enough...

 

Line 6 looks good - not even close to what the variable should be. Line 7 and 8 make sense compared to line 4. Lines 9 and 10 let me down. I should be reading back 33. :( I've tried a couple different initializers for A.a and none seems to work any better.

 

Is there any way to place entire structs in PROGMEM?

 

thanks!

 

 

Edit: I find that if I eliminate the constructor and initialize thusly:

const A  a PROGMEM = {15};

I get the results I was hoping for.

 

Maybe this should be in "compilers."

 

Edit.2: https://gcc.gnu.org/bugzilla/sho...

 

hbarta@cypress:~$ arduino-1.5.8/hardware/tools/avr/bin/avr-g++ --version
avr-g++ (GCC) 4.8.1
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

hbarta@cypress:~$ 

Fixed in 4.9

 

Last Edited: Sat. Jan 10, 2015 - 03:53 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

put in program memory on an AVR can use the const directive.

In C, I've always copied the struct to RAM because some of the stuct members are variables.

 

I use a lot of C++. I confess I've never seen/used 'public' used within a C struct.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

stevech wrote:
I use a lot of C++. I confess I've never seen/used 'public' used within a C struct.

Neither have I.

 

Seems you can get a simulated private region in a struct though.  Interesting find http://stackoverflow.com/questions/3824329/partitioning-struct-into-private-and-public-sections

"I may make you feel but I can't make you think" - Jethro Tull - Thick As A Brick

"void transmigratus(void) {transmigratus();} // recursio infinitus" - larryvc

"It's much more practical to rely on the processing powers of the real debugger, i.e. the one between the keyboard and chair." - JW wek3

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." -  Marcus Aurelius

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

stevech wrote:

put in program memory on an AVR can use the const directive.

In C, I've always copied the struct to RAM because some of the stuct members are variables.

 

I use a lot of C++. I confess I've never seen/used 'public' used within a C struct.

 

Hi Steve,

In the actual app these are all constant and so there is no need to copy to RAM for the purpose of modification. In fact, I thought that by simply declaring these as const would simply put them in the program section and got a rude awakening when I ran out of RAM on my project. sad A little research revealed the non-uniform address space and thus the need for PROGMEM macro and special procedures to access the values.

 

As for public declaration within a struct... If you look at my code I'm sure you'll see a lot of things you have never seen before. smiley There are probably several artifacts in the code that result from my thrashing about for a solution, trying different things like 1.0.6 IDE vs. 1.5.8 and also class vs. struct. Of course members of a struct are public by default so the label is redundant. At one point that was a class declaration. In order for that style of initializer to work, the members must be public.

 

thanks,

hank

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

__flash has replaced PROGMEM. Just make the structure "const __flash" and there's no need for pgm_read*()  access any more. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

__flash has replaced PROGMEM. Just make the structure "const __flash" and there's no need for pgm_read*()  access any more. 

Is that for a newer version of GCC than what Arduino uses or did I do it wrong:

void setup() {<br />
  // put your setup code here, to run once:</p>
<p>}</p>
<p>const __flash int x;</p>
<p>void loop() {<br />
  // put your main code here, to run repeatedly:</p>
<p>}

 

Build options changed, rebuilding all</p>
<p>sketch_jan10b.ino:6:7: error: '__flash' does not name a type<br />
Error compiling

I see it listed here but can't seem to find what version this is applicable to.

 

It would be nice.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your OP shows that you're using the 1.5.8 beta of the Arduino IDE, which ships with AVR GCC 4.8.1, which does in fact support __flash.  However, AVR GCC supports __flash in C source only, not in C++ source.  The Arduino IDE uses avr-g++ to build your sketch as C++ source.  I don't believe there is a way around this, but smarter freaks will surely come after me ;)

 

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Sat. Jan 10, 2015 - 06:48 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Oh sorry. Hadn't realised this was Arduino/C++. Ignore the __flash thing - as Joey says it's for C only.

 

For C++ it has to be "const PROGMEM" and will need to be accessed with pgm_read*() functions.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One more strange point.

When i put in the following line:

 line; Serial.print(F("class  int/PROGMEM value/RAM   ")); Serial.println(a.a);
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a.a);
    
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a);   //<<<<<<<<<<<<<<
  

  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)a.getAaddr());

i get the estimated value of "33" for PROGMEM value/RAM instead of "17967" ! Why ?

 

When i comment out this line, i get the "33" for one or two starts, then the 17967" without recompiling.

 

Here are my (not tested!) expectations:

 

if you have a line  x=3;  in yout code, the code is "burned" into flash. No sram values are sent during flashing.

There must be a software part in init() which reserves space for x and fill it with the value 3 after starting the program.

It seems that your code has a "trick" to give this error.

 

This brings me to the question: when is a class (struct could be replaced by class here ) initialized:

In flash it is not possible!

A const PROGMEM a(33);

could not work for a class in flash - it is done at program start, but it doesn't.

 

The inserted line has nothing to do with the contents of "a", but it changed behavior - maybe a compiler error ?

 

Back to your question:

Writeable varables of classes MUST be in sram, but functions and pointers to them not. So: a class with one variable and 100 functions: is the total class (as instance) copied to

sram ? And what is about  const variables ?

It is possible to find the answer yourself, but maybe an expert can answer this question.

 

Last Edited: Tue. Mar 17, 2015 - 11:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

if you have a line  x=3;  in yout code, the code is "burned" into flash. No sram values are sent during flashing.

There must be a software part in init() which reserves space for x and fill it with the value 3 after starting the program.

It seems that your code has a "trick" to give this error.

For a line such as that the 3 will be part of an AVr opcode. Probably something like:

LDI R24, 3

"x" as such probably won't exist exact that it's temporarily the name of R24. If, however you wrote:

int x = 3;

int main(void) {
    
}

then in this case a byte holding 3 would be put in the .data section in flash. When the C code starts up the C run time (CRT) would include a call to _do_copy_data() and that would copy this byte (and anything else in the flashed copy of .data to a final resting place for x and .data in SRAM (probably at 0x0060 or 0x0100). Any code in the program that accessed "x" would then do something like "LDS r24, x" where x in this case resolves to be 0x0060 or 0x0100 or some offset within .data that starts at one of those addresses.

 

Most of your questions are easily answered by experiment. Just add a -save-temps to your compile flags then study the .s file that is generated. As an alternative use the "avr-objdump -S proj.elf > proj.lss" and sutdy it in the .lss file.

 

Personally I prefer the .s as it is true source not disassembled object and that's why I wrote this to make studying .s even easier:

 

https://spaces.atmel.com/gf/proj...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Do you know, how

Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a);   //<<<<<<<<<<<<<<

changes the output of the Serial.print 2 lines above ?

 

Last Edited: Tue. Mar 17, 2015 - 11:23 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry I have no idea what you are talking about. What "2 lines above"?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
  line; Serial.print(F("class  int/PROGMEM value/RAM   ")); Serial.println(a.a);
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a.a);
    
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a);   //<<<<<<<<<<<<<<
  

Adding the red line to the program (post#1) changes the output of the green line  from     17967    to        33     at the first run,  that means the red line influences the  output of the green one

before the red line is executed! It must have to do with the initialisation of the class and i hoped an expert can explain the differences.

Last Edited: Wed. Mar 18, 2015 - 07:04 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

in C++ a struct IS a class, except it's public by default whereas a class is private.

Just use a class please???

 

you have the cart way ahead of the horse.

Use some white space, you are not running out of memory on your PC.

 

Your code is too clever for me to figure out, make it simpler / more obvious

Keith Vasilakes

Firmware engineer

Minnesota

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is the program of HankB, not from me, but i found the result as strange as he does.

 

I tested it and got the same strange result in the line marked  different output from this line.

 

First of all i changed   struct    to     class     because this struct seems strange to me.

Same result.

 

This line sets  up the     class  A instance a    and sets  the value of a.a  to 33

But the HankB defined the public member    a.a    as  a const value in PROGMEM:

So what will happen ?

To change the value a.a to 33 it must reside in ram.

 

Depending on other lines in the program (which do not contact a.a in any way)   a.a   has different values and that seems strange to me too.

 

 

This line prints the value of    a.a      as   17967

 

I experimented and try to understand.

 

If you delete or add lines the red line will give other results.

 

If you comment out this line. the red line prints the value of  a.a   as    8296

 

Finally if you comment out the brown line you get the result    of   33 , which you could expect from the beginnig from a "normal" class.

 

It is obious, that the green and brown line should not change the value of   a.a   printed before.

This is the mystery HankB presented and i do not have a serious explanation until now. With HankB i would say:  "but this does seem like a compiler question."

Maybe somebody can help.

 

// fun with PROGMEM

#include <avr/pgmspace.h>

#define line {Serial.print(l++);Serial.print(" ");}
typedef unsigned const PROGMEM int prog_uint16_t;

int iRAM=11;
class A {
public:
  prog_uint16_t    a;
  A(int x):a(x){};
  const prog_uint16_t PROGMEM * getAaddr() const { return &a; }
};

prog_uint16_t PROGMEM  iFLASH=22;

A const PROGMEM a(33);


void setup() {
  int l=1;
  Serial.begin(115200);
  delay(2000); //alow some time to open serial console
  
  line; Serial.print(F("single  int/RAM value    ")); Serial.println(iRAM);
  line; Serial.print(F("single  int/RAM address  ")); Serial.println((int)&iRAM);
  Serial.println("");
                                                                                               //1. test:    run it as is
    line; Serial.print(F("single  int/PROGMEM value/RAM   ")); Serial.println(iFLASH);         //2. test:    comment out this line
    line; Serial.print(F("single  int/PROGMEM address     ")); Serial.println((int)&iFLASH);   //3. test:    comment out this line
  line; Serial.print(F("single  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word(&iFLASH));
  Serial.println("");
  
  // different output from this line for all three tests:
    line; Serial.print(F("class  int/PROGMEM value/RAM with different results   ")); Serial.println(a.a);
  
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a.a);
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)a.getAaddr());
  line; Serial.print(F("class  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word((const int PROGMEM *) &a.a));
  line; Serial.print(F("class  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word(a.getAaddr()));

}

void loop() {

}

 

Last Edited: Wed. Mar 18, 2015 - 11:15 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

heweb wrote:
...

To change the value a.a to 33 it must reside in ram.

...

A const PROGMEM a(33);

I believe that that style of constructor does not require the assignment of 33 to the variable a.a but rather it is constructed at compile time with the value of 33. In that regard the entire struct can be placed on non-modifiable memory.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well one thing there is the use of prog_uint16_t - that's surely a typedef with an attribute? They only used to work by a fluke (you aren't supposed to be able to attribute a typedef) and in the recent compiler/library they have been deprecated. Use "const uint16_t PROGMEM" or (possibly better) "const __flash uint16_t" these days.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"const __flash uint16_t" did not compile (unknown type)

 

Removing "typedef" and using "const uint16_t PROGMEM" gives the same strange results:

 

// fun with PROGMEM

#include <avr/pgmspace.h>

#define line {Serial.print(l++);Serial.print(" ");}
//typedef unsigned const PROGMEM int prog_uint16_t;

int iRAM=11;
class A {
public:
  //prog_uint16_t    a;
  const uint16_t PROGMEM a;
  A(int x):a(x){};
  //const prog_uint16_t PROGMEM * getAaddr() const { return &a; }
  const uint16_t PROGMEM *  getAaddr() const { return &a; }
};

//prog_uint16_t PROGMEM  iFLASH=22;
const uint16_t PROGMEM iFLASH=22;

A const PROGMEM a(33);


void setup() {
  int l=1;
  Serial.begin(115200);
  delay(2000); //alow some time to open serial console
  
  line; Serial.print(F("single  int/RAM value    ")); Serial.println(iRAM);
  line; Serial.print(F("single  int/RAM address  ")); Serial.println((int)&iRAM);
  Serial.println("");
                                                                                               //1. test:    run it as is
    line; Serial.print(F("single  int/PROGMEM value/RAM   ")); Serial.println(iFLASH);         //2. test:    comment out this line
    line; Serial.print(F("single  int/PROGMEM address     ")); Serial.println((int)&iFLASH);   //3. test:    comment out this line
  line; Serial.print(F("single  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word(&iFLASH));
  Serial.println("");
  
    // different output from this line for all three tests:
    line; Serial.print(F("class  int/PROGMEM value/RAM with different results   ")); Serial.println(a.a);
  
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)&a.a);
  line; Serial.print(F("class  int/PROGMEM address     ")); Serial.println((int)a.getAaddr());
  line; Serial.print(F("class  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word((const int PROGMEM *) &a.a));
  line; Serial.print(F("class  int/PROGMEM value/Flash ")); Serial.println(pgm_read_word(a.getAaddr()));

}

void loop() {

}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"const __flash uint16_t" did not compile (unknown type)

Sounds like your compiler is out of date. What do you get from "avr-gcc -v"? You need at least a 4.7.x to have "__flash". The Atmel distribution most of us are using at present is 4.8.1