【问题标题】:How to I manage code for different Pin-Out boards in an embedded platform for better HAL management?如何在嵌入式平台中管理不同 Pin-Out 板的代码以更好地管理 HAL?
【发布时间】:2019-09-14 08:00:42
【问题描述】:

这里的问题是,如果您的项目很小,#if defined#define#elif defined 链会变得冗长、乏味且容易失败。在编译时必须有更好的方法来完成这个。

目前我正在通过#if / #elif 链运行我的代码来定义GPIO的引脚和变量名,这里没有问题,这里有一个简化的上下文,甚至添加了一个头文件排除 HAL。

所以主要看起来像:

#include “my_path/hal.h”

#define some_function ()
#if defined(BOARD_ID1)
      Do_something KEYS_GPIO_REG_UP
#elif defined(BOARD_ID1)
    Do_something_different KEYS_GPIO_REG_UP
#endif

main()
..

这是一个将 GPIO 分配给变量的 hal 文件。

// Define hal.h 
    #if defined(BOARD_ID1)
    #define KEYS_GPIO_REG_UP            GPIOD->IDR
    #define BUTTON_GPIO_PIN_UP            GPIO_Pin_1  // PD.01
    #elif defined(BOARD_ID2)
    #define KEYS_GPIO_REG_UP            GPIOB->IDR
    #define BUTTON_GPIO_PIN_UP            GPIO_Pin_2  // PB.01

我想了解的是(请指出最佳实践文章和/或示例代码的方向)如何在编译时提供一个关键 ID 来定义板: set BOARDID ${BOARDID} 我已经这样做了,而是做同样的事情来定义哪个 hal.h 为每种板类型创建一个 BOARID.h 文件以维护要使用的版本。我不确定这是否可能,或者一个选项是在它自己的代码中提供一个脚本变量,并且动态地必须在 make 脚本中更改 ìnclude ${BOARDID}.h

【问题讨论】:

  • c++c99 标签不匹配。你是用 C++ 还是 C 编程?
  • 现在主要是 c,我的目标是将其移至 c++ 以使代码更具可移植性,但您可以说我正在“主要编写脚本”功能方面以确保基础工作正常. ??????
  • if defined (ID1) elif defined (ID1) 没有多大意义。

标签: c++ embedded c99


【解决方案1】:

这不是 HAL,它只是编译器开关的集合——这通常是最糟糕的选择之一,因为它们会使代码变得很混乱。

一个 HAL 将是 board_x.c 中的完整函数集,以及 board_y.c 中的另一完整函数集。每个 .c 文件中的函数具有相同的名称但执行不同的操作。两个 .c 文件都包含带有函数声明的相同标头 API - 这是实际的 HAL,也是调用应用程序知道和关心的唯一文件。

然后为单独的板子创建单独的项目,或者通过外部版本控制来处理它。在一种情况下,您链接到board_x.c,在另一种情况下,您链接到board_y.c

【讨论】:

  • 您好,您的第一次观察正是我要解决的问题。问题是,拥有这些功能会带来很多其他困难,并且还需要在每个board_””.c 文件中转换对这些功能的任何更新,恕我直言,这是一个比我试图解决的问题更大的问题。这就是为什么如果可能的话,使用 at compile 方式来选择目标板并将代码动态链接到每个板的 PINOUT 变体将是一个更好的解决方案。你是否有一个指向源或文献的指针来实现这个,以防我错过了你的意图。 ?
  • @jemo 基本上,HAL 只是设计一个具有标准 OO 设计的程序。 header/HAL API 是一个抽象基类,所有具体实现都继承并实现了 header 中给出的功能。文学可以是任何关于 OO 设计的书。
【解决方案2】:

您的解决方案虽然正确,但不是一个好的解决方案。我知道这可能看起来不错,因为您对它的使用非常满意,但这最终会产生大量宏定义、每个板具有不同名称/参数的函数,而且,最有问题的一个,所有应用程序都会知道关于通过包含的实际硬件。更糟糕。对任何板的任何更改都将强制重新编译所有不同板的所有项目(因为所有项目都包含所有板,尽管没有使用它)。

正确的方法是使用共享 API 的单个 .hfile。这是一组定义行为的函数,而不是硬件。像 void setUartSpeed(uart_t *uart, size_t speed) 之类的函数,其中 uart_t 是一个不透明的指针,每个板的定义不同。是的,对 API 的任何更改都会导致所有板的编译错误,但这很好,因为您会注意到它并且您只需要更改板 .c 文件,而不是所有项目文件。所以,更正确的方法是@Lundin 的回答。


按照你的cmets,如何避免这样的实现? https://github.com/opentx/opentx/blob/2.3/radio/src/targets/taranis/hal.h

只考虑行为,而不是硬件:

即谈论宏KEYS_GPIO_REG_MENU:你应该完全删除它,因为它是一个硬件实现细节,对于应用程序的其余部分不是必需的。取而代之的是GPIOStruct* getPinout()setHigh(GPIOStruct *)setLow(GPIOStruct *) 等函数,其中GPIOStruct.c 文件中定义的不透明指针。 因此,在每个板上,您将有一个 .c 文件定义:

  • getPinout 函数,它将返回一个包含所有可用引脚的数组。
  • setHigh 和 setLow 函数,将引脚设置设置为逻辑电平。也许,这个功能可以放在相同的 uC 架构中,因为它取决于 uC 而不是板上。

因此,特别是在您的示例中,您将拥有:

//hal.h
typedef struct GPIOStruct GPIOStruct;
GPIOStruct* getPinout();
void setHigh(GPIOStruct* pin);
void setLow(GPIOStruct* pin);

//board PCBX9E.c
struct GPIOStruct {
    void *idr; //Set to the correct type, probably volatile uint8_t
    int idx;
};
static const GPIOStruct gpio_reg_menu={.idr=&GPIOD->IDR, .idx=7};
static const GPIOStruct gpio_reg_exit={.idr=&GPIOD->IDR, .idx=2};
//And so on...

GPIOStruct* getPinout()
{
    GPIOStruct gpios[]={
        &gpio_reg_menu,
        &gpio_reg_exit,
        //And so on...
    }
}


//board PCBXLITE.c
//Note: this structs are the same. It could be moved to a file called taranis_gpio.h, since the architecture of the uC is the same for both boards, 
//among with the functions to setHigh/setLow a pin.
struct GPIOStruct {
    void *idr; //Set to the correct type, probably volatile uint8_t
    int idx;
};

//But the pinout is different, so this should be kept in each board file.
static const GPIOStruct gpio_reg_menu={.idr=&GPIOE->IDR, .idx=8};
static const GPIOStruct gpio_reg_exit={.idr=&GPIOE->IDR, .idx=7};
//And so on...

GPIOStruct* getPinout()
{
    GPIOStruct gpios[]={
        &gpio_reg_menu,
        &gpio_reg_exit,
        //And so on...
    }
}

在您的项目中,您只需添加一个或另一个 .c 文件,具体取决于您要编译到的板。


希望不要从这里压倒你......

处理硬件时功能的深入分离可以在以下论文中找到: https://atomicobject.com/uploads/archive/files/EIT2006EmbeddedTDD.pdf

总结起来,他们为每个硬件模块创建三个文件:

  • Hardware:此文件包含硬件访问、寄存器等。这主要是我们要说的。

  • 型号:此文件将包含硬件应该做什么的高级表示。 IE。它将包含以下功能:openMenuisMenuOpened 等...

  • 导体:此文件包含胶水。它监视和更新硬件,并根据另一端的变化监视和更新模型。

通过这种分离,很容易将硬件从一种实现更改为另一种,只需更改.c


在示例中,所有这些文件都是三元组的 hardware 元素。

您可以按照我向您展示的方式保留它,直接处理 GPIO,或者您可以遵循此方案并通过提供 openMenucloseMenu 等函数来进一步抽象它。这些函数将是三元组的model 元素。最后,在 GPIO 上循环的主要代码将是 conductor 元素。

【讨论】:

  • 你好@LoPiTaL,我需要 100 多个板和 100 多个代码文件,我的构建过程很简单,制作 TARGET 并为每个板构建一个固件文件。此外,我可以将单个任务交给承包商,为“KEYS_GPIO_REG_MENU”等引脚功能提供文档,它将与硬件引用一起使用。即github.com/iNavFlight/inav/tree/master/src/main/target 类似需要类似的过程。我明白了,不要误会我的意思,但我需要在每种情况下映射硬件,即 {.idr=&GPIOE->IDR, .idx=7};让编译器完成剩下的工作。它有效! :)
【解决方案3】:

我真正寻找的是一种“管理”if-else-if 长链或为不同板设置属性的方法。我所做的是在我的原始头文件中嵌入了几个 hea 文件:

所以现在我的文件看起来像这样:

// Define hal.h 
    #if defined(BOARD_ID1)
    #include "board_id01.h"
    #elif defined(BOARD_ID2)
    #include "board_id02.h"

然后我可以拥有尽可能多的独立文件并更轻松地管理目标板,即。

// Header File board_id01.h 

#define KEYS_GPIO_REG_UP            GPIOD->IDR
#define BUTTON_GPIO_PIN_UP            GPIO_Pin_1  // PD.01
#define ....

这使我可以一次专注于 HAL 的一个板...然后只处理我的代码。

我希望这可以帮助任何人以一种简单的方式来管理您的多目标嵌入式开发以及那些共享您的 cmets 的人,在我现在看到它时更好地理解我的问题,但我没有很好地构建它。

【讨论】:

  • 这虽然是正确的,但不是一个好的解决方案。我知道这可能看起来不错,因为您对它的使用非常满意,但这最终会产生大量宏定义、每个板具有不同名称/参数的函数,而且,最有问题的一个,所有应用程序都会知道关于通过包含的实际硬件。更糟糕。对任何板的任何更改都将强制重新编译所有不同板的所有项目(因为所有项目都包含所有板,尽管没有使用它)。 [继续下一条评论]
  • 正确的方法是拥有一个带有共享 API 的 .hfile。这是一组定义行为的函数,而不是硬件。像void setUartSpeed(uart_t *uart, size_t speed) 这样的函数,其中uart_t 是一个不透明的指针,并且对每个板都有不同的定义。是的,对 API 的任何更改都会导致所有板的编译错误,但这很好,因为您会注意到它并且您必须更改板 .c 文件,而不是所有项目文件.因此,更正确的方法是@Lundin 的回答。
  • 感谢@LoPiTal 和 Lundin 的反馈。在嵌入式世界中,MCU 封装发生了变化,板上连接了不同的外围设备,是的,您必须编译每个目标板。在这种情况下,您正在对应用程序进行编译管理以及它如何与硬件交互,因此一些熊金属代码位于板之间并且具有不同的路径来实例化硬件,所以我没有关注您。这是我想避免github.com/opentx/opentx/blob/2.3/radio/src/targets/taranis/… 的示例,所以我不知道如何实现。
  • 在下面查看我的答案
猜你喜欢
  • 2020-08-27
  • 1970-01-01
  • 2016-10-28
  • 2019-06-28
  • 1970-01-01
  • 2012-09-23
  • 1970-01-01
  • 1970-01-01
  • 2020-11-06
相关资源
最近更新 更多