Processing Struct variable from a pointer yields different results in LC than in UNO

Status
Not open for further replies.

flashbac

New member
Hello everyone,

I'm a newbie trying to port over code for my Atmega328P to a Teensy LC.

I have the following code running fine on my Atmega328P. Pretty much what it does is creates a Struct Variable and prints the variable's raw values to serial (To console for now, but later to another device via Serial1).
Code:
typedef struct zdo_simple_desc_resp_header_t {
  uint8_t     status;
  uint16_t    network_addr_le;
  uint8_t     length;
} zdo_simple_desc_resp_header_t;

typedef struct zdo_simple_desc_header_t {
  uint8_t     endpoint;
  uint16_t    profile_id_le;
  uint16_t    device_id_le;
  uint8_t     device_version;
} zdo_simple_desc_header_t;

void setup() {

  Serial.begin(9600);
  while(!Serial);
  Serial.println("Started!");

  struct {
    uint8_t                 transaction;
    struct zdo_simple_desc_resp_header_t  header;
    struct zdo_simple_desc_header_t     descriptor;
    uint8_t                 clusters[4];
  } rsp;

  rsp.transaction = 0x01;
  rsp.header.status = 0x02;
  rsp.header.network_addr_le = 0x0403;
  rsp.header.length = 0x05;
  rsp.descriptor.endpoint = 0x06;
  rsp.descriptor.profile_id_le = 0x0807;
  rsp.descriptor.device_id_le = 0x0a09;
  rsp.descriptor.device_version = 0x0b;
  rsp.clusters[0] = 0x0c;
  rsp.clusters[1] = 0x0d;
  rsp.clusters[2] = 0x0e;
  rsp.clusters[3] = 0x0f;

  uint8_t* resp = &(rsp.transaction);

  for (uint8_t i = 0; i < 15; i++)
  {
    Serial.println(resp[i], HEX);
  }
  }

void loop() {
  // put your main code here, to run repeatedly:

}

Everything compiles fine on both Atmega328P and Teensy LC. However, the serial output is different.

On Atmega328P I get what I expect:
Code:
Started!
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F

On the Teensy LC i get:
Code:
Started!
1
0
2
61
3
4
5
0
6
0
7
8
9
A
B

The reason for this code is that the library that I'm using expects me to pass the Struct variable's values as a uint8_t pointer or array to another function so it can be sent via Serial to another connected device. But as you can see, I get different results depending on the platform.

Thanks in advance!
 
Read up on byte padding and structure member alignment. If you really care about how data is aligned and need it to stay aligned across different devices, you should really define an unsigned char array and assign the values to and from the array yourself.
 
The issue is how C/C++ Packs the variables in memory... That is typically when you define something like uint16_t it wants to be aligned a 16 bit boundary, likewise a 32 bit variable wants to be aligned to a 32 bit boundary...

So by default:
Code:
typedef struct zdo_simple_desc_resp_header_t {
  uint8_t     status;
  uint16_t    network_addr_le;
  uint8_t     length;
} zdo_simple_desc_resp_header_t;
The compiler is going to skip a byte between status and network_addr_le...

You might try something like: Serial.println(sizeof(zdo_simple_desc_resp_header_t));

That is why you will see a lot of header files that use #pragma pack...

Example with your structure you could do:

Code:
#pragma pack(push, 1); // no padding
typedef struct zdo_simple_desc_resp_header_t {
  uint8_t     status;
  uint16_t    network_addr_le;
  uint8_t     length;
} zdo_simple_desc_resp_header_t;
#pragma pack(pop)

Some more recent compilers added the ability through using __attribute__((packed))

Code:
typedef struct  __attribute__((packed)) zdo_simple_desc_resp_header_t {
  uint8_t     status;
  uint16_t    network_addr_le;
  uint8_t     length;
} zdo_simple_desc_resp_header_t;
 
In C++ you can't count on the alignment of the elements of a struct. It is more efficient for "uint16_t network_addr_le;" to be aligned on a 16-bit word boundary, so that's what the compiler does.
I added code to print the sizeof the struct:
Code:
sizeof(zdo_simple_desc_resp_header_t) = 6
It's not 4 as you would expect.
The Atmega328p is an 8-bit machine, so alignment doesn't matter as much.

Pete
P.S. Sorry folks - I took too long to type this and two of you beat me to it:)
 
Thanks everyone for the excellent explanation!!!

Now I know why the library uses
Code:
#define PACKED_STRUCT
, for Arduino it defines to just struct. The library uses
Code:
#define PACKED_STRUCT   struct __attribute__((packed))
for other platforms, once I implemented this, now it works perfectly.
 
Note of course if you have a packed structure, depending on the machine, the code to access the structure elements may be horrible.

I've seen some machines that would do the equivalent of memcpy to move the 4 bytes to a stack temporary, and then do a 4 byte load on the value. Or perhaps do a sequence of load byte, shifts, and ors.
 
Status
Not open for further replies.
Back
Top