Получить символы из имени макроса для оптимизации функции макроса - PullRequest
0 голосов
/ 27 мая 2018

Я использую следующую макро-функцию

#define setAsOutput(bit, i)   { *bit ## _DDR[i] |= (1 << bit[i]); }

, чтобы упростить определение и установку некоторых значений регистра

// registers
volatile uint8_t *FOO_DDR[] = {&DDRA, &DDRA};
uint8_t FOO[] = {PA1, PA2};

setAsOutput(FOO, 0);

// these are defined somewhere else
#define PA1     1
#define PA2     2
#define DDRA    _SFR_IO8(0X01)

Это дает код, эквивалентный

DDRA |= (1 << PA1);

Однако строка volatile uint8_t *FOO_DDR[] = {&DDRA, &DDRA}; фактически избыточна, поскольку A в DDRA всегда повторяется в значениях FOO, то есть PA1 и PA2.

В идеале его можно полностью удалить, и макрос может измениться на что-то вроде

#define setAsOutput(bit, i)   { DDR ## <second char of bit[i]> |= (1 << bit[i]); }

, но получение второго символа name из bit[i] не похожевозможно.

Есть ли способ переписать функцию макроса, чтобы FOO_DDR не нужно было явно определять и вместо этого можно было бы подразумевать из {PA1, PA2}?

Ответы [ 3 ]

0 голосов
/ 28 мая 2018

Если вы хотите, чтобы код был эквивалентен DDRA |= (1 << PA1); - т.е. самой простой инструкции, которая была сделана во время компиляции без чтения / записи массивов и указателей на регистры ввода-вывода.Вы можете сделать это следующим образом.

1) Давайте предположим, что мы где-то определили (например, через <avr/io.h>)

#define PA1     1
#define PA2     2
...
#define DDRA    _SFR_IO8(0X01)
#define PB1     1
#define PB2     2
...
#define DDRB    _SFR_IO8(0X01)

2) Вы хотите иметь какое-то объявление вроде этого:

#define BIG_RED_LED PA1
#define SMALL_GREEN_LED PB2

для того, чтобы затем использовать их как

setAsOutput(BIG_RED_LED);
setAsOutput(SMALL_GREEN_LED);
setLow(BIG_RED_LED);
setHigh(SMALL_GREEN_LED);

и т. Д., Где каждая строка представляет собой простую запись в BIT в соответствующем регистре ввода-вывода.

Toдостигните, что вы можете определить тонны

#define DDR_PA0 DDRA
#define PORT_PA0 PORTA
#define PIN_PA0 PINA
#define DDR_PA1 DDRA
#define PORT_PA1 PORTA
#define PIN_PA1 PINA
...
#define DDR_PB0 DDRB
#define PORT_PB0 PORTB
#define PIN_PB0 PINB
...

, а затем

#define setAsOutput(px)   { DDR_ ## px |= (1 << px); }
#define setHigh(px)   { PORT_ ## px |= (1 << px); }
#define setLow(px)   { PORT_ ## px &= ~(1 << px); }
etc.

, тогда каждый раз, когда в вашем коде происходит что-то вроде setAsOutput(PA1), оно будет скомпилировано точно то же самое, что DDRA | = (1 << PA1); </p>

Но , если вы хотите сохранить их в массиве и получить доступ по индексу массива, как в вашем примере, тогдау вас нет другого пути, кроме как определить два массива или массив структур, где оба элемента будут содержать номер бита или маску битов и указатель на регистр ввода / вывода.Поскольку имя PA1 PA2 и т. Д. Содержит букву A, во время выполнения оно будет скомпилировано в значение.Т.е. «PA1» будет 1, но PB1 также будет 1.Таким образом, компилятор не может знать, к какому регистру обращаются, учитывая только индекс внутри этого массива.

Но здесь я могу дать вам несколько маленьких лайфхаков: 1) так как регистры PINx, DDRx, PORTxвсегда идут последовательно в этом порядке (см. сводку набора регистров в таблице данных), вам не нужно хранить их все, достаточно хранить только ссылку на регистр PINx и вычислять местоположение DDRx и PORTx, просто добавляя1 или 2 по адресу, поскольку у AVR есть инструкции для отмены доступа к памяти со смещением, код будет достаточно эффективным.2) эти регистры расположены в нижних адресах памяти, поэтому вместо хранения 2/4-байтовых указателей вы можете привести их к byte и вернуть их к указателю при доступе.Это не только сэкономит место, но и ускорит.Кроме того, всегда рекомендуется хранить такие таблицы во флэш-памяти, а не тратить оперативную память.3) Архитектура AVR имеет только одну команду сдвига битов положения, поэтому (1 << x), где x не известен во время компиляции - компилируется как цикл, который может быть частью, требующей большей части времени в таком коде,Таким образом, вместо хранения <code>uint8_t FOO[] = {PA1, PA2}; вы можете сохранить uint8_t FOO[] = {(1 << PA1), (1 << PA2)}; - то есть предварительно рассчитанные значения маски.

0 голосов
/ 14 августа 2018

В конце я использовал макро функцию _MMIO_BYTE в avr/sfr_defs.h, чтобы основывать новые функции управления битами на:

#define SET_OUTPUT(pin)     (_MMIO_BYTE(OFFSET_ADDR((pin)[0] + 0x1)) |=  _BV((pin)[1]))
#define SET_INPUT(pin)      (_MMIO_BYTE(OFFSET_ADDR((pin)[0] + 0x1)) &= ~_BV((pin)[1]))
// etc

Это дает легкое определение выводов в виде массивов выводов или одиночных выводов:

#define NUM_LEDS 3

const uint16_t LEDS[NUM_LEDS][2] = {
    {PB, 4},
    {PB, 5},
    {PB, 6}
};
const uint16_t BUTTON[2] = {PB, 7};

Затем можно управлять выводами следующим образом:

SET_INPUT(BUTTON);
ENABLE_PULLUP(BUTTON);

for (int i = 0; i < NUM_LEDS; ++i) {
    SET_OUTPUT(LEDS[i]);
    SET_HIGH(LEDS[i]);
}

Исходный код здесь: https://github.com/morefigs/avr-bit-funcs.

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

0 голосов
/ 27 мая 2018

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

Вам не нужно определять вещикак DDRA и PA1 в вашем коде.Просто передайте соответствующую опцию компилятору, чтобы указать, какой AVR вы используете (например, -mmcu=atmega1284p), а затем добавьте #include <avr/io.h> вверху вашей программы, чтобы получить эти определения.И обычно нет особого смысла копировать эти определения из io.h в вопросы о StackOverflow, поскольку они довольно стандартны.Эти определения взяты из avr-libc, поэтому, если вы действительно хотите предоставить эти подробности, вы можете просто сказать, какую версию avr-libc вы используете.

Одним из основных положений вашего вопроса является то, что кодВы отправили с массивами и макросами эквивалентно DDRA |= (1 << PA1);.К сожалению, эта предпосылка неверна.Когда GCC видит DDRA |= (1 << PA1);, он может фактически скомпилировать это до одной атомарной инструкции AVR, которая устанавливает бит 1 регистра DDRA.Когда GCC видит ваш код, он делает что-то намного более сложное, что заканчивается чтением, записью и изменением регистра.Таким образом, код массива тратит впустую циклы процессора и небезопасен для использования, если прерывания могут изменить регистр DDRA.

Если вы мне не верите, вы можете взглянуть на эту ссылку godbolt.org, которая сравнивает сборкудля двух методов:

https://godbolt.org/g/jddzpK

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

volatile uint8_t * const FOO_DDR[] = {&DDRA, &DDRA};
uint8_t const FOO[] = {PA1, PA2};

Теперь перейдем к вашему основному вопросу - как избавиться от избыточных массивов.Я не думаю, что есть простой способ сделать это, и наличие двух константных массивов в вашей программе не имеет большого значения, и в любом случае они, вероятно, будут оптимизированы во время компиляции.Что вы можете сделать, так это расширить эти массивы, чтобы они содержали записи для каждого вывода на вашем чипе.Затем, когда вы хотите записать в вывод, вы просто используете номер вывода, который является индексом для массивов (вместо того, чтобы определять новые массивы).Подавляющее большинство вашего кода будет иметь дело с этими номерами выводов и не будет беспокоиться о массивах.Вот как я бы это написал:

#include <avr/io.h>

// Here is a general GPIO library for your chip.
// TODO: expand these arrays to cover every pin on the chip

#define setAsOutput(i)   { *pin_dir[i] |= (1 << pin_bit[i]); }
#define setHigh(i)   { *pin_value[i] |= (1 << pin_bit[i]); }

static volatile uint8_t * const pin_dir[] = {
  &DDRA,  // Pin 0
  &DDRA,  // Pin 1
};

static volatile uint8_t * const pin_value[] = {
  &PORTA,  // Pin 0
  &PORTA,  // Pin 1
};

static const uint8_t pin_bit[] = {
  PA1,    // Pin 0
  PA2,    // Pin 1
};

// Pin definitions for your particular project.
// (e.g. pin 0 is connected to a green LED)

#define GREEN_LED_PIN 0

void nice()
{
  setAsOutput(GREEN_LED_PIN);
  setHigh(GREEN_LED_PIN);
}

Каждый приведенный выше вызов функции GPIO в конечном итоге компилируется в одну инструкцию по сборке.

Если вы покопаетесь в коде Arduino Core, вы будетенайти массивы так же, как это.(Но люди Arduino совершают ошибку, получая расточительный доступ к этим массивам в своих pinMode и digitalWrite функциях.)

Обратите внимание, что с кодом, который я предоставил выше, существует большой риск, что выслучайно пропустит пин-код, который не является константой времени компиляции, и, следовательно, компилятор не сможет его оптимизировать и произведет расточительный / небезопасный код.Это одна из причин, почему было бы лучше использовать встроенную сборку и шаблоны C ++, как это делает библиотека FastGPIO .

...