相当于嵌入式C/代码组织中的接口

Equivalent to Interfaces in Embedded C / Code organization

我正在 EFM32 Cortex M3 处理器上开发嵌入式 C 代码,几个月后代码开始变得疯狂......我的意思是我们改变了硬件所以我们得到了不同的版本,我们改变了一些组件,移动了一些 IOs,在启动时有不同的论文状态...

所以我想稍微清理一下:

我有一些非常大的文件是这样组织的:

/*============================================================================*/
/* File       : BSP_gpio.h                                                   */
/* Processor  : EFM32GG980F1024                                              */
/*----------------------------------------------------------------------------*/
/* Description : gpio driver                                                  */
/*----------------------------------------------------------------------------*/

#ifndef BSP_GPIO_H
#define BSP_GPIO_H

#ifdef EXTERN
#undef EXTERN
#endif

#ifdef BSP_GPIO_C
#define EXTERN
#else
#define EXTERN extern
#endif

typedef enum
{
  GPIO_INIT   = 0,
  GPIO_WAKEUP = 1,
  GPIO_SLEEP  = 2,  
} GPIO_MODE;

/*      Definition / conts        */

#define PIO_PIN_HIGH        1
#define PIO_PIN_LOW         0

#ifdef HW_v1

... hundreds of lines...

#elif defined HW_v2

... hundreds of lines...

#endif 
#endif

我尝试将不同版本的文件分开并尝试这样的操作:

#ifdef HW_v1

    #include "BSP_gpio_HW1.h"

#elif defined HW_v2

    #include "BSP_gpio_HW2.h"

#endif 

每个 "subfile" 具有相同类型的 header(直到枚举)。目的是在每个其他“.c”文件中包含 "BSP_gpio.h",它会自动包含与所用硬件相对应的文件。

第一个问题是编译取决于我包含子文件的位置。例如,我有一个函数“void BSP_GPIO_set(GPIO_MODE mode)”,它使用枚举 "GPIO_MODE" 并且在两个硬件版本中不同(因为 IOs 的状态在两个硬件上是不一样的)。如果我在声明之前包含子文件,它不知道类型 "GPIO_MODE" 并产生编译错误,即使我在子文件中 #include "BSP_gpio.h" 也是如此。 所以我只是把它放在文件的末尾并且它有效,即使我真的不喜欢它......

当我有一个声明为 extern 的变量时出现第二个问题,我想在子文件和其他 C 文件中使用它。可以说我把这一行放在“#ifdef HW_v1”之前:

EXTERN int numberOfToggles;

"EXTERN" 这个词在我的 "BSP_gpio.c" 文件中什么都没有,因为我在它的开头定义了 BSP_GPIO_C,并且是关键字 extern 在我包含 "BSP_gpio.h" 的所有其他文件中。当我构建我的项目时,它可以编译,但我有一个链接器错误:"Duplicate definitions for numberOfToggles in BSP_gpio.o and BSP_gpio_HW2.o",我找不到解决方案。

如果有人对此有合适的解决方案,我准备更改我的项目的架构!

The first problem is that the compilation depends on where I include the subfile. For example, I have a function "void BSP_GPIO_set(GPIO_MODE mode)" which uses the enum "GPIO_MODE" and is different in the two hardware versions [...]. If I include the subfile before the declaration, it doesn't know the type "GPIO_MODE" and makes a compilation error, even if I #include "BSP_gpio.h" in the subfiles. So I just put this at the end of the file and it works, even if I don't really like it...

我不确定 "The first problem is [...]" 与 "it works" 的搭配如何。我猜你的抱怨是关于编码风格从你的重构中掉下来的。

无论如何,是的,C 对声明出现的顺序很敏感,因此放置 #include 指令的位置很重要。就个人而言,我更喜欢采用这样的方法,即每个 header 文件 H 应该 #include 所有其他 header 提供 H 需要和不需要的宏和声明本身提供。有了防止多重包含的标准防护措施,可以缓解许多(但不是全部)围绕 header 顺序和位置的问题。

可能需要从 header 中分解出更多或更精细的部分,才能将 #include 指令仅放在每个部分的顶部。

The second problem appears when I have a variable declared as extern which I want to use in both subfiles and in others c files. [...] When I build my project, it compiles but I have a linker error : "Duplicate definitions for numberOfToggles in BSP_gpio.o and BSP_gpio_HW2.o" and I can't find a solution for that.

一个全局变量可以在任意数量的编译单元中有任意数量的兼容声明,但它必须恰好有一个 定义。 header(s) 中的 extern 声明完全没问题,只要它们没有全局变量的初始值设定项即可。每个全局变量都应该在一个 .c 源文件中有一个 non-extern 声明作为定义。在定义中使用初始值设定项的奖励积分。

这个问题似乎 100% 与缺乏版本控制有关,而不是真正与编程有关。

不必为编译器开关而烦恼,只需更改代码,使其适用于最新版本的硬件。在进行过程中,为每个硬件修订更改在版本控制系统中创建一个 "tag"。如果您需要使用旧硬件,只需返回到您当时使用的版本即可。

或者创建一个当前最新文件的分支,从旧标签中取出 "hardware routing" 文件并将其转储到最新文件中。

在我看来,您似乎没有明确区分接口和实现。是否有充分的理由在头文件中包含硬件依赖项?使用 GPIO 驱动程序而不是功能特定的驱动程序是否有充分的理由?

在我看来,硬件依赖项应该隐藏在实现中(即 .c 文件),而不是通过头文件 public。例如,用于 LED 的 Signalisation 驱动程序。在那里,头文件可能看起来像这样:

#ifndef DRIVER_SIGNAL_H
#define DRIVER_SIGNAL_H

typedef enum
{
  SIGNAL_STARTED,
  SIGNAL_ERROR,
  SIGNAL_WARNING,
} signal_id_t;

void signal_show(signal_id_t signal_id);

#endif

如果您采用这种方法,您几乎只需要更改驱动程序代码的实现,而不需要更改不同硬件版本之间的头文件。

除此之外,我同意 and 的回答。

根据您的回答,我修改了我的代码,使其更清晰。我现在只有一个 BSP_gpio.h 文件,其中包含我的定义和所有原型:

/*============================================================================*/
/* File       : BSP_gpio.h                                                       */
/* Processor  : EFM32GG980F1024                                               */
/*----------------------------------------------------------------------------*/
/* Description : gpio driver                                                  */
/*----------------------------------------------------------------------------*/

#ifndef BSP_GPIO_H
#define BSP_GPIO_H

#ifdef EXTERN
#undef EXTERN
#endif

#ifdef BSP_GPIO_C
#define EXTERN
#else
#define EXTERN extern
#endif

typedef enum
{
  GPIO_INIT   = 0,
  GPIO_WAKEUP = 1,
  GPIO_SLEEP  = 2,  
} GPIO_MODE;

/*      Definition / conts        */

#define PIO_PIN_HIGH        1
#define PIO_PIN_LOW         0

/*      Variables                 */

EXTERN UINT8 numberOfToggles;

/*      Functions                 */
void BSP_GPIO_set_interupt(UINT8 port, UINT8 pin, BOOL falling);
void BSP_GPIO_set(GPIO_MODE mode)    

#endif

我现在有三个 .c 文件,BSP_gpio.c 我在其中实现了两种硬件通用的所有内容:

/*============================================================================*/
/* File       : BSP_gpio.c                                                       */
/* Processor  : EFM32GG980F1024                                               */
/*----------------------------------------------------------------------------*/

#define BSP_GPIO_C

/*----------------------------------------------------------------------------*/
/*          Includes                                                          */
/*----------------------------------------------------------------------------*/
#include "BSP_type.h"
#include "BSP_gpio.h"

void BSP_GPIO_set_interupt(UINT8 port, UINT8 pin, BOOL falling)
{
    //implementation
}

和 BSP_gpio_HW1(/2).c 文件,我在其中实现了被预处理器指令包围的 void BSP_GPIO_set(GPIO_MODE mode) 函数,因此它只实现了一次:

#ifdef HW_v1(/2)
#include "BSP_gpio.h"

/*============================================================================*/
/*                           BSP_gpio_set                                     */
/*============================================================================*/
void BSP_GPIO_set(GPIO_MODE mode)
{
//Implementation which depends on the hardware
}
#endif

所以这回答了我的第一个 remark/question 我对我的代码不是很满意,但我仍然有重复定义问题,我不明白,因为 BSP_GPIO_C 只定义在BSP_gpio.c 文件而不是 BSP_gpio_HW1(/2).c 文件。

有什么想法吗? 再次感谢您的帮助!