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

时间:2015-05-05 13:18:50

标签: c architecture c-preprocessor

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

所以我试着稍微清理一下:

我在这里组织了一些非常大的文件:

/*============================================================================*/
/* 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 

每个"子文件的标题类型相同" (直到枚举)。目标是在其他所有的" .c"文件我们包括" BSP_gpio.h"它会自动包含与所用硬件相对应的文件。

第一个问题是编译取决于我包含子文件的位置。例如,我有一个功能" void BSP_GPIO_set(GPIO_MODE模式)"它使用enum" GPIO_MODE"并且两个硬件版本不同(因为两个硬件上的IO状态不同)。如果我在声明之前包含子文件,它就不知道类型" GPIO_MODE"并且编译错误,即使我#include" BSP_gpio.h"在子文件中。 所以我只是将它放在文件的末尾并且它可以工作,即使我不是真的喜欢它......

第二个问题出现在我将变量声明为extern时,我想在子文件和其他C文件中使用它。假设我把这条线放在" #ifdef HW_v1":

之前
EXTERN int numberOfToggles;

" EXTERN"在我的" BSP_gpio.c"我在文件开头定义了BSP_GPIO_C,并且在我包含" BSP_gpio.h"的每个其他文件中都是关键字 extern 。当我构建我的项目时,它会编译但我有一个链接器错误:" BSP_gpio.o和BSP_gpio_HW2.o"中的numberOfToggles的重复定义;而我无法找到解决方案。

如果有人有适当的解决方案,我已经准备好改变项目的架构了!

4 个答案:

答案 0 :(得分:1)

  

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

我不确定“第一个问题是[...]”和“它是否有效”。我猜您的抱怨是关于编码风格的重构。

无论如何,是的,C对声明出现的顺序很敏感,因此在你放置#include指令的地方很重要。就个人而言,我更倾向于采用每个头文件H应该#include所有其他头文件提供H需要并且本身不提供的宏和声明的方法。通过标准防范多重包含,可以缓解标题顺序和位置周围的许多(但不是全部)问题。

可能需要将更多或更精细的标题分解出来才能将#include指令仅放在每个标题的顶部。

  

第二个问题出现在我将变量声明为extern时,我想在子文件和其他c文件中使用它。 [...]当我构建我的项目时,它编译但我有一个链接器错误:“BSP_gpio.o和BSP_gpio_HW2.o中的numberOfToggles的重复定义”,我找不到解决方案。

全局变量可以在任意数量的编译单元中具有任意数量的兼容声明,但它必须具有正好一个 定义。只要没有全局变量的初始值设定项,标题中的extern声明就完全可以了。然后,每个全局应在一个.c源文件中具有非外部声明,以用作定义。在定义中使用初始化程序的加分点。

答案 1 :(得分:1)

对我而言,您似乎没有明确区分界面和实现。是否有充分的理由在头文件中具有硬件依赖性?是否有充分的理由拥有GPIO驱动程序而不是功能特定的驱动程序?

在我看来,硬件依赖应隐藏在实现中(即.c文件),而不是通过头文件公开。例如,用于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

如果您遵循这种方法,您通常只需要更改驱动程序代码的实现,而不是更改不同硬件版本之间的头文件。

除此之外,我同意LudinJohn Bollinger的答案。

答案 2 :(得分:0)

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

不要忘记使用编译器开关,只需更改最新版本硬件的代码即可。随你去,创建一个"标签"在您的版本控制系统中为每个硬件版本更改。如果您需要使用旧硬件,只需返回您当时使用的版本即可。

或者创建当前最新文件的分支,采取"硬件路由"来自旧标记的文件,并将其转储到最新文件中。

答案 3 :(得分:0)

我根据你的回答修改了我的代码,使其更加清晰。我现在只有一个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

所以这回答了我的第一个评论/问题,我对我的代码并不满意,但我仍然有我不理解的重复定义问题,因为BSP_GPIO_C仅在BSP_gpio.c中定义文件而不是BSP_gpio_HW1(/ 2).c文件。

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