Есть ли лучший способ определить макрос препроцессора для выполнения битовых манипуляций? - PullRequest
1 голос
/ 26 апреля 2019

Взять макрос:

GPIOxMODE(gpio,mode,port)    ( GPIO##gpio->MODER   = ((GPIO##gpio->MODER &   ~((uint32_t)GPIO2BITMASK << (port*2))) | (mode << (port * 2))) )

Предполагая, что значение сброса регистра равно 0xFFFF.FFFF, я хочу установить ширину 2 бита в произвольное значение. Это было написано для STM32 MCU, который имеет 15 контактов на порт. GPIO2BITMASK определяется как 0x3. Есть ли лучший способ для очистки и установки случайных 2 бит в любом месте в 32-битный регистр

Допустимый диапазон для порта 0 - 15
Допустимый диапазон для режима 0 - 3

Метод, который я придумал, состоит в том, чтобы сдвинуть маску по битам, логически инвертировать ее И с существующим значением регистра, логически ИЛИ результат с новым битовым сдвигом.

Я хочу объединить маску и новое значение, чтобы уменьшить количество логических операций операций сдвига битов. Цель также состоит в том, чтобы сделать процесс достаточно универсальным, чтобы я мог использовать его для операций с битами шириной 1,2,3 или 4 бита.

Есть ли лучший способ?

В общем и целом, есть ли лучший способ действительно открытый вопрос. Я специально искал метод, который уменьшит количество логических операций и операций сдвига битов, будучи простым однострочным оператором.

Ответ НЕТ .

Вы ДОЛЖНЫ выполнить сброс / настройку, чтобы гарантировать, что битовое поле, в которое вы записываете, имеет желаемое значение.

Полученные ответы могут быть лучше (с точки зрения мнения / предпочтения / философии / практики), поскольку они не являются необходимыми макросами и имеют проверку параметров. Также в комментариях и ответах были отмечены ямы этого стиля.

Ответы [ 2 ]

1 голос
/ 26 апреля 2019

Этот вид макросов следует избегать как табличка по многим причинам:

  1. Они не отлаживаемы
  2. Их трудно найти подверженными ошибкам

и многие другие причины

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

static inline __attribute__((always_inline)) void GPIOMODE(GPIO_TypeDef *gpio, unsigned mode, unsigned pin)
{
    gpio -> MODER &= ~(GPIO_MODER_MODE0_Msk << (pin * 2));
    gpio -> MODER |= mode << (pin * 2);
}

, но если вы любите макросы

#define GPIOxMODE(gpio,mode,port)    {volatile uint32_t *mdr = &GPIO##gpio->MODER; *mdr &= ~(GPIO_MODER_MODE0_Msk << (port*2)); *mdr |= mode << (port * 2);}

Я хочу объединить маску и новое значение, чтобы уменьшить количество логическихоперации операции сдвига битов.

вы не можете.Вам нужно сбросить и затем установить биты.

1 голос
/ 26 апреля 2019

Метод, который я придумал, - сдвинуть маску, инвертировать ее, логически И это с существующим значением регистра, логически ИЛИ результат с немного сдвинутым новым значением.

Это или эквивалентный способ сделать это.

Я хочу объединить маску и новое значение, чтобы уменьшить количество логические операции, операции сдвига битов. Цель также сохранить процесс достаточно общий, так что я могу использовать для битовых операций 1,2,3 или шириной 4 бита.

Есть ли лучший способ?

Вы должны выполнить две основные цели:

  1. гарантирует, что биты, которые должны быть выключены в затронутом диапазоне, фактически выключены, и
  2. убедитесь, что биты, которые должны быть включены в затронутом диапазоне, действительно включены.

В общем случае для них требуются две отдельные операции: побитовое И для принудительного отключения битов и побитовое ИЛИ (или XOR, если биты сначала очищаются) для включения желаемых битов. Могут быть способы сокращения для определенных случаев исходных и целевых значений, но если вы хотите что-то общего назначения, как вы говорите, тогда ваши возможности ограничены.

Лично я думаю, что был бы склонен построить его из нескольких частей, отделяя выбор GPIO от фактических вычислений. Как минимум, вы можете выделить общий макрос для установки диапазона битов:

#define SETBITS32(x,bits,offset,mask) ((((uint32_t)(x)) & ~(((uint32_t)(mask)) << (offset))) | (((uint32_t)(bits)) << (offset)))

#define GPIOxMODE(gpio,mode,port) (GPIO##gpio->MODER = SETBITS32(GPIO##gpio->MODER, mode, port * 2, GPIO2BITMASK)

Но обратите внимание, что, похоже, нет хорошего способа избежать такого макроса, который оценивает некоторые из своих аргументов более одного раза. Поэтому было бы безопаснее вместо этого написать SETBITS32 как функцию. Компилятор, вероятно, встроит такую ​​функцию в любом случае, но вы можете максимизировать вероятность этого, объявив ее static и inline:

static inline uint32_t SETBITS32(uint32_t x, uint32_t bits, unsigned offset, uint32_t mask) {
    return x & ~(mask << offset) | (bits << offset);
}

Это также легче читать, хотя он, как и макрос, предполагает, что bits не имеет установленных битов вне области маски.

Конечно, есть и другие, похожие формулировки. Например, если вам не нужно поддерживать прерывистые битовые диапазоны, вы можете указать битовый счет вместо битовой маски. Эта альтернатива делает это, защищает от предоставления пользователем битов за пределами указанного диапазона, а также имеет проверку некоторых параметров:

static inline uint32_t set_bitrange_32(uint32_t x, uint32_t bits, unsigned width,
        unsigned offset) {
    if (width + offset > 32) {
        // error: invalid parameters
        return x;
    } else if (width == 0) {
        return x;
    }
    uint32_t mask = ~(uint32_t)0 >> (32 - width);
    return x & ~(mask << offset) | ((bits & mask) << offset);
}
...