摆脱嵌入式软件中的硬件宏

时间:2011-08-23 02:31:18

标签: c unit-testing refactoring

我正在使用C。

开发嵌入式程序

有很多硬件宏,比如

#ifdef HardwareA 
do A 
#endif

它不可读,并且难以用单元测试覆盖所有不同的路径。

因此,我决定将硬件相关代码移动到arch文件夹,并使用makefile中的宏来决定链接哪个arch文件夹。就像Linux内核代码一样。

但是当我看到Linux内核时,我注意到arch文件夹中有很多重复项。

如果在一个硬件中发现错误,它们如何对所有相关硬件进行更改,但可能会影响所有其他硬件?

我认为这样做不可避免地会给代码库带来重复。

有没有人有这类问题的经验?

如何对包含大量硬件宏的代码进行单元测试?

重构代码以将硬件宏移离源代码?

4 个答案:

答案 0 :(得分:3)

听起来你正在替换这样的函数:

somefunc()
{
    /* generic code ... */

    #ifdef HardwareA 
    do A 
    #endif

    /* more generic code ... */
}

有多个实现,每个arch文件夹中有一个,如下所示:

somefunc()
{
    /* generic code ... */

    /* more generic code ... */
}

somefunc()
{
    /* generic code ... */

    do A 

    /* more generic code ... */
}

通用代码的重复是您所担心的。不要这样做:相反,有一个函数的实现,如下所示:

somefunc()
{
    /* generic code ... */

    do_A();

    /* more generic code ... */
}

..然后在arch文件夹中实现do_A():在硬件A上它具有该硬件的代码,而在其他硬件上,它是一个空函数。

不要害怕空函数 - 如果你在arch头文件中定义inline函数,它们将被完全优化。

答案 1 :(得分:2)

Linux试图避免在多个arch目录之间复制代码。您将看到实现了相同的功能,但实现了不同。毕竟,所有体系结构都需要用于管理页表的代码,但细节不同。所以他们都有相同的功能,但定义不同。

对于某些函数,构建系统定义CONFIG_GENERIC_*,用泛型版本替换不必要的架构钩子(通常是无操作)。例如,没有FPU的拱门不需要挂钩来保存/恢复上下文切换时的FPU状态。

答案 2 :(得分:1)

这种#ifdef地狱肯定是要避免的,但自然你也想避免代码重复。我并不认为这会解决您的所有问题,但我认为您可以将#ifdef#ifdef HardwareX更改为#ifdef HAVE_FeatureY#ifdef USE_FeatureZ,这是最重要的一步。这允许您做的是考虑哪些硬件/ OS /等的知识。目标具有所有源文件中的哪些功能/接口以及单个标头,这可以避免以下情况:

#if defined(HardwareA) || (defined(HardwareB) && HardwareB_VersionMajor>4 || ...

渲染您的来源不可读。

答案 3 :(得分:1)

我倾向于将特定于硬件的#defines移动到每个平台的一个标头中,然后在“platform.h”文件中选择它,所有源文件都包含在该文件中。

platform.h:

#if defined PLATFORM_X86_32BIT
#include "Platform_X86_32Bit.h"
#elsif defined PLATFORM_TI_2812
#include "Platform_TI_2812.h"
#else
#error "Project File must define a platform"
#endif

特定于体系结构的标头将包含两件事。

1)所有常见整数大小的Typedef,如typedef short int16_t;请注意,c99指定了具有这些预定义的'stdint.h'。 (切勿在便携式代码中使用原始int。)

2)所有硬件特定行为的函数头或宏。通过提取函数的所有依赖关系,代码的主体仍然是干净的:

  //example data receive function
  HW_ReceiverPrepare();
  HW_ReceiveBytes(buffer, bytesToFetch);
  isGood = (Checksum(buffer+1, bytesToFetch-1) == buffer[0])
  HW_ReceiverReset();

然后,一个特定于平台的标题可以将原型提供给复杂的HW_ReceiverPrepare()函数,而另一个标题只是用#define HW_ReceiverPrepare()

来定义它。

这在您的评论中描述的情况下非常有效,其中平台之间的差异通常是一行或两行。只需将这些行封装为函数/宏调用,就可以保持代码的可读性,同时最大限度地减少重复。