calling all c gurus

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

I was wondering if the gurus could assist me with placing an array of struct into FLASH. Incorporating the tips from GCC and the PROGMEM Attribute and PROGMEM using structs and arrays I am still having a little trouble placing

str1

into FLASH. Compiling this example in avr-gcc produces a warning

main.c:15: warning: '__progmem__' attribute ignored
//main.cc


#include 
#include 

typedef struct
{
    const uint8_t len; 
    const char const *string;
} string_t;


void main()
{
    string_t str1 PROGMEM = {3, "abc"};
}

The goal is to store the 3 and "abc" in FLASH. Is there a way I can properly instruct avr-gcc to place str1 into FLASH? I assume this is not being done given the warning gcc is giving?

Edit: fixed spelling

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

As a guess, it needs to be a global variable. (It doesn't make much sense to have it be a local/automatic variable.)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Quote:

As a guess, it needs to be a global variable.

Spot on.

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

Or a static local.

Stefan Ernst

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

Quote:

Or a static local.

I'm trying to think whether that would ever make sense... It still takes storage. It would hide the name and give it local scope.

I suppose one could add a flash string called "signature" to every function. And then it could report where it was in an error handler?

(CodeVision disallows the local "static flash" combination. And "static const" still creates the array on the stack since "Store const in flash" only applies to global variables. So GCC swallows the syntax and makes it static local and references to it are to flash?)

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

theusch wrote:
CodeVision disallows the local "static flash" combination.
I never liked that about CodeVision. From time to time I had an array or table in flash that was used in one single function. Naturally, I wanted to define it inside that function. GCC is fine with that.

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

theusch wrote:
Quote:

Or a static local.

I'm trying to think whether that would ever make sense...
If the Flash data is used only in this single function (e.g. a lookup table in a mathematic function), then why "pollute" the global name space with it? And IMO it is good practice to keep the data and it's use as tight as possible together in such case.

Stefan Ernst

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

My findings so far.

str1 is correctly stored in flash

typedef struct
{
	const uint8_t len; 
	const char *string;
} string_t;

string_t str1 PROGMEM  = {4, "abc\0"};

void main()
{
}

str1 is correctly stored in flash

typedef struct
{
	const uint8_t len; 
	const char *string;
} string_t;

void main()
{
   static string_t str1 PROGMEM  = {4, "abc\0"};
}

str1 is not stored in flash.

typedef struct
{
	const uint8_t len; 
	const char *string;
} string_t;

void main()
{
    string_t str1 PROGMEM  = {4, "abc\0"};
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

#include 
#include  

typedef struct
{
	const uint8_t len; 
	const char *string;
} string_t;

string_t str1 PROGMEM  = {4, "abc\0"};

void main()
{
   uint8_t x;
   uint8_t getByte;

   x = pgm_read_byte(&str1.len);  //pgm_read_byte    
                                  //correctly returns 4
	
   // pgm_read_byte returns 0x0C (0x0C
   // is ascii form feed.  
   getByte = pgm_read_byte(&(str1.string[0]));
   // anticipated pgm_read_byte returning 0x61 (0x61 = 'a')
}

What is the proper way to use pgm_read_byte() to read the string? The example is close because pgm_read_byte(&str1.len) returns the correct value but pgm_read_byte(&(str1.string[0])) does not.

Edit: fixed code formatting.

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

It looks like I jumped the gun on thinking str1 is corretly being compiled and stored in FLASH.

compiling

#include 
#include  
 
typedef struct PROGMEM
{
	const uint8_t len; 
	const char *string;
} string_t ;

string_t str1 PROGMEM  = {0x99, "abcd\0"};
uint8_t PROGMEM array_progmem[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};

void main()
{}

and running avr-objdump

avr-objdump -h -S example.elf

shows

00000054 :
  54:	99 60 00

00000057 :
  57:	aa bb cc dd ee

It looks like "abcd" is not making it into FLASH but 0x99 is.

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

When you make this kind of simple code tests you have to make sure that GCC don't optimize the progmem avay, if you only use fixed index numbers it will try to find the number and put into the code to avoid using the table. So either turn off optimizer or make "real" code.

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

Quote:

When you make this kind of simple code tests you have to make sure that GCC don't optimize the progmem away

The above tests are all done with -O0. None of the code or progmem was being optimized away which is shown by array_progmem being stored in FLASH.

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

Quote:

It looks like I jumped the gun on thinking str1 is corretly being compiled and stored in FLASH.

The string within the struct is being stored in .data not in .progmem.data

You can see this by generating the .s file:

.global	str1
	.data
.LC0:
	.string	"abcd"
	.string	""
	.section	.progmem.data,"a",@progbits
	.type	str1, @object
	.size	str1, 3
str1:
 ;  len:
	.byte	-103
 ;  string:
	.word	.LC0

".LCO" is the name of the anonymous string pointer. So the 99 60 00 you saw is 0x99 for the length (correct) then 0x0060 which is a pointer to the first location in SRAM where .data resides.

You really need to read the PROGMEM article in the tutorial forum which explains why you cannot do it like this. What you need is:

#include  
#include  
  
typedef struct PROGMEM 
{ 
   const uint8_t len; 
   const char *string; 
} string_t ; 

char txt1[] PROGMEM = { "abcd\0" };
string_t str1 PROGMEM  = {0x99, txt1}; 

int main(void) 
{} 

which generates:

.global	txt1
	.section	.progmem.data,"a",@progbits
	.type	txt1, @object
	.size	txt1, 6
txt1:
	.string	"abcd"
	.string	""
.global	str1
	.type	str1, @object
	.size	str1, 3
str1:
 ;  len:
	.byte	-103
 ;  string:
	.word	txt1

or with objdump:

00000054 :
  54:   61 62 63 64 00 00                                   abcd..

0000005a :
  5a:   99 54 00 00                                         .T..

The 0x0054 in "str1" being the flash address of "txt1". To read it you need two levels of pgm_read*()

By the way my example changes the prototype of main() to get rid of the build warnings.

Oh and why do you have \0 within "abcd\0"? You do know that C will automatically add a NUL anyway so this gets abcd in memory in fact.

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

Quote:
".LCO" is the name of the anonymous string pointer. So the 99 60 00 you saw is 0x99 for the length (correct) then 0x0060 which is a pointer to the first location in SRAM where .data resides.

The 0x0054 in "str1" being the flash address of "txt1". To read it you need two levels of pgm_read*()

I looked and looked but could not find where "abcd" was being stored. Thank you for the explanation.

Another option that apparently works is changing the pointer in the struct to an empty array. I thought

const char string[]
const char *string

would be the same but I guess they are different concerning use with PROGMEM.


#include 
#include  
 
typedef struct
{
   const uint8_t len; 
   const char string[]; 
} string_t;

uint8_t PROGMEM test1[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
string_t str1 PROGMEM = {3, "abc"};

void main()
{}

objdump

00000068 :
  68:	aa bb cc dd ee            .....

0000006d :
  6d:	03 61 62 63 00            .abc.
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

but I guess they are different concerning

One declares an array. The other declares a 16 bit pointer to char.

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

Since

char *a = "abc";
a[0] = 'z';

char b[] = "abc";
b[0] = 'z';

are both legal, can you comment on the differences? Is one more efficient in speed or in size than the other? Is there a guideline when to select either one?

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

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Your usage was inside a struct:

typedef struct {
 char * ptr;
} struct_type;

This only creates a 16 bit pointer it assigns no storage for the thing that is being pointed at. It would be quite valid to then:

char fred[] = "hello";
struct_type mystruct = { .ptr = fred };

The first line assigns 6 bytes of storage to hold the characters (and NUL) and the second assigns 2 bytes of storage to hold the address of the 1st object. While you can use:

struct_type mystruct = { .ptr = "hello" };

this leads to the creation of an anonymous array holding "hello" outside the struct then creates a 2 byte struct that just holds the address of that anonymous array.

When you used:

typedef struct {
 int n;
 char arr[];
} struct_type;

struct_type mystruct = { .arr = "hello" };

You were lucky on two counts. First that the struct had some other member besides the array. If it had only the array you would have seen:

test.c:6: error: flexible array member in otherwise empty struct

and second you happened to position your "flexible array member" as the last element. If you had used:

typedef struct {
 char arr[];
 int n;
} struct_type;

struct_type mystruct = { .arr = "hello" };

you would have seen:

test.c:5: error: flexible array member not at end of struct

C recognises that you often have packages of information with a "fixed" layout header and then variable amounts of data following (such as your length then variable array) so it allows for that special case in a struct but the rules are that there should be at least one fixed length field in the struct and that the variable entity must be the very last member in the struct and that there can only be one.

So you just stumbled upon something that works. In the .s file for the {n; arr[]} case you will see:

.global	mystruct
	.data
	.type	mystruct, @object
	.size	mystruct, 2
mystruct:
 ;  arr:
	.skip 2,0
	.string	"hello"

By studying this Asm code that the C compiler generated you can see how it was "thinking". It's treating mystruct as 2 bytes long (the "skip" is because I gave no initial value to element 'n') and it just so happens that there's then a (variable length) array of data bolted onto the end. It guarantees that this will be the next thing in memory (as long as they all have the same storage location)

To be honest you are better off, when including char arrays in structs, to pre-allocate the size unless you can guarantee that there is (a) only going to be one variable sized array and (b) it must be at the end.

typedef struct {
 char arr[10];
 int n;
} struct_type;

struct_type mystruct = { .n=5, .arr="hello" };

generates:

.global	mystruct
	.data
	.type	mystruct, @object
	.size	mystruct, 12
mystruct:
 ;  arr:
	.string	"hello"
	.skip 4,0
 ;  n:
	.word	5

The struct is then 12 bytes long so if you dump this into PROGMEM then *everything* in it goes there.

(I just re-ordered things in the struct to show that (a) it doesn't matter where the array is located any more and (b) when you initialise it using .file= notation you can list the initialisers in whatever order you like)

Cliff

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

jheissjr wrote:
Since

char *a = "abc";
a[0] = 'z';

char b[] = "abc";
b[0] = 'z';

are both legal,

No, the first is NOT legal.

Stefan Ernst

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

sternst wrote:
jheissjr wrote:
Since

char *a = "abc";
a[0] = 'z';

char b[] = "abc";
b[0] = 'z';

are both legal,

No, the first is NOT legal.

Interesting! I'm always keen on learning new stuff C'ish, and one of my pet C rants is about the indexed notation only being syntactic sugar for pointer arithmetic.

Care to expand/elaborate on the illegality, Stefan?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

Quote:

No, the first is NOT legal.

Is "behavior is undefined" the same as "NOT legal"?
Quote:

§6.7.8 ...
EXAMPLE 8 The declaration
char s[] = "abc", t[3] = "abc";
defines ‘‘plain’’ char array objects s and t whose elements are initialized with character string literals.

This declaration is identical to
char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

The contents of the arrays are modifiable. On the other hand, the declaration
char *p = "abc";
defines p with type ‘‘pointer to char’’ and initializes it to point to an object with type ‘‘array of char’’ with length 4 whose elements are initialized with a character string literal. If an attempt is made to use p to modify the contents of the array, the behavior is undefined.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Brilliant, Lee!

See, if I tickle you just a wee bit I get away with you Reading The Fine Manual for me. :D

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

theusch wrote:

Is "behavior is undefined" the same as "NOT legal"?
What else is "behavior is undefined" than a more polite form of "Don't do it!"?

Stefan Ernst

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

Quote:

No, the first is NOT legal.

The more normal use is something like this for debugging:

char * colornames[] = { "red", "green", "blue" };

printf("color is %s\n", colornames[color]);

That (read only) use of anonymous arrays with an array of array pointer(s) is fine.