起因

前一篇介绍了一些别人家的代码架构之后,其实最近实践了一下,还是有些感悟。又参考了一些文档,觉得还是要记录一下,最近整理代码的心得。

整理系统架构,主要就是为了:

  • 方便移植
  • 便于复用
  • 模块增加删减方便

其实整理代码结构,最主要想实现的就是:

  • 高内聚
  • 低耦合

这样的代码方便移植管理,流程接口明确。

一般的架构图主要就是两部分:

  • 分层
  • 分模块

随便上百度搜了一张嵌入式的软件架构图为例。

嵌入式软件系统架构接入层_嵌入式软件系统架构接入层

20200611补记:
最近在看韦东山的课程和linux源码。其实我所总结的分层分模块就是linux中的上下分离,左右分离。不过linux中的左右分离,分的不仅仅是模块,而是又抽象了设备、驱动、总线 等等。

1. 分层设计

分层设计,就是把整个流程要做的事情按照模块化各自的特征,进行功能的分层,是和硬件寄存器打交道的,还是完全脱离硬件和业务打交道的。
一般来说,个人总结有几个原则

  1. 接口是上下级调用,很少跨层调用。即第4层的代码一般不会直接去第1/2层调用接口,非要用的话,就从第3层透传封装一下。
  2. 同层级的模块间尽量避免相互调用,需要的数据都是通过高一层的接口获取再下传

1.1 单片机程序

单片机程序就是一个程序驱动硬件,实现一个具体功能,代码特点就是从底到高,基本所有层都会涉及,所以对单片机程序进行分层,相对来说会比较完整。

找到了别人的一些设计,我觉得还挺全面的。

5层软件架构

  • 硬件驱动层
  • 功能模块层
  • 应用接口层
  • 业务逻辑层
  • 应用层

6层软件架构

  • HAL硬件抽象层
  • OSL操作系统层
  • HDL硬件驱动层
  • FML功能模块层
  • BLL业务逻辑层
  • APL应用层

简单列了一下每层功能如下:

简写

含义

包含

说明

HAL

Hardware Abstract Layer

硬件抽象层

内核驱动

寄存器配置

主要是对SFR的配置,不同芯片把这部分封装起来

OSL

Open System Layer

操作系统层

操作系统(eg:FreeRTOS)

文件系统(eg Fatfs)

GUI

这是上文作者分的,按笔者的看法,这一层的意义不大

HDL

Hardware Drvier Layer

硬件驱动层

对HAL层和OSL层进行封装,因为HAL层一般是厂家提供,不是特别方便直接使用

对一些非片内的硬件资源(HAL库不提供)进行驱动

FML

Functional Module Layer

功能模块层

按照功能模块来做划分

BLL

Business Logic Layer

业务逻辑层

按照流程来调用功能模块工作

APL

Application Layer

应用层

运行不同的流程,笔者认为APL 和 BLL的界限不是特别明显

对于这个表,其实看起来有些繁琐了,层级分的太多,隔离的太彻底,有时候反而增加了无谓的损耗。

依笔者的看法,比较简单实用的系统就是分四层或者五层。
操作系统层没有太多必要。
BLL和APL有时可以合并。
HAL和HDL有时可以合并。

  • HAL:对SFR的配置,主要针对片内资源
  • HDL:对厂家给的HAL库进行进一步封装,对于每个真实物理外设有接口
  • FML:功能模块层,封装到
  • APL:应用层

1.2 混淆概念

分层概念中,比如容易混淆的就是HAL和HDL。
区别这里总结的很好:
硬件抽象层和硬件驱动层的主要区别

借一张图和一段话来说明:

嵌入式软件系统架构接入层_封装_02

功能模块层是按照项目需求提取出来的功能,需要硬件抽象层和硬件驱动层的硬件支持才能实现,功能模块层根据项目的功能需求改变而改变,而硬件抽象层和硬件驱动层则是项目需求书中的功耗等硬件相关的需求变动而改变,当然,若子功能的增加而硬件不支持,则也需更换硬件驱动。比如项目中的数据储存功能,硬件支持有AT24C02、W25Q128和芯片本身的FLASH,都可以支持数据储存功能,即使后期因为功耗或节约成本等问题,硬件的更换也不影响数据储存功能的实现(前提规划好标准规范的API函数定义)且避免了重写该功能代码所带来的各种问题,保证了该功能的稳定性。

1.2 计划代码结构:

  • Doc
  • Code
  • HAL
  • Core
  • Hal
  • Startup
  • HDL
  • Gpio对HAL再封装
  • Flash对HAL再封装
  • Uart对HAL再封装
  • Mpu6050
  • 加密芯片
  • FML
  • ThirdParty
  • eg:PumpCtrl:封装了泵的串口控制协议
  • eg: ValueCtrl:封装了阀的串口控制协议和IO控制协议
  • eg: StrrierCtrl:封装了搅拌器的控制
  • BLL
  • eg: WarnProcess
  • eg:CtrlProcess
  • APL
  • main.c
  • Project
  • SI
  • Keil5
  • VsCode
  • CubeIDE

2. 模块设计

其实分层设计好之后,模块化相对来说就简单很多。
做模块化设计的优势就在于方便移植,如果应用层的流程变了,直接改流程就行,FML的模块都不用改。
如果模块变了,比如阀型号变了,只要另外写一个阀功能模块,能兼容以前的模块接口,即可。

3. 几个实践中的疑问:

  1. 同层模块间能不能相互调用?即使是通过上层模块传递,那结构体怎么处理?接口可以不调,但是头文件要包含的。
  2. 如果是公用的结构体,是放到公用模块,还是每个模块自己维护自己的结构体?还是接口入参不传结构体,都用最简单的int ,char 这种。如果放到公用模块,但是这个结构体明显又是私用的,属于某个模块特别明显的,怎么办?

4. 总结

其实单片机的程序相对来说,还是比较简单的,简单的程序,分层太多,反而有时候过于拘泥于形式。
复杂的程序,就上系统了,比如linux,这样就会有别的大师给你做好架构,你只需要按照这个框架进行填充即可。
这也是为什么嵌入式的架构师需求不大的原因。
在上一篇的Demo中,能看出很多厂家其实都是直接分3层,底层,中间层,应用层,简单粗暴,底层和硬件相关,类似本文的HAL和HDL,中间层就是协议,模块,类似本文的FML,应用层就是应用。
其实都是一样的,看个人偏好吧,不要太在意分层这个形式。