1.2 嵌入式系统开发

1.2.1 需求分析和概要设计

1)系统需求分析

嵌入式系统可以看作一种专用的计算机。它与通用意义上的计算机在人机交互界面、有限的功能、时间关键性和稳定性等方面存在很大的差别。从组成来看,嵌入式系统包括硬件和软件两个部分,是两者的紧密结合。整个系统可以看成由微处理器、内存、软件系统、输入和输出四个部分组成。

嵌入式系统的特点决定了系统在开发初期需求分析过程中,需要完成的任务。就一般软件工程概念而言,在需求分析阶段需要分析客户需求,并将需求分类整理——包括功能需求、操作界面需求和应用环境需求等。嵌入式系统也是如此。如何在需求分析阶段,详尽充分地表达客户的需求,并将之加入到后一步的系统模型中去,这是一个嵌入式系统成功设计的第一步。

嵌入式系统应用需求中最为突出的一个特点是注重应用的时效性——在竞争中,产品投放市场时间最短的企业最容易赢得市场。因此,在需求分析的过程中,采用成熟、易于二次开发的系统有利于节省时间,从而以最短的时间面向用户。

2)系统结构模型建立方式

需求分析建立之后,需要建立完整的功能模型,并根据现有的系统结构库对系统模型进行设计。一般而言,在系统结构模型和系统功能模型之间可以通过建立一个映射层,来完成从功能需求到结构模型之间的转化。在映射层上完成的任务主要是选择映射的方式,考虑在性能和应用需求环境方面的因素。图1.2所示描述了在功能层、映射层和模型层之间的关系。

图1.2 系统结构模型建立方式图

需求分析的结果是建立一个功能需求库,将所有需求综合在一起,包括系统需要的功能、输入/输出、需要达到的运行速度和效率等。在系统结构库中存在着针对各种需求功能的各种结构方式和实现方法,用于在设计中进行验证和选择。

从功能到结构的映射在映射层完成。整个映射过程主要完成转换功能需求到系统结构,验证分析实现是否满足性能需求两方面任务。转换功能需求主要是寻求功能所对应的实现,即根据系统结构库寻找合适的实现手段,用硬件或软件实现,以达到最合适的性能。在性能分析方面,可以根据系统结构库中已经存在的性能数据做性能估计,或者建立原型系统做仿真分析。在硬件和软件系统方面都有做性能分析的方式,一般使用仿真的手段建立需要实现的结构模型,在其上进行性能分析。映射过程结束之后,就建立了整个系统的结构模型。

3)系统结构模型实现流程

在映射层建立的系统结构模型,分成硬件和软件两个部分,在系统结构的实现中也分硬件和软件并行完成开发过程。这个过程被称为Hardware/Software Codesign。图1.3所示是一个典型的从上至下的设计、实现过程。

图1.3 HW/SW Codesign系统结构模型

在硬件设计过程中,首先根据模型确定硬件需要实现的功能,接着确定硬件的构成,并确定数据的控制流程,完成结构化设计;然后进行硬件逻辑设计;最后进行物理硬件实现,以开发板的形式出现。在软件设计过程中,首先分析系统需要实现的任务,根据任务划分使用的模块,再通过高级语言实现各个高层模块,并通过交叉开发环境实现目标代码。在集成过程中,需要实现硬件底层代码,完成软、硬件的集成任务。在开发最后阶段,完成系统测试。实际上在开发初期,软件和硬件都是独立进行的,特别是软件开发可以使用软件模拟器。

使用PC及仿真软件开发,不需要硬件。在用软件模拟器(Simulation)将软件调通后,可以使用评估(Evaluation)软件在PC及评估板上调试。最后在硬件设计目标完成后,运用仿真(Emulation)软件使用PC、JTAG及目标板完成最后的调试。如图1.4是Visual DSP++4.5集成环境开发流程示意图。

图1.4 Visual DSP++ 4.5集成环境开发流程示意图

1.2.2 嵌入式系统中的硬件

嵌入式系统中除核心部件嵌入式处理器外,还包含以下硬件设备。

1. 存储设备

嵌入式系统中使用的内存可以分为ROM和RAM两类,前者是只读存储器,后者是可读/写存储器。RAM主要用于主存中。只读存储器有如下几种:PROM(Programmable ROM)、EPROM(Erasable-and-Programmable ROM)、EEPROM(Electrically EPROM)、Masked ROM、Flash ROM等。嵌入式系统应用中一般利用Flash ROM存储代码。

对于一些固定控制指令或操作系统内核,系统只须修改只读存储器中的内容即可完成升级。

2. 外围设备

除了处理器和存储设备之外,嵌入式系统还需要具备输入/输出设备才能称得上是完整的、能够工作的系统。一般而言,输入/输出设备被看作处理器和存储设备之外的外围设备,主要包括如下几类。

1)网络设备

嵌入式系统通过网络设备与外界联系,接收外界数据,并在处理后通过网络传出。按传输媒介来分,网络设备可以划分为有线网络设备和无线网络设备两种。

(1)有线网络设备

最常用的有线网络设备接口是以太网接口。其根据所应用的以太网的不同,可以分为10MB、100MB和1000MB以太网芯片。除了以太网接口,常用的网络接口设备还有USB(Universal Serial Bus,通用串口总线),支持接口的即插即用和USB Hub功能。IEEE 1394,即通常所称的火线(Firewire),是高端嵌入式产品采用的网络设备接口。

(2)无线网络设备

无线网络设备是目前流行的网络接口。比较常用的无线网络接口技术有蓝牙技术(Bluetooth),红外传输技术(IrDA)和IEEE 802.11-a(b)系列标准的无线局域网技术。

蓝牙技术所使用的是无须申请的2.4GHz的ISM(Industrial、Scientific和Medical)频段,并采用额定速率为1600跳每秒(Hops/s)的高速跳频来减少干扰。除跳频外,它还采用了时分双工传输方案(时隙)。该技术支持物理信道中1Mb/s的最大带宽。由于蓝牙面向小功率、便携式的应用,因此,一个典型的蓝牙设备只有大约10m的有效范围。蓝牙能够传送语音和数据业务,并能够同时支持同步通信和异步通信。其物理信道被分成625μs的长时隙,采用的链路协议将根据这些时隙确定。同步语音信道采用具有固定时间间隔的线路交换处理。蓝牙支持的信道配置,如表1-1所示。

表1-1 蓝牙的信道配置

一个蓝牙系统主要由天线单元、链路控制(固件)单元、链路管理(软件)单元和蓝牙软件(协议栈)单元四个部分组成。天线部分遵循FCC有关电平为0db的ISM频段标准。链路控制单元是蓝牙硬件部分,包括3个集成芯片:连接控制器、基带处理器及射频传输/接收器,此外还有3到5个单独调谐元件。链路管理单元包括链路的数据设置、鉴权、链路硬件配置及其他的一些协议,它可以发现其他的链路管理单元并通过LMP(链路管理协议)与之互相通信。软件单元是蓝牙协议栈,它符合已经制定好的蓝牙规范。蓝牙规范包括两个部分:核心部分用以规定诸如射频、基带、连接管理、业务发现、传输层及与不同通信协议间的互用、互操作性等组件。应用规范部分用以规定不同蓝牙应用所需的协议过程。

红外设备在很多笔记本电脑、掌上电脑、手机等设备上都有。它是一种利用红外线进行点对点通信的技术。现在的红外通信协议(IrDA:IrDA是国际红外数据协会的英文缩写)传输速率为16Mb/s(VFIR),比原来的FIR快4倍,接收角度也从原来的30°角变成了120°广角。

红外传输方式主要有如下几个优点:

无须专门申请特定频率的使用执照;

具有移动通信设备所必需的体积小、功率低等优点;

数据传输速率比较高。最高数据传输速率可达16Mb/s。

IrDA具有一定的市场优势。目前,全世界有1亿台设备采用IrDA技术,并且仍在以每年50万台的速度增长。大多数的笔记本电脑及一些外设都安装有IrDA接口。在成本上,红外线LED及接收器等组件也较其他同类产品更便宜。但是红外设备也有其与生俱来的缺点:

IrDA是一种视距传输技术,也就是说在多个具有IrDA接口的设备之间传输数据,中间就不能有阻挡物,这在两个设备之间是容易实现的,但在多个设备之间传输数据就必须调整彼此之间的位置与角度等;

IrDA设备的核心部件——红外线LED是一种不耐用的器件,频繁使用会令其寿命大大缩短。

IEEE802.11协议主要有两种设备,一种是802.11协议设备,又称HomeRF设备,主要提供1.6Mb/s的数据传输速率;另外一种是802.11b协议设备,提供11Mb/s数据传输速率;最近还有公司推出了802.11a协议,提供最高速率为54Mb/s的数据传输速率。

802.11b协议的最高通信速率是11Mb/s,也可以根据实际情况采用5.5Mb/s、2Mb/s和1Mb/s带宽,可以和普通的10Base-T局域网处在同一水平。和蓝牙一样,802.11b采用的也是2.4GHz的ISM频段,不需要申请就可以使用。它的原理与802.3以太网的原理类似,都是用载波侦听来控制网络中的信息传输。但是在无线传输中感测载波和做碰撞检测是不可靠的,因此802.11b中没有使用802.3中的冲突检测机制,而是采用了冲突避免机制,称为CSMA/CA(Carrier Sense Multiple Access with Collision Avoidance)。

经常使用的802.11b设备主要有PCMCIA、USB和PCI三种接口的无线网卡,还有用作连接无线局域网与有线局域网的AP(Access Point)。组成一个基本的无线局域网可以通过两块或者多块无线网卡以点对点的方式建立连接,也可以通过无线网卡和AP建立无线局域网。CISCO、3Com、Lucent公司都提供无线网卡和AP设备以供选择。

2)输入设备

与通用计算机相类似,嵌入式系统有时候也需要键盘或者鼠标一类的输入设备。但不同的是,嵌入式系统需要有限定的小键盘。为了控制方便起见,有时候还采用触摸屏。

3)显示设备

一般在工业控制中采用简单的LED灯作为显示设备。不过,随着液晶显示屏成本的下降和工业控制程序复杂度的上升,液晶屏LCD的采用逐渐成为了潮流。

1.2.3 嵌入式系统中的软件

1. 嵌入式系统的软件特征

嵌入式处理器的软件是实现嵌入式系统功能的关键,对嵌入式处理器系统软件和应用软件的要求也与通用计算机有所不同。

(1)软件固化

为了提高执行速度和系统的可靠性,嵌入式系统中的软件一般都固化在存储器芯片或者处理器中,而不是存储在各种磁介质中。

(2)软件代码高质量和高可靠性

尽管半导体技术的发展,使得处理器速度不断提高,芯片上的存储器的容量不断增加,但在大多数的应用中,存储空间仍然是宝贵的,还存在实时性要求。为此,要求程序编写和编译工具的质量要高,以减小二进制代码的长度,提高执行速度。

(3)嵌入式OS具备实时处理能力

在多任务嵌入式系统中,对于重要性各不相同的任务进行统筹兼顾的合理调度是保证每个任务及时执行的关键。单纯通过提高处理器的速度是无法完成的也是没有效率的。这种任务调度只能由嵌入式操作系统来完成,因此要求操作系统具有实时处理能力。

2. 开发嵌入式系统软件所必须具备的基础知识

在嵌入式系统中,开发程序非常不容易,必须借助各种各样的辅助工具与仪器,考虑各种平台的差异,修正多样化的外围存取程序,并且要求能够达到一种运行稳定、操作容易的条件。整个系统的规划与整合是嵌入式系统软件开发人员所必须建立的观念。

开发嵌入式系统软件,必须了解嵌入式系统既是软件又是硬件的属性,需要软件人力资源的投入,更需要硬件平台来实现具体的功能,它甚至比操作系统本身的开发更需要硬件的背景知识。以往这些类型的软件开发都是由硬件厂商自行包办的,但是面对未来对嵌入式系统的需求,仅仅靠一些硬件厂商旗下的软件工程师,可能无法完成,因此以后提供有关嵌入式系统软件解决方案的厂商将会越来越多。

从Windows CE对嵌入式领域的全面的布局中可以看出端倪,微软对后PC时代的布局手法相当全面,从网站、有线电视、通信到电子商务各个领域,但是目前嵌入式系统的领域还在百家争鸣的时代,后来将会发展成何种格局,没有人能够预测。然而透过Palm OS的发展,Sun的Java,以及各种各样的免费的Embedded OS,以及Linux的发展,可以想象未来将出现非常繁荣的格局。对于一个嵌入式软件工程师来讲,这是非常令人期待的。面对这样的一个局面,不断充实自我,是每一个将要进入嵌入式系统领域的人才必须具备的能力。

3. 从事嵌入式系统的软件开发的工程师必须具备的能力

(1)系统整合能力

由于嵌入式系统属于计算机系统的一个分支,因此基础的计算机结构、组织和操作系统的知识都是必备的基础知识。在软件技术上,应具备的知识主要包括RTOS、数字信号处理和AI,硬件技术应具备的知识主要包括电子电路、集成电路的设计及微机原理等。

由于不同的平台,不同的外围和不同的应用使得嵌入式系统的产品变得相当独特,开发者不能够为了一个特殊的应用产品从头开始摸索。这时系统的整体整合与规划就变得很重要,如何选择合适的平台以配合产品的最终应用,如何有效率地开发产品,缩短研发的制造过程,都是非常重要的事情。

(2)程序语言能力

无论系统规划得多好,如果没有好的软件工程师去实现,整体还是归于一种空想的东西。在嵌入式系统中,所需要的人才相当广泛,不过就狭义的观点看来,系统需要的程序设计人员必须掌握以下几种语言:汇编语言、C/C++、Java(如果有必要)。

除了专业的系统整合与规划及专业的背景知识外,在嵌入式系统中最需要的软件工程人员有3种:应用程序的编写人员、驱动程序的编写人员、系统移植整合编写人员。在整个研发过程中,还需要了解硬件的工程师。虽然对于嵌入式系统的软件设计大多数是和软件相关的知识,但是对于硬件的特性,程序设计人员仍然需要深入地了解。

在嵌入式系统中,程序设计人员必须不断地与外围硬件和系统核心处理器打交道,这是在软件产业中最困难的一个方面。因为大部分的软件设计人员对硬件的了解并不是很深入,有时候需要很长的时间和精力找到解决问题的方法,汇编语言和C语言是嵌入式系统的程序设计人员和硬件沟通所必须掌握的能力。目前可以很方便地存取硬件设备的语言可能只有这两种。事实上很多时候,为了简化程序代码与操作空间,直接使用C语言和汇编语言是最简单快速的方法,就算使用Java,虚拟机也是用C语言来实现的,因此在系统核心与驱动程序的阶段,需要懂得这两种语言并且要求能够操作自如。

在嵌入式系统的开发过程中一般排错的难度要比PC上排错的难度要大很多,这是因为同平台的多样化造成的。因此,要如何让用户在出错的时候快速地抓到故障(Bug)也是嵌入式系统中开发设计所应该考虑的重点。如果开发人员不直接和嵌入式系统的硬件打交道,而是通过它所提供的上层接口API函数来编写应用程序的话,限制就没有那么严重。例如在Windows CE的开发环境中,可以利用现成的SDK for Windows X86 Emulator来做替代目标平台上的测试。

如果采用Jave作为应用程序的开发手段,那么可以适应多种多样的平台,因此越来越多的人采用Java作为开发应用程序的手段。这样可以免除因为硬件处理器和应用程序界面所带来的一些差异。有了虚拟机的帮助,开发人员可以快速地移植其应用程序的代码到不同的平台上。但是Java运行需要C语言写成的虚拟机,这样大大减慢了程序执行的速度和效率。不过随着硬件平台处理器的速度不断提升,Java的这个缺点应该会得到改善。

4. 开发嵌入式系统的软件与开发普通桌面PC软件的差别

(1)是否需要操作系统OS

相对来说,开发人员很容易通过程序去存取8051运算器或外围芯片上的寄存器或内存内容并直接运算,然后将结果写回这些寄存器或内存。这对于在一般的桌上计算机或工作站的程序上来讲是相当不可思议的,因为开发一个桌上计算机的应用程序时,不会直接去取得处理器或外围的寄存器的值来进行运算,而是通过系统手册上的各类函数来间接调用实现目标的,除非是开发驱动程序或者开发底层的操作系统移植的工作。但是这在普通的嵌入式系统开发中,确实是非常常见的事情。如果没有操作系统的概念,则一切动作都由应用程序来自行负责,包括将硬件初始化,让硬件可以直接接收应用程序所想要进行的工作、输入数据的处理、运算动作的进行,将运算结果输出到输出设备上,这可以让设计师了解整个操作系统过程是否有瑕疵。

就算使用了所谓的嵌入式操作系统,许多嵌入式操作系统为了结构的简单都节省了空间,也常常允许应用程序直接使用外围的寄存器来做处理,因此在使用上应该更加注意。

(2)程序编译和程序的执行是在两个目标平台上

一个成为主机(host)端,一个成为目标(target)端,也就是设计人员的目标平台。例如,他们不会直接在嵌入式平台上写程序,就像不会在8051的开发平台上利用板子上面的输入按钮来写代码一样。这是一个非常重要的事实,而且由于这个事实的存在,造成了接下来所有嵌入式系统的开发的特殊现象,包括跨平台编译,跨平台连接,也称为交叉编译和交叉连接,以及下载程序到目的平台执行和远端调试等。

(3)输入/输出的界面不同

这个问题是一个隐藏性的问题,而且将桌面电脑键盘当作输入,显示器当作输出,但是在嵌入式系统中,不一定存在这样的外围设备。例如常见的个人数字处理机PDA,是利用按键当作输入界面的,利用LCD或者RS-232输出当作输出界面。输入/输出界面的不同及限制,也就是开发人员编写程序时所应考虑的问题。

(4)可利用的资源非常有限

这个问题是一个隐藏性的问题,而且是桌上计算机应用程序开发人员常常没有注意到的问题。在桌上计算机上开发程序的时候,常常假设开发人员可以使用的资源无穷无尽,内存和存储空间使用无度,而且没有很正确的释放。这在嵌入式系统中是一种很可怕的行为,因为嵌入式系统中常常采用速度并不是很快的处理器配上一个小小的内存及存储设备,通常没有硬盘可以当作虚拟内存,一旦使用过多的内存,程序可能因为无法申请更多的内存而发生错误。

(5)常常要和硬件打交道

这在嵌入式系统开发中是无法避免的。特别当发生无法理解的错误时,工程师无法确认是程序的问题,还是硬件的问题,因此程序设计工程师除了要理解如何写高级程序与初级程序之外,甚至要了解硬件设计和出错的部分,至少应该拿起万用表或者示波器测量线路。在目前这个讲究合作的团体当中,和硬件人员保持良好的沟通绝对会让程序设计者的工作顺利很多。

5. 使用嵌入式操作系统

由于现在的软件越来越复杂,如果是自行设计一个简单的应用程序,那么以上问题也许可以自己解决,但是当想要设计更加复杂的应用程序时,则需要一个操作系统。特别是牵扯到内存管理、多任务调用、外围资源管理的程序,就非常需要一个操作系统来统一提供这些服务。

一般的操作系统都会提供微内核为主,如果需要GUI系统或者通信协议栈,可能需要另外添加部件。目前大多数的嵌入式操作系统主要提供三大项目的机制。

(1)内存管理

内存管理就是所谓的动态内存管理功能,当程序运行到某一个部分需要使用内存时,开发人员可以利用操作系统中所提供的函数来索取内存和malloc等。当使用完毕之后应释放内存,这样就可以反复使用内存。如果没有这项功能,开发人员就只能在写程序的时候使用静态的内存空间,编译器就会自动空出一部分内存来供应用程序使用,这样将会让内存重复使用度大大降低,同时编译出来的映像(image)也会比较大。

(2)多任务调度

多任务处理主要可以为设计人员提供多个同时存在的执行线程或执行程序,通过操作系统本身的调度机制(schedule),可以简化程序运行。因为要应对各种不同的可能同时存在的情况,这部分功能需要中央处理器的支持。不过现今许多的CPU已经提供多组的寄存器来记录每个执行程序的情况,因此记录每个执行程序的状态已经不再是问题,而操作系统主要提供调度机制来控制这些任务的起始、执行、暂停、结束。通常大多数的嵌入式操作系统都会提供实时调度机制,强调每一个必要的动作都会在一个严格要求的时间内执行完毕。实时多任务的嵌入式操作系统也是每个研究课题常常讨论的问题。除了操作系统本身的结构之外,对于这类操作系统的操作方式也是非常讲究的。

(3)外围资源管理

一般的嵌入式系统中除中央处理器和内存之外,还有很多不同的外围设备,如按键、面板、通信端口或外接的控制器,这些都属于一个系统的资源。既然是资源,就有可能会被很多应用程序使用,不过因为资源有限,操作系统就必须安排这些资源以满足每一个应用程序,因此操作系统要求驱动程序设计师编写一个固定格式的外围资源驱动程序界面,以方便管理资源。对应用程序来讲,则必须向操作系统注册一个索取机制,然后等待操作系统分配资源。

6. 开发平台与目标平台

开发平台(host),也就是PC,通过传输的接口,如串口RS-232、并行接口和以太网口,与目标平台连接。现在大多数可以在Windows 2000或者Linux的平台上进行开发,也有选择高性能工作站进行开发的,但是很罕见。

开发嵌入式系统,不可避免要选择目标平台,前面已经介绍了很多厂商所提供的目标平台,以及当前一些流行的嵌入式微处理器与实验开发板。挑选目标平台必须要审慎,因为牵涉开发环境与技术支持。

当硬件的开发平台选择好以后,接下来的就是最开始的系统开发,也是嵌入式系统开发最困难的一个阶段。如果嵌入式操作系统不是自己开发的,而是购买的整合的操作系统,往往可以得到很多IDE与模拟环境,这样开发者可以加速整个的开发过程。如果是厂商已经移植好的操作系统,并且有充分的文件,则可以自己整合目标平台。

这里所谓的移植好的操作系统,是指可以在所选定的目标平台CPU上正常操作的操作系统。在整个操作系统的底层部分,有一部分程序是和CPU相关的,例如如何使用CPU来进行环境切换(context switch)。这部分的代码程序必须使用相应CPU的汇编语言来编写。保护模式的切换、MMU的实现、中断向量表、堆栈行为配置,都由与CPU相关的汇编指令来做,这部分非常困难,往往需要对CPU的指令系统和操作系统非常熟悉才行。

如果使用的是厂商提供的开发系统或者操作系统,开发者能够得到很多的源代码、开发工具、编译出来的可执行文件或目标码。如何使用这些工具和资源,便是技术文档中所提供的。只有充分掌握技术文档中所提供的内容,建立好相应的开发环境,确认真的符合最初所规定的产品规格,才能开始接下来的开发操作。

选定操作系统后,开发者可以得到各种各样的开发工具,例如编译器、链接器和定址器。因为需要的编译参数可能与每个环境下的编译方法不尽相同,应仔细查阅技术文档。

对于跟踪调试来说,通常开发嵌入式系统就是把厂商提供好的程序配上别人写好的程序,加上自己写好的程序整合在一起。这样,往往错误出现后无法及时得到解决。因为程序运行在目标平台上,设置断点、单步执行等的执行方式往往难度较大,需要通过一些其他的手段,比如调试器、仿真器等。需要目标(target)端能够成功地与开发平台(host)端联系在一起。

1.2.4 嵌入式系统软件开发的一般过程

嵌入式系统的开发工具的架构包括:人机界面、系统程序、模拟程序、模块集(包括CPU模块、I/O模块、功能模块)。整个系统的输出为软件目标文件与硬件的版图文件。用户依照硬件的版图文件制作硬件,然后将软件的目标文件烧写在ROM上,即可以得到所需的嵌入式系统。图1.5所示为系统分析与构建示意图。

建构系统的首要任务就是确定这个系统建构起来到底要做什么?因为用途决定了嵌入式系统整个行为模式和系统架构,例如开发一个ATM提款机系统,开始就必须选择所需要用到的硬件设备和软件开发工具。很明显,需要一个硬件开发的规格,包含电子线路的连接、显示和操作界面的方式。

接下来就是选择适当的开发环境和开发工具,例如编译器、链接器、定址器,或使用硬件厂商提供的IDE,其中还包含了怎么把编译好的程序转换为映像(image),下载到开发者的目标平台上,经由电缆与开发平台连接进行测试。

图1.6所示为多目标的编译与链接流程图。如果都很顺利的话,接下来要进行系统的发布(Release)和测试。嵌入式系统的测试比较复杂,如果没有一个很好的调试环境,那就只能依靠人力和经验来做“碰错误”。这是个非常辛苦的工作,原因在于目标平台往往没有很好的显示或者输出能力,而且开发者不是直接在目标平台上写程序,因此从软件层面来说无法像在桌上计算机上一样直接进行调试。为了缩短调试的时间,一般需要一些现成的辅助工具来进行这样的动作,只不过这些额外的硬件是非常昂贵的,如ICE仿真器。

到此,大家可以基本了解嵌入式系统中软件的开发过程。它是有着特殊目的和应用的,因此有着特殊的开发方式,与桌上计算机的软件的开发方式不一样,但是实际上一旦过了这个门槛,以后的事情都比较容易了。

除了说明上述的嵌入式软件的开发过程,在如图1.5所示中,三个步骤是由上至下表示的,在圆角矩形框中说明了执行该步骤所用到的工具。每一个开发工具都以一个或者多个文件作为输入并且产生一个输出文件。

图1.5 系统分析与构建

1.2.5 嵌入式应用程序的开发

现在嵌入式系统的软件开发一般都会按照以下几个层次来开发。第一个层次是用户界面函数,因为每一台嵌入式系统的硬件所提供的用户界面不一样,虽然现在大都采用LCD屏幕及触控面板的方式,还是有屏幕尺寸不同的问题,所以有关用户界面的设计,必须根据每一个平台的不同而有所调整。第二个层次是环境界面函数,因为每个操作系统所提供的系统调用不一样,甚至因为硬件及编译器不同,会造成数值的长度不一样,例如int这个变量声明,可能在16位CPU上是2B,在32位CPU上会是4B,所以直接用int做运算的话,会因为不同的平台出现不一样的结果,甚至发生死机的情况。另外,开发的程序需要大量CPU运算,例如MP3播放程序,除了要定义基本的CPU运算能力(超过20 MIPS),有些地方还必须使用汇编语言及最优化的方式来处理。这类程序的移植工作一般很困难,甚至还必须和操作系统共用核心模式。

1. 开发工具及软件

(1)编译器

编译器主要是把可读语言所编写的程序,翻译为特定处理器上的等效的一系列操作码。一个汇编器也可以成为编译器,因为可以把汇编器称为汇编编译器,但是它只执行简单地把可读助记符逐行翻译成为对应的操作码的过程,如图1.6所示。

图1.6 多目标的编译与链接

一个运行在计算机平台上,但是产生的是另外一个计算机平台上的代码的编译器,称为交叉编译器。这是嵌入式系统软件开发的固定特征。如图1.7所示为交叉编译建立的示意图。

图1.7 交叉编译的建立

无论输入文件是C,C++,或者是汇编等,交叉编译器的输出肯定总是一个目标文件。一个二进制的代码,有可能是最终可执行程序的一部分,也有可能就是最终的可执行程序。这样体现了大程序的不完备性质。

一般的编译器编译出的格式有coff和elf的格式。在编译出来的目标文件中,已初始化的全局变量收集在data段中,未初始化的全局变量在bss段中,代码段在text段中。通常在目标文件中还有一个符号表,记录了源文件所引用的所有变量和函数的名字和位置。这个表的部分内容可能不完整,因为不是所有的变量和函数都总在同一个文件里定义。这些符号是对别的源文件定义的变量和函数的引用,要一直到链接器的时候才会解决这些不完整的引用。

Makefile就是可以将一个项目中的源代码、目标码及库文件经由其中的指示,包括编译参数、文件路径、输出路径,全部编译,并且链接出所需的结果。它实际上指定了编译器的路径及文件名、编译参数、链接器路径及文件名、链接参数、include文件的路径、函数库的路径及文件名,编译出来的输出路径及方式,当然还有开发人员写好的程序的链接方式。

(2)链接器

写好程序以后,经过编译就会产生很多目标代码,但是要变成一个可执行的文件,还要链接这个程序。一般的桌上计算机的程序开发,都是以链接动作收尾的,这样可以产生可执行的程序;但是在嵌入式系统中,链接动作不是最终的动作,还需要一个重定位器,然后经过重定位,产生映像文件,下载到ROM中,才可以执行。

链接器的作用就是将所有的目的代码文件.o或者.obj,以及Lib中的数据区段,包括text、dat、bss区段的数据进行合并,而且会将所有尚未决定的函数和变量调用对应起来。在嵌入式系统中,要链接出一个可以放到ROM中执行的程序,开发人员还必须启动代码进行链接,并且指定链接关系地址,如此就可以产生重新定位的程序。

(3)定址器

一个可以执行的映像文件,重新定位的动作是不可避免的,定位器本身不知道硬件的情况,所以如果要让定位器能够知道硬件和映像文件之间的关系,就必须制作一个信息存放的地址对应关系表格。

这里所有地址的相对参考地址是0x10C00000,依据data、code、const排列方式放置。这部分的数据必须依据每个硬件的不同来做不同的修改,一般进行编译器整合开发的厂商都会附上一个范例当作参考,开发人员可以通过这些参考来做相应的修改。定位器通常还会产生一个定位信息的表格,告诉开发人员每个函数的地址,有时候需要这个表格的资料作为参考和调试用。通过一些工具可以查看这些表格信息,比如sym。

(4)集成的开发环境IDE

现在已经开始有一些视窗形式的开发环境可以帮助开发者,而不用写Makefile。什么是IDE?最好的例子就是Visual C++了,这是被许多杂志推崇为最好的整合开发环境的一个软件,许多整合开发环境都以Visual C++作为模板。

有了集成开发环境,就减少了许多项目设置和管理的麻烦,特别是当整个系统越来越大的时候,有了集成开发环境的辅助,可以省去很多管理上的麻烦。ARM公司推出的SDT与ARM ADS是非常优秀的两套开发环境。

集成开发环境的好处之一就是可以很方便地管理各种工程项目和函数库等。实际上软件开发环境的发展方向,一般认为应该是向着IDE的方向发展的。

(5)模拟软件

如果开发人员打算编写一些比较高级的应用程序,比如说一个“计算机”,其实就是程序员本身的程序设计功力的问题,拿到哪一个平台或计算机上去执行都一样,因此系统的提供厂商一般都会提供一个模拟器环境,让开发者可以直接在桌上计算机中验证自己程序设计上的逻辑与操作。当确认无误后,才通过环境转换和设置,将写好的程序移植到目标平台上去执行,以减少远端调试所需要的时间和精力,毕竟在一个平台上去找另外一个平台上所发生的问题是比较困难的。

实际上很多厂商为了能够在多种CPU上执行,大都采用类似Windows CE的方式来模拟。这两种模拟的方式都很不错,如果整合开发环境做得好的话,一般的设计人员根本就感受不到其中差异。模拟器的优点就是通过模拟器的帮助,虽然没有实际的硬件,但设计人员还是可以开发出许多应用程序,这对系统开发初期来讲相当有帮助;但缺点是没有硬件,无法完全确切地模拟出来。

(6)调试工具

在实际的目标平台上调试,工程实际上很浩大,特别是如果硬件产品还在开发初期,可能造成的问题不只是软件,也可能是硬件的问题,这时候最困难的问题就是理清思路所在。

开发人员在一般的桌上计算机上开发程序的时候,有许多很不错的开发环境可以使用,例如Visual C++中的debug之类的,可以设置断点、跟踪变量等。

这种调试的原理,就是为CPU设置一个非常特别的中断,让程序运行到开发人员设置断点的地方后,就一直在这个ISR中执行,这样才有办法一步一步寻找问题的所在。同样的道理,开发人员在嵌入式系统的目标平台上执行的时候,也可以在操作系统中设置一个非常特殊的中断服务,类似软件留的后门,通常称之为trap,中文的意思就是陷阱。当执行的程序到了这个trap之后,就无法接着运行,程序就会按照开发人员的意思一步一步运行。开发人员在目标平台和开发主机两端各自安装一个小程序,分别是remote debugger和debug monitor,当设置好断点以后,所有目标平台的信息就会通过链接线传递回来。

ARM SDT中集成了Angle调试程序,Angle需要移植到目标平台上。由于这部分功能是和具体的硬件环境相关的,所以需要根据现有的系统来进行修改。

实际上类似于Angle这样的调试程序在Windows中可以利用embedded Visual C++和ActiveSync来实现在线调试,在Linux下可以配置GDB Server和GDB或者GVD之类的调试工具来实现。

(7)硬件调试器

如果使用硬件工具来调试的话,其功能比软件要强大得多,特别是在系统开发的初期、系统整合、驱动程序编写的时候,利用硬件来帮助是不可避免的。这些硬件大概可以分为ICE和ICD两种类型。

ICE全称是In-Circuit Emulator(电路仿真器),全套设备非常昂贵,它是一种模拟CPU的设备,真正地将CPU动作全部执行,也就是说ICE就是一个CPU。ICE一般具有硬件中断点,模拟内存采用的是DualPort Memory,能在执行时看到数据的变化,同时附有一个功能强大的分析器,可以分析状态、效率和时序等。开发人员不但可以一个指令一个指令地向前执行,还可以倒退执行,可以记录下所有信息,包括定时器的状态、任务切换状态、内存状态、寄存器状态、变量内容等。更重要的是所有这些信息都是实时的,而不是利用软件的方式看似停止的情况。这是利用软件中断所无法实现的,老式的ICE有串行口或者并行口,新式的都是USB或者Ethernet口,速度很快,并且不同来回插拔的ICE。缺点:很昂贵。

ICD的全称为In-Circuit Debugger(电路调试器)。现在很多CPU都具备调试功能,将调试的功能接口拉出来,让外部的硬件能够接到这些引脚上去监控整个CPU的动作。例如JTAG接口,通过这些接口,开发人员就可以很容易利用比较便宜的调试工具和CPU的调试模块相互沟通,一般这些CPU上的调试模块都会提供读出/写入内存,读出/写入CPU寄存器,单步执行和实时执行,硬件中断点及触发的功能设置。

ICD的调试价格要比ICE便宜很多,现在越来越多的开发者开始使用ICD作为调试工具。在硬件的早期设计的时候可以将这些引脚拉出来,等到整个系统都已经设计发展成熟了之后可以把这些引脚去掉不接。

图1.8所示是开发环境的图例。图1.9所示是调试过程的结构图,操作系统内核比较难以调试,因为操作系统内核中不方便增加一个调试器程序。如果需要调试器程序的话,也只能使用远程调试的方法,通过串口和操作系统中内置的“调试桩”通信,完成调试的工作。“调试桩”就是图1.9所示的调试服务器,它通过操作系统获得一些必要的调试信息,或者发送从主机传来的调试命令。比如,Linux操作系统内核的调试就可以这样完成:首先在Linux内核中设置一个“调试桩”,用作调试过程中和主机之间的通信服务器。然后在主机中使用调试器的串口和“调试桩”进行通信。当通信建立完成之后,便可以由调试器控制被调试主机操作系统内核的运行。

图1.8 开发环境的图例

图1.9 调试结构图

软件调试结构如图1.10所示。在该图中涉及交叉编译开发环境中用到的网络协议包括:简单文件传输协议(TFFP)、网络文件系统(NFS)和动态主机配置协议(DHCP),当然还有RS-232协议等。

图1.10 软件调试结构图

(8)应用程序的调试

应用程序的调试可以使用本地调试器和远程调试器两种方法,相对操作系统内核的调试而言,这两种方法都比较简单。我们可以将需要的调试器移植到该系统中,直接在目标板上运行调试器,调试应用程序。也可以使用远程调试,只需要移植一个调试服务器到目标系统中就可以了。由于很多嵌入式系统受资源的限制,而调试器一般需要占用太多的资源,所以使用远程调试的选择占了大多数情况。

使用远程调试器调试应用程序可以采用多种通信方式。一般情况下会使用串口,有时候还会使用以太网。如果使用网络的话,一般将gdbserver移植到嵌入式操作系统中去并在目标上运行,然后在主机端运行gdb,就能开始调试过程。