指向外围硬件的指针作为模板参数
Pointer to peripheral hardware as template parameter
我想通过C++模板访问STM32F0外设寄存器。 GPIO 端口由供应商头文件定义如下:
节选stm32f0xx.h
#define __IO volatile //!< Defines 'read / write' permissions
typedef struct
{
__IO uint32_t MODER;
__IO uint16_t OTYPER;
uint16_t RESERVED0;
__IO uint32_t OSPEEDR;
__IO uint32_t PUPDR;
__IO uint16_t IDR;
uint16_t RESERVED1;
__IO uint16_t ODR;
uint16_t RESERVED2;
__IO uint32_t BSRR;
__IO uint32_t LCKR;
__IO uint32_t AFR[2];
__IO uint16_t BRR;
uint16_t RESERVED3;
} GPIO_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x08000000)
#define GPIOA_BASE (AHB2PERIPH_BASE + 0x00000000)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
我创建了一个模板 class 用于输出处理。
main.cpp:
template <uintptr_t port, uint8_t pin>
class Output {
public:
static void set() {
GPIO_TypeDef *castedPort = reinterpret_cast<GPIO_TypeDef *>(port);
castedPort->ODR = (1 << pin);
}
};
int main(void)
{
Output<GPIOA_BASE, 5>::set();
while(1)
{
}
}
如果我用 launchpad g++ for arm 编译这段代码,它运行良好。但我想测试我的代码
使用GoogleTest,所以我对其进行了测试并尝试对其进行编译。
intArgument.cpp:
#include "gtest/gtest.h"
typedef struct {
/* see above definition */
} GPIO_TypeDef;
uint32_t gpioa[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
template <uintptr_t port, int pin>
class Output {
public:
static void set() {
GPIO_TypeDef * castedPort = reinterpret_cast<GPIO_TypeDef *>(port);
castedPort->ODR = (1 << pin);
}
};
TEST(OutputTest, OutputDataRegisterWritten) {
Output<gpioa, 5>::set();
GPIO_TypeDef * port = reinterpret_cast<GPIO_TypeDef *>(gpioa);
EXPECT_EQ((1 << 5), port->ODR);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
但是现在编译失败了。不允许通过 reinterpret_cast
转换为 int,因为那样它就不再是常量表达式。
fabian@ubuntu:~/workspace/WhosebugQuestion$ g++ -std=c++11 intArgument.cpp -lgtest -pthread -o intptrArgument.out
intArgument.cpp: In member function ‘virtual void OutputTest_OutputDataRegisterWritten_Test::TestBody()’:
intArgument.cpp:23:18: error: conversion from ‘uint32_t* {aka unsigned int*}’ to ‘long unsigned int’ not considered for non-type template argument
Output<gpioa, 5>::set();
^
intArgument.cpp:23:18: error: could not convert template argument ‘gpioa’ to ‘long unsigned int’
intArgument.cpp:23:26: error: invalid type in declaration before ‘;’ token
Output<gpioa, 5>::set();
所以我尝试将 port
的类型更改为 GPIO_TypeDef *
。
pointerArgument.cpp:
typedef struct {
/* see above definition */
} GPIO_TypeDef;
GPIO_TypeDef gpioa;
// using GPIO_TypeDef * as template argument
template <GPIO_TypeDef * port, int pin>
class Output {
public:
static void set() {
port->ODR = (1 << pin);
}
};
TEST(OutputTest, OutputDataRegisterWritten) {
Output<&gpioa, 5>::set();
EXPECT_EQ((1 << 5), gpioa.ODR);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
编译测试通过
fabian@ubuntu:~/workspace/WhosebugQuestion$ g++ -std=c++11 pointerArgument.cpp -lgtest -pthread -o pointerArgument.out
fabian@ubuntu:~/workspace/WhosebugQuestion$ ./test.out
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from OutputTest
[ RUN ] OutputTest.OutputDataRegisterWritten
[ OK ] OutputTest.OutputDataRegisterWritten (0 ms)
[----------] 1 test from OutputTest (1 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[ PASSED ] 1 test.
但是使用这种方法对于 arm 编译器是失败的:
main.cpp
template <GPIO_TypeDef * port, uint8_t pin>
class Output {
public:
static void set() {
port->ODR = (1 << pin);
}
};
int main(void)
{
Output<GPIOA, 5>::set();
while(1)
{
}
}
compiler error:
[cc] main.cpp:13:17: error: '1207959552u' is not a valid template argument for 'GPIO_TypeDef*' because it is not the address of a variable
[cc] main.cpp:13:25: error: invalid type in declaration before ';' token
我理解这两个错误,但是有什么方法可以解决这个问题吗?我搜索了编译器标志,但没有找到
任何,这可能会改变这种行为。 #define TESTING
结合 #ifdef/#ifndef
可能有效,但我没有
喜欢它,因为经过测试的代码与生成的代码不同。也许有更好的解决方案?
使用的编译器:
g++ (i686-posix-dwarf-rev3, Built by MinGW-W64 project), 4.9-2014q4 by Launchpad for STM32F0XX
g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2 for Testing
使用链接器标志Wl,section-start
,您可以定义指定部分的起始地址。所以首先我强制我的寄存器模拟在一个自己的部分中:
GPIO_TypeDef gpioa __attribute__((section(".myTestRegisters")));
GPIO_TypeDef gpiob __attribute__((section(".myTestRegisters")));
我还为节开始和每个寄存器定义了 const uintptr_t
地址值。
const uintptr_t myTestRegisterSectionAddress = 0x8000000;
const uintptr_t gpioaAddress = myTestRegisterSectionAddress;
const uintptr_t gpiobAddress = myTestRegisterSectionAddress + sizeof(GPIO_TypeDef);
我可以将这些值用作模板参数。
template <uintptr_t port, int pin>
class Output {
public:
static void set() {
GPIO_TypeDef * castedPort = reinterpret_cast<GPIO_TypeDef *>(port);
castedPort->ODR = (1 << pin);
}
};
TEST(OutputTest, OutputDataRegisterWritten) {
Output<gpioaAddress, 5>::set();
EXPECT_EQ(1 << 5, gpioa.ODR);
Output<gpiobAddress, 10>::set();
EXPECT_EQ(1 << 10, gpiob.ODR);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
要强制一个部分的起始地址,您可以使用 Wl,-section-start=.{sectionName}={startAddress}
。
所以在我的例子中我使用:
g++ intArgument.cpp -std=c++11 -g -o intArgument.out -lgtest -pthread -Wl,-section-start=.myTestRegisters=0x8000000
运行 申请结果:
fabian@ubuntu:~/workspace/WhosebugQuestion$ ./intArgument.out
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from OutputTest
[ RUN ] OutputTest.OutputDataRegisterWritten
[ OK ] OutputTest.OutputDataRegisterWritten (0 ms)
[----------] 1 test from OutputTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.
并且 objdump 显示以下信息:
fabian@ubuntu:~/workspace/WhosebugQuestion$ objdump -S -j .myTestRegisters intArgument.out
intArgument.out: file format elf64-x86-64
Disassembly of section .myTestRegisters:
0000000008000000 <gpioa>:
...
000000000800000c <gpiob>:
这不适用于优化,因为可以交换元素。
我想通过C++模板访问STM32F0外设寄存器。 GPIO 端口由供应商头文件定义如下:
节选stm32f0xx.h
#define __IO volatile //!< Defines 'read / write' permissions
typedef struct
{
__IO uint32_t MODER;
__IO uint16_t OTYPER;
uint16_t RESERVED0;
__IO uint32_t OSPEEDR;
__IO uint32_t PUPDR;
__IO uint16_t IDR;
uint16_t RESERVED1;
__IO uint16_t ODR;
uint16_t RESERVED2;
__IO uint32_t BSRR;
__IO uint32_t LCKR;
__IO uint32_t AFR[2];
__IO uint16_t BRR;
uint16_t RESERVED3;
} GPIO_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x08000000)
#define GPIOA_BASE (AHB2PERIPH_BASE + 0x00000000)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
我创建了一个模板 class 用于输出处理。
main.cpp:
template <uintptr_t port, uint8_t pin>
class Output {
public:
static void set() {
GPIO_TypeDef *castedPort = reinterpret_cast<GPIO_TypeDef *>(port);
castedPort->ODR = (1 << pin);
}
};
int main(void)
{
Output<GPIOA_BASE, 5>::set();
while(1)
{
}
}
如果我用 launchpad g++ for arm 编译这段代码,它运行良好。但我想测试我的代码 使用GoogleTest,所以我对其进行了测试并尝试对其进行编译。
intArgument.cpp:
#include "gtest/gtest.h"
typedef struct {
/* see above definition */
} GPIO_TypeDef;
uint32_t gpioa[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
template <uintptr_t port, int pin>
class Output {
public:
static void set() {
GPIO_TypeDef * castedPort = reinterpret_cast<GPIO_TypeDef *>(port);
castedPort->ODR = (1 << pin);
}
};
TEST(OutputTest, OutputDataRegisterWritten) {
Output<gpioa, 5>::set();
GPIO_TypeDef * port = reinterpret_cast<GPIO_TypeDef *>(gpioa);
EXPECT_EQ((1 << 5), port->ODR);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
但是现在编译失败了。不允许通过 reinterpret_cast
转换为 int,因为那样它就不再是常量表达式。
fabian@ubuntu:~/workspace/WhosebugQuestion$ g++ -std=c++11 intArgument.cpp -lgtest -pthread -o intptrArgument.out
intArgument.cpp: In member function ‘virtual void OutputTest_OutputDataRegisterWritten_Test::TestBody()’:
intArgument.cpp:23:18: error: conversion from ‘uint32_t* {aka unsigned int*}’ to ‘long unsigned int’ not considered for non-type template argument
Output<gpioa, 5>::set();
^
intArgument.cpp:23:18: error: could not convert template argument ‘gpioa’ to ‘long unsigned int’
intArgument.cpp:23:26: error: invalid type in declaration before ‘;’ token
Output<gpioa, 5>::set();
所以我尝试将 port
的类型更改为 GPIO_TypeDef *
。
pointerArgument.cpp:
typedef struct {
/* see above definition */
} GPIO_TypeDef;
GPIO_TypeDef gpioa;
// using GPIO_TypeDef * as template argument
template <GPIO_TypeDef * port, int pin>
class Output {
public:
static void set() {
port->ODR = (1 << pin);
}
};
TEST(OutputTest, OutputDataRegisterWritten) {
Output<&gpioa, 5>::set();
EXPECT_EQ((1 << 5), gpioa.ODR);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
编译测试通过
fabian@ubuntu:~/workspace/WhosebugQuestion$ g++ -std=c++11 pointerArgument.cpp -lgtest -pthread -o pointerArgument.out
fabian@ubuntu:~/workspace/WhosebugQuestion$ ./test.out
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from OutputTest
[ RUN ] OutputTest.OutputDataRegisterWritten
[ OK ] OutputTest.OutputDataRegisterWritten (0 ms)
[----------] 1 test from OutputTest (1 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (1 ms total)
[ PASSED ] 1 test.
但是使用这种方法对于 arm 编译器是失败的:
main.cpp
template <GPIO_TypeDef * port, uint8_t pin>
class Output {
public:
static void set() {
port->ODR = (1 << pin);
}
};
int main(void)
{
Output<GPIOA, 5>::set();
while(1)
{
}
}
compiler error:
[cc] main.cpp:13:17: error: '1207959552u' is not a valid template argument for 'GPIO_TypeDef*' because it is not the address of a variable
[cc] main.cpp:13:25: error: invalid type in declaration before ';' token
我理解这两个错误,但是有什么方法可以解决这个问题吗?我搜索了编译器标志,但没有找到
任何,这可能会改变这种行为。 #define TESTING
结合 #ifdef/#ifndef
可能有效,但我没有
喜欢它,因为经过测试的代码与生成的代码不同。也许有更好的解决方案?
使用的编译器:
g++ (i686-posix-dwarf-rev3, Built by MinGW-W64 project), 4.9-2014q4 by Launchpad for STM32F0XX
g++ (Ubuntu 4.9.2-0ubuntu1~14.04) 4.9.2 for Testing
使用链接器标志Wl,section-start
,您可以定义指定部分的起始地址。所以首先我强制我的寄存器模拟在一个自己的部分中:
GPIO_TypeDef gpioa __attribute__((section(".myTestRegisters")));
GPIO_TypeDef gpiob __attribute__((section(".myTestRegisters")));
我还为节开始和每个寄存器定义了 const uintptr_t
地址值。
const uintptr_t myTestRegisterSectionAddress = 0x8000000;
const uintptr_t gpioaAddress = myTestRegisterSectionAddress;
const uintptr_t gpiobAddress = myTestRegisterSectionAddress + sizeof(GPIO_TypeDef);
我可以将这些值用作模板参数。
template <uintptr_t port, int pin>
class Output {
public:
static void set() {
GPIO_TypeDef * castedPort = reinterpret_cast<GPIO_TypeDef *>(port);
castedPort->ODR = (1 << pin);
}
};
TEST(OutputTest, OutputDataRegisterWritten) {
Output<gpioaAddress, 5>::set();
EXPECT_EQ(1 << 5, gpioa.ODR);
Output<gpiobAddress, 10>::set();
EXPECT_EQ(1 << 10, gpiob.ODR);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
要强制一个部分的起始地址,您可以使用 Wl,-section-start=.{sectionName}={startAddress}
。
所以在我的例子中我使用:
g++ intArgument.cpp -std=c++11 -g -o intArgument.out -lgtest -pthread -Wl,-section-start=.myTestRegisters=0x8000000
运行 申请结果:
fabian@ubuntu:~/workspace/WhosebugQuestion$ ./intArgument.out
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from OutputTest
[ RUN ] OutputTest.OutputDataRegisterWritten
[ OK ] OutputTest.OutputDataRegisterWritten (0 ms)
[----------] 1 test from OutputTest (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.
并且 objdump 显示以下信息:
fabian@ubuntu:~/workspace/WhosebugQuestion$ objdump -S -j .myTestRegisters intArgument.out
intArgument.out: file format elf64-x86-64
Disassembly of section .myTestRegisters:
0000000008000000 <gpioa>:
...
000000000800000c <gpiob>:
这不适用于优化,因为可以交换元素。