来源|嵌入式客栈
有些刚接触单片机的同学刚开始开发单片机,还没有使用过RTOS。 而且,直接使用RTOS可能会很困难。 有些使用相对较旧的微控制器,资源有限,不适合运行 RTOS。
或者使用RTOS,我对整体思路感到困惑,不知道从哪里开始,所以这篇文章将谈谈我对单片机程序整体框架设计的一些想法。
为什么要讨论架构
微控制器系统开发人员的目标之一是在编程环境中创建固件,以实现低成本系统、软件可靠性和快速开发迭代时间。 实现此编程环境的最佳实践是使用统一的固件架构,该架构在产品开发期间充当框架并支持“固件模块化”或子系统。
如果不采用统一的设计架构,业务需求的耦合关系就会复杂,如果不采用设计先开发的思路,想到什么就写什么,程序的后期维护将变得异常困难。并引入潜在错误/缺陷的风险。 也会大大增加,不存在多人协同开发的可能。
将固件模块化、可测试性和兼容性正确组合的设计架构结构可应用于任何固件开发项目,以最大限度地提高代码可重用性、加快固件调试速度并提高固件可移植性。
模块化架构设计?
模块化编程将程序功能分解为固件模块/子系统,每个模块执行一项功能并包含完成该功能所需的所有源代码和变量。
模块化/子系统化有助于协调团队中许多人的并行工作,管理项目各个部分之间的相互依赖性,并使设计人员、系统集成商和系统集成商能够以可靠的方式组装复杂的系统。 具体来说,它可以帮助设计人员认识和管理复杂性。
随着应用程序规模和功能的增长,需要模块化将它们分成单独的部分(无论是“组件”、“模块”还是“子系统”)。 每个这样的独立部分就成为模块化架构的一个元素。 这样,每个组件都可以使用定义良好的接口进行隔离和访问。 此外,模块化编程提高了固件的可读性,同时简化了固件调试、测试和维护。
即使一个人独立开发一个项目,从代码调试、可读性、可移植性方面来看,这样做仍然是一种最佳实践的整体策略。 如果代码设计得好,可以很容易地应用到其他项目中。 而且该模块在之前的项目中已经过测试验证,在新的项目中再次应用时,出现缺陷的风险将会大大降低。
因此,我们每次做项目的时候,都是通过这个策略来不断积累模块“轮子”组件。 随着经验的增加,积累的“轮子”也越来越好。 因此,其优势是显而易见的。 否则,每个项目都必须从头开始构建。 开发时间长,开发水平上不去,重复性工作枯燥。 例如,上面提到的非易失性存储管理子系统,如果设计得当,就会成为一个可靠且便携的轮子。 请深刻理解这段文字并拿走,不致谢意!
固件模块原理
固件开发中模块化编程的基本概念是创建固件模块。 从概念上讲,模块代表关注点分离。 在计算机科学中,关注点分离是将计算机程序分解为几乎没有重叠功能的独特功能的过程。 关注点是程序的任何关注点或特征,并且与功能或行为同义。 关注点分离的开发传统上是通过模块化和封装来实现的,这实际上就是解耦的思想。
固件模块可以分为以下几种类型:
实现估计的模块化设计的一些规则:
需要注意的是,模块化设计会引入一些调用开销,也可能会增加固件的大小。 在实际实施中,要考虑折衷。 不要过度模块化,建议采用高内聚、低耦合的实现策略。 我在之前的文章中已经讲过呼吸机PB560的设计。 看完代码,本打算解读一下代码设计,但是看完之后发现设计过于模块化,没有做到高内聚。 很多源文件其源代码中只实现了一个功能,而不是关注一类问题的抽象实现,后来就放弃了代码解释。
如何拆分模块?
工程开发必须是需求驱动的。 首先是对需求有更清晰的认识,然后设计更合理的框架。 我们需要实现什么目标? 我基本上采用的总体整体设计流程策略如下图所示(我比较喜欢画图,图片会让人更直观)
首先要问自己的问题是:这个项目的主要功能是什么? 这是从哪里来的? 如果是实际的产品开发,可能来自于市场需求。 如果是你自己的DIY项目,你一定会想出一个大概的想法吗? 总之,无论从哪里来,首先要梳理需求。 那么一般意义上的需求包括哪些内容呢?
结合固件模块的原理和相关指导原则,将高度相关的需求抽象地实现在一系列模块中。 这一系列的模块配合实现某个高度相关的业务需求,然后这些模块就成为一个子系统。 多个子系统在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();
}
.....
}
}
基于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();
}
不同的RTOS有不同的函数名称,但总体思路大体相同。
综上所述
本文从整体架构为什么需要模块化设计,到这样做的好处,以及一些具体的指导原则,到实际中如何实现,如何做到高内聚低耦合,提供了一些个人的工作经验和想法。 。
同时针对裸机程序的整体框架和基于RTOS的集成框架做了两个demo,基本可以解决大部分框架思维问题。 我们把上一篇文章中个人推荐的一些原则用粗体总结一下:
强烈建议采用设计先开发的模式。 更忌讳一步步调试,想到哪儿就写到哪儿。 当然,对于新手学习来说,后一种模式可以逐步迭代,相对较快获得经验。 当然,选择完全取决于个人喜好。
我相信,如果你深入阅读、仔细理解,应该会有一些感悟,提高你的设计思维。 如果能帮到你,我会很高兴,编码这么多字的辛苦也不会白费。 当然,如果您觉得文章有价值,请帮我点击阅读,或者转发分享。 让更多的朋友看到。 当然,对于一个单片机开发高手来说,文章中的观点显得相当肤浅。 至于欣赏,就随心所欲吧。