порядок байтов с использованием GCC struct битовой упаковки - PullRequest
0 голосов
/ 05 декабря 2018

Я использую поля битов структуры 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);
}

1 Ответ

0 голосов
/ 05 декабря 2018

Поскольку порядок упаковки struct варьируется в зависимости от компилятора и архитектуры, лучше всего использовать вспомогательную функцию для упаковки / распаковки двоичных данных.

Например:

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.
*/

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

...