Keil教程(4)
<p><B>Keil </B>的辅助工具和部份高级技巧</p><p>在前面的几讲中我们介绍了工程的建立方法,常用的调试方法,除此之外,Keil 还提供 了一些辅助工具如外围接口、性能分析、变量来源分析、代码作用分析等,帮助我们了解程 的性能、查找程序中的隐藏错误,快速查看程序变量名信息等,这一讲中将对这些功工具作 一介绍,另外还将介绍 Keil 的部份高级调试技巧。</p><p>一、 辅助工具</p><p>这部份功能并不是直接用来进行程序调试的,但可以帮助我们进行程序的调试、程序性 能的分析,同样是一些很有用的工具。</p><p><B>1</B>、外围接口</p></div><br / style="PAGE-BREAK-BEFORE: auto" clear=all><div class=Section2><p>为了能够比较直观地了解单片机中定时器、中断、</p><p>并行端口、串行端口等常用外设的使用情况,Keil 提 供了一些外围接口对话框,通过 Peripherals 菜单选择, 该菜单的下拉菜单内容与你建立项目时所选的 CPU 有关,如果是选择的 89C51 这一类“标准”的 51 机, 那么将会有 Interrupt(中断)、I/O Ports(并行 I/O 口)、 Serial(串行口)、Timer(定时/计数器)这四个外围设</p><p><img src="http://www.51hei.com/UpFiles/Pic/2007-09/20079120280013046.gif" width=197 style="cursor: pointer" alt="点此在新窗口浏览图片" onclick="javascript:window.open(this.src);" onmousewheel="return bbimg(this)" onload="javascript:resizepic(this)" border="0"/></p><p>图 1 外围设备之并行端口</p></div><br / style="PAGE-BREAK-BEFORE: auto" clear=all><div class=Section3><p>备菜单。打开这些对话框,列出了外围设备的当前使用情况,各标志位的情况等,可以在这 些对话框中直观地观察和更改各外围设备的运行情况。</p><p>下面我们通过一个简单例子看一看并行端口的外围设备对话框的使用。例 4:</p> <table cellSpacing=0 cellPadding=0 border=0> <tr> <td vAlign=top width=48> </td> <td vAlign=top width=54> <p>MOV</p></td> <td vAlign=top width=154> <p>A,#0FEH</p></td> </tr> <tr> <td vAlign=top width=48> <p>LOOP:</p></td> <td vAlign=top width=54> <p>MOV</p></td> <td vAlign=top width=154> <p>P1,A</p></td> </tr> <tr> <td vAlign=top width=48> </td> <td vAlign=top width=54> <p>RL</p></td> <td vAlign=top width=154> <p>A</p></td> </tr> <tr> <td vAlign=top width=48> </td> <td vAlign=top width=54> <p>CALL</p></td> <td vAlign=top width=154> <p>DELAY ;延时 100 毫秒</p></td> </tr> <tr> <td vAlign=top width=48> </td> <td vAlign=top width=54> <p>JMP</p></td> <td vAlign=top width=154> <p>LOOP</p></td> </tr> </table></div><div class=Section4><p>其中延时 100 毫秒的子程序请自行编写。</p><p>编 译 、 连 接 进 入 调 试 后 , 点 击 Peripherals->I/O-Ports->Port 1 打开,如图 1 所示,全速运 行,可以看到代表各位的勾在不断变化(如果看不到变化, 请点击 View->Periodic Window Updata),这样可以形象地 看出程序执行的结果。</p><p>注:如果你看到的变化极快,甚至看不太清楚,那么 说明你的计算机性能好,模拟执行的速度快,你可以试着 将加长延时程序的时间以放慢速度。模拟运行速度与实际 运行的速度无法相同是软件模拟的一个固有弱点。</p><p>点击 Peripherals->I/O-Ports->Timer0 即出现图 2 所示 定时/计数器 0 的外围接口界面,可以直接选择 Mode 组中 的下拉列表以确定定时/计数工作方式,0-3 四种工作方式,<br / clear=all></p><p><img src="http://www.51hei.com/UpFiles/Pic/2007-09/20079127127623767.jpg" width=183 style="cursor: pointer" alt="点此在新窗口浏览图片" onclick="javascript:window.open(this.src);" onmousewheel="return bbimg(this)" onload="javascript:resizepic(this)" border="0"/></p><p>图 2 外围设备之定时器</p></div><br / style="PAGE-BREAK-BEFORE: always" clear=all><div class=Section5><p>设定定时初值等,点击选中 TR0,status 后的 stop 就变成了 run,如果全速运行程序,此时</p><p>th0,tl0 后的值也快速地开始变化(同样要求 Periodic Window Updata 处于选中状态),直观地 演示了定时/计数器的工作情况(当然,由于你的程序未对此写任何代码,所以程序不会对 此定时/计数器的工作进行处理)。</p><p><B>2</B>、性能分析</p><p>Keil 提供了一个性能分析工具,利用该工具,我们可以了解程序中哪些部份的执行时间 最长,调用次数最多,从而了解影响整个程序中执行速度的瓶颈。下面通过一个实例来看一 看这个工具如何使用,例 5:<br / style="PAGE-BREAK-BEFORE: auto" clear=all></p></div><div class=Section6><p>#include "reg51.h"</p><p>sbit P1_0=P1^0; //定义 P1.0</p><p>void mDelay(unsigned char DelayTime)</p><p>{ unsigned int j=0;</p><p>for(;DelayTime>0;DelayTime--)</p><p>{ for(j=0;j<125;j ) {;} }</p><p>}</p><p>void mDelay1(unsigned char DelayTime)</p><p>{ unsigned int j=0;</p><p>for(;DelayTime>0;DelayTime--)</p><p>{ for(j=0;j<125;j ) {;} }</p><br / clear=all><p>}</p><p>void main()</p><p>{ unsigned int i;</p><p>for(;;){ mDelay(10); // 延时 10</p><p>毫秒</p><p>i ;</p><p>if(i==10)</p><p>{ P1_0=!P1_0; i=0; mDelay1(10);}</p><p>} }<br / style="PAGE-BREAK-BEFORE: auto" clear=all></p></div><div class=Section7><p>编译连接。进入调试状态后使用菜单 View->Performance Analyzer Window,打开性能分 析对话框,进入该对话框后,只有一项 unspecified,点鼠标右键,在快捷菜单中选择 Setup PA 即打开性能分析设置对话框,对于 C 语言程序,该对话框右侧的“Function Symbol”下的 列表框给出函数符号,双击某一符号,该符号即出现在 Define Performance Analyzer 下的编 缉框中,每输入一个符号名字,点击 Define 按钮,即将该函数加入其上的分析列表框。对 于汇编语言源程序,Function Symbol 下的列表框中不会出现子程序名,可以直接在编缉框 中输入子程序名,点击 Close 关闭窗口,回到性能分析窗口,此时窗口共有 4 个选项。全速 执行程序,可以看到 mDelay 和 mDelay1 后出现一个蓝色指示条,配合上面的标尺可以直观 地看出每个函数占整个执行时间的比例,点击相应的函数名,可以在该窗口的状态栏看到更 详细的数据,其中各项的含义如下:</p><p>Min:该段程序执行所需的最短时间;Max:该段程序执行所需的最长时间;Avg:该 段程序执行所花平均时间;Total:该段程序到目前为目总共执行的时间;%:占整个执行时 间的百分比;count:被调用的次数。</p><p>本程序中,函数 mDelay 和 mDelay1 每次被调用都花费同样的时间,看不出 Min、Max、</p><p>和 Avg 的意义,实际上,由于条件的变化,某些函数执行的时间不一定是一个固定的值, 借助于这些信息,可以对程序有更详细的了解。下面将 mDelay1 函数略作修改作一演示。</p><p>void mDelay1(unsigned char DelayTime)</p><p>{ static unsigned char k;</p><p>unsigned int j=0;</p><p>for(;DelayTime>0;DelayTime--)</p><p>{ for(;j<k;j )</p><p>{;}</p><p>} k ; }</p><p>程序中定义了一个静态变量 K,每次调用该变量加 1,而 j 的循环条件与 k 的大小有关,</p></div><br / style="PAGE-BREAK-BEFORE: always" clear=all><div class=Section8><p><img src="http://www.51hei.com/UpFiles/Pic/2007-09/20079128696575342.gif" width=560 style="cursor: pointer" alt="点此在新窗口浏览图片" onclick="javascript:window.open(this.src);" onmousewheel="return bbimg(this)" onload="javascript:resizepic(this)" border="0"/></p><p>这使每次执行该程序所花的时间不一样。编译、执行该程序,再次观察性能分析窗口,可以</p><p>看出 Min、Max、Avg 的意义。</p><p><B>3</B>、变量来源浏览</p><p>该窗口用于观察程序中变量名的有关信息,如该变量名在那一个函数中被定义、在哪里 被调用,共出现多少次等。在 Source Browse 窗口中提供了完善的管理方法,如过滤器可以 分门别类地列出各种类别的变量名,可以对这些变量按 Class(组)、Type(类型)、Space</p><p>(所在空间)、Use(调用次数)排序,点击变量名,可以在窗口的右侧看到该变量名的更 详细的信息。</p><p><B>4</B>、代码作用范围分析</p><p>在你写的程序中,有些代码可能永远不会被执行到(这是无效的代码),也有一些代码 必须在满足一定条件后才能被执行到,借助于代码范围分析工具,可以快速地了解代码的执 行情况。</p><p>进入调试后,全速运行,然后按停止按钮,停下来后,可以看到在源程序的左列有三种 颜色,灰、淡灰和绿,其中淡灰所指的行并不是可执行代码,如变量或函数定义、注释行等 等,而灰色行是可执行但从未执行过的代码,而绿色则是已执行过的程序行。使用调试工具 条上的 Code Coverage Window 可打开代码作用范围分析的对话框,里面有各个模块代码执 行情况的更详细的分析。如果你发现全速运行后有一些未被执行到的代码,那么就要仔细分 析,这些代码究竟是无效的代码还是因为条件没有满足而没有被执行到。</p><p>二、部份高级调试技巧</p><p>Keil 内置了一套调试语言,很多高级调试技巧与此有关,但是全面学习这套语言并不现 实,这不是这么几期连载可以胜任的,这里仅介绍部份较为实用的功能,如要获得更详细的 信息,请参考 Keil 自带的帮助文件 GS51.PDF。</p><p><B>1</B>、串行窗口与实际硬件相连</p><p>Keil 的串行窗口除可以模拟串行口的输入和输出功能外还可以与 PC 机上实际的串口相 连,接受串口输入的内容,并将输出送到串口。这需要在 Keil 中进行设置。方法是首先在 输出窗口的 Command 页用 MODE 命令设置串口的工作方式,然后用 ASSIGN 命令将串行 窗口与实际的串口相关联,下面我们通过一个实例来说明如何操作。例 6:</p></div><br / style="PAGE-BREAK-BEFORE: auto" clear=all><div class=Section9><p>ORG 0000H JMP START</p><p>ORG 3 4*8 ;串行中断入口</p><p>JMP SER_INT START:</p><p>MOV SP,#5FH ;堆栈初始化</p><p>CALL SER_INIT ;串行口初始化 A SETB EA ;</p><br / clear=all><p>SETB ES ;</p><p>JMP $ ;主程序到此结束</p><p>SER_INT:</p><p>JBC RI,NEXT ; 如果串口接收到字 符,转</p><p>JMP SEND ;否则转发送处理</p><p>NEXT:</p><p>MOV A,SBUF ;从 SBUF 中取字符</p></div><br / style="PAGE-BREAK-BEFORE: always" clear=all><div class=Section10><p><img src="http://www.51hei.com/UpFiles/Pic/2007-09/20079128696575342.gif" width=560 style="cursor: pointer" alt="点此在新窗口浏览图片" onclick="javascript:window.open(this.src);" onmousewheel="return bbimg(this)" onload="javascript:resizepic(this)" border="0"/></p></div><br / style="PAGE-BREAK-BEFORE: auto" clear=all><div class=Section11><p>MOV SBUF,A ;回送到发送 SBUF 中</p><p>JMP OVER SEND:</p><p>clr ti</p><p>OVER:</p><p>reti</p><p>SER_INIT: ;中断初始化</p><p>MOV SCON,#50H</p><br / clear=all><p>ORL TMOD,#20H</p><p>ORL PCON,#80H</p><p>MOV TH1,#0FDH ;设定波特率 SETB TR1 ;定时器 1 开始运行 SETB REN ;允许接收</p><p>SETB SM2</p><p>RET END</p></div><br / style="PAGE-BREAK-BEFORE: auto" clear=all><div class=Section12><p>这个程序使用了中断方式编写串行口输入/输出程序,它的功能是将接串行口收到的字 符回送,即再通过串行口发送出去。</p><p>正确输入源文件、建立工程、编译连接没有错后,可进行调试,使用 Keil 自带的串行 窗口测试功能是否正确,如果正确,可以进行下一步的连机试验。</p><p>为简单实用,我们不借助于其它的硬件,而是让 PC 机上的两个串口互换数据,即 COM1 发送 COM2 接收,而 COM2 发送则由 COM1 接收,为此,需要做一根连接线将这两个串口 连起来,做法很简单,找两个可以插入 PC 机串口的 DIN9 插座(母),然后用一根 3 芯线将 它们连起来,连线的方法是:</p><p>2——3</p><p>3——2</p><p>5——5</p><p>接好线把两个插头分别插入 PC 机上的串口 1 与串口 2。找一个 PC 机上的串口终端调 试软件,如串口精灵之类,运行该软件,设置好串口参数,其中串口选择 2,串口参数设置 为:</p><p>19200,n,8,1 其含义是波特率为 19200,无奇偶校验,8 位数据,1 位停止位。 在 Keil 调试窗口的 command 页中输入:</p><p>>mode com1 19200,0,8,1</p><p>>assign com1 <sin>sout</p><p>注意两行最前面的“>”是提示符,不要输入,第二行中的“<”和“>”即“小于”和 “大于”符号,中间的是字母“s”和“input”的前两个字母,最后是字母“s”和“output” 的前三个字母。</p><p>第一行命令定义串口 1 的波特率为 19200,无奇偶校验,8 位数据,1 位停止位。第二 行是将串口 1(com1)分配给串行窗口。</p><p>全速运行程序,然后切换串口精灵,开始发送,会看到发送后的数据会立即回显到窗口 中,说明已接收到了发送过来的数据。切换到 uVison,查看串行窗口 1,会看到这里的确接 收到了串口精灵送来的内容。</p><p><B>2</B>、从端口送入信号</p><p>程序调试中如果需要有信号输入,比如数据采集类程序,需要从外界获得数据,由于 Keil 的调试完全是一个软件调试工具,没有硬件与之相连,所以不可能直接获得数据,为此 必须采用一些替代的方法,例如,某电路用 P1 口作为数据采集口,那么可以使用的一种方 法是利用外围接口,打开 PORT 1,用鼠标在点击相应端口位,使其变为高电平或低电平, 就能输入数据。显然,这种方法对于要输获得数据而不是作位处理来说太麻烦了,另一种方 法是直接在 command 页输入 port1=数值,以下是一个小小的验证程序。例 7:</p><p>LOOP: MOV A,P1</p></div><br / style="PAGE-BREAK-BEFORE: always" clear=all><div class=Section13><p> </p><p>JZ NEXT</p><p>MOV R0,#55H JMP LOOP</p><p>NEXT: MOV R0,#0AAH JMP LOOP</p><p>END</p><p>该程序从 P1 口获得数据,如果 P1 口的值是 0,那么就让 R0 的值为 0AAH,否则让 R0 的值为 55H。输入源程序并建立工程,进入调试后,在观察窗口加入 R0,然后全速运行程 序,注意确保 View->Periodic Window Updata 处于选中状态,然后在 Command 后输入 PORT1=0 回车后可以发现观察窗口中的 R0 的值变成了 0AAH,然后再输入 PORT1=1 或其 它非零值,则 R0 的值会变为 55H。</p><p>同样的道理,可以用 port0、port2、port3 分别向端口 0、2、3 输入信号。</p><p><B>3</B>、直接更改内存值</p><p>在程序运行中,另一种输入数据的方法是直接更改相应的内存单元的值,例如,某数据 采集程序,使用 30H 和 31H 作为存储单元,采入的数据由这两个单元保存,那么我们更改</p><p>了 30H 和 31H 单元的值就相当于这个数据采集程序采集到了数据,这可以在内存窗口中直 接修改(参考上一讲),也可以通过命令进行修改,命令的形式是: _WBYTE (地址,数据),</p><p>其中地 址是 指待写 入内 存单元 的地 址,而 数据 则是待 写入 该地址 的数 据。例 如</p><p>_WBYTE(0x30,11)会将值 11 写入内存地址十六进制 30H 单元中。页:
[1]