C中将大端序字节数组转换为小端序结构体元素,arm

3

在我的应用程序中,微控制器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


1
你必须重新排列字节,而且可能存在对齐问题,为什么不使用可靠、便携的代码呢?移位和或操作并不能让机器码更好,试图使用高级语言的魔法也无济于事。 - old_timer
我忘了,结构体是使用紧凑属性typedef struct attribute((packed))定义的,在这个时刻它按照我上面写的方式工作。 - Piotr Lewandowski
4个回答

2

由于这是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);
}

任何评论沉默的 DV-ter? - 0___________
抱歉沉默了一段时间,我在使用SWD编程器时遇到了一些小问题:D,你的答案看起来是最好的选择,我明天会尝试一下。 - Piotr Lewandowski

1
以那种方式进行转换不起作用。
您可以执行“反序列化”操作。
尽管这可能比其他某些方法慢,但它允许您更好地控制协议(例如,结构成员顺序不必遵循协议)。而且,结构中可能会有填充,如果我们在结构的末尾添加(例如)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;
}

更新:

是的,这样做可以,但我希望尽可能少地使用处理器。这是手动将字节放入正确顺序的方式。同时,过程Mst * struct_p = (Mst*)myArray是安全的,因为在定义struct时我使用了__attribute__((packed)),只是忘记写了这个。

我打算提到/建议使用packed作为可能性。

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

因此,您可以执行以下操作[即用bswap_*替换for循环](例如):

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

你错了。指针游戏在这种情况下是可行的。问题不在于游戏,而在于字节序的改变。 - 0___________
@P__J__,我并没有说你必须这样做,只是说你可以这样做,所以我并没有“错”。 - Craig Estey
“以那种方式进行强制转换是行不通的。” 1. 在这种情况下,使用Punning将起作用。2. 这里没有“强制转换”。 - 0___________
没错,也可以正常工作,但我发现在Cortex M3处理器上,由单个汇编行表示的__REV()函数具有更高的性能。谢谢你的回答,这对于那些在其他设备上工作或只是不关心性能的人来说都是有帮助的。 - Piotr Lewandowski
bswap_32 生成一条[单行]的 rev 指令,而 bswap_16 则生成 rev16 - Craig Estey
显示剩余5条评论

0
将结构体指针覆盖到字节数组上可能由于对齐问题和结构填充而无法正常工作。
正确的方法是首先使用 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);

0

如果您只需要将8位数据转换为大端序,假设数据格式按顺序排列(A B C D),而在小端序数据格式中,数据格式将为(D C B A),如果最终结果将存储在32位变量中,则16位变量的格式将为(A B)和(B A)。

因此,如果您知道您的UART数据以哪种格式传输,可以使用一些位移技巧来完成此操作。

首先,让我们假设您的UART数据以小端序传输,并且您希望将其转换为大端序,则可以执行以下操作。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接