表示微芯片上引脚的宏

Macro representing a pin on a microchip

我正在尝试用 C 语言为 atmega 微控制器编写一些代码,我有一个工作宏 BIT_SET 用于设置单个位:

#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))

现在我想定义 one-line 代表 I/O 引脚的宏。类似于此:

#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

最终代码中会有更多针对不同周边的宏,这只是一个简化的例子。

问题是我不知道如何定义宏PORTDDR, 这样我就可以像这样使用 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 行设置输出引脚的逻辑值。
因为两条线都影响同一个引脚,所以我想 select 引脚在 1 个位置 (#define LED ...) 稍后在代码中对两个操作(配置方向和设置输出值)仅使用符号名称(LEDBUTTON)。

DDRBPORTB 必须能够进一步扩展(而且不止一次),因为它们是在外部 header 中定义的(不受我的控制)。 我也被一个事实所困,即使用 ## 的连接会阻止宏的进一步扩展。

您可以编写宏,以便预处理器展开

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))))

请注意,通过将 LED_CODE 连接并扩展结果得到的宏 DDRB 本身已扩展。这种方法确实让您为每个引脚定义两个单独的宏(在本例中为 LED_CODELED_BIT),名称必须遵循固定模式,但提供一种形式(例如 X_BIT_SET(DDR, LED, 0) ) 它在源代码中的位置非常容易阅读。

我认为像这样的东西应该可以解决问题:

免责声明:未经测试,可能有问题,但你明白了......

// 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;

最后,我决定按照一些评论的建议使用内联函数而不是宏。 这是我的结果:

// 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);

我必须承认,严格来说,这不是我原来问题的答案,因为我要求的是宏的解决方案。但我希望这对某些人仍然有用。