【发布时间】:2017-07-29 15:47:07
【问题描述】:
我正在尝试为我正在使用的微控制器编写一个通用类。这些野兽使用<register prefix> <index> <suffix>形式的寄存器并不罕见,例如UCSR0B或TCCR1A
所以我编写了用于连接其参数以形成新标记的宏:
#define uart_is_enabled(i) (UCSR ## i ## B)
#define uart_putchar(i, c) UDR ## i = c
注意:这里我使用的是 UART 寄存器,但这只是一个示例,我并不是要让 UART 工作,我已经为它编写了代码,我想对其进行增强。
编辑:出于好奇,这里有一个寄存器的定义,例如 UCSR0B,根据 Atmel 库:
#ifndef __SFR_OFFSET
# if __AVR_ARCH__ >= 100
# define __SFR_OFFSET 0x00
# else
# define __SFR_OFFSET 0x20
# endif
#endif
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define UCSR0B _SFR_IO8(0x25)
现在我想尝试使用模板类来处理这些宏,而不需要对每个可能的索引进行专门化:
template <const unsigned index>
class Uart
{
public:
static bool is_enabled() { return uart_is_enabled(index); }
static void putchar(uint8_t) { uart_putchar(index, c); }
};
当然,使用uart<0> 或uart<1> 或任何unsigned int 作为模板参数,我会收到如下错误消息:
error: 'UCSRindexB' was not declared in this scope
这是因为如果我的理解是正确的,那么在编译模板参数值之前,宏参数会被“评估”(请原谅我使用了不恰当的术语)。有没有办法让它工作?
编辑:虽然Phil's answer 100% 直接解决了我的问题,但我改变了主意并选择了不同的方法。我认为 C/C++ 是一种需要详细说明的语言,即最好声明所有要管理的案例。在某些情况下,像这样,偷工减料可能会产生更繁琐的代码——这只是我的看法,当然,YMMV。
因此,我通过duck-typing 实现了某种形式的硬件抽象:我定义的硬件驱动程序类与我使用的 Atmel 处理器中有许多不同的 UART 一样多——我很幸运没有那么多的 UART 类型以 Atmel 微控制器为型号。
这是一个带有 LIN/UART 驱动程序类的示例:
// lin.h (excerpt)
// Auto-detect the one and only serial interface (set UART mode)
#ifdef LINDAT
/*
* UART0 driver — for microcontroller which UART module is shared
* with LIN, e.g. ATmega64M1
*/
struct uart0
{
static INLINE void power_on() { power_lin_enable(); }
static INLINE void power_off() { power_lin_disable(); }
static INLINE void enable() { uart_enable(); }
static INLINE void disable() { uart_disable(); }
...
static void reset();
};
#endif
以下是定义 1 个或多个 UART 模块的微控制器示例:
// uart.h (excerpt)
// Auto-detect first serial interface aka U[S]ART0
#ifdef UDR0
struct uart0
{
static INLINE void power_on() { power_usart0_enable(); }
static INLINE void power_off() { power_usart0_disable(); }
static INLINE void enable() { uart_enable(0); }
static INLINE void disable() { uart_disable(0); }
...
};
#endif
请注意,我仍在使用我在本文上面介绍的 C 宏来概括寄存器处理...虽然有些往往变得很难阅读,但我承认。
然后我写了一个模板类(实际上是一个接口类),其中参数是驱动类,接口类继承自该类。
// Generic UART wrapper. Comes with a circular input buffer
template <class driver>
class tty : public driver
{
protected:
...
public:
static void putchar(char) { driver::putchar(c); }
static void power_off()
{
driver::disable();
driver::power_off();
}
...
};
根据检测到的寄存器名称包含控制器特定的头文件:
#if defined(UDR0) || defined(UDR1) || defined(UDR)
#include <drv/uart.h>
#endif
#ifdef LINDAT
#include <drv/lin.h>
#endif
通过这种方法,特定于我正在编译的体系结构的驱动程序成员通过接口类公开。它允许我为单个应用程序支持的尽可能多的处理器编写相对通用的代码:
typedef serio tty<uart0>; // same code for ATmega328p, ATmega64M1, ATtiny1634...
serio::putchar('a');
如果我想将我的应用程序专用于特定的微控制器,我也可以编写非常具体的代码。我只需要使用特定于架构的驱动程序成员。
对于通用方法,按照惯例,我的所有驱动程序类都必须公开一定数量的公共成员才能使该概念起作用,但只需执行一次,即添加对新微控制器的支持时。这确实是某种重复性任务(参见我关于“冗长”的观点),我的意思是(几乎)一遍又一遍地看起来相同的代码(用您想要支持的每个控制器冲洗/重复)但最后这个是我想要的灵活性。
我还检查了生成的汇编代码,我可以确认优化器做得非常好,尤其是我何时何地要求它内联代码。
【问题讨论】:
-
简短且唯一的答案是“否”。根本不可能使用例如的值
index在宏中。 -
代码中的
UCSR0B;是什么?是变量吗? -
预处理发生在将
Uart解析为模板之前,并且只执行基本的文本替换。 -
好吧,太糟糕了。我想我将不得不坚持专业化,放弃我的宏,因为在这种情况下它们将毫无用处。感谢您的见解。
标签: c++ templates macros arguments