大端字节数组到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);
然后使用 ntohs
和 ntohl
分别将 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
}
在我的应用程序中,微控制器 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);
然后使用 ntohs
和 ntohl
分别将 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 definingstruct
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
}