LUT в макросе C - PullRequest
       70

LUT в макросе C

0 голосов
/ 18 марта 2012

В настоящее время я работаю над настройкой фреймворка на C для использования между несколькими микроконтроллерами. Фреймворк должен нести весь специфичный для устройства код, поэтому приложение содержит только абстрактное использование периферийных устройств (таких как SerialPort_Read, write, SetBaudRate и т. Д. И т. Д.)

Одна из вещей, с которой я борюсь, чтобы найти решение в C, это карта выводов ввода / вывода. Я видел проекты (например, очень популярный Arduino), в которых карта выводов помещается в LUT (таблица поиска), которая используется во время выполнения. Однако этот LUT никогда не будет изменен во время выполнения, поэтому нет смысла хранить его в памяти. Например, эта функция разрешает некоторые битовые индексы и регистры из некоторых таблиц 'const uint' и устанавливает или очищает бит:

void pinMode(uint8_t pin, uint8_t mode)
{
        uint8_t bit = digitalPinToBitMask(pin);
        uint8_t port = digitalPinToPort(pin);
        volatile uint8_t *reg;

        if (port == NOT_A_PIN) return;

        // JWS: can I let the optimizer do this?
        reg = portModeRegister(port);

        if (mode == INPUT) { 
                uint8_t oldSREG = SREG;
                cli();
                *reg &= ~bit;
                SREG = oldSREG;
        } else {
                uint8_t oldSREG = SREG;
                cli();
                *reg |= bit;
                SREG = oldSREG;
        }
}

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

GPIO_Write(PORTA, 5, 1); // Write '1' to pin 5 on PORTA
> LATA |= 1<<5; // Sets bit 5 high
GPIO_Tris(PORTA, 4, OUTPUT); // Set pin 4 on PORTA to output
> PORTA &= ~(1<<4); // sets pin 4 as output I/O type

Кто-нибудь знает, возможно ли (и как) определить и использовать справочную таблицу с макросом в C?

В данный момент я использую компилятор MicroChip C30, который, как мне кажется, основан на GCC. Предполагается, что он будет переносимым между различными компиляторами, включая MicroChip C18, C32, а также ARM и AVR.

Ответы [ 4 ]

2 голосов
/ 18 марта 2012

Для вашего конкретного примера будет работать что-то вроде этого:

#define WRITE_PORTA LATA
#define GPIO_Write(port, pin, value)         \
    (value ? WRITE_##port |=  (1U << (pin))  \        
           : WRITE_##port &= ~(1U << (pin)))

#define INPUT  0
#define OUTPUT 1
#define GPIO_Tris(port, pin, direction)                     \
     ((direction) == INPUT ? port |=  (1U << (pin))  \
                           : port &= ~(1U << (pin)))

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

1 голос
/ 18 марта 2012

Рассмотрим следующий макрос:

#define write(port, pin, value) do { \
  if (value) \
    LAT##port |= 1 << pin; \
  else \
    LAT##port &= ~(1 << pin); \
} while (0)

Использование:

write(A, 3, 1);   // compiles to LATA |= 1 << 3;
write(B, 2, 0);   // compiles to LATB &= ~(1 << 2);

Это то, за чем вы охотились?

1 голос
/ 18 марта 2012

На какой процессор или микроконтроллер вы ориентируетесь?Вы можете недооценивать полезность LUT.

Для многих процессоров LUT делает больше, чем просто отображает «логический» номер пина в одно значение, «физический» номер пина.LUT отображает «логический» номер контакта на несколько частей информации.

В общем случае «логический» вывод отображается на адрес порта соответствующего регистра чтения / ввода или записи / вывода и битсмещение в регистре чтения или записи.Таким образом, значение вывода на многих микроконтроллерах действительно отображается на структуру.Он также может включать в себя отображение на регистр направления данных и поля внутри него, а также регистры, которые устанавливают состояние повышающих или понижающих резисторов.

Например, у меня есть код для мультиплексирования 8x8дисплей.Во время выполнения мне нужно использовать pinMode, чтобы превратить вывод с выхода на вход с высоким импедансом, и, таким образом, информацию нужно как-то кодировать.

Это можно сделать с помощьюнекоторая изобретательность, на некоторых MCU.Микроконтроллеры ARM (и я полагаю, что 8051, хотя я никогда не использовал их) с использованием «адресации битовой полосы» http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0179b/CHDJHIDF.html

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

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

Если вы готовы отказаться от идеи использования целых чисел для выводов, и вместо этого использовать имена, такие как P0, P1, тогда вы можете инициализировать множество const struct, по одному на имя вывода, и ваши функции будутпринять значения структуры const.Структура будет содержать инициализированный порт и смещение бит или значения битовой маски.Компилятор может оптимизировать скорость.Это позволило бы избежать использования LUT, но все равно использовало бы аналогичное количество места для используемых выводов.Возможно, вам удастся расположить его так, чтобы неиспользуемые булавки не нужно было включать в код, и, следовательно, экономить место.

Редактировать: Если вы хотите использовать C ++, я бы предложил шаблоны C ++, которые могутдать гораздо лучшее решение, чем макросы.Они могут быть безопасными по типу, и их часто легче отлаживать (если у вас есть аппаратная отладка, например, JTAG и gdb)

0 голосов
/ 25 марта 2013

Я видел, как это было сделано (https://github.com/triffid/Teacup_Firmware/blob/Gen7/arduino.h) с парой макросов:

/// Read a pin
#define     _READ(IO)                   (IO ## _RPORT & MASK(IO ## _PIN))
/// write to a pin
#define     _WRITE(IO, v)           do { if (v) { IO ## _WPORT |= MASK(IO ## _PIN); } else { IO ## _WPORT &= ~MASK(IO ## _PIN); }; } while (0)

/// set pin as input
#define     _SET_INPUT(IO)      do { IO ## _DDR &= ~MASK(IO ## _PIN); } while (0)
/// set pin as output
#define     _SET_OUTPUT(IO)     do { IO ## _DDR |=  MASK(IO ## _PIN); } while (0)



//  why double up on these macros? see http://gcc.gnu.org/onlinedocs/cpp/Stringification.html

/// Read a pin wrapper
#define     READ(IO)                    _READ(IO)
/// Write to a pin wrapper
#define     WRITE(IO, v)            _WRITE(IO, v)
/// set pin as input wrapper
#define     SET_INPUT(IO)           _SET_INPUT(IO)
/// set pin as output wrapper
#define     SET_OUTPUT(IO)      _SET_OUTPUT(IO)

с:

#define DIO0_PIN       PIND0
#define DIO0_RPORT     PIND
#define DIO0_WPORT     PORTD
#define DIO0_PWM       &OCR0B
#define DIO0_DDR       DDRD

#define DIO1_PIN       PIND1
#define DIO1_RPORT     PIND
#define DIO1_WPORT     PORTD
#define DIO1_PWM       &OCR2B
#define DIO1_DDR       DDRD
...

Вы можете изменить макросы, чтобы получить прямые целые числаа не DIOn

...