在bootloader的深入学习中,深感调试的重要性。毛德操书中曾提过,在软件开发的“生命周期”中,程序调试(debug)以及调试手段
的重要性是“怎么强调也不为过”的。诚然,尤其是在bootloader这样的底层软件开发中,调试能起到的作用就更为突出了。下面进行具体分析。
一、最简单的调试方法---状态灯
一般地,在ARM开发板上总会留几个Led灯,在开发bootloader或者学习不带OS的程序编写时,就要充分利用这几个Led灯了。也就是说,把
Led灯作为状态灯。如果有n个可以自由支配的Led灯,那么可以表达2^n个状态。比如,现在的EDUKIT-III实验箱上,有四个led,有
GPF[7:4]来控制,低电平点亮,高电平熄灭,电路就不必画了,最简单的部分。在写led灯的驱动时,不妨采用查表法,把led的状态做成一个表,这
样在表达的时候要方便的多。这部分实验在前面已经做过,ARM汇编和C版本都有,不再详述。下面主要谈几点方法:
·把流程按照功能分为几块,在每块的结束放一个状态灯。这样通过状态灯可以显示到了第几个功能块,一般要逐块测试,放状态灯后,加个死循环。
·利用条件判断语句来进行逻辑测试。比如有一个寄存器状态,假设为register1,正常应该是value1,那么就可以如下判断:
if (register1 == value1) {
led_state1; /* 设置灯状态1 */
} else {
led_state2; /* 设置灯状态2 */
}
while (1) {
;
}
|
还有很多其他方法,不过核心还是靠自己的逻辑分析。
二、打印信息,辅助调试
这种方法在PC的程序调试中也比较常用。比如熟悉的printf,在Linux内核的printk,不过这些都是打印到终端上。在嵌入式系统中则一般是没
有终端的,特别是在bootloader阶段,就是“裸机”啊。不过可以借助于这种思想,利用串口来实现打印信息(当然,得有串口支持了。现在ARM一般
都会有UART外设,而且很常用的手段就是把一个rs232串口作为debug口)。在bootloader阶段,可以利用状态灯法先简单的实现一个串口
驱动程序,可以采用简单的实现方式,只实现查询发送就可以了。这样,就可以需要了解的信息,通过debug口来输出到相应的串口调试助手上。根据这些信
息,来确定程序问题所在,从而尽快排除故障。
这种方式就是串口作为底层来实现的printf,在ARM中应用比较广泛。在C语言中,可以通过一个调试开关开控制发行版/调试版,如下所示:
#define DEBUG
//#undef DEBUG
#ifdef DEBUG
uart_printf("debug info\n");
#endif
|
三 现代调试技术
调试的基础是检测,而检测有“无损(non-intrusive)”和“无损(intrusive)”之分。理想的检测手段应该是不使被测对象的状态(包
括时序)因此而造成任何改变,否则测到的数据就不准确了。可是,严格意义上的无损检测不可能实现,因此实际上是“测不准”的。人们所能做到的只是尽量减少
对被测对象的状态改变,将误差减少到可以接受的程度。这还是比较容易理解的。不过原来看ARM资料的时候,调试手段一大堆,但是都没有深刻的印象,以致于
以前还一直在怀疑,为什么下载的jflash-s3c2410,不能通过仿真器下载呢?现在想想,其实对于什么是调试器,什么是仿真器,调试的模型是什么
都是非常模糊的,所以下决心首先从概念上理解调试模型。之后随着技术深入,可以逐步深入理解调试技术。
现代调试技术大致可以归为指令级仿真调试和硬件仿真调试两种。
1 指令级仿真调试
我想这个比较容易理解。指令集仿真调试属于纯软件仿真,比如ARM公司的ARMulator。因为有的时候嵌入式软件的开发需要在目标系统(硬件)并不存
在的条件下进行,所以需要这种通过软件来模拟目标系统的CPU。现在有个开源项目skyeye,也是这样一个指令级仿真调试工具。这一系列的软件以数据结
构来模拟目标机CPU中各个寄存器和其他资源,以及目标系统的有关资源(比如内存等),并且通过软件模拟,即逐条指令地解释执行目标机可执行映象中的程
序。例如,“mov r1,
#0”,就代表往寄存器r1的数据结构中写0,如此。模拟执行的速度当然慢一些,但是可以验证逻辑,在某些条件下是一种重要的手段。
2 硬件仿真调试
这才是探讨的重头戏。在这里,就要从历史的角度去看一下了。
从前的时候,元器件的面积和pcb的面积普遍较大,pcb板的层数较少,并且元器件只是安装于pcb的一面,也就是单面板。这样调试并不困难,加工工艺不
复杂。pcb的加工制造者可以专门为具体的pcb配套制造用于测试的模板。模板上有许多触针,将pcb板和配套的测试模板叠在一起,模板上的触针正好与
pcb“焊接面”许多监测点接触,从而可以监测pcb板上各点的逻辑电平及其变化。另一方面,pcb板的“元件面”也可以方便的将示波器或逻辑分析仪的探
针连接到元器件的引线上。还有,当时的许多芯片,像cpu一类的大规模芯片,都不是直接焊接在pcb上,而是采用芯片插座。这样就可以作出特殊插头,冒充
芯片插到其插座,而用外部的仪器来“仿真”这种芯片(通常是cpu)在整个目标系统中的作用和运行,就是“在线仿真ICE(In-Circuit
Emulation)”。无论对于软件还是硬件,ICE在当时都是一种非常有效的开发/调试手段。我在本科时用的51单片机,但是就是采用ICE,记得有
个仿真头,插到插座上。ICE还有一种方式,就是使用真实的cpu芯片,可以用特制的插头骑到芯片背上,以监测芯片的各条引线上的逻辑电平。
可以随着pcb板和元器件的面积越来越小,pcb板层数也越来越多,特别是“表面贴焊”式芯片的出现打破了元件面和焊接面的划分,并且直接将芯片的引线焊接(而不是穿透),在pcb上,以前的那些手段都用不上了。
在到后来,“集成电路级”向“集成系统级”发展,现在的芯片大多是SoC,原有的测试手段就更用不上了。这些芯片设计者都是重点考虑的问题。专业上成为
DFT(design-for-test,可测试性设计),也就是在设计一款SoC,必须要考虑它的可测试性能。另外,为什么要在对资源要求比较苛刻的情
况下,把调试电路集成到SoC中,看似矛盾,实际上,SoC集成调试电路已经成了普遍的共识:只有这样,才能减少调试成本,方便开发者进行调试,从而吸引
开发者选择使用SoC。如果没有良好的调试手段,我想很少有开发者会选择使用的。好了,现在介绍调试检测技术。现在比较成熟的技术有如下四种:
·Ad-hoc test
·Scan-based test
·Build-in-self test
·Boundary-scan test
因为不是专业研究这个方向,对这四个测试技术的区别也没大有必要深究。现在重点来看第四种技术,Bounary-scan
test,即边界扫描技术。ARM普遍采用了边界扫描技术,协议标准为IEEE
1149.1,简介原来的文章也谈到过了,就不必多说了。关于JTAG,它的作用主要有如下两个方面:
·映象的下载。嵌入式系统软件的开发,在Host上完成目标映象之后,要在“裸机”上实现第一个程序的“自举”下载固化到flash中,就需要通过
JTAG接口下载。当然,完成bootloader之后,也可以采用JTAG接口下载,另外也可以采用USB或者TFTP下载。后续的下载方式就多了。但
是现在看来,“自举”下载方式还是比较单一的,ARM一般还得依赖于JTAG接口下载bootloader。
·软件(以及硬件)的调试。更重要的是,目标系统的调试也要在宿主机的控制/辅助下进行,而JTAG接口为宿主机与目标系统之间的通信、控制提供了重要的
手段。【但是需要注意的是,这并不是绝对的。通信手段倒是还可以借助于网络实现,调试也可以通过gdb远程串口调试。这里的意思是,它是当前采用的调试的
一种重要的手段。】
现在可以分析基于JTAG技术的ARM Debug Architecture。下面给出一个结构图:
·Debug Host
· Protocol converter
· Debug Target
也就是ARM Debug Architecture分为三层,这也可以认为是一个完整的调试系统。当然这是理论上的划分,在实际实现中也可以在形式上有所变通。下面先解释一下这三层结构。
·Debug Host
Host computer running ARM or third party toolkit.
这是调试系统的Host前端。也就是我们常见到的IDE的debugger,比如ARM提供的ADW(ARM Debugger
for Windows,相应的unix版本为ADU,即ARM Debugger for
Unix)。它所提供的功能是提供一个良好的图形交互界面,使用户可以方便的进行调试操作,也可以完成下载等。另外,它实际上完成调试命令的驱动部分。
ARM制定了一个协议,就是ADP(Angel Debug
Protocol),这是一个比较复杂的协议,分为三层,主要就是保证可靠稳定的与目标机进行通信。
· Protocol converter
for example, multi-ICE。
这部分完成host端协议到jtag协议的转换。转换过程是双向的。这个在这里也不太容易说,可以通过后面仿真器来进行分析。
·debug target
development system containing an ARM7TDMI processor
也就是你的目标SoC。
有了这个模型作为基础,就可以对现有的调试工具进行一个大体的分析了。在这里,仿真器又可以称为调试代理,它充当了protocol converter的角色。
·第一层次 简易JTAG小板
市场上有不少提供简易JTAG小板,大多只是具备下载功能。其中,debug
host可以是jflash-s3c2410这样的工具,它实际上就是用软件实现JTAG时序,完成“串并”和“并串”转换。即把目标JTAG向量中的每
个字节都逐位地转换成JTAG接口各条引线上的串行的波形,或者反过来。而jtag小板,只是完成一个基本的电平转换电路,最差的只有几个电路,普通的就
是一个74HC244做驱动。简单的说,就是:host(jflash-s3c2410等)--> 并口 --> jtag小板
--> SoC
JTAG接口。这样完成了下载功能。如果在host端软件增强,可以完成一些调试功能。相对而言,这样技术是用纯软件去实现JTAG时序,效率比较低。市
场上的wiggle、H-jtag、2410jtag等都是如此,只是在对应的引脚设置和host软件上有所不同。
·第二层次 cpld/fpga实现jtag状态机
用软件模拟jtag时序效率低,那么可以通过cpld等可编程器件实现jtag时序,那么host的软件只要简单的写字节就可以了。这样,效率就高的多了。
·第三层次 mcu+cpld/fpga实现基于以太网/USB的仿真器
其中,mcu用来实现tcp/ip协议,完成host调试协议的解析,cpld/fpga实现jtag状态机。这种组合方式的效率又远远高于第二层次。现在的realview就是采用这种方案,原有的ads系列则不提供更新了。
我把使用的powerICE拆开看了一下,发现里面核心芯片就是cpld。由此可见,这个简化版的仿真器也只是第二层次的产品,硬件实现jtag时
序,host IDE进行调试协议处理。原来的jflash-s3c2410不能够使用就很明显了,不同层次,也不会兼容。
如果要深入研究,可以参考:
·IEEE 1149.1协议
·《嵌入式系统--采用公开源代码和StrongARM/XScale处理器》
·OpenJTAG论坛
·ARM7TDMI Datasheet
---------------------------------------
几个名词:
spp: standard parallel protocol 标准并口协议
epp: enhanced parallel protocol 增强并口协议
dft: design for test 可测试性设计
tap: test access port 测试访问口
tdi: test data input 测试数据输入
tdo: test data output 测试数据输出
tms: test mode select 测试模式选择
tck: test clock 测试时钟
trst: test reset 测试复位
wdm: win32 driver model win32驱动模型
isp: in system programming 在系统编程
isc: in system config 在系统配置
adp: angel debug protocol 调试代理协议
rsp: remote serial protocol gdb的远程串行协议
rdi: remote debug interface ARM的远程调试接口