2015-11-23 2 views
2

Я пытаюсь написать код в C для ATmega микроконтроллера и у меня есть рабочий макрос BIT_SET для установки одного бита:Macro, представляющая булавку на микрочипе

#define BIT_MASK(b) (0x01 << (b)) 
#define BIT_ON(p,b) ((p) |= BIT_MASK(b)) 
#define BIT_OFF(p,b) ((p) &= ~BIT_MASK(b)) 
#define BIT_SET(p,b,v) (v ? BIT_ON(p,b) : BIT_OFF(p,b)) 

Теперь я хотел бы определить однострочные макросы, представляющие контакты ввода/вывода. Что-то похожее на это:

#define LED B,5 // an LED diode is connected to bit 5 of port B 
#define BUTTON B,4 // a button is connected to bit 4 of port B 

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

Проблема заключается в том, что я не знаю, как определить макросы PORT и DDR, , так что я мог бы использовать LED (или BUTTON) макрос так:

BIT_SET(DDR(LED), 1); // which should expand to: BIT_SET(DDRB, 5, 1) 
BIT_SET(PORT(LED), 0); // which should expand to: BIT_SET(PORTB, 5, 0) 


Это является моей мотивацией:

Линия DDRB управляет направлением штифта (независимо от того, является ли вывод входным или выходным) ,
Строка PORTB устанавливает логическое значение выходного вывода.
Поскольку обе линии влияют на один и тот же вывод, я хотел бы выбрать штырь в 1-м месте (#define LED ...) , а позже в коде использовать только символические имена (LED, BUTTON) для обеих операций (настройка направления и настройка выхода стоимость).

Макросы DDRB и PORTB должны иметь возможность расширения дальше (и не только один раз), поскольку они определены во внешнем заголовке (не под моим контролем). Также я был в ловушке того факта, что конкатенация с использованием ## предотвращает дальнейшее расширение макроса.

+0

Не проще ли использовать встроенные функции? –

+0

@WeatherVane Вы, вероятно, правы, это может быть лучший способ. Перечисление для портов, возможно, структура для хранения портов и контактов. И пусть компилятор сделает для меня оптимизацию. – bedrorom

+0

Предположим, вы попробуете перевернуть вещи, то есть '#define LED (p) p (B, 5)', '#define PORT (a, b) PORT ## a',' #define PIN (a, b) (b) ',' BIT_SET (светодиод (PORT), светодиод (PIN)) '. Ужасно, как грех. – doynax

ответ

0

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

BIT_SET(DDR(LED), 1); 

в

BIT_SET(DDRB, 5, 1) 

, как описано, но это не совсем то, что вы хотите. BIT_SET() сам по себе является макросом, и вы хотите в конечном итоге получить результат расширения этого макроса на основе аргументов, полученных другим расширением. Это не может быть. Препроцессор присваивает макроопределения макропараметрам перед выполнением каких-либо расширений, и с определением, которое вы дали для макроса BIT_SET(), препроцессор должен всегда отклонять предлагаемый вызов за неправильное количество аргументов.

Обновлено Добавить:

С другой стороны, обычный трюком для непрямых маркеров склеивания, чтобы обернуть его в двойном слое макросов. Например, препроцессор расширяет этот стек макросов ...

#define CONCAT2(x, y) x ## y 
#define CONCAT(x, y) CONCAT2(x, y) 

#define BIT_MASK(b) (0x01 << (b)) 
#define BIT_ON(p,b) ((p) |= BIT_MASK(b)) 
#define BIT_OFF(p,b) ((p) &= ~BIT_MASK(b)) 
#define BIT_SET(p,b,v) (v ? BIT_ON(p,b) : BIT_OFF(p,b)) 

#define DDRB somevar 

#define LED_CODE B 
#define LED_BIT 5 

#define X_BIT_SET(x, y, v) BIT_SET(CONCAT(x, y ## _CODE), y ## _BIT, v) 

X_BIT_SET(DDR, LED, 0) 

...чтобы

(0 ? ((somevar) |= (0x01 << (5))) : ((somevar) &= ~(0x01 << (5)))) 

Обратите внимание, что макро DDRB, полученного путем конкатенации LED с _CODE и расширением результата сам расширяются. Этот подход не позволяет вам определять два отдельных макроса для каждого вывода (в данном случае LED_CODE и LED_BIT) с именами, которые должны придерживаться фиксированного шаблона, но предоставляет форму (например, X_BIT_SET(DDR, LED, 0)), которую довольно легко читать там, где она отображается в вашем исходный код.

1

Я думаю, что-то вроде этого следует сделать трюк:

Отказ от ответственности: непроверенные и, возможно, глючит, но вы получите идею ...

// Use of PINB, PINC, ... macros and PINB0, PINB1, ... macro from avr/io.h 
#define LED PORT_PIN(PINB, PINB0) 

#define PORT_PIN(port, pin) (((unsigned int) (&(port) - &PINB)/(unsigned int) &PINB) \ 
          << 4 + (pin)) 
#define DDR(port_pin) *(((port_pin) >> 4) & 0xf) \ 
         * (unsigned int) (&PINC - &PINB) + &PINB)) 
#define PORT(port_pin) *(((port_pin) >> 4) & 0xf) \ 
         * (unsigned int) (&DDRC - &DDRB) + &DDRB)) 
#define PIN(port_pin) ((port_pin) & 0xf) 

STATIC_ASSERT(&DDRC - &DDRB == &PINC - &PINB); 
STATIC_ASSERT(sizeof PINB == 1 && sizeof DDRB == 1); 

, то вы можете получить доступ к макросы как:

BIT_SET(DDR(LED), PIN(LED), 1); 
BIT_SET(PORT(LED), PIN(LED), 0); 

Как Замечание, в том же духе и в зависимости от компилятора вы также можете сделать что-то вроде этого:

typedef struct 
{  
    uint8_t PIN_0: 1; 
    uint8_t PIN_1: 1; 
    uint8_t PIN_2: 1; 
    uint8_t PIN_3: 1; 
    uint8_t PIN_4: 1; 
    uint8_t PIN_5: 1; 
    uint8_t PIN_5: 1; 
    uint8_t PIN_6: 1; 
    uint8_t PIN_6: 1; 
} REG_t; 

#define MY_PINB (*(volatile REG_t *) &PINB) 
#define MY_DDRB (*(volatile REG_t *) &DDRB) 

, а затем вы можете получить доступ к штырям, как это:

#define LED (MY_PINB.PIN0) 

LED = 0; 
+0

в общем, так ТИ определяет свои «биты в регистре» и группируя такие определения вместе, определяя набор регистров для периферии. – user3629249

0

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

// enumeration for I/O ports 
typedef enum 
{ 
    IO_B, 
    IO_C, 
    IO_D 
} ioPort; 

// structure representing one I/O pin 
typedef struct 
{ 
    ioPort port; 
    uint8_t bitIx; 
} ioPin; 

// converts ioPort to corresponding PORTx pointer 
inline volatile uint8_t* port(ioPort iop) 
{ 
    switch(iop) 
    { 
    case IO_B: return &PORTB; 
    case IO_C: return &PORTC; 
    case IO_D: return &PORTD;  
    default: return NULL; 
    } 
} 

// converts ioPort to corresponding DDRx pointer 
inline volatile uint8_t* ddr(ioPort iop) 
{ 
    switch(iop) 
    { 
    case IO_B: return &DDRB; 
    case IO_C: return &DDRC; 
    case IO_D: return &DDRD; 
    default: return NULL; 
    } 
} 

// sets one bit of a port to given value 
static inline void bitSet(volatile uint8_t* port, const uint8_t bitIx, const uint8_t value) 
{ 
    if(port) 
    { 
     if(value) 
      *port |= (1L << bitIx); 
     else 
      *port &= ~(1L << bitIx); 
    }   
} 

// configuring where peripheral devices are connected 
const ioPin led = {IO_C, 5}; 
const ioPin button = {IO_C, 6}; 

// (later in a code) 
// setting direction of the pin with an led connected 
bitSet(ddr(led.port), led.bitIx, 1); 

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