为啥要讨论架构 单片机系统开发人员的目标之一是在编程环境中创建固件,以实现低成本系统、软件可靠性以及快速的开发迭代时间。实现这种编程环境的最佳方法实践是使用统一的固件架构体系结构,该体系结构在产品开发过程中充当框架并支持“固件模块化”,或称为子系统。 如果不采用统一的设计架构,那么其业务需求耦合关系复杂,不采用先设计-后开发的方法论,想到哪里写到哪里,则程序后期维护将变得异常艰辛,而引入潜在bug/缺陷的风险也将大大增加,且不具备多人协同开发的可能。 可以结合固件模块化、可测试性和兼容性的正确组合的设计体系架构结构应用于任何固件开发项目,以最大程度地提高代码可复用性,加快固件调试速度并提高固件可移植性。 模块化架构设计?模块化编程将程序功能分解为固件模块/子系统,每个模块执行一个功能,并包含完成该功能所需的所有源代码和变量。 模块化/子系统化有助于协调团队中许多人的并行工作,管理项目各个部分之间的相互依赖关系,并使设计人员、系统集成人员能够以可靠的方式组装复杂的系统。具体来说,它可以帮助设计人员实现和管理复杂性。随着应用程序的大小和功能的增长,需要模块化才能将它们分成单独的部分(无论是作为“组件”,“模块”还是“子系统”)。然后,每个这样分离的部分就成为模块化体系结构的一个元素。这样,可以使用定义明确的界面隔离和访问每个组件。此外,模块化编程可提高固件的可读性,同时简化固件的调试,测试和维护。 即便是一个人独立开发一个项目,这样做依然在代码的调试、可读性、可移植性方面是最佳实践的整体策略。如果代码设计良好,则在其他项目可以轻松应用。而且模块经过上一项目的测试验证,在新的项目中再次应用其缺陷风险将大幅降低。所以每做一个项目,以这种策略不断积累模块"轮子"组件,随着经验的增长,积累的“轮子”就越来越多,也越来越好。所以其优点是显而易见的,否则每做一个项目,都从轮子造起,开发时间长不说,开发水平也得不到提高,重复性工作也很枯燥。比如前文中谈到的非易失存储管理子系统,如设计良好,就变成一个可靠的可移植的轮子。这段话请深入理解,并拿走不谢! . }/ \! L8 y, y, H3 d固件模块原理 固件开发中模块化编程的基本概念是创建固件模块。从概念上讲,模块代表关注点分离。在计算机科学中,关注点分离是将计算机程序分解为功能很少重叠的独特功能的过程。关注点是程序的任何关注点或功能,并且与功能或行为同义。关注点分离的发展传统上是通过模块化和封装来实现的,其实也就是解耦思想。 固件模块可以分为几种类型:
实施估计模块化设计的一些规则:
需要注意的是,模块化设计会引入一些调用开销,也可能增加固件尺寸大小。在实际实现时,折中考量。不要过度模块化,所以建议采用高内聚、低耦合的实现策略。在前面文章中有谈到过的呼吸机PB560的设计,看过其代码,本打算解读一下其代码设计,但读下来发现,其设计过度模块化了,没有实现高内聚的思想。其源代码很多源文件仅仅实现了一个函数,而不是把一类问题集中抽象实现,后来就放弃了其代码解读。 如何拆分模块?做工程开发,一定是需求驱动的。第一件事需要对需求有比较清晰的认知,然后才能设计一个比较合理的框架。我们需要实现什么?大致总体设计过程策略我的基本采用如下图所示思路(我比较喜欢绘图,图会让人比较直观) 问自己第一个问题是:这个项目要实现什么主要功能?这个来自哪里?如果是实际产品开发,则可能来自市场的需求,如果是自己的DIY项目,也一定会YY出一个大致的想法?总之不管源自何方,需求总要先梳理清楚。那么需求一般意义上包含哪些呢?
结合固件模块原理以及相关指导原则,那么将相关性高的需求,抽象实现在一系列的模块中,在由这一系列模块配合实现某个相关性高的业务需求,再进一步这些模块就变成一个子系统。多个子系统在main.c的调度下,协调完成产品的整体功能。 如何集成调度 对于某些不使用RTOS的应用而言,可以使用如下的框架进行: void main(void)7 z/ u1 c' S9 ?+ e# ^7 u{' r5 y& i9 W6 ?! ~7 F( ] /*各模块初始化*/ init_module_1(); l2 o6 `( I: p% {' u! }# A, t F init_module_2(); ....5 J' ?- A2 n1 D: J8 ? while(1)! G5 O9 p0 l4 g { /*实现一个定时调度策略*/2 @& e5 k/ P9 j+ a if(timer50ms)' _ O7 l# w# M n3 B! j {8 n4 @0 M: h7 P, s7 |) V8 x: e timer50ms = 0; app_module_1();/ l a& J! u6 \( H" M' s5 h. z } if(timer100ms) {6 P1 z# y b6 ~ timer100ms = 0; app_module_2(); }% }6 \1 H. `1 i' r6 x8 h' G / G5 X, D0 D3 L* W /*异步请求处理,如中断后台处理*/ if(flag1)3 o- S: ~( \/ q" ], F! c {# @( ~( m& `; ?4 e0 }2 E W# Q- u communication_handler();# V: C a% G8 z/ `. V) K }* m, c9 k5 c! e Q+ E8 Z' S .....4 B4 _- ^9 a1 j: B5 M4 r }! C3 b+ ^" m: E6 V }2 h% g F/ M- M 对于基于RTOS的集成实现举例: void task1(void){0 G0 A$ z* M- H /*处理子系统相关的初始化*/7 z8 ^, S% E7 C# O" E+ [9 l/ ^! O init_task1(); while(1)8 G7 G' W9 o; Z$ d& t2 o; _0 u/ E8 b { /*应用相关调用*/3 z% R$ ?4 b- r! l+ X task1_mainbody(); ....- q3 e7 V' {- u* \ }& ~8 Q" b. B" P" F( n% y! ? }+ j4 j& ~" t- x, s0 s f& a .... void taskn(void)$ `6 ^, h6 C$ ^( k5 q2 ]! ~1 Q { /*处理子系统相关的初始化*/& }, m3 l9 R! y4 g6 S' | init_taskn();+ G/ b, F: k" K6 ]/ \7 k while(1) { /*应用相关调用*/: P( `, p2 M( w) } taskn_mainbody(); .... } } void main(void) {$ A+ O6 W. c' U0 s4 @' t% A% g /*一些基本硬件相关初始化,比如IO,时钟,OS tick定时器等*/5 b2 r/ q) {/ y. M init_hal(); ......; z G" x! v0 K. g /*一些基本RTOS初始化*/ init_os();, C) d! H8 q+ P- @ / r. X7 f- W" W1 h# r3 w /*任务创建*/ os_creat("task1",task1,栈设置,优先级,...); ...... os_creat("taskn",taskn,栈设置,优先级,...);: e: b# j3 J3 \1 d% G; M* l /*启动OS调度器,交由OS调度管理应用任务*/8 H8 ^1 Y0 O7 r: Y( A os_start();! J) B: W! W$ B/ }) z1 l } 具体不同的RTOS,其函数名各有不同,但大致思路一般都差不多。 总结一下本文从为什么需要模块化设计整体架构,到这样做的好处,以及具体做的一些指导原则,再到实际中如何实现,怎么做到高内聚低耦合,提供了一些个人工作中的体会以及思路。同时对于裸机程序整体框架、基于RTOS的集成框架做了两个demo,基本能解决大部分的框架思路问题。将前文中的一些个人推崇的原则,在加粗总结下:
极力建议采用先设计-后开发的模式,比较忌讳逐步debug,想到哪里写到哪里。当然对于新手学习而言,后一种模式,可以逐步渐进迭代,也可以比较快的增长经验。当然如何取舍,全凭个人意愿。 相信您如深入阅读,细细体会,应该从设计思想上得到些领悟,有所提高。如果能帮助到您,则我心甚慰,也不枉辛苦码了这么多字。当然如果觉得文章有价值,也麻烦帮忙点在看,或者转发分享。让更多朋友能看到,当然对于单片机开发大神而言,文中观点则显得颇为粗浅了。至于赞赏,则随心即可。 0 R: s- o% K& m |
谢谢分享 |