上一课:
【小黑嵌入式系统第四课】嵌入式系统硬件平台(二)——I/O设备、通信设备(UART&USB&蓝牙)、其他(电源&时钟&复位&中断)
下一课:
【小黑嵌入式系统第六课】嵌入式系统软件设计基础——C语言简述、程序涉及规范、多任务程序设计、状态机建模(FSM)、模块化设计、事件触发、时间触发
文章目录
一 嵌入式软件开发工具的分类
二 嵌入式软件的交叉开发环境
通用计算机的软件开发一般以本地(Native)方式(开发软件与生成的目标代码运行于同一款处理器上)进行,而嵌入式软件开发一般不支持本地环境开发,通常采用交叉开发的方式。
交叉开发环境是用于嵌入式软件开发的所有工具软件的集合,一般包括:
- 文本编辑器
- 交叉编译器(/交叉链接器)
- 交叉调试器
- 仿真器、下载器等配套软硬件工具
宿主机、目标机
交叉开发环境由宿主机和目标机组成,宿主机与目标机之间在物理连接的基础上建立起逻辑连接(通信协议)。
开发软件建立在宿主机(Host)上,宿主机又称开发机、上位机,一般是一台通用计算机,如PC。
对应的嵌入式系统称为目标机(Target)。目标机可以是各式各样的嵌入式设备,例如手机、掌上电脑等;或者是厂商提供的一套专用于开发的评估板(evaluation board),又称开发板;甚至是基于软件的模拟器。
开发时使用宿主机上的交叉编译、汇编和链接工具形成可在目标机上执行的二进制代码,然后把可执行代码下载到目标机上运行。此外嵌入式系统的调试也通常采用交叉的方式, 在调试过程中,目标机需接收和执行宿主机发出的各种命令如设置断点、开始/停止运行、读/写存储器等,将结果返回给宿主机,配合宿主机各方面的工作。
宿主机与目标机的区别
-
体系结构的不同。宿主机和目标机通常是异构的。宿主机一般采用x86体系结构,但是目标机的体系结构则可能非x86的,如是ARM、MIPS、PowerPC等各式各样。
-
对于某些系统这一点不一定成立。如Atom系统,宿主机和目标机都是x86系统,当然执行的指令集可能不一样,如宿主机(如Core2 Duo)可能支持SSE4,而Atom支持到SSE3和SSSE3,因此编译时还是要考虑到目标机型号和指令集的选择。
-
处理能力不同。通常宿主机的处理速度、存储容量等会远远好于目标机。
-
运行的操作系统不同。宿主机一般运行通用操作系统,而目标机通常运行各种嵌入式操作系统。
-
输入输出方式不同。相对宿主机,目标机的输入输出功能可能比较单一。
为什么要交叉编译?
-
目标机上往往无法进行有效率的本地(native)编译
-
目标机硬件可能本身在开发过程中,还不能使用或还不够稳定;
-
目标机系统性能太低,无法实现完整的本地编译工具、环境;
如51单片机系统 -
目标机系统性能不够,导致编译太慢。
◇嵌入式系统的软件编译,与windows开发不一样,不仅要编译应用程序,还要编译相应的依赖库、操作系统内核等。所以一次完整的编译非常费时。
如编译一个Linux内核在奔4级别的PC上都需要十几分钟
◇从硬件角度,决定编译速度的主要因素是CPU速度、内存容量和文件系统I/O速度。
这些在嵌入式系统上往往都要比PC差很多
编程语言
嵌入式系统比较常用编程语言有C/C++、ADA、Python等。
在底层方面不得不用到汇编语言,虽然其使用量不大,如在操作系统移植、处理速度极致改善等方面。复杂的嵌入式系统一般由多种语言混合编写而成。
由于C/C++拥有便于底层编程等诸多优点,目前C/C++为多数嵌入式系统开发的首选语言。但受限于嵌入式系统的硬件性能,嵌入式C++常为标准C++的部分实现版。
三 嵌入式软件实现阶段的开发过程
系统设计完成后,嵌入式软件的开发进入实现阶段,可分为三个大的步骤:生成、调试和固化运行。
- 软件的生成在宿主机上进行,利用各种工具完成程序的编辑、交叉编译和链接工作,生成可供调试或固化的目标代码。
- 调试是通过宿主机上运行的交叉调试软件(配合调试器硬件)完成目标机软件的调试工作。基本调试完成后还需进行必要的测试工作。
- 固化运行是先用一定的工具将代码固化到目标机存储器上,然后启动目标机,在没有任何工具干预的情况下使代码能自动地启动运行。
具体地来说,嵌入式软件开发依次要经历编辑(代码准备)、编译、重定位(定址和打包)、烧写、下载、调试、优化等步骤,许多嵌入式系统还需要进行充分的测试和验证等步骤。
从流程上说,可分为编码阶段、构建阶段、部署阶段、调优和其他阶段。
1. 嵌入式软件生成阶段
三个过程
- 源代码程序的编写
- 编译成各个目标模块
- 链接成可供下载调试或固化的目标程序
编码阶段
编码阶段是软件开发的开始,其任务是编写软件的源代码,使用的工具是各种文本编辑器。
构建(Build)阶段
构建阶段的任务是把源代码转化成在嵌入式处理器上可执行程序的过程。此阶段包括编译、链接、定址和打包。
构建的第一步是编译,即将源代码文件翻译成目标文件。编译工作是由编译器完成的,编译器的任务是将由某种程序设计语言编写的源代码翻译成特定处理器上等效的一系列操作码,这些操作码对应的文件称为目标文件。
构建的第二步,就是将所有目标文件链接成一个目标文件。链接工作由链接器完成,其主要工作是扫描所有输入的目标文件,然后将多个目标文件的段合并,并解决它们之间的依赖关系(这一过程称为符号解析),最终生成一个可执行文件。
在许多开发工具中,将编译器和链接器的过程在使用形式上做在了一起,开发者可以直接将源代码文件转换成可执行文件,这一过程常称为生成(Build)。
可执行文件的可重定位和不可重定位
- 一般来说嵌入式系统的可执行文件有**可重定位(re-locatable)和不可重定位(non-relocatable)**两种版本。
- 可执行文件最后需要操作系统或加载程序将其加载到内存中才能执行。对于不可重定位版本的可执行文件,加载器只能把它加载到固定的地址运行;而对可重定位版本的可执行文件,可以加载到任何地址执行。
- 源代码程序生成可执行文件时既可生成为可重定位版本的,也可生成不可重定位版本的,在编译-链接时必须选择不同参数。
- 对于简单的嵌入式系统软件,一般并不需要重定位功能,链接器直接生成只能在某个固定的内存地址运行的程序即可。
构建的第三步是定址和打包。定址和打包的目标是将可重定位执行文件整合为一个可运行在嵌入式系统中的二进制映像文件。
- 对可重定位执行文件而言,在下载到目标机运行之前需要进行定址工作。定址又称重定位,其任务是解决程序中函数和变量的地址绑定问题。
做法是对于编译器和链接器生成的每一条可能涉及内存地址的指令和数据(例如函数调用的跳转指令),都把指令中的内存地址存储为一个偏移量而不是绝对地址。这样当程序加载器(Boot Loader或者嵌入式操作系统)把程序加载到机器内存时,可以根据实际的加载地址,把代码中的相对偏移量再加上实际加载基地址,就可以得到实际的内存地址。
- 若整个系统的软件只由一个可执行文件构成,只要通过定址,就可以生成能在嵌入式系统中运行的映像了。但是很多时候,构成整个嵌入式软件的是多个可执行文件(如一个车载终端,有一个可执行文件负责从车身传感器获取行车数据,另一个负责把数据显示在屏幕上,还有一个负责播放视音频),这时候需要把多个可执行文件进行打包成一个映像,烧写到嵌入式系统外存中。
一般打包的方式有两种:压缩打包和非压缩打包。
要说明的是,对于应用程序开发,上述第三步定址和打包并不是必需的。某些嵌入式系统拥有功能比较强大、完善的操作系统,可执行文件的定址由目标机操作系统自动完成,用户看不到这一独立的步骤;打包也不是必需的,在不少Linux系统中用户可直接通过网络、串口等下载或复制独立的文件到本地运行,而不要求一定是打包后的完整镜像。
2. 嵌入式软件的调试
嵌入式系统调试的特色
-
嵌入式软件开发用到的调试技术和技巧繁多,有许多方法是在通用计算机系统中难以见到的,而且所用到的许多设备也是嵌入式系统所独有的。
-
与普通桌面应用程序开发相比,嵌入式软件调试的最大问题是缺少监控程序实时运行的手段。
许多嵌入式设备的I/O系统都相对简陋。嵌入式处理器提供的调试功能也不如桌面处理器丰富。因此在程序运行过程中一旦出现程序“跑飞”(PC寄存器指到了不应该指向的位置,或程序的执行超出了开发人员的预期,从而引发程序执行混乱),很难第一时间发现并找出原因。
调试的基本原则
-
调试的一个基本原则是不要做“睁眼瞎”,需要找出一种监控程序运行的手段,让自己知道代码到底是如何运行的。
在某些时候,并不一定非要借助重量级的调试器或仿真器才可以监控程序的运行。通过一两行代码让开发板上的LED灯闪烁,或者初始化一个开发板上的串口,然后在PC端通过终端软件接收开发板上串口发来的信息,也是行之有效的监控措施。
交叉调试器
- 调试程序和被调试程序运行在不同款处理器上
- 通过调试器能控制目标机上被调试程序的运行方式
- 通过调试器能查看和修改目标机上的寄存器、内存、以及被调试程序中的变量等
交叉调试方式
- Crash and Burn
- ROM Monitor - 驻留监控程序
- ROM Emulator - ROM仿真器
- In Circuit Emulator - 在线仿真器
- On Chip Debugging - 片上调试
- Simulator方式(软件模拟器,非交叉)
1、Crash and Burn
最早的嵌入式软件调试方法,只要有个烧录器就能调试,硬件投入少,方式简单粗暴。对于较简单的程序和思路清晰的程序员适用。
2、ROM Monitor
ROM Monitor是被固化且运行在目标机上的一段程序,负责监控目标机上被调试程序的运行,与宿主机端的调试器一起完成对应用程序的调试。
调试器与ROM Monitor之间的通信遵循某种远程调试协议。
在目标机上电或复位后首先执行的就是ROM Monitor,它对目标机进行一些必要的初始化
- 初始化要求的外围设备,如最基本的串口和用于内存刷新的系统计时器芯片;
- 初始化用于下载映像的内存系统;
- 初始化中断控制器和安装中断处理程序。
- 初始化自己的程序空间
- 等待宿主机端的命令
ROM Monitor调试过程
(1)启动目标机,监控器掌握对目标机的控制,等待和调试器建立连接;
(2)启动调试器,并和监控器建立起通信连接;
(3)使用调试器将应用程序下载到目标机上的RAM空间中;
(4)使用调试器进行调试,发出各种调试命令,监控器解释并执行这些命令,将命令执行结果回传给调试器;
(5)如果程序有问题,在调试器帮助下定位错误。修改之后再重新编译链接并下载程序,开始新的调试,如此反复直至程序正确运行为止。
ROM Monitor能配合调试器完成:
- 程序映像下载
- 对寄存器的读写
- 对目标机系统内存的读写
- 设置和清除不同类型的断点
- 单步执行指令
- 复位系统
- …等调试功能
ROM Monitor调试方式的优点:
- 提高调试程序的效率 ,缩短开发周期,降低成本;
- 简单、方便;
- 可扩展性强,可支持许多高级调试功能;
- 成本低廉,不需专门的调试硬件支持。
- 几乎所有的交叉调试器都(能)支持这种方式
缺点:
-
ROM Monitor需要用Crash and Burn方法开发,其开发难度比较大;
-
当ROM Monitor占用CPU时,应用程序不响应外部的中断,因此不便调试有时间特性的程序;
-
某些调试功能依赖于CPU硬件的支持(如硬件断点功能);
-
ROM Monitor要占用目标机一定数量的资源,如CPU、RAM、ROM和通信设备等资源;
-
调试环境不同于实际目标环境。
嵌入式应用的开发经常会遭遇缺少目标机环境、缺乏目标机芯片等资源的问题,而开发过程又不可能停止,因此自然就提出了根据不同的应用需要,利用仿真器件、仿真环境进行开发的方法。
硬件仿真开发
ROM Emulator
- ICE
- OCD
软件仿真开发 - Simulator
Emulator、Simulator中文都叫“仿真器”,但Emulator是有硬件基础的(硬件仿真器),而Simulator是纯软件的(软件模拟器)。
3、ROM Emulator
ROM Emulator是一种用于替代目标机上的ROM芯片的设备,即ROM仿真器。
利用这种设备,目标机可以没有ROM芯片,但目标机的CPU可以读取ROM Emulator设备上ROM芯片的内容:ROM Emulator设备上的ROM芯片的地址可以实时地映射到目标机的ROM地址空间,从而仿真目标机的ROM。
ROM Emulator的调试方式是一种不完全的调试方式:ROM Emulator设备只是为目标机提供ROM芯片和在Target和Host间建立一条高速的通信通道,因此它经常和前面两种调试方式结合起来形成一种完备的调试方式。ROM Emulator的典型应用就是和ROM Monitor的调试方式相结合。
优点:
目标机可以没有ROM芯片、可以使用ROM Emulator提供的ROM空间且不需要用别的工具来写ROM。
缺点:
目标机必须能支持外部ROM存储空间,而且由于其通常要和ROM Monitor配合使用,因此它拥有ROM Monitor的所有缺点。
4、ICE
术语ICE(In-Circuit Emulator、在线/在电路仿真器)的一种含义是指用于替代目标机上CPU的设备。
它比一般的CPU有更多的引出线,能引出一些内部信号。
ICE上的Memory也可以被映射到用户的程序空间,这样即使目标机不存在的情形下也可以进行代码的调试。
连接ICE和目标机时,一般是将目标机的CPU取下,而将ICE的CPU引出线接到目标机的CPU插槽。
用ICE进行调试时,在Host端运行的调试器通过ICE来控制目标机上运行的程序。
ICE功能特点:
- 同时支持软件断点和硬件断点的设置
- 设置各种复杂的断点和触发器
- 实时跟踪Trace目标程序的运行,并可实现选择性的跟踪
- 支持“Time Stamp”
- 允许用户设置“Timer”
- 提供“Shadow RAM”,能在不中断被调试程序的运行下查看内存和变量即非干扰调试查询
ICE适用于:
- 调试实时的应用系统
- 调试设备驱动程序
- 对硬件进行功能和性能的测试
- 实时性能分析
缺点:
- 价格太昂贵,不利于团队开发
- 所仿CPU有限
5、OCD
OCD(On Chip Debugging,片上调试)是CPU芯片提供的一种调试功能(芯片内部集成了用于调试的硬件电路部分),可以认为是一种廉价的ICE功能:OCD的价格只有ICE的20%,但提供了ICE 80%的功能。
OCD调试方法
将CPU的运行模式分为一般模式和调试模式
-
一般模式下,CPU从内存读取指令执行;
-
调试模式下,CPU首先从调试端口读取指令, Host端的调试器可以通过调试端口直接向目标机发送要执行的指令、读写目标机的内存和各种寄存器、控制目标程序的运行以及完成各种复杂的调试功能。
Host端的调试器通过调试端口可以控制CPU进入和退出调试模式;
优点:
- 不占用目标机的资源
- 调试环境和最终的程序运行环境基本一致
- 支持软硬断点、Trace功能
- 精确计量程序的执行时间
- 提供时序分析功能
缺点:
- 调试的实时性不如ICE
- 不支持非干扰调试查询
- 处理器必需具有OCD功能
OCD存在各种实现:
- BDM(Background Debugging Mode)
- OnCE(On Chip Emulation)
- JTAG(Joint Test Access Group)(主流方式)
6、Simulator
交叉开发方式存在如下缺点:
- 硬件支持 必须有目标机或评估板、硬件仿真器
- 易使用性 普通编程人员不熟悉
- 廉价性 成本较高
- 可移植性、可扩展性 不高
- 团队开发 有时较难
一种软件仿真器,在宿主机上创建一个虚拟的目标机环境,再将应用系统下载到这个虚拟目标机上运行/调试。
软件仿真的对象
- 仿真处理器
- 仿真外设
- 仿真环境
软件仿真的级别
- 指令级仿真开发
- API级仿真开发
一款运行在Linux平台下的模拟Atom手机芯片指令集的模拟器QEMU
安卓开发模拟器:
一些常见的Simulator:
Simulator优点:
最大好处就是可以不用真正的硬件目标机,可以在目标机环境并不存在的条件下开发目标机上的应用系统,并且在调试时可以利用Host资源提供更详细的错误诊断信息。
缺点:
- 和实际的运行环境差别很大
- 设备模拟的局限性较大
- 实时特性较差
- 对Host的资源要求较高
适用范围:
对时间特性没有严格要求、没有特殊外设、只需要验证逻辑正确的应用程序。
3. 嵌入式软件的测试
测试工具:能够用来辅助测试的工具,主要用来支持测试人员的工作,本身不能直接用来进行测试。测试工具一般都是通用工具,测试人员应该根据实际情况对它们进行适当的调整。
嵌入式软件测试中经常用到的测试工具有:
- 内存分析工具
- 性能分析工具
- 覆盖分析工具
- 缺陷跟踪工具等
1、内存分析工具
嵌入式系统的内存资源通常是受限的,内存分析工具可以用来处理在进行动态内存分配时产生的缺陷。当动态分配的内存被错误地引用时,产生的错误通常难以再现,出现的失效难以追踪,使用内存分析工具可以很好地检测出这类缺陷。
目前常用的内存分析工具有软件和硬件两种:
基于软件的内存分析工具可能会对代码的执行性能带来很大影响,从而影响系统的实时性;
基于硬件的内存分析工具对系统性能影响小,但价格昂贵,并且只能在特定的环境中使用。
2、性能分析工具
嵌入式系统的性能通常是一个非常关键的因素,开发人员一般需要对系统的某些关键代码进行优化来改进性能。
性能分析工具
- 可以提供有关数据,帮助确定哪些任务消耗了过多的执行时间,从而可以决定如何优化软件,以获得更好的时间性能。
- 引导开发人员发现在系统调用中存在的错误以及程序结构上的缺陷。
3、覆盖分析工具
在进行白盒测试时,可以使用代码覆盖分析工具追踪哪些代码被执行过。
开发人员通过对分析结果进行总结,可以确定哪些代码被执行过,哪些代码被遗漏了。
分析过程一般通过插桩来完成,插桩可以是在测试环境中嵌入硬件,也可以是在可执行代码中加入软件,或者是两者的结合。
目前常用的覆盖分析工具一般都提供有关功能覆盖、分支覆盖、条件覆盖等信息。
4. 嵌入式软件的固化运行(部署阶段)
当调试完成之后,程序代码需要被完全烧入到目标板的非易失性存储器(如UV-EPROM或闪存)中,并且在真实的硬件环境上运行,这个过程叫做固化。将数据写入到这些存储器中需要有一个专门的烧写过程。
调试环境与固化环境的主要区别:
- 代码定位不同
- 初始化部分不同
Boot模块:当应用程序在真实的目标环境下运行时将首先执行该
程序,它至少由系统加电时执行的代码组成。
Boot模块的主要功能:初始化CPU环境,使目标机硬件到已知的状态。
- 初始化芯片的引脚
- 初始化系统外部控制寄存器
- 初始化基本输入输出设备
- 初始化MMU,包括片选控制寄存器等
- 执行初始化数据拷贝
Flash芯片的烧写,类似可编程器件的烧录,可分为
-
离线(Offline)方式
◇ 需要将Flash ROM芯片从目标板上取下
目标板上将Flash ROM做成插座式安装,而非焊死
◇使用专门的编程器和配套软件 -
在线编程(In-system-programming)方式
◇无需将Flash ROM芯片从目标板上取下
一般就是通过JTAG-ICE仿真器
◇方便,廉价,重用了JTAG-ICE的投资
网络式目标代码下载机制
- 常见于基于Linux的嵌入式系统中。
- MeeGo上网本应用软件目标代码下载是借助操作系统所提供的文件复制(上载)功能来完成的,这一过程为部署(deployment)。
- 主流安卓、苹果手机均采用此方式
四 嵌入式软件开发工具的发展趋势
- 向着开放的、集成化的方向发展
- 具有系统设计、可视化建模、仿真和验证功能
- 自动生成代码和文档
- 具有更高的灵活性