我有一个没有操作系统的嵌入式系统的现有代码库。我正在尝试使用 x86 gcc 而不仅仅是交叉编译器来构建它,作为能够在主机上进行单元测试的第一步。不幸的是,到处都有直接的寄存器读写,而不是使用提供的抽象层。当然,一种选择是解决这个问题;将直接寄存器访问替换为对 GPIO 访问函数的调用。我希望通过引入一个宏(仅在 x86 版本中使用)来重新定义代码片段,例如
来加快速度myvar = (GPIOC->IDR >> 6) & 0x03; // Read bits 6 and 7
GPIOD->CRL &= 0xFFFFFF0F;
类似:
myvar = (myGpiocIdrReadFunc() >> 6) & 0x03; // Read bits 6 and 7
myGpiodClrWriteFunc() &= 0xFFFFFF0F;
GPIOx 被定义为指向物理地址的指针。当然,如果我尝试使用 x86 构建的可执行文件直接读取和写入 PC 上的地址,这将是访问冲突。不幸的是,如果我尝试这样的事情:
#define GPIOC->IDR { myGpiocIdrReadFunc() }
编译器不喜欢“->”,说“宏名称后面缺少空格。”
如果您以前解决过此类问题,您是如何做到的?
typedef struct {
int IDR;
int CRL;
} gpio_t;
gpio_t c;
gpio_t d;
gpio_t * GPIOC = &c;
gpio_t * GPIOD = &d;
只要继续将
IDR
之类的寄存器添加到 struct
中,编译器就会对你尖叫。
编辑在看到您的评论后,我编辑了答案,您实际上想模拟这些值,这样就可以,但请记住,您需要像在硬件中一样初始化它们。
看来您正在逐字替换源中的该字符串。为什么不使用像
sed
这样的非交互式编辑器来做到这一点呢?您可能有一个构建系统,可以根据架构进行替换或不进行替换。
如果 hexa 的解决方案不够充分,这里有第二个选项,它允许您实际模拟所引用的寄存器的行为(包括副作用等)。缺点是,这需要 C++(并且现有的 C 代码可以编译为 C++,这通常是一个困难的要求),并且结构不直接使用本机类型,而是使用一些别名。
您可以定义一个实现
operator=
、operator int
以及您可能需要的任何其他访问功能(&=
等)的类。如果结构体 GPIOC
指向的字段属于您可以更改的类型,则您可以简单地更改该类型以引用您的寄存器类。
大致是这样的:
#ifdef USE_REAL_HW
typedef volatile uint32_t HW_REGISTER32;
#else // (USE_REAL_HW not defined)
template<class T>
class RegisterAbstractionClass {
public:
const T operator= (const T value) {
data = value;
return value;
};
operator const T() {
return data;
};
// Other operators...
protected:
volatile T data;
};
typedef RegisterAbstractionClass<uint32_t> HW_REGISTER32;
#endif // (USE_REAL_HW)
typedef struct {
HW_REGISTER32 IDR;
HW_REGISTER32 CRL;
} gpio_t;
(请注意,上面的内容有些简化:在这种情况下,
IDR
和CRL
的行为相同,这可能不是硬件的情况。)
你的#define 似乎是错误的。在测试系统上尝试使用“#define MyGpiocIdrReadFunc() x86ReadFunc()”作为宏。当然,您需要将 GPIOC->IDR 的所有读取替换为 MyGpiocIdrReadFunc()。您还需要实现该功能。这可能与修复代码以使用您提到的抽象层一样多。
据我所知,您无法使用宏替换
->
运算符。
这可能不是您正在寻找的解决方案,但这就是我处理模拟硬件外设的方式。
考虑外围
GPIOC
。它在一些 HAL/CMSIS 文件中定义。
在我的 GPIO 驱动程序中,使用
#ifdef
检查,我将 GPIOC
替换为我自己的外设。我将 GPIOC
结构/外围设备称为“对象”,因为它可以帮助我更好地可视化它。
在
gpio.c
:
#ifdef _UNIT_TESTS_ENABLE_
static GPIO_TypeDef *mock_GPIOC; // Creating a mock object pointer
#undef GPIOC // Undefining the real object
#define GPIOC mock_GPIOC // Replacing real with mock
#endif // _UNIT_TESTS_ENABLE_
因此,当
gpio.c
中更下方的代码访问GPIOC
时,它实际上正在访问mock_GPIOC
。这很棒,因为 GPIOC
是 GPIO_TypeDef *
,mock_GPIOC
也是。它们是同一类型。
要获取和设置指针
mock_GPIOC
,您可以创建这样的函数。也由#ifdef
守护。
#ifdef _UNIT_TESTS_ENABLE_
GPIO_TypeDef *get_gpioc_object_ptr(void) { return GPIOC; }
void set_gpioc_object_ptr(GPIO_TypeDef *gpio_c) { GPIOC = gpio_c; }
#endif // _UNIT_TESTS_ENABLE_
当然,也在
.h
文件中公开这些函数。
最后,要在测试中使用它,请创建一个新的
GPIO_TypeDef
对象并使用 set_gpioc_object_ptr()
传递其指针。然后运行目标函数,并断言任何内容。
void my_test_function(void)
{
// Arrange
GPIO_TypeDef *orig_gpioc = get_gpioc_object_ptr();
GPIO_TypeDef mock_gpio =
{
.IDR = some_val,
.CLR = some_other_val,
};
set_gpioc_object_ptr(&mock_gpio);
// Act
your_target_gpio_function();
// Assert
ASSERT(mock_gpio.IDR == compare_value);
ASSERT(mock_gpio.CLR == other_compare_value);
set_gpioc_object_ptr(orig_gpioc); // revert to old GPIOC pointer
}
我认为这并不容易。
请注意,SFR 通常是
volatile
,并且许多算术运算都是原子的。所以,代码
GPIOD->CRL &= 0xFFFFFF0F;
不等于
setter(CLR_reg, getter(CLR_reg) & 0xFFFFFF0F);
另请注意,可能存在中断的繁忙等待循环,甚至中断处理程序。