表示微芯片上引脚的宏
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
最终代码中会有更多针对不同周边的宏,这只是一个简化的例子。
问题是我不知道如何定义宏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
行设置输出引脚的逻辑值。
因为两条线都影响同一个引脚,所以我想 select 引脚在 1 个位置 (#define LED ...
)
稍后在代码中对两个操作(配置方向和设置输出值)仅使用符号名称(LED
、BUTTON
)。
宏 DDRB
和 PORTB
必须能够进一步扩展(而且不止一次),因为它们是在外部 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_CODE
和 LED_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);
我必须承认,严格来说,这不是我原来问题的答案,因为我要求的是宏的解决方案。但我希望这对某些人仍然有用。
我正在尝试用 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
最终代码中会有更多针对不同周边的宏,这只是一个简化的例子。
问题是我不知道如何定义宏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
行设置输出引脚的逻辑值。
因为两条线都影响同一个引脚,所以我想 select 引脚在 1 个位置 (#define LED ...
)
稍后在代码中对两个操作(配置方向和设置输出值)仅使用符号名称(LED
、BUTTON
)。
宏 DDRB
和 PORTB
必须能够进一步扩展(而且不止一次),因为它们是在外部 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_CODE
和 LED_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);
我必须承认,严格来说,这不是我原来问题的答案,因为我要求的是宏的解决方案。但我希望这对某些人仍然有用。