使用GCC结构位打包的字节顺序

时间:2018-12-05 03:13:49

标签: c endianness can-bus

我在尝试解释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);
}

1 个答案:

答案 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

“名称”字段反映了文档中的名称。