昨天在“电子工程世界”看到一篇讲嵌入式软件架构的,我很不赞同他的观点。我准备把我对嵌入式软件架构的一些认识写出来,供大家学习,批评。
这儿必须先说说我们博大精深的中华文化了, <<周易>>,<<道德经>> 都非我所能讲解的. 只能简单讲讲自己在嵌入式软件架构中领悟的一点感受: "大道至简", "动静之机". 很多东西我大慨知道,但是因为我自己尚在懵懂之中,所以无法表述. 需要读者自己在书中领悟.
大约在2003年或者2004年,我看了一本书《Java与模式》。快看完的时候,突然领悟了一点模式设计的思想:变与不变。
变化是绝对的,不变是相对的。在后来的工作实践中,我逐渐使用这种思想构建嵌入式的软件架构。
对于一个项目来说,什么都有可能改变。用户需求可能会变化,单片机的选型可能会变化,电路板可能会变化,使用的RTOS(实时操作系统)可能会变化。
那什么是不变的呢?假设我已经完成了一个项目,然后客户需求变了,也许我可以使用之前使用的单片机,可以使用之前使用的电路板,可以使用之前使用的RTOS。使用之前的东西,这些东西对应的代码可能不需要任何改变,这就是不变。
下图是我创建的嵌入式软件架构,分为5层:
![769f5e13e57f1b5d5ecedff92c06a653.png](https://i-blog.csdnimg.cn/blog_migrate/60c610aa488fa25d12932aa99a6663ce.jpeg)
在解说各层之前,需要说明一些处理规则:上层能调用下层的函数接口,而下层函数不知道上层函数的存在。板级支持包的模块只能调用驱动层的函数接口。中间件层的模块只能调用板级支持包的函数接口。应用层的模块只能中间件的函数接口。
驱动层:这一层是为单片机而设定的。只要单片机没变,驱动层的代码就不会变。可以参考stm32的固件库。(感觉stm32的 stm32g0xx_ll_xxx比之前的固件库好很多)
板级支持包(BSP):这一层是为单片机在硬件上的资源分配而设定的。一个项目,根据需求分解成多个功能模块,我们需要为这些功能模块在硬件上分配资源。比如:实现ModBus协议功能,使用串口0还是串口1?使用那个管脚?波特率是多少?是查询模式,中断模式还是DMA模式?等等。有些使用的参数,在上层调用时给出(如:波特率)。
BSP实现了所有与单片机硬件相关的功能。一般情况下,新项目的电路板没有修改,BSP就不需要修改。但是这并不绝对,比如:需求中ADC的采样频率需要提高很多,ADC在BSP的功能实现就会从原来的中断方式改为DMA方式,相关的函数接口名字都需要修改。
RTOS:实时操作系统。这个没什么说的。一般移植程序都是现成的。只需要在项目的资源分配中留出相应的硬件资源就行了。
中间件:应用层:这两层放在一起说。因为它们比较容易被弄混,不知道该怎么去区分。一个项目,被划分成各种功能。每种功能需要分成两部分,一部分是怎么做,另一部分是什么时候做。它们分别对应在中间件层和应用层。
中间件层中的各个模块也是可以分层的。在中间件层里,有些的功能模块需要调用另一个功能模块的接口函数。这样中间件层内部也就划分成多个层次。应用层也一样。
中断处理函数是一个特殊的函数。一般在中断函数中,先判断是那个中断,然后调用一个还未实现的函数,再清中断退出。这个未实现的函数在BSP层定义,但是在中间件层实现。C语言支持这种先定义,后实现的操作。
举个例子:电路板上有个功能芯片,芯片内部有寄存器。这个芯片的驱动模块放在中间件层,驱动模块并分成3层:第一层为移植层;第二层是寄存器层;第三层是芯片功能层。
芯片的移植层很好理解。假如此芯片是通过spi总线与单片机相连的,单片机的bsp层有spi的驱动函数,而芯片的驱动模块也有一组操作spi总线的函数接口。这两个函数哪怕功能是一样的,名字也不一样,所以有个移植层进行接口转换。
芯片的寄存器层也很好理解。它就是芯片功能层提供基础功能的接口,功能层的一个函数中都可能多次调用寄存器层的函数。
芯片功能层就是此芯片对外展示的所有可以实现的功能。
总结一下:我将嵌入式软件架构分成5个层。每层包含很多模块(整个项目都是模块化编程,每个模块一个.c和.h文件)。每层都有自己的一些特性,在项目需求变化时,只需要修改相应的模块。模块与模块之间没有全局变量,都只能通过函数接口进行通讯。
软件架构设计中,思想很重要,它是灯塔,指引着我们改进的方向。没有灯塔,我们也许改着改着就把代码改成一团乱麻了。这个灯塔就是 变与不变. 可以多读<<周易>>和<<道德经>>,应该能加深理解.
我的文笔不好,网友可以在此基础上进行修改,扩展。