专家
公告
财富商城
电子网
旗下网站
首页
问题库
专栏
标签库
话题
专家
NEW
门户
发布
提问题
发文章
51单片机
单片机程序架构 --- 二层架构
2020-03-07 17:52
发布
×
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
站内问答
/
51单片机
9171
12
12
本帖最后由 会笑的星星 于 2019-12-27 15:04 编辑
在单片机程序设计中,在着手编程之前都应该想好一个大概的程序框架,有了一个好的框架,程序的复用性、可移植性、清晰度等等都能得到提高。那么,
在编程之前,如何设计程序的框架呢。为了说清楚设计方法,可以先从最简单的单片机程序架构说起,我把它叫做二层架构,如下图所示。
所谓的二层架构,指的是程序里面只分为硬件层、应用层两个层。硬件层顾名思义,就是与硬件相关的寄存器初始化、寄存器设置之类的功能,
比如定时器的初始化,I/O口的高低电平设置等。而应用层就是我们要做的产品逻辑功能。
乍看上去这种结构比较简单,却很实用。首先,硬件层从应用层剥离后,在你更换单片机时只需要更改硬件层的相关的程序,而此时应用层不用
变化(当然,根据情况也可能有少量变化)。其次,硬件层本身可以应用到其他项目中,增加代码的复用性,这无疑有利于你使用同款单片机做
其他的产品开发。
知道了二层架构的好处,现在来看看在单片机程序中如何实现这种架构。我们先来看看硬件层的设计。
硬件层主要封装硬件寄存器相关的操作,我把所有涉及硬件相关的代码全部放在了两个文件,一个为hal.c,用于存放函数实现。一个hal.h,
用于对外声明以供应用层调用。
硬件层主要分为三个部分,第一是硬件寄存器的初始化以及寄存器状态的获取的相关函数封装,第二个是硬件相关的中断,第三是硬件层的
对外接口,我分别来讨论。
首先,看函数的封装。
对于程序设计来说,为了确保代码的复用性,降低代码之间的耦合度并且使得程序结构更为清晰,设计函数时,必须满足下面两点要求:
函数的功能要尽量单一
除非特殊情况,硬件层的函数中不要调用应用层的函数或者变量,更简单的说就是尽量不要出现下层调用上层的情况
按照上述两个原则,看看硬件层中函数封装的几个例子。
系统时钟初始化
单片机IO初始化
串口初始化以及相关寄存器的设置、访问
如上图中串口模块初始化所示,初始化函数hal_uart_init()的入口没有参数传递,这意味着串口模块的波特率被初始化为固定的9600,
如果调用者想修改为115200的波特率则无法通过参数传递的方式直接修改,而必须要修改这个函数中的相关寄存器(这里是_brg)。
这样做看似有点麻烦,但是并非不可取。因为串口初始化函数要支持波特率可选,必然要在该函数中根据函数形参进行一个乘除运算得出
相应的波特率(如下图所示)。而做比较复杂的乘除运算本身不是一些普通单片机擅长的,对于有些低端8位的单片机来说,在局部函数中
做乘除运算可能会占用全局空间,从而减少本身就紧张的RAM空间。当然,对于一些资源充足、性能强劲的单片机来说这一点是不用担心
的,通过函数的参数来自动完成波特率的计算是更好的选择,但也不一定非要如此,因为很多时候我们并不需要硬件层有这么好的通用性。
因此,我更倾向于在串口初始化时不传递参数
其次,来看看如何在硬件层中处理中断
中断函数与单片机是捆绑的,因此,中断最好也一起写到hal.c中,我一般集中写在文件的开头位置,以便后续好统一管理,如图1ms定时器
中断的例子
这里要注意的是,一般而言,中断中一般是要调用应用层的变量或者函数的,以便为应用层提供所需要的一些定时、串口数据接收或者发送
任务等等,这时会涉及到硬件层向应用层调用的问题。要处理这个问题,我们有两个办法:
直接在中断中调用应用层变量或者函数
通过回调函数调用应用层的函数
如果使用第一种方法,违背了我们之前说过的硬件层函数的设计原则二。如果我们需要选择第二种方法,硬件层与应用层可以很好的实现隔离,
这也是很多大型软件比如蓝牙协议栈所采用的方法 --- 底层通过回调函数的方式调用应用层函数,如此实现底层的通用性。
通过回调函数的方式实现下调上虽然想法好,但是在程序实现上往往比直接调用上层更为复杂且麻烦,一般而言还需要耗费更多的单片机资源,
有时候一些低端单片机甚至连函数指针都无法支持,这个时候根本就不可能使用回调函数。而且,对于大型软件而言,比如蓝牙协议栈,它由
于需要面向不同的开发人员以开发不同的产品,对于软件隔离要求是很高的,而我们自己设计的单片机程序相比而言对通用性则没有这么高的
要求。因此,
综合来看,虽然使用第一种办法虽然违背了原则2,但是考虑到上述所说的各种原因,我认为很多时候使用方法1更好,不过调用
上层函数时依然需要遵循 --- 尽可能的少、尽肯能的简单
。
最后,来看看硬件层的对外接口
硬件层的对外接口定义在hal.h文件中,这个文件声明了hal.c中所有上层会使用的函数或者宏定义,如下面几个图所示:
GPIO部分的宏定义以及函数声明
全局中断的宏定义
串口部分的宏定义以及函数声明
值得注意的是,我把相同模块相关的函数声明放在了一起,不同模块间的函数声明通过符号分割,使得整个文件组织更有结构,也更为清晰。
接口准备好后,应用层就可以直接调用了,如下图所示。
我们上面实现了硬件层代码(hal.c、hal.h),还剩下最后一个事情,那就是组织一下文件。这个很简单,没啥技术含量,如下图所示。
工程内的文件组织
工程外的文件组织
工程外文件,app文件夹内的内容
工程外文件,hal文件夹内的内容
二层架构虽然简单,但却实用,通过这样的有结构的组织程序,使得硬件层的复用性得以提高,并且整个代码的清晰度相比没有架构的程序
要好不少,这样有利于减少程序的bug。二层架构虽然也有不少好处,但是只实现了硬件层的抽象,而应用层并没有实现抽象,这意味着应
用层的代码过于耦合,并没有多少可复用性,要解决这个问题,我们必须要将应用层也分离出两层,一层是所谓的中间层,另一层就是应用
层,这就是所谓的三层架构,这会在后面讲到。
友情提示:
此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
12条回答
老舍农夫
2020-03-08 23:33
精彩回答 2 元偷偷看……
0人看过
加载中...
查看其它12个回答
一周热门
更多
>
相关问题
【东软载波ESF0654 PDS开发板活动】开箱
1 个回答
东软载波ESF0654 PDS开发板外部中断
1 个回答
东软载波ESF0654 PDS开发板高级控制定时器AD16C4T
1 个回答
用串口调试助手为什么只能在hex模式接收发送而在文本模式不行
9 个回答
触摸芯片SC02B/SC04B在地砖灯的设计方案
1 个回答
相关文章
51单片机与蓝牙模块连接
0个评论
51单片机的硬件结构
0个评论
基于51单片机的无线遥控器制作
0个评论
51单片机 AD转换
0个评论
51单片机数码管递增显示
0个评论
如何实现对单片机寄存器的访问
0个评论
基于51单片机的指纹密码锁
0个评论
×
关闭
采纳回答
向帮助了您的知道网友说句感谢的话吧!
非常感谢!
确 认
×
关闭
编辑标签
最多设置5个标签!
51单片机
保存
关闭
×
关闭
举报内容
检举类型
检举内容
检举用户
检举原因
广告推广
恶意灌水
回答内容与提问无关
抄袭答案
其他
检举说明(必填)
提交
关闭
×
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
×
付费偷看金额在0.1-10元之间
确定
×
关闭
您已邀请
0
人回答
查看邀请
擅长该话题的人
回答过该话题的人
我关注的人
一周热门 更多>