|
嵌入式软件的开发可以分为几个阶段:源代码程序的编写;将源程序交叉编译成各个目标模块;将所有目标模块及相关的库文件链接成目标程序;代码调试等。在不同的阶段需要用到不同的软件开发工具,如编辑器、编译器、调试工具、软件工程工具等。
|
|
|
|
从理论上来说,任何一个文本编辑器都可以用来编写源代码。但是为了提高编程的效率,一个好的编辑器应该具备如下一些特点:
|
|
|
(1)支持C语言、汇编语言等程序设计语言的语法高亮显示;
|
|
|
(2)支持文件管理操作(如打开文件、保存文件、关闭文件等)、文件编辑操作、文件打印、文本查找等功能;
|
|
|
(3)编辑窗口可以同时作为调试时源代码执行的跟踪窗口;
|
|
|
(4)通过“编译结果输出窗口”可以直接定位到相应的源代码编辑窗口;
|
|
|
|
(6)编辑器可以同时打开多个窗口进行编辑,可编辑的文件大小理论上无限制;
|
|
|
(7)编辑器的编辑命令和编辑操作最好与标准的Windows编辑器功能一致,以便熟悉Windows的用户使用。
|
|
|
在各种集成开发环境中,一般都会提供一个功能强大的编辑器。UltraEdit和Source Insight是两个常用的独立编辑器。
|
|
|
UltraEdit是一个功能强大的文本编辑器。它可以取代记事本,用来编辑文本文字,也可以用来编写各种语言的源代码。它内建英文单词检查、C++及Visual Basic语法加亮显示,可同时编辑多个文件。即使打开一个很大的文件,速度也不会慢。UltraEdit附有HTML Tag颜色显示、搜寻替换以及无限制的还原功能。它支持二进制和十六进制编辑,可以用来直接修改EXE或DLL文件。
|
|
|
Source Insight是一款面向工程项目的源码编辑和查看软件,其用户界面友好,变量和函数名都以特定的颜色表示出来,非常直观。对于各种语言的源文件,如C/C++、C#和Java,它能自动解析程序的语法结构,动态地保持符号信息数据库,并主动显示有用的上下文信息。Source Insight不仅是一个功能强大的程序编辑器,它还能显示参考树、类继承图和调用树等信息。它具有快速源代码导航功能,用户可以使用各种搜索命令,在各个源文件的不同函数和变量定义之间来回跳转,非常方便,因此它很适合于编辑大型软件。
|
|
|
|
编译阶段要做的工作是用交叉编译或汇编工具处理源代码,产生目标文件。在嵌入式系统中,宿主机和目标机所采用的处理器芯片通常是不一样的。例如,目标机采用的CPU是DragonBall M68x系列或ARM系列,而宿主机采用的是x86系列。因此,为了把宿主机上编写的高级语言程序编译成可以在目标机上运行的二进制代码,就需要用到交叉编译器。
|
|
|
与普通PC中的C语言编译器不同,嵌入式系统中的C语言编译器要进行专门的优化,以提高编译效率。一般来说,优秀的嵌入式C编译器所生成的代码,其长度和执行时间仅比用汇编语言编写的代码长5%~20%。编译质量的不同,是区别嵌入式C编译器工具的重要指标。因此,硬件厂商往往会针对自己开发的处理器的特性来定制编译器,既提供对高级语言的支持,又能很好地对目标代码进行优化。
|
|
|
GNU C/C++(gcc)是目前比较常用的一种交叉编译器,它支持非常多的宿主机/目标机组合。宿主机可以是Unix、AIX、Solaris、Windows、Linux等操作系统,目标机可以是x86、Power PC、MIPS、SPARC、Motorola 68K等各种类型的处理器。
|
|
|
gcc是一个功能强大的工具集合,包含了预处理器、编译器、汇编器、连接器等组件。它在需要时会去调用这些组件来完成编译任务,而输入文件的类型和传递给gcc的参数决定了它将调用哪些组件。对于一般或初级的开发者,它可以提供简单的使用方式,即只给它提供C源码文件,它将完成预处理、编译、汇编、连接等所有工作,最后生成一个可执行文件。而对于中高级开发者,它提供了足够多的参数,可以让开发者全面控制代码的生成,这对于嵌入式系统软件开发来说是非常重要的。
|
|
|
gcc识别的文件类型主要包括:C语言文件、C++语言文件、预处理后的C文件、预处理后的C++文件、汇编语言文件、目标文件、静态链接库、动态链接库等。以C程序为例,gcc的编译过程主要分为4个阶段:
|
|
|
(1)预处理阶段,即完成宏定义和include文件展开等工作;
|
|
|
(2)根据编译参数进行不同程度的优化,编译成汇编代码;
|
|
|
(3)用汇编器把上一阶段生成的汇编码进一步生成目标代码;
|
|
|
(4)用连接器把上一阶段生成的目标代码、其他一些相关的系统目标代码以及系统的库函数连接起来,生成最终的可执行代码。
|
|
|
用户可以通过设定不同的编译参数,让gcc在编译的不同阶段停止下来,这样可以检查编译器在不同阶段的输出结果。
|
|
|
在gcc的高级用法上,一般希望通过使用编译器达到两个目的:检查出源程序的错误;生成速度快、代码量小的执行程序。这可以通过设置不同的参数来实现,例如,“-Wall”参数可以发现源程序中隐藏的错误;“-O2”参数可以优化程序的执行速度和代码大小;“-g”参数可以对执行程序进行调试。
|
|
|
|
在开发嵌入式软件时,交叉调试是必不可少的一步。嵌入式软件的特点决定了其调试具有如下特点:
|
|
|
(1)对于通用的计算机,调试器与被调试程序一般位于同一台计算机上,操作系统也相同,调试器进程通过操作系统提供的调用接口来控制被调试的进程。而在嵌入式系统中,由于目标机的资源有限,调试器和被调试程序运行在不同的机器上。调试器主要运行在宿主机上,而被调试程序则运行在目标机上。
|
|
|
(2)调试器通过某种通信方式与目标机建立联系。通信方式可以是串口、并口、网络、JTAG或专用的通信方式。
|
|
|
(3)在目标机上一般有调试器的某种代理,这种代理能配合调试器一起完成对目标机上所运行程序的调试。这种代理可以是某种软件,也可以是支持调试的某种硬件。
|
|
|
总之,在交叉调试方式下,调试器和被调试程序运行在不同的机器上。调试器通过某种方式能控制目标机上被调试程序的运行方式,并能查看和修改目标机上的内存、寄存器以及被调试程序中的变量。在嵌入式软件的开发实践中,经常采用的调试方法有直接测试法、调试监控器法、ROM仿真器法、在线仿真器法、片上调试法及模拟器法。
|
|
|
|
直接测试法是嵌入式系统发展早期经常采用的一种调试方法。这种方法需要的调试工具非常简单,比较适合当时的实际情况。采用这种方式进行软件开发的基本步骤是:
|
|
|
|
(2)在宿主机上反复地检查源代码,直到编译通过,生成可执行程序。
|
|
|
(3)将可执行程序固化到目标机上的非易失性存储器(如EPROM、Flash等)中。
|
|
|
(4)在目标机上启动程序运行,并观察程序的运行结果。
|
|
|
(5)如果程序不能正常工作,则在宿主机上反复检查代码,查找问题的根源,然后修改代码,纠正错误,并重新编译。
|
|
|
(6)重复执行(3)~(5),直到程序能正常工作。
|
|
|
从这些开发步骤可以看出,这种调试方法基本上无法监测程序的运行。虽然也有人提出了一些调试的小窍门,例如,从目标机打印一些有用的提示信息(通过监视器、LCD或串口等输出信息),或者利用目标机上的LED指示灯来判断程序的运行状态。但这些窍门的作用有限,如果一个程序在运行时没有产生预想的效果,那么开发者只能通过检查源程序来发现问题。显然,这种调试方法的效率很低,难度很大,开发人员也很辛苦。但由于开发条件特别是开发工具的限制,在嵌入式系统的早期阶段,程序的开发只能采用这种方法。甚至目前在开发一些新的嵌入式产品时,也往往要采用这种方法。
|
|
|
|
调试监控器法的工作原理如下图所示。在这种调试方式下,调试环境由三部分构成,即宿主机端的调试器、目标机端的监控器(监控程序)以及两者之间的连接(包括物理连接和逻辑连接)。
|
|
|
|
|
监控器是运行在目标机上的一段程序,它负责监视和控制目标机上被调试程序的运行,并与宿主机端的调试器一起,完成对应用程序的调试。监控器预先被固化到目标机的ROM空间中,在目标机复位后将被首先执行。它对目标机进行一些必要的初始化,然后初始化自己的程序空间,最后就等待宿主机端的命令。监控器能配合调试器完成被调程序的下载、目标机内存和寄存器的读/写、设置断点以及单步执行被调试程序等功能。一些高级的监控器能配合完成代码分析、系统分析、ROM空间的写操作等功能。
|
|
|
利用监控器方式作为调试手段时,开发应用程序的步骤如下:
|
|
|
(1)启动目标机,监控器掌握对目标机的控制,等待与调试器建立连接。
|
|
|
|
(3)调试器将应用程序下载到目标机上的RAM空间中。
|
|
|
(4)开发人员使用调试器进行调试,发出各种调试命令。监控器解释并执行这些命令,并通过目标机上的各种异常来获得对目标机的控制,将命令执行结果回传给调试器。
|
|
|
(5)如果程序有问题,则开发人员在调试器的帮助下定位错误。修改之后再重新编译链接并下载程序,开始新的调试。如此反复直到程序能正确运行为止。
|
|
|
监控器方式明显地提高了程序调试的效率,降低了调试的难度,缩短了产品的开发周期,有效地降低了开发成本。而且这种方法的成本也比较低廉,基本上不需要专门的调试硬件支持。因此它是目前使用最为广泛的嵌入式软件调试方式之一,几乎所有的交叉调试器都支持这种方式。
|
|
|
|
ROM仿真器可看作是一种用于替代目标机上ROM芯片的硬件设备。它一边和宿主机相连,一边通过ROM芯片的插座和目标机相连。对于嵌入式处理器,它就像一个只读存储芯片;而对于宿主机上的调试器,它又像一个调试监控器。由于仿真器上的地址可以实时地映射到目标机的ROM地址空间中,所以在目标机上可以没有ROM芯片,而是用仿真器提供的ROM空间来代替。
|
|
|
实际上ROM仿真器是一种不完全的调试方式,它只是为目标机提供ROM芯片,并在目标机和宿主机之间建立了一条高速的通信通道。因此它经常和调试监控器法相结合,形成一种功能更强的调试方法。
|
|
|
|
.在目标机上可以没有ROM芯片,因此也就不需要用其他的工具来向ROM中写入数据和程序。
|
|
|
|
.由于是通过ROM仿真器上的串行接口、并行接口或网络接口与宿主机相连,所以不必占用目标机上通常很有限的资源。
|
|
|
|
在线仿真器(In Circuit Emulator,ICE)是一种用于替代目标机CPU的设备。对目标机来说,在线仿真器就相当于它的CPU。事实上,ICE本身就是一个嵌入式系统,有自己的CPU、RAM、ROM和软件。它的CPU比较特殊,可以执行目标机CPU的所有指令,但有更多的引出线,能将内部信号输出到被控制的目标机上。在线仿真器的存储器也可以被映射到用户的程序空间。因此,即使没有目标机,仅用ICE也可以进行程序的调试。
|
|
|
ICE和宿主机一般通过串口、并口或网络相连。在连接ICE和目标机时,需要先将目标机的CPU取出,然后将ICE的CPU引出线接到目标机的CPU插槽上。在使用ICE来调试程序时,在宿主机上也有一个调试器用户界面。在调试过程中,这个调试器将通过ICE来控制目标机上的程序。
|
|
|
|
.同时支持软件断点和硬件断点的设置。软件断点只能到指令级别,也就是说,只能指定程序在读取某一指令前停止运行。而在硬件断点方式下,多种事件的发生都可使程序在一个硬件断点上停止运行。这些事件不仅包括取指令,还包括内存读/写、I/O读/写以及中断等。
|
|
|
.能够设置各种复杂的断点和触发器。例如,可以让程序在“当变量m等于100,同时AX寄存器等于0”时停止运行。
|
|
|
.能实时跟踪目标程序的运行,并可实现选择性的跟踪。在ICE上有大块RAM,专门用来存储执行过的每个指令周期的信息,使用户可以得知各个事件发生的精确次序。
|
|
|
.能在不中断被调试程序运行的情况下查看内存和变量,即非干扰的调试查询。
|
|
|
在线仿真器特别适用于调试实时应用系统、设备驱动程序以及对硬件进行功能测试。它的主要缺点就是价格昂贵,一般都在几千美元,有的甚至要几万美元。这显然阻碍了团队的整体开发,因为不可能给每位开发人员都配备一套在线仿真器。所以,现在ICE一般都用于普通调试工具解决不了的问题,或者用它来做严格的实时性能分析。
|
|
|
|
片上调试(On Chip Debugging,OCD)是CPU芯片提供的一种调试功能,可以把它看成是一种廉价的ICE功能。OCD的价格只有ICE的20%,但却提供了80%的ICE功能。
|
|
|
最初的OCD是一种仿调试监控器方式,即将监控器的功能以微码的形式来体现,如Motorola的CPU 32系列处理器。后来的OCD摒弃了这种结构,采用了两级模式的思路,即将CPU的工作模式分为正常模式和调试模式。
|
|
|
当满足了特定的触发条件时,CPU就可进入调试模式。在调试模式下,CPU不再从内存读取指令,而是从调试端口读取指令,通过调试端口可以控制CPU进入和退出调试模式。这样在宿主机端的调试器就可以直接向目标机发送要执行的指令,通过这种形式调试器可以读/写目标机的内存和各种寄存器,控制目标程序的运行以及完成各种复杂的调试功能。
|
|
|
OCD方式的主要优点是:不占用目标机上的通信端口等资源;调试环境和最终的程序运行环境基本一致;支持软硬件断点;提供跟踪功能,可以精确计量程序的执行时间;支持时序分析等功能。
|
|
|
OCD方式的主要缺点是:调试的实时性不如ICE强;不支持非干扰的调试查询;使用范围受限,目标机上的CPU必须具有OCD功能。
|
|
|
目前比较常用的OCD的实现有:后台调试模式(Background Debugging Mode,BDM)、连接测试存取组(Joint Test Access Group,JTAG)和片上仿真器(On Chip Emulation,OnCE)等,其中JTAG是主流的OCD方式,OnCE是BDM和JTAG的一种融合方式。
|
|
|
|
模拟器是一个运行在宿主机上的纯软件工具。它通过模拟目标机的指令系统或目标机操作系统的系统调用来达到在宿主机上运行和调试嵌入式程序的目的。
|
|
|
模拟器主要有两种类型:一类是在宿主机上模拟目标机的指令系统,称为指令级的模拟器;另一类是在宿主机上模拟目标机操作系统的系统调用,称为系统调用级的模拟器。指令级模拟器相当于在宿主机上建立了一台虚拟的目标机,该目标机的CPU种类与宿主机不同。例如,宿主机的CPU是Intel Pentium,而虚拟机是ARM、Power PC或MIPS等。比较高级的指令级模拟器还可以模拟目标机的外部设备,如键盘、串口、网口和LCD等。系统调用级的模拟器相当于在宿主机上安装了目标机的操作系统,使得基于目标机操作系统的应用程序可以在宿主机上运行。两种类型的模拟器相比,指令级模拟器所提供的运行环境与实际的目标机更接近;而系统调用级的模拟器本身比较容易开发,也容易移植。
|
|
|
使用模拟器的最大好处是:可以在实际的目标机环境并不存在的条件下开发其应用程序,并且在调试时可以利用宿主机的资源来提供更详细的错误诊断信息。但模拟器也有许多不足之处,包括:
|
|
|
.模拟环境与实际的运行环境差别较大,无法保证在模拟条件下调试通过的程序就一定能在真实环境下顺利运行。
|
|
|
.不能模拟所有的设备。嵌入式系统中经常包含许多外围设备,但除了一些比较常见的设备之外,多数设备是不能模拟的。
|
|
|
.实时性差。在使用模拟器调试程序时,被调试程序的执行时间和在真实环境中的运行时间差别较大。
|
|
|
尽管模拟器有许多不足,但是在项目开发的早期阶段,尤其是在还没有任何硬件可供使用时,模拟器还是非常有用的。对那些实时性不强,没有特殊外设,只需验证其逻辑的程序,用模拟器基本可以完成所有的调试工作。而且在使用模拟器调试程序时,不需要额外的硬件来协助,因此降低了开发成本。
|
|
|
|
软件工程工具是指在分布式开发环境或大型嵌入式软件项目中使用的各种管理软件,如CVS、GNU make等。
|
|
|
|
CVS(Concurrent Version System)是一个版本控制软件,用来记录源码文件和其他相关文件的修改历史。对于一个文件的各个版本,CVS只存储版本之间的区别,而不是把每个版本都完整地保存下来。当一个文件的内容发生变化时,CVS会在一个日志中记录每一次修改的作者、修改的时间以及修改的原因。CVS能够有效地管理软件的发行版本,以及多位程序员同时参与的分布式开发环境。它把一个软件项目组织成一个层次化的目录结构,里面包含了与项目有关的所有文件,如源文件、文档文件等。这些目录和文件合并起来,就构成了该软件项目的一个发行版本。
|
|
|
|
GNU make是一种代码维护工具,在大中型软件开发项目中,它将根据程序各个模块的更新情况,自动地维护和生成目标代码。make的主要任务是读入一个文本文件(默认的文件名是makefile或Makefile),并根据这个文件所定义的规则和步骤,完成整个软件项目的维护和代码生成等工作。在这个文本文件中,定义了一些依赖关系(即哪些文件的最新版本是依赖于哪些其他的文件)和需要用什么命令来产生文件的最新版本或管理各种文件。有了这些信息,make会检查文件的修改或生成时间戳,如果目标文件的时间戳比它的某个依赖文件要旧,那么make就会执行makefile文件中描述的相应命令,来更新目标文件。make工具的特点如下:
|
|
|
.适合于文件较多的大中型软件项目的编译、连接、清除中间文件等管理工作;
|
|
|
.只更新那些需要更新的文件,而不重新处理那些并不过时的文件;
|
|
|
.提供和识别多种默认规则,方便对大型软件项目的管理;
|
|
|
|
|