【编程之美】详论单片机固件模块化架构设计

💡 原文中文,约4400字,阅读约需11分钟。
📝

内容提要

本文介绍了单片机程序的模块化设计思路和经验,包括固件模块的原理和设计规则,以及拆分模块和集成调度的方法。模块化编程可以提高代码的可复用性和可读性,简化调试和维护。建议采用先设计后开发的模式,避免逐步debug。

🔎

延伸解读

模块化设计的适用场景与权衡

文章指出,模块化设计并非适用于所有情况。对于资源有限的单片机,直接使用RTOS可能有困难,而模块化设计可以作为一种替代方案。但模块化会引入调用开销,可能增加固件尺寸,因此需要折中考虑,避免过度模块化。作者以呼吸机PB560为例,说明过度模块化(如一个源文件只实现一个函数)反而违背了高内聚原则。

固件模块的划分依据与类型

文章将固件模块分为几种类型:与硬件相关的抽象层(如hal_adc.c)、纯软件算法模块(如alg_filter.c)、特定应用程序模块(如app_battery.c)以及工具模块(如debug_print.c)。划分模块时,应基于需求驱动,识别硬件IO、业务逻辑、算法、通信协议、数据存储和日志等需求,将相关性高的功能抽象到同一模块中。

模块接口设计与可移植性

模块对外提供头文件,声明所有资源,并尽量用结构体封装相关变量。接口需精心设计,确保可测试性和兼容性。由于固件依赖硬件,应在源文件头中明确硬件相关性,通过宏或函数封装硬件操作,以便移植时只需修改这部分。此外,源文件头应包含作者、版本和验证过的开发环境信息,便于团队协作和模块维护。

集成调度:裸机与RTOS框架

对于裸机程序,文章提供了一个基于定时调度的框架:在main函数中初始化各模块,然后在while循环中根据定时标志调用不同模块,并处理异步请求。对于RTOS,则创建多个任务,每个任务负责一个子系统的初始化和主循环,由OS调度器管理。两种框架都体现了模块化思想,具体实现可根据项目需求选择。

❓

Q&A

为什么单片机开发需要采用模块化架构设计?

模块化架构设计可以提高代码的可复用性和可读性,简化调试和维护,减少业务需求之间的耦合,便于团队协作和多人协同开发,并能加快固件调试速度和提高可移植性。

什么是单片机固件模块化架构设计?

模块化架构设计是将程序功能分解为固件模块或子系统,每个模块执行一个功能,并包含完成该功能所需的所有源代码和变量。它通过定义明确的接口隔离和访问每个组件,实现关注点分离。

实施单片机固件模块化设计有哪些具体规则?

规则包括:所有与模块相关的功能集成到单个源文件中(高内聚);模块对外提供一个头文件声明所有资源,尽量用struct封装相关变量;在源文件中包括自检代码;接口应精心设计和定义;在源文件头中明确硬件相关性;包含作者和版本信息;声明验证过的开发环境。

如何拆分单片机程序的模块?

拆分模块应需求驱动,先梳理清楚需求,包括硬件IO接口需求、业务逻辑需求、算法相关技术需求、通信协议需求、数据存储需求、日志打印需求等。然后将相关性高的需求抽象实现在一系列模块中,由这些模块配合实现业务需求,形成子系统,再由main.c调度协调完成整体功能。

在裸机程序和RTOS中如何集成调度模块?

裸机程序:在main函数中初始化各模块,然后进入while(1)循环,使用定时调度策略(如if(timer50ms))调用应用模块,并处理异步请求。RTOS:创建多个任务,每个任务处理子系统初始化和主循环,在main中初始化硬件和OS,创建任务,启动OS调度器。

模块化设计可能带来哪些缺点?如何避免?

模块化设计会引入一些调用开销,可能增加固件尺寸大小。应折中考量,不要过度模块化,建议采用高内聚、低耦合的实现策略。例如,呼吸机PB560的设计过度模块化,没有实现高内聚,很多源文件仅实现一个函数,应避免。

🏷️

标签

➡️

继续阅读