Сначала немного предыстории:
Эта проблема возникла при написании драйвера для датчика во встроенной системе (STM32 ARM Cortex-M4).
Компилятор: ARM NONE EABI GCC 7.2.1
Лучшим решением для представления внутреннего управляющего регистра датчика было использование объединения с битовым полем вдоль этих линий
enum FlagA {
kFlagA_OFF,
kFlagA_ON,
};
enum FlagB {
kFlagB_OFF,
kFlagB_ON,
};
enum OptsA {
kOptsA_A,
kOptsA_B,
.
.
.
kOptsA_G // = 7
};
union ControlReg {
struct {
uint16_t RESERVED1 : 1;
FlagA flag_a : 1;
uint16_t RESERVED2 : 7;
OptsA opts_a : 3;
FlagB flag_b : 1;
uint16_t RESERVED3 : 3;
} u;
uint16_t reg;
};
Это позволяетЯ обращаюсь к битам регистра индивидуально (например, ctrl_reg.u.flag_a = kFlagA_OFF;
), и это позволяет мне устанавливать значение всего регистра сразу (например, ctrl_reg.reg = 0xbeef;
).
Проблема:
При попытке заполнить регистр значением, извлеченным из датчика посредством вызова функции, передавая объединение по указателю, а затем обновляя только часть opts_a
регистра, прежде чем записать его обратно в датчик(как показано ниже), компилятор генерирует неправильную bitfield insert
инструкцию по сборке.
ControlReg ctrl_reg;
readRegister(&ctrl_reg.reg);
ctrl_reg.opts_a = kOptsA_B; // <-- line of interest
writeRegister(ctrl_reg.reg);
приводит к
ldrb.w r3, [sp, #13]
bfi r3, r8, #1, #3 ;incorrectly writes to bits 1, 2, 3
strb.w r3, [sp, #13]
Однако, когда я использую промежуточную переменную:
uint16_t reg_val = 0;
readRegister(®_val);
ControlReg ctrl_reg;
ctrl_reg.reg = reg_val;
ctrl_reg.opts_a = kOptsA_B; // <-- line of interest
writeRegister(ctrl_reg.reg);
Выдает правильную инструкцию:
bfi r7, r8, #9, #3 ;sets the proper bits 9, 10, 11
Функция readRegister не делает ничего лишнего и просто записывает в память по указателю
void readRegister(uint16_t* out) {
uint8_t data_in[3];
...
*out = (data_in[0] << 8) | data_in[1];
}
Почему компилятор неправильноperly установить начальный бит инструкции вставки битового поля?