传递IO寄存器作为模板参数

时间:2016-11-23 21:43:52

标签: c++ c++11 templates avr

我想使用IO-Register(==静态内存地址)作为模板参数。问题是,寄存器通常被定义为扩展为类似于(*(volatile uint8_t*)(11 + 0x20))的宏,我无论如何都无法正常使用我的模板。

我想写代码如下:

Foo<PORTB> foo;

这样我就可以轻松更改类使用的IO寄存器,而无需任何运行时开销(这对微控制器至关重要)。 我在下面列出了一个完整的例子:

#include <stdint.h>
#include <stdio.h>
#include <utility>

#define PORTB  (*(volatile uint8_t*)(11 + 0x20))

template<volatile uint8_t* PortRegister>
class ControllerPtr final
{
public:
    static void SetHigh() { *PortRegister |= 0x2; }
};

template<volatile uint8_t& PortRegister>
class ControllerRef final
{
public:
    static void SetHigh() { PortRegister |= 0x2; }
};


int main()
{
    ControllerPtr<&PORTB> ptr;
    ControllerRef<PORTB> ref;

    ptr.SetHigh();
    ref.SetHigh();

    // Both statements should be equal to:
    // PORTB |= 0x2;
}

每当我尝试将&PORTB传递给ControllerPtr时,g ++都无法编译:

  

错误:(volatile uint8_t*)((long int)(11 + 32))不是volatile uint8_t* {aka volatile unsigned char*}的有效模板参数,因为它不是变量的地址

     

错误:表达式*(volatile uint8_t*)((long int)(11 + 32))有副作用

尝试将PORTB传递给ControllerRef中使用的引用类型时,错误略有不同:

  

错误:*(volatile uint8_t*)((long int)(11 + 32))不是类型volatile uint8_t& {aka volatile unsigned char&}的有效模板参数,因为它不是具有链接的对象

我实际上不明白为什么这个错误是错误的,因为我没有看到将静态地址传递给模板的任何问题。

2 个答案:

答案 0 :(得分:4)

非类型模板参数必须是常量表达式,并且在常量表达式中不能有reinterpret_cast(除非它未被评估)。

由于您已表示除了通过PORTB之类的宏之外无法访问数字地址,因此我建议您采用解决方法。虽然PORTB不能在模板参数中使用,但我们可以合成一个可以在模板参数中使用的唯一类型,如下所示:

struct PORTB_tag {
    static volatile uint8_t& value() { return PORTB; }
};
template <class PortTag>
class ControllerRef final {
  public:
    static void SetHigh() { PortTag::value() |= 0x2; }
};
int main() {
    ControllerRef<PORTB_tag> ref;
    ref.SetHigh();
}

为了在有大量端口时保存重复输入,我们可以使用宏:

#define PORT_TAG(port) port_tag_for_ ## port
#define MAKE_PORT_TAG(port) struct port_tag_for_ ## port { \
    static volatile uint8_t& value() { return port; } \
}
template <class PortTag>
class ControllerRef final {
  public:
    static void SetHigh() { PortTag::value() |= 0x2; }
};
MAKE_PORT_TAG(PORTB);
int main() {
    ControllerRef<PORT_TAG(PORTB)> ref;
    ref.SetHigh();
}

http://coliru.stacked-crooked.com/a/401c0847d77ec0e0

答案 1 :(得分:0)

Brian在他的回答中指出,模板参数必须是一个常量表达式。在较旧的GCC(6.0之前版本)中,以下 hack 应该有效:

#include <stdint.h>
#include <stdio.h>
#include <utility>

#define PORTB (*(volatile uint8_t*)(11 + 0x20))

// Helper constant for 0 address
#define ZERO  (*(volatile uint8_t*)(0))

// A ptrdiff_t is OK
template <std::ptrdiff_t PortRegister>
class ControllerPtr final
{
public:
  // Get back to the actual desired address
  static void SetHigh() { *(volatile uint8_t*)(&ZERO+PortRegister) |= 0x2; }
};

// Define helper constants for all relevant registers
static constexpr std::ptrdiff_t C_PORTB = &PORTB-&ZERO;

int main()
{
  // Now this works
  ControllerPtr<C_PORTB> ptr;

  ptr.SetHigh();
}

Inspect the result here。我称之为黑客,因为较新的GCC版本(正确地)拒绝使用错误编译它

  

错误:从整数到指针

的reinterpret_cast

constexpr声明的行上,因为从技术上讲,你不能在常量表达式中使用reinterpret_cast的结果。 This SO post提供了更多背景信息。

相关问题