大端字节数组到C,arm中的小端结构元素

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

在我的应用程序中,微控制器 stm32f103 通过 USART 接收固定长度的消息,它们包含 gps 速度,这是大端数据。但是结构中的元素是小端。有什么办法不用手动写就可以写正确吗?

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;
}

但之后myStruct_p->test1等于0x030201,但应该是0x010203,而myStruct_p->test2等于0x0605,但应该是0x0506 .

由于对齐问题和结构填充,将结构指针叠加到字节数组上可能不一定有效。

正确的方法是先使用memcpy复制元素:

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

然后使用 ntohsntohl 分别将 16 位和 32 位值从大端格式转换为主机端格式。

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

因为它是 ARM-Cortex M3,所以我们可以使用特殊的处理器指令。 ARM CMSIS 有一个非常方便的内部函数 __REV__REV16,它们实际上编译为单个机器代码指令。

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);
}

以这种方式投射是行不通的。

您可以进行“反序列化”操作。

虽然这可能比其他一些方法慢,但它允许您更好地控制协议(例如 struct 成员顺序不必遵循协议)并且,可能有填充结构,如果我们添加(例如)uint32_t test3; 到结构的末尾,它就会显示出来。

代码如下:

#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;
}

更新:

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

我打算 mention/suggest packed 作为一种可能性。

在任何一种情况下,您都可以使用[在 GNU 下]:byteswap.h 来获得 bswap_*。 Endian 交换非常普遍,因此这些都针对给定的 arch [高度] 优化了。他们甚至可以调用编译器内部函数(例如 __builtin_bswap32),它利用 arch 具有的任何特殊指令(例如 x86 具有 bswap 指令,而 arm 具有 rev16 ).

所以,你可以[即将 for 循环替换为](例如)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;
}

如果您只需要将 8 位数据转换为大端数据,假设数据格式为 (A B C D),如果最终结果存储在 32 位变量中,则小端数据格式为 (D C B A),如果 16位变量然后格式将 (A B) 和 (B A) .

因此,如果您知道您的 UART 数据以哪种格式传输,则可以通过一点位移技术来完成。

让我们首先假设您的 UART 数据来自 Little endian,而您希望它是 big endian,那么您可以这样做。

//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
}