Макрос препроцессора C для упаковки битовых полей в байт? - PullRequest
5 голосов
/ 16 сентября 2009

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

Например, в регистрах много настроек:

/* Provided by the compiler */
#define SPIE 7
#define SPE 6
#define DORD 5
#define MSTR 5
#define CPOL 4
#define CPHA 3

void init_spi() {
  SPCR = (1 << SPE) | (1 << SPIE) | (1 << MSTR) | (1 << SPI2X);      
}

К счастью, есть макросы, которые скрывают фактические операции ввода-вывода порта (слева), поэтому это выглядит как простое назначение. Но весь этот синтаксис для меня грязный.

Требования:

  • он должен обрабатывать только до 8 бит,
  • битовые позиции должны передаваться в любом порядке, а
  • должен требовать только передачи установленных битов.

Синтаксис, который мне нужен:

SPCR = биты (SPE, SPIE, MSTR, SPI2X);

Лучшее, что я до сих пор придумал, это комбинированный макрос / функция:

#define bits(...) __pack_bits(__VA_ARGS__, -1)

uint8_t __pack_bits(uint8_t bit, ...) {
    uint8_t result = 0;
    va_list args;
    va_start(args, bit);

    result |= (uint8_t) (1 << bit);

    for (;;) {
        bit = (uint8_t) va_arg(args, int);
        if (bit > 7) 
            break;
        result |= (uint8_t) (1 << bit);
    }
}

Это компилирует до 32 байтов в моей конкретной архитектуре и требует 61-345 циклов для выполнения (зависит от того, сколько битов было передано).

В идеале это должно быть сделано в препроцессоре, поскольку результат является константой, а машинные инструкции вывода должны быть просто присвоением 8-битного значения регистру.

Можно ли сделать это лучше?

Ответы [ 2 ]

6 голосов
/ 16 сентября 2009

Да, переопределите макросы ABC как 1 << ABC, и вы упростите это. Объединение битовых масок - очень распространенная идиома, которую каждый может узнать. Получение сдвиговых позиций с вашего лица очень поможет.

Ваш код идет от

#define SPIE 7
#define SPE 6
#define DORD 5
#define MSTR 5
#define CPOL 4
#define CPHA 3

void init_spi() {
  SPCR = (1 << SPE) | (1 << SPIE) | (1 << MSTR) | (1 << SPI2X);      
}

к этому

#define BIT(n) (1 << (n))
#define SPIE BIT(7)
#define SPE BIT(6)
#define DORD BIT(5)
#define MSTR BIT(5)
#define CPOL BIT(4)
#define CPHA BIT(3)

void init_spi() {
  SPCR =  SPE | SPIE | MSTR | SPI2X;
}

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


Я чувствую, что может быть какой-то способ использовать переменные макросы для этого, но я не могу понять, что можно легко использовать в качестве выражения. Рассмотрим, однако, создание литерала массива внутри функции, генерирующей вашу константу:

#define BITS(name, ...) \
     char name() { \
         char[] bits = { __VA_ARGS__ }; \
         char byte = 0, i; \
         for (i = 0; i < sizeof(bits); ++i) byte |= (1 << bits[i]); \
         return byte; }

/*  Define the bit-mask function for this purpose */
BITS(SPCR_BITS, SPE, SPIE, MSTR, SPI2X)

void init_spi() {
    SPCR = SPCR_BITS();
}

Если ваш компилятор работает хорошо, он увидит, что вся функция постоянна во время компиляции, и вставит результирующее значение.

0 голосов
/ 21 сентября 2009

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

#define BIT_TO_MASK(n) (1 << (n))

#define SPIE_MASK BIT_TO_MASK(SPIE)
#define SPE_MASK  BIT_TO_MASK(SPE)
#define DORD_MASK BIT_TO_MASK(DORD)
#define MSTR_MASK BIT_TO_MASK(MSTR)
#define CPOL_MASK BIT_TO_MASK(CPOL)
#define CPHA_MASK BIT_TO_MASK(CPHA)

void init_spi() {
  SPCR =  SPE_MASK | SPIE_MASK | MSTR_MASK | SPI2X_MASK;
}
...