порядок байтов с использованием упаковки битов структуры GCC

Я использую битовые поля структуры GCC, пытаясь интерпретировать 8-байтовые данные сообщения CAN. Я написал небольшую программу в качестве примера одного из возможных макетов сообщений. Код и комментарии должны описывать мою проблему. Я назначил 8 байтов так, чтобы все 5 сигналов были равны 1. Как показывает вывод на ПК с Intel, это вряд ли так. Все данные 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);
}

person Doug    schedule 05.12.2018    source источник


Ответы (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  */
}

Обратите внимание, что, поскольку последовательные байты интерпретируются как одно целое число без знака (в порядке байтов с обратным порядком байтов), приведенное выше будет полностью переносимым.

Вместо массива полей вы, конечно, можете использовать структуру; но он вообще не должен иметь никакого сходства с проводной структурой. Однако, если у вас есть несколько разных структур для распаковки, массив полей (максимальной ширины) обычно оказывается проще и надежнее.

Все разумные компиляторы отлично оптимизируют приведенный выше код. В частности, GCC с -O2 отлично справляется со своей задачей.

Обратное, упаковка тех же полей в буфер, очень похоже:

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 битов), 0xFFFF = 0b1111111111111111 (16 битов)); и величина сдвига зависит от битовой позиции младшего значащего бита в каждом поле.

Чтобы убедиться, что такие функции работают, упаковывайте, распаковывайте, переупаковывайте и снова распаковывайте буфер, который должен содержать все нули, кроме одного из полей, состоящего из единиц, и убедитесь, что данные остаются правильными в течение двух круговых обходов. Обычно достаточно выявить типичные ошибки (неверные величины битового сдвига, опечатки в масках).

Обратите внимание, что документация будет иметь ключевое значение для обеспечения возможности сопровождения кода. Я бы лично добавил блоки комментариев перед каждой из вышеперечисленных функций, подобно

/* message1_unpack(): Unpack 8-byte message to 5 fields:
       field[0]: Foobar. Bits 32..63.
       field[1]: Buzz.   Bits 26..31.
       field[2]: Wahwah. Bits 10..25.
       field[3]: Cheez.  Bits  2..9.
       field[4]: Blop.   Bits  0..1.
*/

с полем «имена», отражающим их имена в документации.

person Nominal Animal    schedule 05.12.2018
comment
Да, это направление, в котором я двигался. Я надеялся, что смогу использовать битовые поля GCC, чтобы упростить это, поскольку мне придется иметь дело с сотнями, даже тысячами сообщений CAN. - person Doug; 06.12.2018
comment
@Doug: вы можете рассматривать каждое сообщение как поток битов и использовать функцию для извлечения из него следующих N битов с небольшими дополнительными накладными расходами. Хотите пример такого подхода? - person Nominal Animal; 07.12.2018