将 IO 寄存器作为模板参数传递
Passing an IO register as template parameter
我想使用 IO 寄存器(== 静态内存地址)作为模板参数。问题是,寄存器通常被定义为扩展为类似于 (*(volatile uint8_t*)(11 + 0x20))
的宏,我不知何故无法正确使用我的模板。
我想写这样的代码:
Foo<PORTB> foo;
这样我就可以轻松更改 class 使用的 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++ 都无法编译:
error: (volatile uint8_t*)((long int)(11 + 32))
is not a valid template argument for volatile uint8_t* {aka volatile unsigned char*}
because it is not the address of a variable
error: expression *(volatile uint8_t*)((long int)(11 + 32))
has side-effects
尝试将 PORTB
传递给 ControllerRef
中使用的引用类型时,错误有点不同:
error: *(volatile uint8_t*)((long int)(11 + 32))
is not a valid template argument for type volatile uint8_t& {aka volatile unsigned char&}
because it is not an object with linkage
我其实不明白为什么这个错误是一个错误,因为我没有看到将静态地址传递给模板有任何问题。
非类型模板参数必须是常量表达式,常量表达式中不能有 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();
}
正如 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。我称它为 hack 因为较新的 GCC 版本将(正确地)拒绝编译它并出现错误
error: reinterpret_cast from integer to pointer
在 constexpr
声明的行,因为从技术上讲,您不能在常量表达式中使用 reinterpret_cast
的结果。 This SO post 提供了更多背景知识。
我想使用 IO 寄存器(== 静态内存地址)作为模板参数。问题是,寄存器通常被定义为扩展为类似于 (*(volatile uint8_t*)(11 + 0x20))
的宏,我不知何故无法正确使用我的模板。
我想写这样的代码:
Foo<PORTB> foo;
这样我就可以轻松更改 class 使用的 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++ 都无法编译:
error:
(volatile uint8_t*)((long int)(11 + 32))
is not a valid template argument forvolatile uint8_t* {aka volatile unsigned char*}
because it is not the address of a variableerror: expression
*(volatile uint8_t*)((long int)(11 + 32))
has side-effects
尝试将 PORTB
传递给 ControllerRef
中使用的引用类型时,错误有点不同:
error:
*(volatile uint8_t*)((long int)(11 + 32))
is not a valid template argument for typevolatile uint8_t& {aka volatile unsigned char&}
because it is not an object with linkage
我其实不明白为什么这个错误是一个错误,因为我没有看到将静态地址传递给模板有任何问题。
非类型模板参数必须是常量表达式,常量表达式中不能有 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();
}
正如 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。我称它为 hack 因为较新的 GCC 版本将(正确地)拒绝编译它并出现错误
error: reinterpret_cast from integer to pointer
在 constexpr
声明的行,因为从技术上讲,您不能在常量表达式中使用 reinterpret_cast
的结果。 This SO post 提供了更多背景知识。