Electronic – What’s wrong with this casting in C code for AVR


I defined two variables:

uint8_t a[2];
uint16_t b;

Next I want to use a as variable of type uint16_t, e. g.

b = (uint16_t)a;

But this is wrong! My programs doesn't works correctly with such code. All is OK when I replace b to uint8_t b[2] and use elementwise operations.


Best Answer

a is a pointer to an array of bytes. If you cast it to a uint16_t and assign it to b, then b will contain the address of the base of the array (where it is stored) in SRAM. If you want to treat the two bytes of the array a as an integer, then use a union as suggested by user14284, but be aware that the union will represent the byte array in the memory byte ordering of the architecture (in AVR that would be little-endian, which means byte 0 is the least significant byte). The way to write that in code is:

  uint8_t a[2];
  uint16_t b;
} x;

x.b[0] = 0x35;
x.b[1] = 0x4A;

// by virtue of the above two assignments
x.a == 0x4A35 // is true

Another way to do this without using a union is to cast a to a uint16_t pointer and then dereference it like so:

uint8_t a[2] = {0x35, 0x4A};
uint16_t b = *((uint16_t *) a);
b == 0x4A35; // because AVR is little endian

If you are using the buffer to store big endian data (e.g. network byte order), then you'll need to byte-swap to use either of these techniques. A way to do that without any branches or temporary variables is:

uint8_t a[2] = {0x35, 0x4A};
a[0] ^= a[1];
a[1] ^= a[0];
a[0] ^= a[1];

a[0] == 0x4A; // true
a[1] == 0x35; // true

Incidentally this is not an AVR or even an embedded-only problem. Application level networking code written for PCs typically calls functions called htonl, htons (host to network, 32- and 16-bit variants) and ntohl, ntohs (network to host, 32- and 16-bit variants) whose implementations are target architecture dependent as to whether they swap the bytes or not (under the assumption that bytes as transmitted 'on the wire' are always big-endian when they are part of multi-byte words).