指向外围硬件的指针作为模板参数

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>:

这不适用于优化,因为可以交换元素。