嵌入式系统是软件与硬件的综合体,硬件设计和软件设计相辅相成。嵌入式系统中的驱动程序是直接工作在各种硬件设备上的软件,是硬件和高层软件之间的桥梁。正是通过驱动程序,各种硬件设备才能正常运行,达到既定的工作效果。
嵌入式软件构件(Embedded Software Component,ESC)是实现一定嵌入式系统功能的一组封装的、规范的、可重用的、具有嵌入特性的软件单元,是组织嵌入式系统的功能单位。
嵌入式软件构件分为高层软件构件和底层软件构件(以下简称高层构件和底层构件)。高层构件与硬件无关。而底层构件与硬件密不可分,是硬件驱动程序的封装。前面提到,在硬件构件中,核心构件为MCU的最小系统。通常,MCU内部包含有GPIO(即通用I/O)口和一些内置功能模块,可将通用I/O口的驱动程序封装为GPIO构件,各内置功能模块的驱动程序封装为功能构件,如芯片内含模块的功能构件有串行通信构件、Flash构件、定时器构件等。
在硬件构件层中,相对于核心构件而言,中间构件和终端构件是核心构件的“外设”。由这些“外设”的驱动程序封装而成的软件构件称为底层外设构件。注意,并不是所有的中间构件和终端构件都可以作为编程对象。例如,键盘、LED、LCD等硬件构件与编程有关,而电平转换硬件构件就与编程无关,因而不存在相应的底层驱动程序,当然也就没有相应的软件构件。嵌入式硬件构件与软件构件的层次模型如图 1-6 所示。
图 1-6 嵌入式硬件构件与软件构件的层次模型
由图 1-6 可看出,底层外设构件可以调用底层内部构件,如LCD构件可以调用GPIO构件、PCF8563 构件(时钟构件)可以调用I2C构件等。而高层构件可以调用底层外设构件和底层内部构件中的功能构件,而不能直接调用GPIO构件。另外,考虑到几乎所有的底层内部构件都涉及MCU各种寄存器的使用,因此将MCU的所有寄存器定义组织在一起,形成MCU头文件,以便其他构件头文件中包含该头文件。
底层构件是与硬件直接打交道的软件,由头文件和源程序文件两部分组成。
头文件中的内容主要有:包含下层构件头文件的#include语句、用以描述构件属性的宏定义语句,以及对外接口函数原型说明。在头文件中使用函数原型,对于建立代码模块和外部接口的规范,便于他人使用,都是很有帮助的。使用这些函数的用户,不需要查找源代码去了解参数的具体类型,直接查看函数原型即可。
源程序文件中存放构件的内部函数和外部函数的定义,即函数的实现代码,以完成函数所要实现的功能。
在对底层构件进行设计时,最为关键的工作是要对构件的共性和个性进行分析,抽取出构件的属性和对外接口函数。尽量做到:当一个底层构件应用到不同系统中时,仅需修改构件的头文件,对于构件的源程序文件则不必修改或改动很小。
例如,串行通信模块SCI是大多数MCU都具有的内部模块。仔细分析各种MCU串行通信程序可发现:在查询方式下,各种MCU都是根据状态寄存器中的两个标志位来判断是否接收到数据和数据是否发送完毕,这就是SCI模块的共性。对于不同的MCU,该状态寄存器的名称可能不同,这两个标志位的位号也有可能不同。此外,用以设置波特率、通信格式、是否校验、是否允许中断等参数的寄存器也不同,这就是SCI模块的个性。分析出了共性和个性之后,就可以抽取出SCI构件的属性和操作,编制构件头文件和程序文件了。有关内容参见第5章。
在编写构件时,一般应注意以下几方面的内容。
(1)构件的头文件和源程序文件的主文件名一致,且为构件名。
(2)属性和操作的命名统一以构件名开头。这样做的好处是:当使用底层构件组装软件系统时,避免构件之间出现同名现象。同时,名称要使人有“顾名思义”的效果。
(3)对MCU内的模块寄存器名和端口名进行重定义,在其他的代码里面都将使用宏名对模块寄存器和端口进行操作。这样,当底层驱动程序移植到其他MCU时,只要修改重定义语句就可以了。
(4)内部函数与外部函数要设计合理,函数参数个数及类型要考虑全面。内部函数仅提供给同一构件中的其他内部函数或外部函数调用,作用域仅限于定义该函数的文件。外部函数是对外接口函数,供上层应用程序调用。在定义外部函数时,应该对函数名、函数功能、入口参数、函数返回值、使用说明、函数适用范围等进行详细描述,以增强程序的可读性。上层应用程序不能直接对构件的属性进行读取或设置,必须借助于该构件提供的接口操作函数来实现。
(5)应用程序在使用底层构件时,严格禁止通过全局变量来传递参数,所有的数据传递都要通过函数的形式参数来接收。这样做不但使得接口简洁,而且避免了全局变量可能引发的安全隐患。
重用是指在一个系统中,同一构件可被重复使用多次。移植是指将一个系统中使用到的构件应用到另外一个系统中。
对于以单MCU为核心的嵌入式应用系统而言,当用硬件构件“组装”硬件系统时,核心构件(即最小系统)有且只有一个,而中间构件和终端构件可有多个,并且相同类型的构件可出现多次。下面以终端构件LCD为例,介绍硬件构件的移植方法。
在应用系统A中,若LCD的数据线(LCD-D0~LCD-D7)与芯片AW60(8 位MCU)芯片的通用I/O口的B口相连,C口作为LCD的控制信号传送口,其中,LCD寄存器选择信号LCD-RS与C口第 0 引脚连接,读写信号LCD-RW与C口第 1 引脚连接,使能信号LCD_E与C口第 2引脚连接,则LCD硬件构件实例如图 1-7(a)所示。虚线框左边的文字(如PTC0、PTC1 等)为接口网标,虚线框右边的文字(如LCD-RS、LCD-RW等)为接口注释。
在应用系统B中,若LCD的数据线(LCD-D0~LCD-D7)与MCF52233(32 位MCU)芯片的通用I/O口的AN口相连,TA口的第 0、1、2 引脚分别作为寄存器选择信号LCD-RS、读写信号LCD-RW、使能信号LCD_E,则LCD硬件构件实例如图 1-7(b)所示。
当一个已设计好的底层构件移植到另外一个嵌入式系统中时,其头文件和程序文件是否需要改动呢?这要视具体情况而定。例如,系统的核心构件发生改变(即MCU型号改变)时,底层内部构件头文件和某些对外接口函数也要随之改变,如模块初始化函数。
而对于外接硬件构件,希望不改动程序文件,而只改动头文件,那么,头文件就必须充分设计。以LCD构件为例,与图 1-7(a)相对应的底层构件头文件LCD.h可如下编写。
当LCD硬件构件发生如图 1-7(b)所示的移植时,显示数据传送口和控制信号传送口发生了改变,此时,只要将上面的第 1 条和第 2 条宏定义语句修改成:
必须申明的是,本书提出构件化设计方法的目的是,在进行软硬件移植时,设计人员所做的改动要尽量小,而不是不做任何改动。希望改动在头文件中进行,而不希望改动程序文件,事实上,不做任何改动是不现实的。
图 1-7 LCD构件在实际系统中的应用