Pointer to union misbehave

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

Hi all,

 

I have defined a union which stores data in the flash memory:

__attribute__((used, section(".romtable"))) const union {
 uint16_t A;
 uint8_t B;
 } rom[] PROGMEM = { ... data ... };

I have read in a book, that in C an increment of a pointer always points to the next element regardless of the array type. Now, I have defined the pointer as:

 const uint8_t *pnt = &rom->B;

​It works, but I have to increment the pointer by two in order to get the next element. What am I doing wrong? - or is unions a special case where pointers should misbehave by design laugh

 

This topic has a solution.
Last Edited: Wed. Apr 19, 2017 - 06:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How can you have:

section(".romtable")

and

PROGMEM

at the same time?!?

 

PROGMEM actually expands out to be:

__attribute__((progmem))

but in effect this really means:

__attribute__((section(".progmem")))

so it appears you are trying to assign this data to both ".romtable" (which is what I guess you want) and ".progmem" (which you almost certainly do not want) at the same time.

 

Did you think that "PROGMEM" just means "in flash" perhaps? The linker is going to know that anyway when you give an address to ".romtable". If it is in the range 0x000000 to 0x7FFFFF then linker knows this means "flash" anyway. You don't need "PROGMEM" to say the same. If it is 0x00800000 to 0x0080FFFF the linker knows this means "RAM". If it is 0x00810000 to 0x0081FFFF it knows you mean "EEPROM" and so on.

 

As for your pointer. I would do this properly (with typedef):

typedef union {
 uint16_t A;
 uint8_t B;
} myUnionType;

__attribute__((used, section(".romtable"))) const myUnionType rom[] = { .. };

const myUnionType * pnt;

pnt++;

Now when you increment "pnt" it will step on by 2 bytes (ie sizeof(myUnionType) each time because the way C knows how much to move a pointer on by when it comes to "++" is because you created the pointer with:

const myUnionType *

so pnt is a pointer to myUnionType which is a "pointer to something that is 2 bytes in size". What you have at the moment is:

 const uint8_t *pnt 

well sizeof(uint8_t) is 1. So when the compiler comes to pnt++ it will add one byte to the contents of the pointer.

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

mikeraw wrote:

​It works, but I have to increment the pointer by two in order to get the next element. What am I doing wrong? - or is unions a special case where pointers should misbehave by design laugh

 

 

You don't fully understand pointers as @clawson said. You did read correctly, however you tell the compiler what the array type is, it can't guess for you.

The compiler does not know what you have in memory, it knows what you tell it.

By doing

const uint8_t *pnt = &rom->B;

You just told it at that memory location you have uint8's. So it will happily increment the pointer so you get the next uint8.

 

If you have 

typedef struct {
    uint8_t filler[500]
} tFiller;

tFiller* ptr = 0x10000;

The compiler will know at that memory location you have a tFiller structure, the next increment (ptr++) will increase the location with 500, so the pointer will be at addr+500, because you want the next tFiller (even if you don't have a tFiller struct there)

However we know better what we want so we could do

 

// Same memory address as our tFiller array
//  Treat it as an array of uint8's
uint8_t* ptr2 = 0x10000;
ptr2++; // new addres 0x10001 (0x10000 + sizeof(uint8_t))

// Same memory address as our tFiller array
//  Treat it as an array of uint16's
uint16_t* ptr3 = 0x10000;
ptr3++; // new address 0x10002 (0x10000 + sizeof(uint16_t))

// Same memory address as our tFiller array
//  Treat it as an array of uint32's
uint32_t* ptr4 = 0x10000;
ptr4++; // new address 0x10004 (0x10000 + sizeof(uint32_t))

// tFiller pointer
ptr++; // new address 0x10500 (0x10000 + sizeof(tFiller))

 

Pointer++ increments with the size of the pointer type, that is the only thing the compiler knows about that address. In your case sizeof(uint8_t), in the case of tFiller it is sizeof(tFiller)

Last Edited: Tue. Mar 14, 2017 - 03:22 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mikeraw wrote:

__attribute__((used, section(".romtable"))) const union {
 uint16_t A;
 uint8_t B;
 } rom[] PROGMEM = { ... data ... };

A union type has to be big enough to hold the biggest "variant" - which is uint16_t in the above; ie, two bytes.

 

I have read in a book, that in C an increment of a pointer always points to the next element regardless of the array type.

Correct. And not just for arrays.

 

 

Now, I have defined the pointer as:

 const uint8_t *pnt = &rom->B;

So you have declared a pointer to uint8_t - which is a single byte.

Therefore, incrementing this pointer will cause it to point to the next byte.

 

I have to increment the pointer by two in order to get the next element.

Of course you do - because, as noted above, the size of your union is two bytes!

 

As Cliff said, doing it properly with typedefs would help make things clearer ...

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

I am unclear if the OP really wants a union or a struct?

Are A and B to be two separate entities or is B to be a part of A?

It would make more sense (to me at least) if the union were defined something like:

union {
    uint16_t A;
    uint8_t B[2];
}

or perhaps:

union {
    uint16_t A;
    struct {
        uint8_t B;
        uint8_t C;
    }
}

or was the intent really to be a three byte entity:

struct {
    uint16_t A;
    uint8_t B;
}

Guess we will have to wait for OP to respond...

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

Hi, let me answer you, even though it is while ago. B should be a part of A. I want to read the lower byte of A as B.

 

I havn't tried the suggestions in the other answers yet, but typedef seems to be the way to go. Thanks.

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

Hi guys,

 

I was trying to finish this task today, but I don't get it to work. I follow the suggestion from clawson (#2):

typedef union {
 uint16_t A;
 uint8_t B;
} myUnionType;

__attribute__((used, section(".romtable"))) const myUnionType rom[] = { .. };

const myUnionType * pnt;

pnt++;

But how does the compiler know, that pnt is pointing to rom[]? I could have multiple of these, right? E.g.:

__attribute__((used, section(".romtable"))) const myUnionType rom[] = { .. };
__attribute__((used, section(".romtable2"))) const myUnionType rom2[] = { .. };

​If I try to give the pointer the address, I get the warning "initialization from incompatible pointer type", I have tried both of these ways:

const myUnionType *pnt;
pnt = &rom;
//or
pnt = &rom->A;

I simply don't understand how to initialize the pointer in the right way. Can anyone explain to me, how to do this? Also, how to get the pointer to point to the N'th element and get the value of rom->B.

 

Thanks

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mikeraw wrote:
But how does the compiler know, that pnt is pointing to rom[]?
Why does it need to? A pointer (an "address") is just some number between 0 and 65535 isn't it? When the final access is made to the data it is THEN that you need to know that the 0..65535 is a ROM not a RAM address and you then dereference the pointer accordingly. If you want to give the compiler a hand with this use "__flash". If you have something like:

const __flash myUnionType * pnt;

Then when you try to access *pnt or pnt[5] or whatever the compiler knows this read needs to be made using LPM not LD/LDS as the "__flash" tells it that the pointer target is ROM not RAM.

 

Anyway, forget all that. If I try to put together a whole program based on what you have said you are trying...

#include <avr/io.h>

typedef union {
 uint16_t A;
 uint8_t B;
} myUnionType;

__attribute__((used, section(".romtable"))) const myUnionType rom[] = 
{ 
	12345,
	22222
};

const myUnionType * pnt;

int main(void){
	pnt = rom;
	TCNT1 = pnt[1].A;
	PORTD = pnt[0].B;
	return 0;
}

then it builds without error:

C:\SysGCC\avr\bin>avr-gcc -mmcu=atmega16 -Os avr.c -o avr.elf

C:\SysGCC\avr\bin>

and the code generated is:

        TCNT1 = pnt[1].A;
  88:   8e ec           ldi     r24, 0xCE       ; 206
  8a:   96 e5           ldi     r25, 0x56       ; 86
  8c:   9d bd           out     0x2d, r25       ; 45
  8e:   8c bd           out     0x2c, r24       ; 44
        PORTD = pnt[0].B;
  90:   89 e3           ldi     r24, 0x39       ; 57
  92:   82 bb           out     0x12, r24       ; 18

where 0x56CE is 22,222 and 0x39 is the low byte of 0x3039 which is 12,345.

 

In fact for completeness this is the __flash version...

        pnt = rom;
  7c:   26 ea           ldi     r18, 0xA6       ; 166
  7e:   30 e0           ldi     r19, 0x00       ; 0
  80:   30 93 61 00     sts     0x0061, r19
  84:   20 93 60 00     sts     0x0060, r18
        TCNT1 = pnt[1].A;
  88:   88 ea           ldi     r24, 0xA8       ; 168
  8a:   90 e0           ldi     r25, 0x00       ; 0
  8c:   fc 01           movw    r30, r24
  8e:   85 91           lpm     r24, Z+
  90:   95 91           lpm     r25, Z+
  92:   9d bd           out     0x2d, r25       ; 45
  94:   8c bd           out     0x2c, r24       ; 44
        PORTD = pnt[0].B;
  96:   f9 01           movw    r30, r18
  98:   84 91           lpm     r24, Z
  9a:   82 bb           out     0x12, r24       ; 18

In this you can see the data being accessed using LPM. The ONLY line I change in the source to achieve this was:

const __flash myUnionType * pnt;

Have fun.

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

Thanks a lot clawson, now it works.

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

mikeraw wrote:
Thanks a lot clawson, now it works.

Pointer to union misbehave

Gotta change that thread title to Ain't Misbehavin' -- https://en.wikipedia.org/wiki/Ai...(musical)

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.