基于硬件构件的嵌入式底层编程方法*
2010-03-20王志超王宜怀
王志超,王宜怀
(1.宿迁学院计算机科学系,宿迁223800;2.苏州大学)
引 言
构件本质上是把对象对外的接口声明与对象内部的接口实现相隔离,一个构件在修改自己的接口时,只影响与修改接口相交互的构件,与被修改构件相交互的其他构件不受影响,因此它的重用性和可扩展性更高[1]。
嵌入式底层构件(Embedded Underlying Component,EUC)是在硬件构件的基础上,根据硬件构件的实际功能和接口,实现与之相对应的硬件驱动模块的分解,并将硬件驱动底层程序的实现、头文件定义及其文件描述封装成一个可重用的构件实体,并提供一系列规范的输入/输出接口,供其他嵌入式应用程序调用。底层构件同硬件构件一样,也具有被独立部署和被第三方组装的特性[2]。而嵌入式底层软件则与嵌入式系统硬件模块相关联,具体实现硬件构件功能的程序代码。
1 嵌入式硬件构件和软件构件的层次模型
嵌入式软件构件(Embedded Software Componen t,ESC)是实现一定嵌入式系统功能的一组封装的、规范的、可移植的、具有嵌入特性的软件单元,是组织嵌入式系统的功能单位。
嵌入式软件构件分为高层软件构件和底层软件构件(以下简称“高层构件”和“底层构件”)。高层构件与硬件无关,而底层构件与硬件密不可分,是硬件驱动程序的封装。在硬件构件中,核心构件为MCU的最小系统。通常,MCU内部包含有GPIO(即通用I/O)口和一些内置功能模块,开发者可以将通用I/O口的驱动程序封装为GPIO构件。各内置功能模块的驱动程序封装为功能构件,如Freescale公司的MCF52233的功能构件有UART构件、Flash构件、USB构件、I2C构件等。
在硬件构件中,相对于核心构件而言,中间构件和终端构件是核心构件的“外设”。由这些“外设”的驱动程序封装而成的软件构件称为底层外设构件。不过,值得注意的是,并不是所有的中间构件和终端构件都可以作为编程对象[3-4]。例如:键盘、LED、LCD等硬件构件与编程有关,而电平转换硬件构件就与编程无关,因而不存在相应的底层驱动程序,当然也就没有相应的软件构件。
嵌入式硬件构件与软件构件的层次模型如图1所示。底层外设构件可以调用底层内部构件,例如LCD构件可以调用GPIO构件,PCF8563构件(时钟构件)可以调用I2C构件等。而高层构件可以调用底层外设构件和底层内部构件中的功能构件,而不能直接调用GPIO构件。另外,考虑到几乎所有的底层内部构件都涉及到MCU各种寄存器的使用,因此将MCU的所有寄存器定义组织在一起,形成MCU头文件,以便其他构件头文件中包含该头文件[5-6]。
图1 嵌入式硬件构件与软件构件的层次模型
2 底层构件的实现方法与编程思想
底层构件是与硬件直接打交道的软件,由头文件和源程序文件两部分组成。
头文件中的内容主要有:包含下层构件头文件的#include语句、用于描述构件属性的宏定义语句以及对外接口函数原型说明。在头文件中使用函数原型,对于建立代码模块和外部接口的规范、便于他人使用,都是很有帮助的。使用这些函数的用户,不需要查找源代码去了解参数的具体类型,直接查看函数原型即可。
源程序文件中存放构件的内部函数和外部函数的定义,即函数的实现代码,以实现函数的功能。
在对底层构件进行设计时,最关键的工作是要对构件的共性和个性进行分析,抽取出构件的属性和对外接口函数。尽可能做到:当一个底层构件应用到不同系统中时,仅需修改构件的头文件,对于构件的源程序文件则不必修改或改动很小。
例如,串行通信模块SCI是大多数MCU都具有的内部模块。仔细分析各种MCU串行通信程序发现:在查询方式下,各种MCU都是根据状态寄存器中的两个标志位来判断是否接收到数据和数据是否发送完毕,这就是SCI模块的共性。对于不同的MCU,该状态寄存器的名称可能不同,这两个标志位的位号也有可能不同。此外,用以设置波特率、通信格式、是否校验、是否允许中断等参数的寄存器也不同,这就是SCI模块的个性。分析出了共性和个性之后,就可以抽取出SCI构件的属性和操作,编制构件头文件和程序文件了。
以Freescale公司的MC68HC908GP32的SCI模块为例,数据寄存器名称为SCDR,空标志位(第7位)和满标志位(第5位)均位于状态寄存器SCS1中。SCI构件的头文件(SCI.h)设计如下:
在编写构件时,须注意以下几方面的内容:
①构件的头文件和源程序文件的主文件名一致,且为构件名。
②属性和操作的命名统一以构件名开头。这样做的好处是,当使用底层构件组装软件系统时,可以避免构件之间出现同名现象;同时,含义清晰、一目了然。
③对MCU内的模块寄存器名和端口名进行重定义,其他的代码都将使用宏名对模块寄存器和端口进行操作。这样,当底层驱动程序移植到其他MCU时,只要修改重定义语句就可以了。
④内部函数与外部函数要设计合理,函数参数个数及类型要考虑全面。内部函数仅提供给同一构件中的其他内部函数或外部函数调用,作用域仅限于定义该函数的文件。外部函数是对外接口函数,供上层应用程序调用。在定义外部函数时,应该对函数名、函数功能、入口参数、函数返回值、使用说明、函数适用范围等进行详细描述,以增强程序的可读性。上层应用程序不能直接对构件的属性进行读取或设置,必须借助于该构件提供的接口操作函数来实现。
⑤应用程序在使用底层构件时,严格禁止通过全局变量来传递参数,所有的数据传递都要通过函数的形式参数来接收。这样做不但使得接口简洁,而且避免了全局变量可能引发的安全隐患。
3 底层构件的移植
当一个已设计好的底层构件移植到另外一个嵌入式系统中时,其头文件和程序文件是否需要改动,这要视具体情况而定。例如:系统的核心构件发生改变(即MCU型号改变)时,底层内部构件头文件和某些对外接口函数也要随之改变,如模块初始化函数。以SCI构件为例,由于不同MCU的SCI模块的数据寄存器名称、状态寄存器名称及位定义不同,初始化工作所涉及的波特率、通信格式、是否校验等设置值也不同,因此当MCU改变时,构件头文件中相关宏定义值和SCI_Init的函数体实现代码也会有所不同。以Freescale公司的DG 128单片机为例,其有两个串行口SCI0和SCI1。若使用串行口SCI0进行收发数据,则数据寄存器名称为SCI0DRL,空标志位(第7位)和满标志位(第5位)位于状态寄存器SCI0SR1中。因此需要将MC68HC908GP32的SCI构件头文件中前三行修改成如下内容:
结 语
底层构件的编程方法在嵌入式系统的开发中具有举足轻重的地位。底层软件开发的成败直接关系着整个系统稳定性和可靠性的好坏。如果开发人员在编写底层软件时采用软件工程思想的方法,就可以最大程度地使底层软件具有良好的可移植性和复用性。不过必须说明的是,本文提出构件化设计方法的目的是,在进行底层软件移植时,设计人员所做的改动尽量小,而不是不作任何改动。事实上,不作任何改动是不现实的。
[1]王宜怀,陈建明,蒋银珍.基于32位ColdFire构建嵌入式系统[M].北京:电子工业出版社,2009:413-417.
[2]荐红梅.基于硬件构件的嵌入式底层软件开发方法研究及其应用[D].苏州:苏州大学,2008:28-30.
[3]沙占友,王彦朋,孟志永.嵌入式外围电路设计[M].北京:电子工业出版社,2003:161-173.
[4]桑楠.嵌入式系统原理及应用开发设计[M].北京:北京航空航天大学出版社,2004:121-126.
[5]王宜怀,刘晓升.嵌入式应用技术基础教程[M].北京:清华大学出版社,2007:8-15.
[6]王志超.基于MC68HC908GP32的射频卡密码认证系统设计与实现[J].微计算机信息,2009:79-81.