
嵌入式架构有多重要?
要做到嵌入式应用的代码逻辑清晰,
且避免重复的造轮子,
没有好的应用架构怎么行?
如果没有好的架构,
移植将会是一件很痛苦的事情。
如果没有好的架构,
复用是最大的难题,
没法更大限度的复用原有的代码。
如果没有好的架构,
一旦驱动改了,
所有的地方都要改,
费时费力且很容易出错。
如果没有好的架构,
应用层中穿插着硬件驱动层的代码,
看着会是一片混乱,
逻辑不清,
代码维护起来会很困难。

01
嵌入式系统的基本架构
硬件架构

软件架构

大多数人参与的是嵌入式软件设计,更多的是接触的是上层软件系统部分,可以分为两大类型嵌入式软件应用工程师以及嵌入式驱动工程师。

前者主要负责 linux APP 设计,负责应用层业务开发,主要具备如下几个专业技能:
2.熟悉多线程管理、进程间通信、文件IO操作
3.了解基本的shell编程
4.熟悉数据库操作
5.了解QT或者Android
后者负责驱动开发,更加涉及底层。
2.熟悉Linux驱动模型
3.熟悉ARM架构
4.熟悉基本的电路原理
02
嵌入式程序设计思路

如果功能模块变动了,只需升级功能功能模块,其他的模块不受影响,应用层也不受影响。




休息一下,推荐一篇文章《怎样混好嵌入式/MCU/ARM/DSP这一行?》
03
一个引以为戒的实例
一、错误的示范
版本一的架构设计
2.1系统体系结构
系统分为两层:硬件驱动层、应用层。
2.1.1硬件驱动层
硬件驱动层包含板载硬件资源正常运行所需的所有驱动程序。
1)MCU初始化
2)I2C数据存取
3)SPI数据读取
4)加速度计初始化
5)蓝牙模块启动
6)BC95模块启动
7)485通讯模块启动
2.2.2应用层
1)Mcu运行模式切换
2)震动及倾斜
3)数据解析
4)开/关锁
5)数据发送
6)历史数据保存
版本二的架构设计

1.对架构的理解还不是很清晰,既然是做架构设计,那就应该从整体来看,而不是仅仅只是局限于一个模块,或者功能里面。
2.还是每个层次的理解也还不是很清晰,比如讲MCU的初始化,归于硬件驱动层里面。MCU的初始化,严格意义上来说,是属于流程的一部分了,而不是驱动。比如电脑的开启启动,把这个归于硬件的驱动里面,肯定是属于牛头不对马嘴的。
3.还有就是各个模块的启动,也是不能属于硬件驱动层的,也都是业务流程的一部分了,都不应该属于驱动层的一部分。
4.还有就是总线数据的读写,虽然驱动的作用也就是读写,但是数据总线的读写不能写成硬件驱动。
5.应用层的系统参数初始化,也还是属于流程。
6.数据的解析和数据的发生,都是属于通信功能里面的,不应该单独独立出来,属于单个的应用。
1.应用的代码逻辑清晰,且避免重复造轮子。
2.如果没有好的架构,移植将会是一件很痛苦的事情,因此一个好的架构设计,方便软件的移植。
3.最大限度地复用。
4.高内聚低耦合。
(2)设计思路
如何把硬件的驱动和一个功能封装成一个个的模块,然后可以像小朋友搭积木一个,一个个模块可以快速的拼接起来,组成一个个不同的模型。
我们的嵌入式架构思路也是来源于此,即功能模块化设计、分层设计。
这个设计和WEB开发的MVC模式类似,都是注重分层设计。
模块化设计:将收集到的需求,进行归类,总结和分析,将这些需求概括为一个个单独的功能,每一个功能,做成一个单独的功能模块。
分层设计一句话不好直接表达,其主要体现在一下几方面:
1.功能模块对外调用的模块封装成一个个API,将底层驱动做个API以供功能模块调用。(各个功能模块可以独立编译(如通信模块纯ANSI C,可在任意平台复用),或者调用驱动层接口(日志库模块调用了驱动读写Flash),总而言之,言而总之,封装出各个功能独立的可复用的功能模块。)
2.API分为驱动层API和应用层API,而不是所有程序都调用驱动层API。(整个应用中都调用驱动层API会导致应用中驱动调用随处可见,无法移植和最大限度的复用)
总体分 硬件驱动层-->功能模块层-->业务逻辑层-->应用层
总体结构示意框图:

1.层与层之间不能跨层调用。
2.模块与模块各自独立,无依赖关系。
3.模块提供统一的接口供上层调用,模块的内外接口分明。
4.模块的功能只能增,不能改。
5.各个功能模块层也还可以进行继续分层,比如接口层、驱动层、硬件层。
(3)模块层次说明
硬件驱动层包含板载硬件资源正常运行所需的所有驱动程序并提供API给功能模块调用。
功能模块层包括实现具体功能的函数,通过调用驱动层API实现相应功能,同时提供可调用的API给业务逻辑层。
业务逻辑层包括产品整体功能的各个业务流程,通过调用功能模块层的API实现。
应用层将各个业务逻辑进行整合调用,完成整个产品的功能。
(4)优势
如果驱动变动了,或者换不同平台,只需更改驱动层,应用层不受影响。
如果功能模块变动了,只需升级相应的功能模块,其他的模块不受影响,应用层也不受影响。
按照这种逻辑设计好之后,主要的工作就是在业务逻辑层。应用层则为程序的总体流程和框架,主要调用业务逻辑层实现不同的功能。
04
给嵌入式代码也来个分层
一、遇到的问题
代码结构也会有缺陷:
(1)开发效率低:每次使用片内的某一资源(例如定时器等),笔者都要去查询CC2430中文手册,比较eggache~
正是由于以上问题,笔者决定暂停了该系列博文的续写,抽出时间来思考一下解决办法。
二、由网站分层引起的思考
笔者在学习嵌入式编程之前,曾有过 ASP.NET 网站开发经验,对其分层理论也有所实践,下面简单提一下:
一般的有一定复杂度的网站可分为以下三层:
总之,分层以后,大大提高了代码的复用性与扩展性。
那么在嵌入式开发中,能否也利用分层的思想,来提高开发效率,增强其可维护性与可扩展性呢?下面,是一些笔者思考后的浅见。
三、嵌入式项目也来分个层
当然不能照搬ASP.NET 的具体分层思想,具体问题得具体分析嘛~
首先,嵌入式开发的核心就是芯片,它提供固定的片内资源共开发者使用。而且它具有一个很重要的特点就是,不随项目的需求变动而变动。所以应将其作为最底层,为上层提供基础支持。我们将其命名为 硬件抽象层(Hardware Abstract Layer)。
芯片有了当然还不够,通常我们会在片外扩展一些功能模块来满足具体的项目需求,例如:传感器、键盘、LCD屏等。这一层的特点是,随项目的变动而以模块为单位动态增减。这一层的运作需要芯片内部资源的支持,所以应处于硬件抽象层之上,并为上层调用。我们将其命名为 功能模块层(Functional Module Layer)。
OK,现在原材料都准备齐了:芯片+扩展模块,接下来就要开始真正的加工了:我们需要灵活调用之前两层所提供的接口,实现具体的项目需求。我们将其命名为应用程序层(Application Layer)。
图文:

(1)硬件抽象层(HAL)
实现对片内资源 (如定时器、ADC、中断、I/O等) 的通用配置,隐藏具体的SFR操作细节,为上层提供简单清晰的调用接口。
嵌入式开发的核心就是芯片,它提供固定的片内资源(常用的有I/O,ISR,TIMER等,稍微好点的还有ADC,SPI等硬件资源,不需要芯片外围ADC采集芯片或模拟SPI)共开发者使用。而且它具有一个很重要的特点就是,不随项目的新增需求变动而变动。所以应将其作为最底层,为上层提供基础支持。
(2)硬件驱动层(HDL)
嵌入式开发基本都会使用片外资源,如AT24C02,W25Q128等常见的外围EEPROM芯片,需要SPI通信(硬件SPI或I/O模拟的SPI)发送相应指令驱动该芯片,实现该芯片能正常工作。因此驱动这部分的API函数实现程序即为硬件驱动层。即使换了MCU,也只需将调用过硬件抽象层的API函数替换即可。
(3)功能模块层(FML)
通过调用 HAL,实现项目中所涉及到的各片外功能模块,隐藏具体的模块操作细节,并为上层提供简单清晰的调用接口。
硬件抽象层和驱动层主要就是为功能模块层提供的,实现该项目需要的功能,比如KEY、LED和EEPROM等功能,其中LEY、LED基本调用硬件抽象层的API函数(更复杂的可能通过片外芯片获取/控制等,因此可能也需要使用硬件驱动层),EEPROM调用硬件驱动层的API函数,即使EEPROM芯片更换(AT24C02或W25Q128等),也不影响EEPROM之前编写含的功能代码程序(前提是AT24C02,W25Q128提供的API函数提供的是统一标准)。
(4)应用程序层(APL)
通过调用 HAL 与 FML,实现最终的应用功能。
负责的就是功能模块的使用和之间的逻辑关系处理等等,比如用户交互界面应用程序可能需要KEY、LED、LCD等。
四、硬件抽象层和硬件驱动层的主要区别
硬件抽象层使用的芯片内本身的资源(芯片手册都有介绍),而硬件驱动层使用的是芯片本身不存在的资源,而且需要编写相应代码才能实现的资源。
比如正点原子STM32中CAN使用的TJA1050芯片,CAN属于STM32的片内资源,TJA1050属于片外资源,但由于TJA1050不需要额外的代码就能通过STM32中CAN本身提供API函数正常 工作;因此可以认为TJA1050不属于硬件驱动层,而若使用TJA1041,则需要编写额外代码才能使正常工作才能使STM32中CAN本身提供API函数正常工作,因此可以将TJA1041归为硬件驱动层。
若不要分这么细,可以将硬件抽象层和硬件驱动层统一归为硬件抽象层。
五、功能模块层和硬件抽象层、硬件驱动层的主要区别
功能模块层是按照项目需求提取出来的功能,需要硬件抽象层和硬件驱动层的硬件支持才能实现,功能模块层根据项目的功能需求改变而改变,而硬件抽象层和硬件驱动层则是项目需求书中的功耗等硬件相关的需求变动而改变,当然,若子功能的增加而硬件不支持,则也需更换硬件驱动。
比如项目中的数据储存功能,硬件支持有AT24C02、W25Q128和芯片本身的FLASH,都可以支持数据储存功能,即使后期因为功耗或节约成本等问题,硬件的更换也不影响数据储存功能的实现(前提规划好标准规范的API函数定义)且避免了重写该功能代码所带来的各种问题,保证了该功能的稳定性。


推荐阅读
(点击标题可跳转阅读)
【编程之美】用C语言实现状态机(实用)
10 个常用的软件架构模式
