Maintaining a single codebase between embedded and non-embedded code
我正在从事一个涉及微控制器编程的机器人研究项目。 我希望能够最大程度地将测试软件与测试硬件分离。 这不仅可以提高开发速度,而且还可以使我在将代码放到机器人上之前更轻松地对其进行单元测试/模拟。 因此,例如,我可以编写一个" MyRobot"库。 然后,我既可以在嵌入式代码中也可以在非嵌入式仿真/测试代码中包含此库。 在运行时,我将提供函数指针,这些指针将读取(在嵌入式情况下)或模拟(在模拟情况下)传感器数据,并将其输入库中。
这样看来,我需要做的就是在编译时生成两个库:一个用于嵌入式代码,一个用于非嵌入式代码。
我的问题是这是否可行/是否有更好的方法/是否有任何陷阱需要我注意。
提前致谢!
-
理念; 生成3个代码库,A_embedded,A_non-embedded和B_common。 某些代码在另一个世界中毫无意义。
-
是的,那是我的计划。 有一个"通用"库,然后有两个使用通用库的不同的应用程序/库:一个用于嵌入式,一个用于非嵌入式
-
这里的关键是编写所有可移植的代码。 例如,在资源受限的MCU和繁琐的64位Windows PC上完全可以使用相同的代码。 但是,这需要程序员提供更丰富的C知识:必须考虑整数限制,隐式类型提升,符号性,内在性,实现定义的行为等。 您可能要考虑使用某种MISRA-C来通过静态分析消除大多数此类可移植性漏洞。
这是嵌入式系统开发中的常见情况,通常建议您使用创建两个库的方法。将底层硬件与嵌入式系统固件中的软件解耦是一种最佳实践。
您提到的库通常称为"硬件抽象层"或HAL。可以在名为hal.h之类的单个头文件中提供HAL的API(应用程序编程接口)。您的软件中需要访问硬件的每个源模块在源文件的顶部都有以下行:
像这样设计系统的好处包括:
-
模块化。如果您需要更改时序,例如读取传感器的UART或SPI接口,则即使您的代码中可能有多个位置读取该传感器,也只需更改HAL库。
-
可移植性。如果以后需要将项目迁移到其他微控制器,则仅需要更改HAL层。
-
封装。硬件的详细信息隐藏在HAL层中,这使您的其他软件可以在更高的抽象级别上运行。如果您使用的是由微控制器制造商提供的设备库,该设备库提供寄存器,I / O端口等的地址,则可以将对该库的引用封装在HAL库中,因此您的应用程序代码无需了解它。
-
可测试性。这是您问题的主要重点。您可以编写可以在其他平台(例如Windows)上运行的HAL层的特殊版本,以测试应用程序软件。此特殊版本不需要包含微控制器制造商提供的设备库,因为当您在测试环境中运行时,微控制器不存在,因此不需要通过以下方式访问其寄存器和I / O端口:您的软件。
对于您的两种情况,如您所建议的,您将创建HAL库的两个版本:标准版本,其中包含在嵌入式硬件上运行的代码;模拟版本,该模拟版本用于在硬件中测试软件的目的。控制方式。您可以命名标准库hal.lib(根据开发环境的不同,扩展名可能不同)和模拟库hal_simulated.lib。两者将具有相同的接口,如hal.h中所述。也就是说,两个库都将包含在hal.h中声明的所有函数,例如void halInit(),int halReadProximitySensor()等。
假设您的IDE支持Release和Debug配置,则可以为软件测试创建名为SW_Test的第三个配置。此配置将与您的Debug配置重复,除了hal_simulated.lib将链接到项目中而不是标准hal.lib。
也可以看看
硬件抽象(维基百科)
-
正是我想要的!感谢您的详细回复。
-
总的来说,这是一个很好的建议,但是我不建议将所有驱动程序都转储到具有公共标头的单个库中,因为这会使所有项目都特定于项目。这样做的问题是,您在每个硬件驱动程序之间创建了紧密的耦合:它们都需要头文件。反过来,这意味着要在仅需要SPI的项目中使用SPI驱动程序,还必须提供UART驱动程序等,以便可以进行链接。最好使用每个HAL自主创建每个模块。您将拥有一个SPI HAL和一个UART HAL,而他们一点也不了解。
-
@Lundin:这些是有关如何创建HAL库的详细信息。 OP的项目需要HAL,所以是的,它应该针对项目。 hal.h是HAL库和使用它的应用程序代码之间的接口。不需要在HAL库中的每个模块中#include d;它仅需要包含在需要访问HAL的应用程序源文件中。
-
@Lundin:这将解决您对耦合的担忧:hal_uart.c包含行#include"hal_uart.h",但对hal_spi.h或hal.h一无所知。 hal.h将包含行#include"hal_uart.h"和#include"hal_spi.h"。应用程序源文件将仅包含行#include"hal.h"。因此,我的回答并不意味着所有硬件驱动程序之间都有紧密的联系。
-
@sifferman我看不出这怎么解决。如果hal.h包括hal_spi.h,则每当希望将hal.h用于完全不相关的UART用途时,必须始终在项目中包括SPI驱动程序。那仍然是一个不应该存在的依赖关系。如果hal.h是所有使用的驱动程序的特定于项目的摘要,那没关系,但是在这种情况下,应将其命名为其他名称,例如robot_project.h或其他名称。
-
@Lundin该项目适用于机器人。机器人具有硬件,例如接近传感器,步进电机和螺线管。可以在HAL中提供该硬件的接口。希望该接口不会包含" UART"和" SPI"之类的术语,因为机器人用户不想用这些术语来思考。 OP并没有询问如何创建可在不同项目中使用的多功能HAL,而是希望为其机器人提供HAL。如果您不喜欢名称hal.h,可以,但是hal.h对我有用,因为它充当单个简单的头文件,为机器人提供了访问整个HAL的权限。
-
我对我的答案进行了较小的编辑,以便对使用单个头文件作为HAL的入口点的教条少一些。
考虑到您使用的不是面向对象语言的C语言,我将使用具有一些内部逻辑甚至#ifdefs(如果必须要有性能)的单个库,例如:
1 2 3 4 5 6 7 8 9 10 11 12
| bool turnOn ()
{
#ifdef DEV
printf ("Turned On\
");
return true;
#endif
#ifdef PROD
return robot_command_turnOn ();
#endif
} |
要么
1 2 3 4 5 6 7 8 9
| bool turnOn ()
{
if (inProduction ())
return robot_command_turnOn ();
printf ("Turned On\
");
return true;
} |
甚至更好:
1 2 3 4 5 6 7 8 9 10
| bool turnOn ()
{
printf ("Turned On\
");
if (!inProduction ())
return true;
return robot_command_turnOn ();
} |
有几种方法可以做到这一点。在我看来,我不会选择两个库,因为我需要保持功能签名和版本的同步,这可能是一团糟。
秘诀是为您的硬件(如果是机器人)构建一个接口库,并开发该库以融合所有可能的交互,从而保持与硬件层的抽象水平。该接口库可以控制您是否正在测试设备(使用上面的inProduction()之类的功能),并在允许的情况下将命令发送到硬件。
使用面向对象的语言(如C ++),您可以使用各种模式来帮助您:即:接口模式,工厂模式等。
-
您能否详细说明"接口库"的含义?
-
像您所说的库:robotlib.c-我们可以将其作为接口库,因为它仅包含将与您的硬件进行处理(接口)的函数。即:turnon(),turnoff(),stepup(),stepdown(),等等...
-
"甚至更好"的版本也不等效-它对所有构建都无条件调用printf()。