【问题标题】:Macro representing a pin on a microchip代表微芯片上引脚的宏
【发布时间】:2015-11-23 20:05:41
【问题描述】:

我正在尝试用 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))

现在我想定义代表 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 行设置输出引脚的逻辑值。
因为两条线都影响同一个引脚,所以我想在 1 个位置选择引脚 (#define LED ...) 稍后在代码中仅对两个操作(配置方向和设置输出值)使用符号名称(LED、BUTTON)。

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

【问题讨论】:

  • 使用内联函数不是更简单吗?
  • @WeatherVane 你可能是对的,这可能是一个更好的方法。端口的枚举,可能是用于将端口和引脚保持在一起的结构。并让编译器为我做优化。
  • 我想你试着把东西翻过来,即#define LED(p) p(B, 5)、#define PORT(a,b) PORT##a、#define PIN(a,b) (b)、BIT_SET(LED(PORT), LED(PIN))。虽然丑得像罪。
  • 我反对使用宏,除了简单的参数替换,最终结果是创建一种“新”语言,跟随你的人必须学习或解开。我只会在没有可用的语言语法时使用它们。
  • @WeatherVane:访问这些类型的寄存器令人讨厌的是,许多 MCU 供应商倾向于使它们相当非正交,在大多数等效外设阵列中布局和功能集略有不同。据推测,这节省了硅空间,但它也使编写通用代码变得复杂,在尝试它时,我经常以宏 hacks 来通过名称绑定事物或无穷无尽的专业化。在这种有限的情况下,干净的方法当然是可行的,但是当您需要为端口 B 指定中断向量时,事情就会变得很棘手。

标签: c macros atmega


【解决方案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;

【讨论】:

  • 一般来说,这是 TI 在寄存器中定义其位并将这些定义组合在一起定义外设的寄存器集的方式。
【解决方案2】:

最后,我决定使用内联函数而不是宏,正如一些 cmets 所建议的那样。 这是我的结果:

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

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

【讨论】:

    【解决方案3】:

    您可以编写宏以使预处理器扩展

    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))阅读它在源代码中出现的位置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-11
      • 2011-07-16
      • 1970-01-01
      • 1970-01-01
      • 2010-10-20
      • 2014-05-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多