Piotr Lewandowski
Piotr Lewandowski

Reputation: 43

Big endian byte array to small endian struct elements in C, arm

In my application microcontroller stm32f103 is receiving by USART fixed lenght messages, they contains gps velocity which is big endian data. But elements in structure are small endian. Is there any way without doing it manually to write it in correct way?

typedef struct {
    uint32_t test1;
    uint16_t test2;
}Mst;

uint8_t myArray[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };

void main()
{
    Mst * myStruct_p = (Mst)myArray;
}

But after that myStruct_p->test1 equals 0x030201, but should be 0x010203, and myStruct_p->test2 equals 0x0605, but should be 0x0506.

Upvotes: 3

Views: 1285

Answers (4)

Craig Estey
Craig Estey

Reputation: 33631

Casting in that way won't work.

You can do a "deserialize" operation.

Although this might be slower than some other methods, it allows you to control the protocol better (e.g. the struct member order doesn't have to follow the protocol) And, there might be padding in the struct, which would show up if we added (e.g.) uint32_t test3; to the end of the struct.

Here's the code:

#include <stdio.h>
#include <stdint.h>

typedef struct {
    uint32_t test1;
    uint16_t test2;
} Mst;

uint8_t myArray[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };

uint32_t
get32(uint8_t **base)
{
    uint8_t *ptr;
    uint32_t val = 0;

    ptr = *base;

    for (int len = sizeof(uint32_t);  len > 0;  --len) {
        val <<= 8;
        val |= *ptr++;
    }

    *base = ptr;

    return val;
}

uint16_t
get16(uint8_t **base)
{
    uint8_t *ptr;
    uint16_t val = 0;

    ptr = *base;

    for (int len = sizeof(uint16_t);  len > 0;  --len) {
        val <<= 8;
        val |= *ptr++;
    }

    *base = ptr;

    return val;
}

int
main(void)
{
    Mst myS;

    uint8_t *arr = myArray;

    myS.test1 = get32(&arr);
    myS.test2 = get16(&arr);

    printf("test1=%8.8X test2=%4.4X\n",myS.test1,myS.test2);

    return 0;
}

UPDATE:

Yes guys that will work but, i would like to use the processor as little as possible. This is rather manualy putting bytes in to correct order. Also Procedure Mst * struct_p = (Mst*)myArray works safe, because while defining struct i use __attribute__((packed)), just forgoten to write this

I was going to mention/suggest packed as a possibility.

In either case you can use [under GNU]: byteswap.h to get bswap_*. Endian swapping is quite common, so these are [highly] optimized for the given arch. They can even invoke compiler intrinsics (e.g. __builtin_bswap32) which utilize any special instructions the arch has (e.g. x86 has the bswap instruction, and arm has rev16).

So, you can do [i.e. replace the for loop with] (e.g.) bswap_*:

#include <stdio.h>
#include <stdint.h>
#include <byteswap.h>

typedef struct {
    uint32_t test1;
    uint16_t test2;
    uint32_t test3;
} __attribute__((__packed__)) Mst;

uint8_t myArray[] = {
    0x01, 0x02, 0x03, 0x04,
    0x05, 0x06,
    0x07, 0x08, 0x09, 0x0A
};

void
getall(Mst *myS)
{

    myS->test1 = bswap_32(myS->test1);
    myS->test2 = bswap_16(myS->test2);
    myS->test3 = bswap_32(myS->test3);
}

int
main(void)
{
    Mst *myS = (Mst *) myArray;

    getall(myS);

    printf("test1=%8.8X test2=%4.4X test3=%8.8X\n",
        myS->test1,myS->test2,myS->test3);

    return 0;
}

Upvotes: 1

Dharmik
Dharmik

Reputation: 44

If you just need to convert 8bit data into big endian let say data format will be in order (A B C D) and in little endian data format will be (D C B A) if final result will be stored in 32bit variable and if 16 bit variable then format will (A B) and (B A) .

So this can be done with little bit shifting technique if you know your UART data comming in which format.

Let us first assume that your UART data comes in Little endian and you want it in big endian then you can do like this.

//no structure required
uint32_t myValue;
uint16_t value;
uint8_t myArray[4] = { 0x04, 0x03, 0x02, 0x01 }; 
// little endian data for 32 bit

uint8_t A[2] = {0x02 ,0x01};  // little endian data for 16 bit
void main()
{
    myValue = myArray[3]<<24 | myArray[2]<<16 | myArray[1]<<8 | myArray[0]; // result in big endian 32 bit value

   value = A[1]<<8 | A[0];  // result in big endian 16bit value
}

Upvotes: 0

0___________
0___________

Reputation: 68023

As it is ARM-Cortex M3 we can use special processor instructions. ARM CMSIS have a very handy intrinsic functions __REV & __REV16 which actually compile to the single machine code instruction.

typedef union 
{
    struct 
    {
        uint32_t test1;
        uint16_t test2;
    };
    uint8_t bytes[6];
}Mst;

Mst mst = {.bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }};

void main()
{
    mst.test1 = __REV(mst.test1);
    mst.test2 = __REV16(mst.test2);
}

Upvotes: 2

dbush
dbush

Reputation: 225537

Overlaying a struct pointer onto a byte array may not necessarily work due to alignment issues and structure padding.

The proper way would be to first use memcpy to copy over the elements:

Mst myStruct;
memcpy(&myStruct.test1, myArray, sizeof(myStruct.test1);
memcpy(&myStruct.test2, myArray + 4, sizeof(myStruct.test2);

Then use ntohs and ntohl which converts 16 and 32 bit values respectively from big endian format to the host's endianness.

myStruct.test1 = ntohl(myStruct.test1);
myStruct.test2 = ntohs(myStruct.test2);

Upvotes: 0

Related Questions