[应用方案]
单片机固件模块化架构设计
[复制链接]
4703|56
电梯直达
楼主
楼主|
jackcat
发表于 2025-5-12 15:31
|
只看该作者
|倒序浏览
|阅读模式
单片机, 固件, 模块化, 设计
单片机系统开发人员的目标之一是在编程环境中创建固件,以实现低成本系统、软件可靠性以及快速的开发迭代时间。实现这种编程环境的最佳方法实践是使用统一的固件架构体系结构,该体系结构在产品开发过程中充当框架并支持“固件模块化”,或称为子系统。如果不采用统一的设计架构,那么其业务需求耦合关系复杂,不采用先设计-后开发的方**,想到哪里写到哪里,则程序后期维护将变得异常艰辛,而引入潜在bug/缺陷的风险也将大大增加,且不具备多人协同开发的可能。可以结合固件模块化、可测试性和兼容性的正确组合的设计体系架构结构应用于任何固件开发项目,以最大程度地提高代码可复用性,加快固件调试速度并提高固件可移植性。模块化架构设计?模块化编程将程序功能分解为固件模块/子系统,每个模块执行一个功能,并包含完成该功能所需的所有源代码和变量。模块化/子系统化有助于协调团队中许多人的并行工作,管理项目各个部分之间的相互依赖关系,并使设计人员、系统集成人员能够以可靠的方式组装复杂的系统。具体来说,它可以帮助设计人员实现和管理复杂性。随着应用程序的大小和功能的增长,需要模块化才能将它们分成单独的部分(无论是作为“组件”,“模块”还是“子系统”)。然后,每个这样分离的部分就成为模块化体系结构的一个元素。这样,可以使用定义明确的界面隔离和访问每个组件。此外,模块化编程可提高固件的可读性,同时简化固件的调试,测试和维护。即便是一个人独立开发一个项目,这样做依然在代码的调试、可读性、可移植性方面是最佳实践的整体策略。如果代码设计良好,则在其他项目可以轻松应用。而且模块经过上一项目的测试验证,在新的项目中再次应用其缺陷风险将大幅降低。所以每做一个项目,以这种策略不断积累模块"轮子"组件,随着经验的增长,积累的“轮子”就越来越多,也越来越好。所以其优点是显而易见的,否则每做一个项目,都从轮子造起,开发时间长不说,开发水平也得不到提高,重复性工作也很枯燥。比如前文中谈到的非易失存储管理子系统,如设计良好,就变成一个可靠的可移植的轮子。这段话请深入理解,并拿走不谢!固件模块原理固件开发中模块化编程的基本概念是创建固件模块。从概念上讲,模块代表关注点分离。在计算机科学中,关注点分离是将计算机程序分解为功能很少重叠的独特功能的过程。关注点是程序的任何关注点或功能,并且与功能或行为同义。关注点分离的发展传统上是通过模块化和封装来实现的,其实也就是解耦思想。固件模块可以分为几种类型:与很多上层用户模块都有关的代码被实现为单独的固件模块。常见的如底层硬件相关的抽象实现。例如,hal_adc.c 是ADC用户模块的固件模块,而hal_timer.c是Timer用户模块的固件模块。用于特定纯软件算法的代码被实现为单独的固件模块。例如,alg_filter.c是执行软件过滤器(例如中值过滤器,均值过滤器或加权均值过滤器、IIR/FIR滤波)的固件模块。特定应用程序的代码实现为单独的固件模块。例如,app_battery.c是电池充电器应用程序的固件模块。特定工具的代码实现为单独的固件模块。例如,debug_print.c是用于实现日志打印功能的固件模块。......
实施估计模块化设计的一些规则:所有与模块相关的功能都应集成到单个源文件中,这是高内聚的体现。模块对外提供一个头文件,该文件声明了该模块的所有资源(硬件依赖/宏/常量/变量/函数)。尽量用struct将紧密相关的变量进行集总封装。在源文件中包括自检代码部分,以实现该模块模块的所有自检功能。固件模块的接口应经过精心设计和定义。由于固件取决于硬件,因此需要在源文件头中明确提及硬件的相关性。比如利用宏将硬件依赖转定义,或者利用函数将基本操作进行封装。则在新的架构体系,仅仅需要移植这部分实现即可使用。通常,固件模块可供其他团队成员在其他项目中使用。可能涉及到管理更改,缺陷修复、所有者应维护模块。源文件头应包含“作者”和“版本”信息。固件在某种程度上取决于编译器。源文件头中应声明基于什么开发环境进行过验证,以指定编译器或与IDE相关的信息。
需要注意的是,模块化设计会引入一些调用开销,也可能增加固件尺寸大小。在实际实现时,折中考量。不要过度模块化,所以建议采用高内聚、低耦合的实现策略。在前面文章中有谈到过的呼吸机PB560的设计,看过其代码,本打算解读一下其代码设计,但读下来发现,其设计过度模块化了,没有实现高内聚的思想。其源代码很多源文件仅仅实现了一个函数,而不是把一类问题集中抽象实现,后来就放弃了其代码解读。如何拆分模块?做工程开发,一定是需求驱动的。第一件事需要对需求有比较清晰的认知,然后才能设计一个比较合理的框架。我们需要实现什么?大致总体设计过程策略我的基本采用如下图所示思路(我比较喜欢绘图,图会让人比较直观)问自己第一个问题是:这个项目要实现什么主要功能?这个来自哪里?如果是实际产品开发,则可能来自市场的需求,如果是自己的DIY项目,也一定会YY出一个大致的想法?总之不管源自何方,需求总要先梳理清楚。那么需求一般意义上包含哪些呢?哪些是硬件IO接口需求,比如开关量输入,ADC采样,I2C/SPI通信等等哪些是业务逻辑需求,比如要采集一个传感器量数据,控制一个加热装置,那么这是高内聚的需求。哪些是算法相关的技术需求,比如产品中哪些信号需要滤波处理,哪些需要做频域分析等等。是否有对外的通信协议需求。是否有业务数据需要历史存储,或者设备参数需要掉电保存是否需要有日志打印需求。........不一而足。
结合固件模块原理以及相关指导原则,那么将相关性高的需求,抽象实现在一系列的模块中,在由这一系列模块配合实现某个相关性高的业务需求,再进一步这些模块就变成一个子系统。多个子系统在main.c的调度下,协调完成产品的整体功能。如何集成调度对于某些不使用RTOS的应用而言,可以使用如下的框架进行:void main(void){ /*各模块初始化*/ init_module_1(); init_module_2(); .... while(1) { /*实现一个定时调度策略*/ if(timer50ms) { timer50ms = 0; app_module_1(); } if(timer100ms) { timer100ms = 0; app_module_2(); } /*异步请求处理,如中断后台处理*/ if(flag1) { communication_handler(); } ..... }}Copy
对于基于RTOS的集成实现举例:void task1(void){ /*处理子系统相关的初始化*/ init_task1(); while(1) { /*应用相关调用*/ task1_mainbody(); .... }}....void taskn(void){ /*处理子系统相关的初始化*/ init_taskn(); while(1) { /*应用相关调用*/ taskn_mainbody(); .... }}void main(void){ /*一些基本硬件相关初始化,比如IO,时钟,OS tick定时器等*/ init_hal(); ...... /*一些基本RTOS初始化*/ init_os(); /*任务创建*/ os_creat("task1",task1,栈设置,优先级,...); ...... os_creat("taskn",taskn,栈设置,优先级,...); /*启动OS调度器,交由OS调度管理应用任务*/ os_start();}Copy
具体不同的RTOS,其函数名各有不同,但大致思路一般都差不多。总结一下本文从为什么需要模块化设计整体架构,到这样做的好处,以及具体做的一些指导原则,再到实际中如何实现,怎么做到高内聚低耦合,提供了一些个人工作中的体会以及思路。同时对于裸机程序整体框架、基于RTOS的集成框架做了两个demo,基本能解决大部分的框架思路问题。将前文中的一些个人推崇的原则,在加粗总结下:所有与模块相关的功能都应集成到单个源文件中,这是高内聚的体现。模块对外提供一个头文件,该文件声明了该模块的所有资源(硬件依赖/宏/常量/变量/函数)。尽量用struct将紧密相关的变量进行集总封装。在源文件中包括自检代码部分,以实现该模块模块的所有自检功能。固件模块的接口应经过精心设计和定义。由于固件取决于硬件,因此需要在源文件头中明确提及硬件的相关性。比如利用宏将硬件依赖转定义,或者利用函数将基本操作进行封装。则在新的架构体系,仅仅需要移植这部分实现即可使用。通常,固件模块可供其他团队成员在其他项目中使用。可能涉及到管理更改,缺陷修复、所有者应维护模块。源文件头应包含“作者”和“版本”信息。固件在某种程度上取决于编译器。源文件头中应声明基于什么开发环境进行过验证,以指定编译器或与IDE相关的信息。
回复
收藏0
举报
相关帖子
• 捷多邦提醒:埋容埋阻≠无限减少元件,设计别想得太美
• 捷多邦前线观察:埋容埋阻工艺成熟度,还差哪一步?
• 捷多邦案例:做服务器核心板时我们踩过的坑
• 捷多邦解析|高速时代,埋容埋阻与SI的关系走向何方?
• 捷多邦讨论:埋容埋阻加工中最容易翻车的环节
• 高压漏电起痕试验仪,其使用效果和与普通环境下的设备区别
• 推荐一款 Jtrace ULINKpro 带 ETM 模块的隔离器
• pcba方案开发|充气泵pcb在设计时要考虑到哪些保护功能?
• 有没有朋友写凌欧的一些外设 LKS05系列
• 没学历还敢去找技术工作吗
沙发
tifmill
发表于 2025-5-21 15:42
|
只看该作者
单片机的模块化架构设计是一种将单片机程序划分为多个独立、可重用的模块的方法,这些模块各自承担特定的功能
回复
收藏0
举报
板凳
maudlu
发表于 2025-5-21 17:34
|
只看该作者
在每个模块内部设计合理的错误检测和处理机制,确保即使某个模块出现问题也不会导致整个系统崩溃。同时,提供清晰的日志记录帮助定位问题。
回复
收藏0
举报
地板
hudi008
发表于 2025-5-21 18:38
|
只看该作者
常见的做法是采用分层架构,比如硬件抽象层(HAL)、中间件层、应用层等。每一层只与其相邻的上下层交互,降低了整体复杂度。
回复
收藏0
举报
5楼
gygp
发表于 2025-5-21 22:35
|
只看该作者
采用高内聚、低耦合的实现策略,确保模块的合理划分。
回复
收藏0
举报
6楼
作业天敌在此
发表于 2025-5-23 09:01
|
只看该作者
模块化设计确实能够提高开发效率和代码质量,但如何平衡模块化和性能开销是一个值得探讨的问题。
回复
收藏0
举报
7楼
mollylawrence
发表于 2025-5-23 09:15
|
只看该作者
将整个应用功能划分为若干个模块,每个模块负责实现一个特定的功能。
回复
收藏0
举报
8楼
懒癌晚期患者
发表于 2025-5-23 09:43
|
只看该作者
非常赞同模块化设计的理念,它确实可以提高代码的可维护性和可复用性。在实际项目中,我通常会根据功能将代码划分为不同的模块,每个模块负责一个明确的功能,这样可以大大减少代码间的耦合。
回复
收藏0
举报
9楼
复古留声机
发表于 2025-5-23 10:07
|
只看该作者
非常赞同模块化设计的重要性,它不仅有助于代码的可维护性,还能提高开发效率。在实际项目中,我通常会根据功能将代码划分为不同的模块,每个模块负责一个独立的功能,这样在后期维护和升级时会方便很多。
回复
收藏0
举报
10楼
pixhw
发表于 2025-5-23 12:57
|
只看该作者
每做一个项目,不断积累和优化模块,提高开发效率和代码质量。
回复
收藏0
举报
11楼
kmzuaz
发表于 2025-5-23 13:28
|
只看该作者
关键任务用中断,非关键任务降低优先级。
回复
收藏0
举报
12楼
yorkbarney
发表于 2025-5-23 14:07
|
只看该作者
根据功能将程序划分为多个模块,每个模块负责一项特定的任务
回复
收藏0
举报
13楼
adolphcocker
发表于 2025-5-23 16:12
|
只看该作者
递归、大局部变量可能导致堆栈溢出,需限制深度或增大堆栈尺寸。
回复
收藏0
举报
14楼
rosemoore
发表于 2025-5-23 18:27
|
只看该作者
模块不宜过大或过小,应根据功能合理划分。
回复
收藏0
举报
15楼
ulystronglll
发表于 2025-5-23 18:37
|
只看该作者
设计良好的模块可以在其他项目中轻松复用,减少重复劳动。
回复
收藏0
举报
16楼
mmbs
发表于 2025-5-23 19:17
|
只看该作者
通过使用抽象层来隔离硬件细节与应用程序逻辑,使得上层应用不直接依赖于底层硬件的具体实现方式。
回复
收藏0
举报
17楼
earlmax
发表于 2025-5-23 19:33
|
只看该作者
根据功能需求,将系统划分为多个独立的模块。
回复
收藏0
举报
18楼
dspmana
发表于 2025-5-23 20:11
|
只看该作者
为每个模块定义清晰的接口,包括输入输出参数、功能描述等
回复
收藏0
举报
19楼
chenci2013
发表于 2025-5-23 20:28
|
只看该作者
可以使用全局变量、函数调用、消息队列等方式实现模块间的通信。
回复
收藏0
举报
20楼
zerorobert
发表于 2025-5-23 20:52
|
只看该作者
每个模块应具有高内聚性,即模块内部的功能紧密相关。
回复
收藏0
举报