C语言嵌入式系统编程软件设计架构研究

2018-04-15 13:45
单片机与嵌入式系统应用 2018年1期
关键词:罗盘功能模块C语言

(北京信息职业技术学院,北京 100015)

引 言

C语言是嵌入式软件开发使用最多的语言[1],主要是由于C语言兼具高低级语言的特性,简洁高效、灵活方便,支持对硬件的直接操作,但其灵活性也往往会带来复杂的代码管理和维护问题。不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,面向的是一种专用的计算机系统[2],既有对硬件操作的复杂性,也有应用层次上的通用性。因此,在软件开发过程中,采用良好的软件框架和设计方法,对项目进行工程化管理,能够更好地指导软件开发的层次划分和功能模块设计。既能提高软件系统的开发、执行和维护效率,又有利于提高程序代码的重用性、拓展性和可靠性。本文在当前流行的软件工程思想基础上,将面向对象设计技术、分层技术应用到C语言嵌入式系统编程中,探讨在C语言嵌入式系统开发中的系统设计思路、分层实际、程序架构以及模块重用等问题。

1 系统设计思路

无论是面向过程思想,还是面向对象思想,都是为了更好地将开发需求转变成软件模块划分,进而转变成能够用代码实现的程序功能[3]。在实际系统开发中,不是说一定要用C++或Java等面向对象语言才能进行面向对象程序设计,用C语言也一样可以实现程序模块的封装、继承等特性,关键是如何体现模块划分的“高内聚、低耦合”特点,提高代码的重用性和拓展性。随着嵌入式软件系统的规模和复杂度日益增长,如何更好地进行模块划分,开发出可正确工作的复杂软件,成为系统设计面临的主要问题。

1.1 自顶向下与自底向上

在进行模块化的过程中,通常采用分层技术对应用需求问题进行梳理,抽象出不同层次的模块结构,界定各层次之间的依赖关系,最终将应用需求转变为软件设计。一个方向是自顶向下,从抽象到具体,从最顶层的程序或者逻辑整体描述规范出发向下到具体的操作模块,这是目前嵌入式系统应用层开发常用的方法。比如,液晶屏幕显示控制,可以细化到对点阵的一些操作,如“点亮一个点”、“点灭一个点”等。另一个方向是自底向上,从具体到抽象,从某个应用对象的操作出发分析常用的操作方法,这是在硬件驱动开发中常常采用的方法。比如,设计液晶屏幕的驱动,可以分析设计出一些操作原语,如“置一个点位亮”、“置一个点位灭”等,供上层开发调用。

在嵌入式系统设计过程中,可以将两种方法结合使用,针对硬件的操作采用自底向上,尽可能抽象出所有的元操作,应对不同上层应用的重用要求;在逻辑应用上,则采用自顶向下,对应用逻辑表达进行抽象规范,尽量使得模块划分便于开发实现、重用和维护。

1.2 最优模块化

功能模块是独立实现某一特定功能的最小代码集。软件模块实现的功能应该简单明了,方便理解和应用,而且对外依赖关系越少越好,能够更好地组织程序开发、集成和重用。在操作模块的设计过程中,应该遵循两个原则:一是紧凑性,封装良好的模块决不互相暴露内部信息,也不去调用其他模块的操作实现,而是通过函数接口来相互通信;二是正交性,任何模块的功能点应当是唯一的、无歧义的,在系统中以确定无疑的方式存在。在纯正交的模块设计中,每一个操作行动只限于该项功能,系统的每一属性只有一条途径改变,不影响其他功能,这有助于将复杂的设计紧凑化。比如,显示器功能设计的正交性,在调节明暗时不会影响到饱和度,色彩平衡的控制也彼此独立,否则将会对显示方式的调整带来很大的麻烦。对于有些太复杂的问题域,可能无法实现模块完全的紧凑设计,但要尽可能地保持模块封装的安全可靠。

2 嵌入式系统开发的分层架构

分层技术是应付软件日益复杂、功能不断拓展的重要手段。通过采用分层技术,很多复杂的问题得以分割、简化,转化成具体的应用功能实现,衍生出多层结构以及中间件技术等,在软件开发活动中的作用日益凸显。随着嵌入式系统应用复杂程度不断提高,采用分层技术对嵌入式系统进行合理设计,成为提高软件开发效率、执行效率和维护效率的关键。

2.1 分层原则

分层的目的是更好地对开发需求进行分解,合理区分软件功能层次,将软件划分为不同概念层次、不同功能的软件模块,确定不同模块之间的关系,从而实现复杂的软件系统功能。

在软件逻辑架构的分层设计上,一般遵循以下三个方面的原则:一是层次划分兼顾功能颗粒度和重用可能性,每层解决不同的问题,下层要能够为上层应用提供支撑,比如环境温度监测功能,可以从概念上划分为数据采集层、处理层、显示层等,层层递进实现;二是层与层之间的相关性尽量小,确保某一层的软件设计出现问题,只会影响到该层次的上下结构,不会影响到软件系统的整体(比如,显示层不应对温度数据进行处理或修改,避免影响整个处理层的逻辑实现);三是每层内部按照任务分解、功能优化、重用程度进行模块划分,尽量实现软件功能的高内聚、低耦合。理论上,功能分解得越简单,实现起来越容易,重复使用频次就会越高,但目标过度细化会使设计管理、功能调度的复杂度迅速上升,所以一般划分到概念上能够独立完成一项功能、与其他功能相关性合适的程度[5]。

2.2 分层设计方法

按照自顶向下、自底向上和最优模块化的系统设计思路,针对嵌入式应用与硬件结合紧密、属于专用系统、软硬层次比较明显等特点,对系统逻辑架构进行详细设计,梳理明确软件功能模块划分。

首先,采取自顶向下的方法对嵌入式系统应用需求进行梳理,抽象出不同的逻辑功能要求,明确概念层次,再转化成软件层次。这是一个逐步理解需求、转化成开发需求的过程。比如,开发电子罗盘,需要采集传感器的x、y、z轴数据,转换成方位数据,在液晶屏上显示输出,就分别涉及到界面显示、数据处理、硬件访问、硬件驱动等逻辑层次。

其次是采取自底向上的方法对涉及到的硬件功能进行抽象,应尽可能细化出应用开发需要的硬件操作原语。对于嵌入式系统而言,大量的开发工作是通过软件驱动底层硬件实现相应的专用功能,对硬件功能的封装既有利于降低当前系统开发的复杂度,又便于实现硬件的无关性,提高程序代码的复用性。比如传感器数据的采集,可以区分为硬件驱动层和功能拓展层,分别用来实现硬件的无关性和器件的无关性。

再次,采用自顶向下和自底向上相结合的方法,逐层检验相邻层次间的信息交互和调用关系,确保每一个上层的调用都能得到满足。

最后,对每一层的功能进行合并整合,优化功能模块设计,努力实现最优模块化。在实际系统开发中,最优模块化的过程也是对现有程序代码重用的优化选择过程。

2.3 分层技术的应用

通过对嵌入式系统进行分层设计,有利于理清层次结构、优化功能模块组织,使得系统设计过程敏捷灵活、产品功能可扩展性强。常见的功能模块划分是围绕中心处理器/控制器来设计系统逻辑架构,采用面向过程的设计思路,区分为输入/输出、应用调度、设备驱动、网络通信等功能模块。这样的划分方式能够充分利用系统的处理能力,进行精细化的存储空间管理,但也带来应用逻辑交叉重复、与硬件依赖关系强等缺点,很难进行功能拓展,代码重用性也较差。采用本文描述的设计思路和分层设计方法,对嵌入式系统进行面向对象、去中心化设计,可以将系统逻辑架构区分为以下4个层次[4]:

① 应用管理层。主要实现界面交互、业务逻辑调度等功能。

② 算法协议层。主要实现模型算法、协议解析、文件管理、数据库管理等功能,如位置转换计算、罗盘指针方位计算等。

③ 功能拓展层。主要实现器件的无关性,提供各种器件的通用性处理、接口访问等功能,如LCD的线、圆、矩形处理,传感器数据转换等功能。

④ 硬件驱动层。主要实现硬件的无关性,提供硬件的操作原语功能,如LCD的定位、写点、写字节、传感器数据采集等功能。

上述分层设计方案,将同类或相似技术实现的功能进行聚合,减少业务应用、模型算法和硬件操作之间的耦合性,避免功能在分析设计中的交叉混淆,整个应用程序的结构变得更加清晰和灵活,使得一个成熟的模型算法能够支持多个应用逻辑,一个成熟的软件功能模块能够适应不同的硬件环境,提高了软件功能模块的开发效率和可重用性。

3 基于C语言的系统软件设计

软件编程实现与采用的编程语言紧密相关,基于C语言的嵌入式系统开发必须遵循C语言的编程原则。灵活运用C语言的编程模式,能够提高项目开发效率和代码编写质量,也便于对代码进行维护。

3.1 代码管理

C语言的灵活性往往会导致文件组织混乱、代码可阅读性下降等问题。虽然标准的C语言开发工具并不提供软件框架管理,但根据本文提供的系统逻辑架构设计,可以建立自己的工程文件管理原则,提高代码文件的组织管理和协同开发能力。

一是文件目录管理。按照分层原则组织文件目录,主程序文件、全局变量头文件放在根目录,其他文件按照应用管理层、算法协议层、功能拓展层、硬件驱动层分别存放在AppFunc、ModelFunc、HardExt、HardOpt文件夹,所有文件命名遵循统一的规范。如果有第三方的通用函数库,可以建立ComFunc文件夹来存放。这样在开发过程中,可以充分利用分层模型的优势,各层功能的开发人员可以在不同的文件夹内进行并行工作,实现工程化管理。

二是功能模块管理。为了实现模块化设计的高内聚性,应少用或不用全局变量,尽量通过函数参数来传递数据。同一类的业务应用功能、同一硬件的操作功能尽量放在同一文件内实现。上层功能模块的开发可以调用下层功能模块,下层功能模块尽量避免交叉调用或越级调用。

3.2 面向对象设计

在实际编程过程中,可以通过灵活运用C语言的结构类型和函数指针,实现类似面向对象的继承、封装、多态等重要特性,从而提高编程的效率和代码复用。

(1)继承

通过结构嵌套可以实现对象属性的继承。下面为罗盘对象参数继承的简化示例:

typedef struct_compassbase{ //罗盘基类

int radius; //罗盘半径

int centerx,centery; //罗盘中心

}CompassBase;

typedef struct_compass{

struct_compassbase;

int handle; //指针位置

}Compass;

(2)封装

利用函数指针将数据和函数进行绑定,可以实现对象属性和对象实现的封装。下面为罗盘基类封装的简化示例:

struct _compassbase;

typedef void (*drawcompass)(struct_compassbase*pComBase);

typedef struct_compassbase{ //罗盘基类

int radius; //罗盘半径

int centerx,centery; //罗盘中心

drawcompass pDrawcompass;

}CompassBase;

(3)多态

上述的示例中已经隐含了多态,在调用showgrade的实现时并不用考虑该函数的具体数据处理方式,可以有多种实现方法。

3.3 模块重用设计

C语言代码重用一般通过函数模块来实现,包括头文件和函数实现文件,也就是.h和对应的.c文件。函数定义可以通过两种方式实现:一是宏定义,如#define maxi(a,b) (a>;b?a:b),而且宏是与类型无关的,不会带来额外的开销,但有些任务是无法通过宏来实现的;二是函数,函数是一段可以重复使用的代码,用来独立地完成某个功能,可以接收用户传递的数据,也可以将计算结果通过函数值返回或通过地址参数返回。下面是分层设计逻辑框架下的函数调用示例,也可采用相同的调用实现不同项目代码的复用,对于罗盘中心位置、显示区域等变量则采用了面向对象设计方法进行封装,在此仅简单描述函数的调用关系。

本应用案例是利用角速度传感器制作一个电子罗盘,在LCD显示屏上实时显示当前方位,可以复用已有算法协议层、功能拓展层、硬件驱动层的功能模块。其中LCD显示功能在各层的示例代码如下:

① 应用管理层:uint Draw_Compass(uint angle,uint pcolor);显示当前angle角度的电子罗盘,pcolor为当前显示颜色,对angle的计算通过调用算法模型层中罗盘角度函数获取,画指针函数则调用功能拓展层的画线函数。

② 算法模型层:uint Cac_Compass(uint x,uint y,uint z);计算罗盘指针方位,x、y、z为传感器获取的数值,转换成指针的角度。

③ 功能拓展层:uint Lcd_Line(uint x1,uint y1,uint x2,uint y2,uint pcolor);这是画线、调用画点函数。

④ 硬件驱动层:uint Lcd_Pixel(uint x,uint y,uint pcolor);驱动LCD进行画点。

结 语

[1] Barr M.Real men program in C[J].Embedded Systems Design,2009(7).

[2] 田泽.嵌入式系统开发与应用[M].北京:北京航空航天大学出版社,2005.

[3] 林越,王翠珍.浅谈面向对象开发思想与软件设计架构分析[J].信息通信,2016(3):152-154.

[4] 张智慧.多层模型在嵌入式软件开发中的应用研究[J].计算机时代,2017(4):17-20.

[5] 郭潇濛,王崑声.面向对象系统工程方法改进探索[J].科学决策,2016(6):73-94.

猜你喜欢
罗盘功能模块C语言
基于Visual Studio Code的C语言程序设计实践教学探索
基于C语言的计算机软件编程
不宜上课
高职高专院校C语言程序设计教学改革探索
基于ASP.NET标准的采购管理系统研究
输电线路附着物测算系统测算功能模块的研究
M市石油装备公服平台网站主要功能模块设计与实现
印尼《罗盘报》之中国国家形象
功能模块的设计与应用研究
论子函数在C语言数据格式输出中的应用