【问题标题】:Equivalent to Interfaces in Embedded C / Code organization等效于嵌入式 C/代码组织中的接口
【发布时间】:2015-05-05 13:18:50
【问题描述】:

我正在 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 mode)”,它使用枚举“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.c”的所有其他文件中的关键字extern。 H”。当我构建我的项目时,它会编译,但我有一个链接器错误:“BSP_gpio.o 和 BSP_gpio_HW2.o 中 numberOfToggles 的定义重复”,我找不到解决方案。

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

【问题讨论】:

  • 听起来你应该考虑为不同版本的板子构建一些 BSL(板子支持库)。
  • 看看How do I use extern to share variables between source files in C?。很明显,您知道其中的大部分内容,但它可能会向您提出一些新的建议,尤其是讨论“extern 与没有extern”和初始化变量等的结尾部分。

标签: c architecture c-preprocessor


【解决方案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 源文件中具有一个非外部声明,以用作定义。在定义中使用初始化器的奖励积分。

【讨论】:

  • 是的,你是对的,我只是说我不太喜欢我必须把它放在文件末尾的事实。通常我将每个包含都放在文件的顶部,使用标准保护并且它可以工作,但在这种情况下不是。我知道全局变量应该只有一个定义。问题是这个“技巧”将 EXTERN 定义为没有定义 BSP_GPIO_C 并作为关键字 extern 在其他文件中,加上 BSP_gpio.h 文件的“分解”,我找不到一个良好的代码组织,一切正常。
【解决方案2】:

在我看来,您似乎没有明确区分接口和实现。是否有充分的理由在头文件中包含硬件依赖项?是否有充分的理由拥有 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的回答。

【讨论】:

  • 我同意你的例子。事实上,在我的子文件中,我有相同的原型“void BSP_GPIO_set(GPIO_MODE mode)”。不同之处在于此功能的实现,例如,端口 A 的引脚 1 在初始化 HW v1 时应为高电平,而对于 HW v2 应为低电平。包括好的头文件只是将编译器重定向到函数的好的实现,具体取决于硬件。因此,假设我在“BSP_gpio.h”文件中使用一个原型“void BSP_GPIO_set(GPIO_MODE 模式)”。你如何根据硬件改变这个函数的实现?
  • 您可以在 IDE 中创建项目的副本,然后添加不同的源文件。
【解决方案3】:

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

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

或创建当前最新文件的分支,从旧标签中获取“硬件路由”文件并将其转储到最新文件中。

【讨论】:

  • 是的,我想你是对的。不幸的是,我没有启动这个程序,而是开始使用预处理器指令而不是使用版本控制......我只是想让它工作而不会那么混乱。不管怎样,谢谢你的回答。
  • @ThibautGenet 解决这个问题永远不会太晚。
【解决方案4】:

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

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

【讨论】:

  • 我不知道该高兴还是该生气,因为我清理并重建了我的项目很多(很多)次,尝试更改链接器中的选项,重建项目等。我得到了每次重复定义错误。作为一个绝望的举动,我将类型更改为 UINT16 和变量的名称......魔术发生了,我没有错误......所以我回到了 UINT8 和以前的名称,并且再次没有错误。我真的不明白,但现在它可以工作了(我正在使用 IAR 6.6,使用 Cortex M 编译器版本 7.2,到目前为止运行良好)。所以现在一切都很好。感谢您的帮助!
猜你喜欢
  • 2014-11-03
  • 2015-07-06
  • 2011-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-15
  • 1970-01-01
相关资源
最近更新 更多