使用基于模板的 API 在嵌入式单元测试中设置虚拟内存映射 IO

Setting up a dummy memory mapped IO in Embedded Unit-Tests with a template-based API

我正在开发一个基于 API 的模板,用于访问 stm32f7xx 上的 GPIO 和其他外围设备。 API 基于 Christopher Kormanyos 的 "Real time c++ - efficient object-oriented and template microcontroller programming"。

// gpio.h
template <std::uintptr_t BASE, std::uint8_t PIN>
class Gpio final {...}

// main.cpp
// this is how the API is usally used.
// note that GPIOA_BASE is defined in stm header files and 
// is an uint that directly points to the memory mapped IO
using gpio_a0 = Framework::Stm32F7xx::Gpio::Gpio<GPIOA_BASE, 0>;

这本书本身没有涉及测试,IMO 在没有测试的情况下编写代码感觉很糟糕。所以我最终决定赶上并让我的 CMake 与 Gtest 一起 运行ning.

我的问题是测试是针对 (host/not ARM) 平台编译的,因此我可以 运行 在管道中进行测试。所以我不能也不应该使用 stm 头文件中的定义,例如 GPIOA_BASE。因为它们会指向一个在执行平台的测试中不可用的位置。 (结果是 SIGSEGV 因为我访问了我不应该访问的内存)

我的问题是现在。如何传递指向大小为 GPIO_Typedef 并且也在编译时定义的内存位置的模板 a uintptr_t

这可能吗?还有其他选择吗?

我试了几个不同的版本都不行。 在下面的示例中,我希望我可以进一步详细说明我需要什么。

class GpioFixture : public ::testing::Test {
};

static constexpr char mem[sizeof(GPIO_TypeDef)] {};
static constexpr std::uintptr_t memptr = &mem; // <- this is not working

TEST_F(GpioFixture, ...){
    using gpio_a1 = Framework::Stm32F7xx::Gpio::Gpio<memptr, 1>;
    gpio_a1 a1 {...};

    // perform test

}

编辑: 所以主要问题是我需要在 POINT X 处提供一些内存并在编译时获取它的地址。

所以我可以简单地做一个 using gpio_a0 = Framework::Stm32F7xx::Gpio::Gpio<0x..., 0>;

也许可以使用链接描述文件来实现? 创建一个部分并导出该部分的开头? 我不确定,因为我真的不知道这是否适用于 OS?

解析的虚拟内存地址
static constexpr char mem[sizeof(GPIO_TypeDef)] {};
static constexpr std::uintptr_t memptr = &mem; // <- this is not working

这个代码片段的问题是你需要一个 constexpr reinterpret_cast 作为第二行,这是语言不允许的。我知道这是嵌入式设备的一种非常常见的模式,我个人曾希望 std::bit_cast or std::bless 考虑这一点,但似乎我们不得不放弃。

尽管如此,仍然有办法让它在您的主机上进行编译。您可以像这样使用自动模板参数:

template <auto BASE, std::uint8_t PIN>
class Gpio {};

Gpio class 的成员函数将不得不大量使用 reinterpret_cast 无论如何,例如设置可能如下所示:

static void set(uint32_t bit) {
  reinterpret_cast<GPIO_TypeDef*>(BASE)->BSRR = 1u << bit;
}

由于模板参数 BASE 现在接受指针,您甚至可以直接将指针传递给 GPIO_TypeDef 的实例(作为一个很好的副作用,这也解决了 UB 问题,因为现在参数实际上是 GPIO_TypeDef*):

GPIO_TypeDef mem{};
Gpio<&mem, 0u> gpio_a;

在您的目标上,您可以继续使用 STM32F7 中的定义 header:

Gpio<GPIOA_BASE, 0u> gpio_a;

并回答您上一条评论中的问题

But I quite don't really understand why this fails when using constexpr.

这可能有两个原因。

创建 GPIO_TypeDef 的 constexpr 实例也意味着将指向 const object 的指针传递给模板 class。如果您已经定义了 reinterpret_cast 到 GPIO_TypeDef* 的任何成员函数,您会收到一个错误,提示您试图放弃 const 限定符。

此外,您不能创建 non-literal types 的 constexpr 实例。 GPIO_TypeDef 是 non-literal,因为它的成员都是易变的(隐藏在名为 __IO 的定义后面)。