我是老温,一名热爱学习的嵌入式工程师
. D8 r& L" Z" g6 m6 A: y. ^关注我,一起变得更加优秀!
: `2 F/ k A( I u G" \在如今的嵌入式软硬件技术开发领域,几乎每一位工程师都会大谈模块化设计,硬件工程师在设计原理图的时候,电源要模块化,核心板要模块化,功能电路要模块化。软件工程师在coding的时候,CPU初始化要模块化,IIC代码要模块化,RTC代码要模块化,等等。
, M+ X* U7 x0 t: n. I/ P7 R8 a于是,当大家对模块化设计的概念和思想有了普遍共识后,我们在写单片机代码的时候,(大多数工程师极有可能)是以下面这种代码形式去组织产品的功能模块代码。
: t/ E" G7 R7 f' {void main(void){ gpio_init(); //初始化GPIO iic_init(); //初始化IIC总线 rtc_init(); //初始化RTC while(1) { function_code_1(); //功能代码1 function_code_2(); //功能代码2 function_code_3(); //功能代码3 }}" A. V% T& z; ~+ l& f+ t$ a ?+ M( j
这看上去似乎是非常合情合理合逻辑的,先把需要用到的硬件接口都初始化完成,然后在 while(1) 循环里面不断地轮询,处理我们的业务逻辑。
e6 e q2 ^3 g+ x关于源代码文件的存放,可能是 iic.c 和 iic.h ,gpio.c 和 gpio.h 都放在MCU的外设驱动文件夹,功能模块源代码放在另一个文件夹。 1 O6 r- j- }4 q9 s- A I
(几乎所有的单片机入门教程都是这么教我们的,外设驱动放一块,业务逻辑放一块,这看上去很模块化呀,没啥毛病~)8 a2 ]1 a8 _8 t% d7 K
然而,当我们对这个世界的客观事物理解越来越深刻的时候,才发现这个世界的大多数事物,都是以“模块”的形式存在着。
) j( s( w7 y+ H% G2 L6 j我是一个模块(职场吗喽),给公司当牛马;公司是一个模块,给行业提供解决方案或产品;行业是一个模块,给产业链提供完善的行业支持;产业链是一个模块,给社会主义经济建设提供可靠的资源支撑;社会主义是。。。* Y9 S6 A' S4 R0 l7 R* C
扯远了:)
# I: J6 ^2 R6 c9 j, ?, A
xludfadpv356407377357.png
9 S! m- S8 B. ]: [% v X于是,我们上面的单片机伪代码,比如它是一个给广州塔按时点亮流水灯的控制器产品,用模块化的思想就可以优化为以下的形式;
2 q3 f4 n6 v/ F0 ^: `3 evoid main(void){ light_module_init(); //灯光模块,里面包含GPIO接口初始化 storage_module_init(); //存储模块,里面包含IIC初始化,存储设备初始化 clock_module_init(); //时钟模块,里面包含RTC额初始化。 while(1) { light_module.handler(); //处理灯光模块的逻辑,比如定时点灯 storage_module.handler(); //处理存储器逻辑,比如定时保存配置数据 clock_module.handler(); //处理时钟或闹钟,比如发出定时或计时信号 }}6 Y1 ]- @/ g$ N4 _5 s
从以上代码可以看出,我们把MCU外设驱动的初始化,放在模块的初始化函数里面进行处理了,主循环while(1)里面也是调用了模块提供的事务处理函数。各个模块分工明确,只扫自家门前雪,不管他人瓦上霜。
7 f" {( ?" r) ~* r3 U4 J- |模块之间可以通过约定的接口进行数据的交互和通信,并且严格禁止跨模块使用全局变量,模块的接口头文件禁止放入 “extern xxx变量” 的代码,一旦放入这种代码,大概率是会被下一位接坑者“友好问候”的 。+ n! `# w4 a; r! O6 q* Y- n1 `
模块化之后,事情一下子变得简单起来了,当我们发现塔上的灯不亮了,就去检查一下 light_module 和电源总闸呗,当我们发现定时时间不准了,就去检查一下时钟模块呗。怀疑硬件驱动有问题的,就去看看 xxx_module_init 的代码,怀疑业务逻辑的,就去看看 xxx_module_handler 代码。, ] ]" I0 B- D* \% U
模块化设计是嵌入式系统设计中的一种重要方法论,但通常这种方法论描述得过于抽象,难以理解,以下是模块化设计的核心哲学思想,我们可以尝试通俗地理解这些思想背后的目标。
* g$ ]! j+ w3 T1 t* b1、高内聚、低耦合、接口明确
9 c; U7 v( Y+ }- k6 C模块内部的各个变量各个函数,都给咱统一管理起来,不对外的函数要static,不想让模块使用者知道的内容,就不要放在接口头文件,对外接口要规范,提供明确的接口使用指导。
) F: B. I9 C6 F) X O2、可重用性、可扩展性、可维护性
( a8 Z2 @: f( x9 G1 ~1 y模块源码被使用时,可以轻松复制粘贴,模块使用者只需遵循接口规范,通过正确的输入即可获得正确的输出。模块作者给模块扩展功能时,不需要重写所有代码。模块设计者在维护模块某个功能时,只需专注某个局部,而不是模块全局。
& ]1 M, t% o9 b! C7 K/ D/ O3、单一职责原则' |3 \' ~6 C0 L& v/ n$ ^
一个模块只负责一项任务(或者一项功能),其他不属于自己分内的事情,就别管了(也管不了),各家只扫门前雪就行了!
4 t+ [# b8 ^( l6 j6 a) ^' z1 Z3 g4、分层架构、配置管理
) H; D. g c7 t模块化的内在设计可以采用分层架构,每一层负责处理不同的抽象级别。也就是说,模块内部之间也要有层次感,模块内部也不是一锅乱炖的东西,最起码的层次阶级还是要有的,并且还要提供适当的配置文件(或数据结构)来控制模块的功能与行为。) D4 r* v1 Y0 R7 J. m
5、测试与验证# ~# M8 g' M* Q! Q/ ]! D
哪里出问题了,直接找出问题的模块就行,电源出问题了,一开始该不会去怀疑网络通信不了而导致电源出问题吧?!模块化设计的软件,使得对单个模块进行测试验证或者找问题变得更加容易,有助于提高软件整体质量。; {- `4 U7 n+ @, S, M
通过遵循这些设计哲学,嵌入式软件的开发可以更加高效,最后,需要补充的一点就是,如果产品的功能逻辑肉眼可见的简单,就不用扯啥模块化了,用最直接简单粗暴的方式来操作硬件,完成功能开发,怎么方便怎么来,别整啥模块化给自己制造麻烦了。8 n6 B) N m6 B2 h. }6 ]9 S5 C3 M
模块化思想,是适用于对中大型的产品业务软件进行规划设计的,并不具备普适性用途,也不是放之四海而皆准的真理,工程师们还是需要具体问题具体分析,按需使用。/ w2 |8 E# v' H
-END-
3 x; F7 A* \) O0 |往期推荐:点击图片即可跳转阅读
: h2 r' v0 M1 g4 Q( \
. F5 q# I# U- R6 S
( T; F( Y% a6 V
3 v E1 t1 Z5 s2 s4 G- P
1 O% P1 v& \: y3 V, G% O: l
5 t+ O1 \% ~( F! w: d1 {! Q2 p
vfiafwdzs0m6407377457.jpg
% n/ l+ t/ L7 U+ w- \ 4 M, c% Q# {/ D
嵌入式 C 语言知识点,掩码结构体: E! X! X- Y/ f
# M/ U% z$ ^4 J# ?" l( m7 f, F; u; P
) O0 H- ?, U. v; q
: X1 t5 k: h& C- l; b
8 v6 @4 `) d2 f6 @% D; A
hbrtq4lyxl16407377557.jpg
; G, P6 }7 q6 k6 q% V4 z: ?" F# d
" a# M* k" f* ?0 O- f 面试官问我,怎样把 1000 TB 文件快速从南京传输到北京?(文末附最佳答案)7 @* A4 b" H6 [. z# @* a
" q' N9 b8 B! k. ^3 A! Q
# `1 `/ w( O# M# ^# u/ p/ T
# a% o; W$ Y: ?6 M, G) P
qfoqzbph1aw6407377657.jpg
9 Q5 b# c9 d6 ?
% u { ]/ L$ g# k4 W# v 做项目时,嵌入式方案的选型套路有哪些?1 _6 u% M/ r7 T- F$ `' u( ~
我是老温,一名热爱学习的嵌入式工程师/ \3 q9 ?" b6 a8 ?) h
关注我,一起变得更加优秀! |