16-битная структура с разрядными битами дает размер в 3 байта, а также неправильные назначения - PullRequest
1 голос
/ 16 марта 2020

В настоящее время я пишу код для микроконтроллера STM32 для работы с периферийным устройством, чтобы упростить доступ к отдельным битам, я использовал структуру с разбивкой по битам следующим образом:

typedef struct
{
  uint8_t ch3_unreadconv : 1; // 0
  uint8_t ch2_unreadconv : 1; // 1
  uint8_t ch1_unreadconv : 1; // 2
  uint8_t ch0_unreadconv : 1; // 3
  uint8_t not_used_01    : 2; // 4-5
  uint8_t drdy           : 1; // 6
  uint8_t not_used_02    : 2; // 7-8
  uint8_t err_alw        : 1; // 9
  uint8_t err_ahw        : 1; // 10
  uint8_t err_wd         : 1; // 11
  uint8_t not_used_03    : 2; // 12-13
  uint8_t err_chan       : 2; // 14-15
} fdc_status_reg_t;
fdc_status_reg_t statusReg;

// Assign value 13 to the register.
*((uint16_t*) &statusReg) = 0xD;

Но когда я делаю sizeof(statusReg); Я получаю 3 в качестве ответа. Когда я присваиваю значения всему регистру с помощью кода, биты устанавливаются правильно, за исключением последних 2 битов, т. Е. err_chan. Я попробовал то же самое на G CC и получил аналогичные результаты, чтобы проверить, является ли проблема, связанная со спецификацией STM c. После дальнейшего изучения я обнаружил, что установка 7-го и 8-го битов на два отдельных бита, похоже, решает проблему, т. Е.

typedef struct
 {
     uint8_t ch3_unreadconv : 1; // 0
     uint8_t ch2_unreadconv : 1; // 1
     uint8_t ch1_unreadconv : 1; // 2
     uint8_t ch0_unreadconv : 1; // 3
     uint8_t not_used_01    : 2; // 4-5
     uint8_t drdy           : 1; // 6
     uint8_t not_used_02    : 1; // 7
     uint8_t not_used_02_01 : 1; // 8
     uint8_t err_alw        : 1; // 9
     uint8_t err_ahw        : 1; // 10
     uint8_t err_wd         : 1; // 11
     uint8_t not_used_03    : 2; // 12-13
     uint8_t err_chan       : 2; // 14-15
 } fdc_status_reg_t;

Дает правильный вывод для sizeof как 2 байта. А также дает прогнозируемое поведение для назначения. Это (для меня) выглядит как проблема выравнивания / выравнивания памяти, но я не понимаю, как, я был в состоянии воссоздать это всякий раз, когда uint8_t использовался через 7-й и 8-й бит, но отлично работает, когда используется uint16_t через 7-8. Пожалуйста, посоветуйте, в чем проблема, и возможно, лучше обойти эту проблему, чем разделять биты на 7-й бит. Заранее спасибо.

Ответы [ 4 ]

4 голосов
/ 16 марта 2020

Число байтов в первой структуре увеличивается против границы выравнивания байтов в этом элементе поля:

uint8_t not_used_02    : 2; // 7-8

Это приводит к тому, что число битов в этой точке равно 9, не 8, таким образом вызывая заполнение поля битов , размещая дополнительный бит, добавляя еще один байт, доводя счетчик байтов до 3.

Если бы вы могли настроить порядок полей в структурах битовых полей можно уменьшить количество байтов до 2. Или ...

В битовых полях реализация системы не гарантируется согласованной от системы к системе, но вы можете поэкспериментировать с своей реализацией чтобы получить желаемый результат. Например, попробуйте использовать type достаточно большой, чтобы вместить все биты, например uint16_t. В моей системе использование uint16_t привело к sizeof 2 для тех же полей с идентичным порядком полей:

typedef struct
{
  uint16_t ch3_unreadconv : 1; // 0
  uint16_t ch2_unreadconv : 1; // 1
  uint16_t ch1_unreadconv : 1; // 2
  uint16_t ch0_unreadconv : 1; // 3
  uint16_t not_used_01    : 2; // 4-5
  uint16_t drdy           : 1; // 6
  uint16_t not_used_02    : 2; // 7-8
  uint16_t err_alw        : 1; // 9
  uint16_t err_ahw        : 1; // 10
  uint16_t err_wd         : 1; // 11
  uint16_t not_used_03    : 2; // 12-13
  uint16_t err_chan       : 2; // 14-15
} fdc_status_reg_t;
fdc_status_reg_t statusReg;
2 голосов
/ 16 марта 2020

Измените тип на uint16_t, чтобы "игнорировать" границу байта в 7-8 байт.

Следующий исходный файл 1.c, скомпилированный с arm-none-eabi-gdb 9.1 и выполненный в симуляторе в arm-none-eabi-gdb , вывод 2 для sizeof:

$ cat 1.c
#include <stdint.h>
#include <stdio.h>
typedef struct
{
  uint16_t ch3_unreadconv : 1; // 0
  uint16_t ch2_unreadconv : 1; // 1
  uint16_t ch1_unreadconv : 1; // 2
  uint16_t ch0_unreadconv : 1; // 3
  uint16_t not_used_01    : 2; // 4-5
  uint16_t drdy           : 1; // 6
  uint16_t not_used_02    : 2; // 7-8
  uint16_t err_alw        : 1; // 9
  uint16_t err_ahw        : 1; // 10
  uint16_t err_wd         : 1; // 11
  uint16_t not_used_03    : 2; // 12-13
  uint16_t err_chan       : 2; // 14-15
} fdc_status_reg_t;
int main() {
    printf("sizeof(fdc_status_reg_t)=%d\n", (int)sizeof(fdc_status_reg_t));
}

$ arm-none-eabi-gcc -specs=rdimon.specs ./1.c && arm-none-eabi-gdb -ex 'target sim' -ex 'load' -ex 'run' -ex quit -quiet ./a.out
Reading symbols from ./a.out...
(No debugging symbols found in ./a.out)
Connected to the simulator.
Loading section .init, size 0x18 lma 0x8000
Loading section .text, size 0xbf8c lma 0x8018
Loading section .fini, size 0x18 lma 0x13fa4
Loading section .rodata, size 0x30c lma 0x13fc0
Loading section .ARM.exidx, size 0x8 lma 0x142cc
Loading section .eh_frame, size 0x4 lma 0x142d4
Loading section .init_array, size 0x8 lma 0x242d8
Loading section .fini_array, size 0x4 lma 0x242e0
Loading section .data, size 0xad4 lma 0x242e8
Start address 0x80e8
Transfer rate: 421280 bits in <1 sec.
Starting program: /tmp/a.out 
sizeof(fdc_status_reg_t)=2
[Inferior 1 (process 42000) exited normally]

Кроме того, вы можете добавить __attribute__((__packet__)), просто чтобы быть уверенным.

Упаковка битового поля в G CC и Clang от Jo sh Kunz Я думаю, что это лучшее объяснение, которое я нашел, как g cc ведет себя с битовыми полями и различными типами.

2 голосов
/ 16 марта 2020

Я бы предложил использовать правильный целочисленный размер для структуры:

typedef struct
 {
     uint16_t ch3_unreadconv : 1; // 0
     uint16_t ch2_unreadconv : 1; // 1
     uint16_t ch1_unreadconv : 1; // 2
     uint16_t ch0_unreadconv : 1; // 3
     uint16_t                : 2; // 4-5
     uint16_t drdy           : 1; // 6
     uint16_t                : 1; // 7
     uint16_t                : 1; // 8
     uint16_t err_alw        : 1; // 9
     uint16_t err_ahw        : 1; // 10
     uint16_t err_wd         : 1; // 11
     uint16_t                : 2; // 12-13
     uint16_t err_chan       : 2; // 14-15
 } fdc_status_reg_t;

Вам не нужны имена неиспользуемых полей

Вы также можете упаковать структуру

 typedef struct
 {
     uint8_t ch3_unreadconv : 1; // 0
     uint8_t ch2_unreadconv : 1; // 1
     uint8_t ch1_unreadconv : 1; // 2
     uint8_t ch0_unreadconv : 1; // 3
     uint8_t                : 2; // 4-5
     uint8_t drdy           : 1; // 6
     uint8_t                : 1; // 7
     uint8_t                : 1; // 8
     uint8_t err_alw        : 1; // 9
     uint8_t err_ahw        : 1; // 10
     uint8_t err_wd         : 1; // 11
     uint8_t                : 2; // 12-13
     uint8_t err_chan       : 2; // 14-15
 } __attribute__((packed)) fdc_status_reg_t1;

Вот вам пример:

https://godbolt.org/z/xUhUT-

0 голосов
/ 20 марта 2020

Вы упоминаете битовую полосу (в заголовке), но вам нужно обратиться к документации компилятора (пример arm cc), чтобы определить, использует ли она битовую полосу для реализации битовых полей и как она используется.

Битовая полоса - это эффективный способ доступа к одиночным битам, но для членов более одного бита потребуется более одного чтения при чтении из области битовой полосы, поэтому компилятор будет вместо этого прочитайте слово из обычной области памяти, и для того, чтобы это было эффективным, охват границы слова будет исключен, поэтому 2-битное поле в битах 7 и 8 вызовет пробел. То есть полоса битов не относится к полям более чем одного бита.

Это, конечно, не проблема, определяемая STM32 c, а проблема ARM Cortex-M3 / M4. Полоса битов не реализована в Cortex-M7 - так же не все STM32.

Если полоса битов не используется, ни одно поле не будет охватывать границу слова в любом случае. Использование большего типа устройства, такого как uint32_t, позволит избежать этого в этом случае.

...