我在尝试解释8字节CAN消息数据时使用GCC结构位字段。我写了一个小程序作为一个可能的消息布局的例子。代码和注释应该描述我的问题。我分配了8个字节,以使所有5个信号均应等于1。正如输出在Intel PC上所示,情况并非如此。我处理的所有CAN数据都是大端字节序,并且几乎从未打包8位对齐的事实,使得htonl()和朋友在这种情况下毫无用处。有人知道解决方案吗?
#include <stdio.h>
#include <netinet/in.h>
typedef union
{
unsigned char data[8];
struct {
unsigned int signal1 : 32;
unsigned int signal2 : 6;
unsigned int signal3 : 16;
unsigned int signal4 : 8;
unsigned int signal5 : 2;
} __attribute__((__packed__));
} _message1;
int main()
{
_message1 message1;
unsigned char incoming_data[8]; //This is how this message would come in from a CAN bus for all signals == 1
incoming_data[0] = 0x00;
incoming_data[1] = 0x00;
incoming_data[2] = 0x00;
incoming_data[3] = 0x01; //bit 1 of signal 1
incoming_data[4] = 0x04; //bit 1 of signal 2
incoming_data[5] = 0x00;
incoming_data[6] = 0x04; //bit 1 of signal 3
incoming_data[7] = 0x05; //bit 1 of signal 4 and signal 5
for(int i = 0; i < 8; ++i){
message1.data[i] = incoming_data[i];
}
printf("signal1 = %x\n", message1.signal1);
printf("signal2 = %x\n", message1.signal2);
printf("signal3 = %x\n", message1.signal3);
printf("signal4 = %x\n", message1.signal4);
printf("signal5 = %x\n", message1.signal5);
}
答案 0 :(得分:1)
由于编译器和体系结构之间的结构打包顺序不同,因此最好的选择是使用辅助函数来打包/解压缩二进制数据。
例如:
static inline void message1_unpack(uint32_t *fields,
const unsigned char *buffer)
{
const uint64_t data = (((uint64_t)buffer[0]) << 56)
| (((uint64_t)buffer[1]) << 48)
| (((uint64_t)buffer[2]) << 40)
| (((uint64_t)buffer[3]) << 32)
| (((uint64_t)buffer[4]) << 24)
| (((uint64_t)buffer[5]) << 16)
| (((uint64_t)buffer[6]) << 8)
| ((uint64_t)buffer[7]);
fields[0] = data >> 32; /* Bits 32..63 */
fields[1] = (data >> 26) & 0x3F; /* Bits 26..31 */
fields[2] = (data >> 10) & 0xFFFF; /* Bits 10..25 */
fields[3] = (data >> 2) & 0xFF; /* Bits 2..9 */
fields[4] = data & 0x03; /* Bits 0..1 */
}
请注意,因为连续的字节被解释为单个无符号整数(按大端字节顺序),所以上面的内容很容易移植。
您当然可以使用结构来代替字段数组;但它根本不需要与在线结构有任何相似之处。但是,如果要解压缩几种不同的结构,通常会发现(最大宽度)字段数组更容易且更可靠。
所有明智的编译器都会优化上面的代码。尤其是,带有-O2
的GCC做得很好。
将相同的字段包装到缓冲区中的逆过程非常相似:
static inline void message1_pack(unsigned char *buffer,
const uint32_t *fields)
{
const uint64_t data = (((uint64_t)(fields[0] )) << 32)
| (((uint64_t)(fields[1] & 0x3F )) << 26)
| (((uint64_t)(fields[2] & 0xFFFF )) << 10)
| (((uint64_t)(fields[3] & 0xFF )) << 2)
| ( (uint64_t)(fields[4] & 0x03 ) );
buffer[0] = data >> 56;
buffer[1] = data >> 48;
buffer[2] = data >> 40;
buffer[3] = data >> 32;
buffer[4] = data >> 24;
buffer[5] = data >> 16;
buffer[6] = data >> 8;
buffer[7] = data;
}
请注意,掩码定义了字段长度(0x03
= 0b11(2位),0x3F
= 0b111111(16位),0xFF
= 0b11111111(8位),{{ 1}} = 0b1111111111111111(16位));移位量取决于每个字段中最低有效位的位位置。
要验证此类功能是否正常运行,请对一个缓冲区进行打包,拆包,重新打包和重新拆包,该缓冲区应包含全零,但其中一个字段全为1,并验证数据在两次往返过程中是否保持正确。通常足以检测典型的错误(错误的移位量,掩码中的错字)。
请注意,文档将是确保代码可维护的关键。我个人会在上述每个功能之前添加注释块,类似于
0xFFFF
“名称”字段反映了文档中的名称。