片机的程序设计与调试.ppt
1,第4章 单片机的程序设计与调试,【本章内容】本章主要介绍伪指令、源程序汇编以及常用程序的设计方法。【项目驱动的学习要点】应用项目中各段程序功能分析。应用项目中的程序流程图分析。应用项目中各段程序设计方法分析。,2,第4章 单片机的程序设计与调试,4.1 源程序的设计与汇编4.2单片机开发系统与源程序的调试4.3 顺序和分支程序设计4.4 循环和查表程序设计4.5 子程序和中断程序设计练习题,END,3,4.1 源程序的设计与汇编,4.1.0 几个概念4.1.1 伪指令4.1.2 源程序的设计4.1.3 源程序的汇编,4,二进制形式的机器指令称为机器语言,采用机器语言编写的程序能直接被计算机识别和执行。由于机器语言不容易理解和记忆,所以人们通常用符号指令来编写源程序。采用符号指令编写的程序必须通过编译软件(也叫汇编程序)或手工翻译(汇编)成机器指令的形式才能被计算机执行。前者称为机器汇编,后者称为手工汇编。,4.1.0 几个概念,5,用符号指令的计算机语言称汇编语言。符号指令也称为汇编语言指令;采用汇编语言指令编写的程序叫做汇编语言源程序。汇编的反过程称为反汇编。,几个概念,6,图4-1 源程序的汇编与反汇编,源程序的汇编与反汇编,7,4.1.1 伪指令,为了使编译软件能按设计者的要求汇编源程序,编写汇编语言源程序时,还要用到伪指令。伪指令是供汇编程序识别和执行并对汇编过程进行某种控制的指令性语句。它不是真正的单片机指令,无对应的机器码。所以汇编后产生的目标程序中不会出现伪指令。,8,指令格式:ORG 表达式指令功能:用于向汇编程序说明下面紧接的程序段或数据段存放从表达指定的起始地址开始存放。表达式通常为十六位地址或自定义的标号地址。通常每一个汇编语言源程序的开始,都要设置一条ORG伪指令来指定该程序在ROM中存放的起始位置。可以在源程序中使用多条ORG伪指令来规定不同程序段或数据段存放的起始地址,但要求地址值由小到大顺序排列,不允许空间重叠。,1起始汇编伪指令,9,1ORG0000H2LJMPMAIN;转主程序3 ORG 0003H4LJMP BREAK0;转 中断5 ORG 000BH6LJMP CLOCK;转定时器T0中断7ORG 0013H8LJMP BREAKl;转 中断;主程序:9ORG0050H10 MAIN:MOVA,#03H;8155初始化;控制字码表:137ORG1010H138DB0FEH,06H,20H,00H,0FFH,06H,20H,15H,【项目应用】以下是应用项目中使用的ORG伪指令,请分析各条ORG指令的作用。,10,解:,(1)第一条ORG伪指令用于说明整个程序的起始地址为0000H,其后的LJMP MAIN指令的机器码便从ROM的0000H单元开始存放。这是单片机开机或复位时的PC初值,所以单片机在开机或复位时总是从这个地址开始读出程序。(2)第二至第四条ORG指令中的地址分别对应系统规定的3个中断入口地址,用于使其后的中断服务程序能存放在正确的位置(详见第5章)。(3)第五条ORG伪指令用于说明从标号为MAIN的指令往后的程序存放在0050H开始的ROM单元中。于是应用项目中的主程序,便由0000H单元通过无条件长转移指令LJMP MAIN转移到0050H往后的ROM单元,从而防止系统设定的中断入口地址被占用。(4)第六条ORG伪指令用于指定作为控制字码表的一个数据区的起始地址为1010H。从应用项目的反汇编源程序可以看出,ORG伪指令指定的地址没有发生重叠。,11,指令格式:END指令功能:结束汇编。放置于汇编源程序的末尾,当汇编程序遇到END伪指令时,即结束汇编。处于END之后的程序,汇编程序不会进行处理。,2结束汇编伪指令,12,指令格式:符号名 EQU 表达式指令功能:将表达式的值赋给指定的符号名。表达式通常是8位或16位的地址、数据或汇编符,如果是位地址,则必须采用位的物理地址。符号的命名与标号的命名规定相同:可以由18个ASCII字符组成。首字符必须是字母,其余字符可以是字母、数字或其他特定字符,不能使用汇编语言已经定义了的符号,如指令助记符、寄存器符号名称等。“符号名”一旦被赋值,就可以在源程序的任意地方使用,汇编时,汇编程序自动用表达式的值进行代真。使用EQU伪指令有两个优点,一是当源程序中有多处引用“符号名”的地方要修改时,只要修改为“符号名”赋值的指令即可;二是当“符号名”具有见字明义的名称时,可增加程序的可读性。,3通用赋值伪指令,13,符号的命名与标号的命名规定相同:可以由18个ASCII字符组成。第一个字符必须是字母,其余字符可以是字母、数字或其他特定字符,不能使用汇编语言已经定义了的符号,如指令助记符、寄存器符号名称等。“符号名”必须先赋值后使用,一旦“符号名”被赋值,它就可以在源程序的任意地方使用,汇编时,汇编程序自动用表达式的值进行代真。使用EQU伪指令有两个优点,一是当源程序中有多处引用“符号名”的地方要修改时,只要修改为“符号名”赋值的指令即可;二是当“符号名”具有见字明义的名称时,可增加程序的可读性。,说明:,14,ORG 0100H;起始汇编 LEN EQU 10;LEN=“10”UM EQU 20H;SUM=“20H”BLOCK EQU 30H;BLOCK=“30H”START:CLR A;A清0 MOV R2,LEN;计数初值送R2 MOV R0,BLOCK;指针初值送R0LOOP:ADD A,R0;累加 INC R0;指针加1 DJNZ R2,LOOP;未完继续 MOV SUM,A;保存结果 HERE:SJMP$;动态停机 END;结束汇编,【例4-1】如下一段程序的功能是将BLOCK单元开始存放的10个无符号数进行求和,并将结果存入SUM单元中(假设结果小于255)。,15,可见,若要改变BLOCK单元、SUM单元或累加数据个数,则只要改变相应的EQU指令便可。细心的读者可以发现,程序中每条指令都加了注释,而且注释的不是单纯的指令功能,而是指令的程序功能,旨在给读者一个示范,希望读者从本章开始,逐步学习单片机的程序设计并学会注释指令的程序功能。,说明:,16,指令格式:符号名 BIT 位地址指令功能:用于将位地址赋给指定的符号名。其中,位地址可用位名、物理位地址、“字节地址位序号”、“寄存器名位序号”等形式表示(详见位寻址)。例如:MARK BIT F0 其中的位地址是PSW中的用户标志位F0,它也可以用0D5H、0D0H5、PSW5等形式表示。,4位地址赋值伪指令,17,指令格式:标号:DB 单字节数据表指令功能:用于从标号(即汇编程序为该指令分配的首地址)对应的地址单元开始,将“单字节数据表”中的数据按从左到右的顺序依次存入ROM中,一个数据占一个存储单元。单字节数据表可以是一个或多个单字节数据、字符串或表达式(其值为单字节),表项之间用逗号分隔。项数多于一行时,可使用多条DB伪指令。,5单字节定义伪指令,18,(详见指令格式)。十六进制数:加上后缀H表示,以字母AF开头时,在它的前面加一个前导“0”。二进制数:加上后缀B表示。十进制数:加上后缀D表示,也可以省略后缀。ASCII码:用“单引号”括起来表示,如A,ABC,1,123。,数据的表示方法,19,134 DONE2:RET;字形码表:135 SEGTAB:DBFH,06H,5BH,4FH,66H,6DH,7DH136 DB07H,7FH,6FH;控制字码表:137 CODE:ORG1010H138 DB0FEH,06H,20H,00H,0FFH,06H,20H,15H139 DB0EFH,06H,25H,00H,0FFH,06H,40H,00H,【项目应用】以下是应用项目中使用的DB伪指令,20,编号为135的DB伪指令的功能是指示汇编程序从上一条指令(RET)存放完成后的下一个ROM单元开始,依次存入10个字形码。该条伪指令一行写不下,所以分成两条DB伪指令来书写;编号为138的DB伪指令的功能则是从它上一条ORG伪指令指定的1010H单元开始,依次存入数据表中的控制码。表项较多,所以用多条DB伪指令来书写。,说明:,21,指令格式:标号:DW 双字节数据表 指令功能:与DB伪指令类似,只是指令中数据表的表项应为双字节,即16位二进制数。存入ROM中时,高8位存放在低地址单元中,低8位存放在高地址单元中,对于不足16位的表项数据,存放时在前面补0。例如:ORG0759HDATA:DW3295H,2800H,0BDH,汇编后,在ROM中的数据为:(0759H)=32H,(075AH)=95H,(075BH)=28H,(075CH)=00H,(075DH)=00H,(075EH)=BDH。,6双字节定义伪指令,22,指令格式:标号:DS 表达式 指令功能:用于从标号对应的地址单元开始,在程序存储器中预留出一段存储单元作为备用空间,预留单元数量由表达式确定。例如:ORG0800H SPARE:DS20H 汇编后,从ROM中地址为0800H的单元开始,预留32个空的存储单元作为备用单元。,7空间定义伪指令,23,4.1.2 源程序的设计,1源程序设计的步骤2源程序设计的基本要求3源程序设计注意事项,24,1源程序设计的步骤,(1)明确设计任务(2)选择适当的算法(3)确定系统规划(4)确定程序结构(5)绘制程序流程图(6)编写源程序(7)汇编和调试,25,(1)明确设计任务,在开始源程序设计前,首先要对所要设计的单片机应用系统进行深入地分析,明确应用系统所要实现的功能和技术要求,综合考虑应用系统的可行性、先进性、可靠性、可维护性、成本及经济效益等。选择合适的器件,设计出合理的硬件电路。因为单片机的程序设计与硬件电路的结构是紧密联系在一起的,面板的开关、按键、显示、输入/输出引脚的定义等也与程序设计密切相关,所以要事先作好准备。,26,算法是解决具体问题的方法。明确设计任务后,设计者应把应用系统所要实现的功能和技术要求利用严密的数学方法或数学模型来描述,从而把一个实际问题转化成由计算机进行处理的问题。同一个问题的算法可能有多种,实现方案也可能不尽相同,所以应对各种算法进行分析、比较,合理优化,从中找出一种切合实际的最佳算法。,(2)选择适当的算法,27,系统规划包括合理地选择和分配内存工作区及有关端口地址,规划好寄存器和存储器的使用;确定数据和工作单元,分配存储单元;对数据暂存区、堆栈区、显示缓冲区、标志单元等作好统一安排;根据程序区、数据区、字形表等预计所占字节的多少,对程序存储器ROM做出空间规划,合理分配并确定每个区域的首地址,以及系统主程序、子程序的入口地址,中断服务程序的存放地址等。,(3)确定系统规划,28,程序结构的设计是把算法转化为程序的准备阶段。如果算法比较简单,这一步可以省略,直接按算法编写程序。如果算法比较复杂,则需要进行程序结构的设计。程序的基本结构通常有顺序结构、分支结构、循环结构、子程序及中断服务程序等5类。程序结构的设计一般通过绘制程序流程图来实现。,(4)确定程序结构,29,(5)绘制程序流程图,程序流程图是用规定的图形符号配以文字说明来表示算法或处理问题的步骤,它具有直观、易懂的特点,是程序结构设计的有力工具。,30,常用的程序流程图符号如图4-2所示。,图4-2 常用的程序流程图符号,程序流程图符号,31,端点框:表示程序的起止。在端点框中可输入开始、结束、程序名、起始地址等相应的文字。处理框:表示一种处理功能或者过程,处理框中的文字简要说明一段程序的功能或处理过程。判断框:表示一个判定点,从该点产生分支,在判断框内应注明测试条件,而测试结果则在各分支流程线上注明。连接框:表示流程中止而并非流程结束,通常用来连接同一页的流程,以避免流程线的交叉,也可以用来连接不同页上的流程,以避免流程线的跨页中断。绘图时,表示连在一起的连接框内的标识符要相同 流程线:表示程序执行的流向。,程序流程图符号说明:,32,源程序的编写是根据绘制好的程序流程图,运用单片机指令集中的合法指令和伪指令编写出能实现流程图规定功能的程序。所编写的源程序要力求简单明了、层次清晰、可靠、高效。,(6)编写源程序,33,汇编是将源程序翻译成能被单片机识别和执行的目的程序。调试则是检验所编写的程序是否正确,能否正常运行。如果这两步不能通过,则需要重复以上步骤,对程序进行反复修改,直到获得正确的结果为止。,(7)汇编和调试,34,(1)正确性:要求严格按指令格式书写指令,不能臆造非法指令。(2)可靠性:由于单片机主要用于控制,所以程序的可靠性很重要。(3)可读性:为了增加程序的可读性,可在程序段前或每条指令后添加注释。(4)有效性:要求程序占用存储空间尽量小,执行时间尽可能短。,2源程序设计的基本要求,35,(1)采用模块化的程序设计方法(2)尽量采用循环结构和子程序(3)合理分配内存单元(4)正确使用转移指令,3源程序设计注意事项,36,模块化的程序设计方法具有如下优点:单个模块的程序功能单一,结构层次一目了然,易于编写、调试和修改。便于分工,从而可使多个程序员同时进行程序的编写和调试工作,加快软件研制进度。程序可读性好,便于程序的优化、扩充和版本升级。对程序可进行局部修改,其他部分可以保持不变。对于使用频繁的子程序可以建立子程序库,便于多个模块调用。,(1)采用模块化的程序设计方法,37,采用循环结构和子程序可以使程序所占用的存储空间大大减少,节省了存储空间单元,提高程序的运行效率。对于循环程序尤其是多重循环程序,要注意循环初值的设置和循环结束条件的判断,避免出现程序无休止循环的“死循环”现象。对于通用的子程序,考虑到其通用性,除了用于存放子程序入口参数的寄存器外,程序中用到的其他寄存器的内容应压入堆栈,即保护现场。但要特别注意恢复现场的出栈顺序要与入栈顺序相反,符合堆栈的操作规则。对于中断处理子程序也要注意类似的问题。,(2)尽量采用循环结构和子程序,38,工作寄存器R0和R1具有地址指针功能,应充分发挥其作用,避免用作其他寄存器。20H2FH单元具有位寻址功能,通常用来存放各种软件标志、逻辑变量、状态信息等。30H7FH单元作为一般寄存器区域,通常用来存放各种参数、指针、中间结果或作为数据缓冲区。如果程序中需要使用08H单元以后的工作寄存器组,通常把堆栈设置在内部RAM的高端处,即30H7FH单元。,(3)合理分配内存单元,39,(4)正确使用转移指令,尽量少用无条件转移指令。这样可以使程序的条理更加清楚,从而减少错误。子程序内部的转移最好使用相对转移指令,使所编写的子程序可以放在64KB程序存储器ROM的任何地方,增加子程序的通用性,便于主程序调用。,40,4.1.3 源程序的汇编,1手工汇编2机器汇编,41,1手工汇编,是指程序设计者通过手工方式,对照指令代码表(见附录B)将汇编语言源程序中的每一条符号指令转换成对应机器码的过程。对于无分支源程序可以一次性完成汇编;对于包含转移指令和标号在内的源程序,通常需要进行两次汇编才能完成整个汇编过程:第一次汇编完成指令码的“代真”,第二次汇编完成地址偏移量的“代真”。举例说明如下。,42,ORG0100H;起始汇编LENEQU10;LEN=10SUMEQU20H;SUM=20HBLOCK EQU 30H;BLOCK=30HSTART:CLR A;A清0 MOV R2,LEN;计数初值送R2 MOV R0,BLOCK;指令初值送R0LOOP:ADDA,R0;累加 INCR0;指针加1 DJNZ R2,LOOP;未完继续 MOV SUM,A;保存结果HERE:SJMP$;动态停机 END;结束汇编,【例4-2】请对例4-1的源程序进行手工汇编。该源程序的功能是将BLOCK单元开始存放的10个无符号数进行求和,并将结果存入SUM单元中(假设结果小于255)。,43,根据起始汇编伪指令,确定源程序的起始地址,对照指令表(见附录B),依次找出每条指令的机器码,并确定它们的起始地址,对于无法确认的标号和地址偏移量,把它们按原样抄录在指令码的相应位置上。第一次手工汇编的结果如表4-1所列。,解:(1)第一次汇编,44,表41第一次手工汇编结果:,45,确定指令码中的标号或地址偏移量,即需要计算出表4-1中的有关标号LOOP、$(HERE)的实际值,结果为负数时用补码表示,计算公式为:地址偏移量=目标地址(转移指令始址+转移指令字节数)求得为:LOOP=0105H(0107H+2)=4补码为:FCH$(HERE)=010BH(010BH+2)=2 补码为:FEH 用计算结果置换表4-1中的地址偏移量,结果如表4-2所列,从而完成汇编。,(2)第二次汇编,46,表42第二次手工汇编结果:,47,机器汇编是指在微机上利用编辑软件编写好单片机的汇编语言源程序后,再通过运行于微机中的汇编程序(编译软件)对源程序进行汇编,把汇编语言源程序翻译成目标程序。最后经调试、仿真成功后,通过编程器(或称为烧写器)将目标代码写入ROM中,提供给单片机执行。,2机器汇编,48,4.2 单片机开发系统 与源程序的调试,4.2.1 开发系统的组成4.2.2 开发系统的功能4.2.3 源程序的调试,49,4.2.1 开发系统的组成,单片机开发系统通常由PC机、仿真机、编程器、擦除器等组成,如图4-3所示。,图4-3 单片机开发系统,50,用于运行由产品制造商提供的包括编辑程序、汇编程序、调试程序等在内的单片机开发系统集成操作软件,构成一个集成的开发平台,使开发系统具有各种所需的单片机开发功能,或者用于运行编程器支持软件,使编程器可以将汇编好的目标代码下载(写入)到程序存储器ROM中。,1PC机,51,仿真机本身也是一个已开发好的单片机系统,它具有模拟目标系统中的单片机、ROM、RAM和I/O端口的功能,而且基本上不占用目标系统单片机的任何资源。有些仿真机上还提供部分独立的单元电路或扩展单元,通过适当的连接就可以在仿真机上构成与目标系统相同的硬件电路,使仿真目标系统的运行环境与实际运行环境完全“逼真”。并且通过开发系统的监控软件使开发者能对仿真过程实施监控。,2仿真机,52,3仿真头,仿真头实际上是一个引脚数量与单片机引脚数量相同的插头。用于将仿真机上的单片机信号延伸到目标系统上,代替目标系统上的单片机。,53,目标系统就是需要开发的单片机应用系统。,4目标系统,54,编程器也称为烧写器或固化器,通过并行接口、串行接口或USB接口与PC机相连,用于把保存在PC机中的已调试好的目标代码写入EPROM或E2PROM中。大部分编程器还具有从ROM中读出未加密的代码,或擦除E2PROM中的代码的功能。,5编程器,55,擦除器一般为紫外线擦除器,用于擦除靠紫外线擦除信息的EPROM中的代码,以便重新写入新的代码。,6擦除器,56,4.2.2 开发系统的功能,不同的单片机开发系统,功能各异,但一般都具有如下一些基本的功能。,57,1源程序编辑功能,大多数单片机开发系统都自带一个文本编辑器,具有录入、编辑源程序的功能。,58,2源程序的编译功能,单片机开发系统能对源程序进行编译,从而生成目标代码。,59,使用单片机开发系统对源程序进行编译的过程中,如果发现错误,单片机开发系统能给出错误提示信息,以便用户进行修改。,3排错功能,60,通过通信电缆将在PC机中生成的目标代码装载到仿真机中。,4目标代码装载功能,61,单片机开发系统能有效监控目标程序的运行,以检查运行结果,对硬件故障和软件错误进行定位,其运行方式可设置为连续运行、单步运行或带断点运行等。,5运行监控功能,62,单片机开发系统可读出或修改仿真机中的仿真ROM、RAM、工作寄存器、特殊功能寄存器等,以便对目标程序或运行参数、状态等进行修改,满足源程序调试的需要。,6读出/修改功能,63,通过仿真机上的仿真头,将仿真机上的单片机出借给目标系统,当在开发系统上通过仿真头调试目标系统时,就像使用目标系统中真实的单片机一样,这就是在线仿真。仿真调试成功后,拔掉仿真头,并把开发调试好的程序固化到单片机中的EPROM或片外EPROM芯片中,再把单片机(或连同程序存储器)装回目标系统,目标系统就可以独立运行了。,7仿真功能,64,4.2.3 源程序的调试,源程序的调试大致可分为如下步骤,根据所使用的单片机开发系统的不同以及调试过程的具体情况,有些步骤是必须的,有些步骤是选做的,可以使用开发系统用户界面中的菜单或功能键进行选择。,65,(1)编辑,利用开发系统自带文本编辑器录入、编辑源程序。当然,开发者也可以利用任何一种文本编辑软件编辑源程序,但要注意汇编指令和伪指令的书写格式,不能使用全角的标点符号或其他非法字符。编辑好的源程序一般是以“ASM”为扩展名存盘,以便汇编程序识别。,66,对编辑好的源程序进行汇编,但通常需要先对源程序中的单条指令进行翻译。,(2)汇编,67,对上一步骤已汇编好的单条指令进行代真、链接,形成真正的可供单片机执行的目标代码。,(3)链接,68,将汇编好的目标代码,通过通信电缆从PC机下载到仿真机中。,(4)装载,69,是装载的反过程,将下载到仿真机中的目标代码反过来读到PC机中,再反过来由目标代码翻译为符号指令,目的是供开发者通过对比反汇编的程序与原来的源程序,验证是否汇编、装载成功。如有错误,则需重复上述步骤进行修改。,(5)反汇编,70,(6)运行:通过开发系统提供的运行控制命令,对下载到仿真机的目标代码进行试运行,运行方式通常有连续运行或单步运行,这时可以根据开发系统程序调试界面上显示的工作寄存器、特殊功能寄存器等的状态,或查看有关的RAM中的数据以及硬件电路设置的输出指示等,判断源程序是否正确。必要时还可以使用开发系统的控制命令在程序中设置断点,通过带断点运行,进一步对目标程序实施监控、跟踪。如有错误,重复以上步骤,直到完成源程序的最终调试。(7)固化程序:把经过上述步骤调试好的目标代码通过编程器写入单片机的EPROM或片外EPROM中,从而完成单片机的整个程序设计与固化的工作。,(5)反汇编,71,通过开发系统提供的运行控制命令,对下载到仿真机的目标代码进行试运行,运行方式通常有连续运行或单步运行,这时可以根据开发系统程序调试界面上显示的工作寄存器、特殊功能寄存器等的状态,或查看有关的RAM中的数据以及硬件电路设置的输出指示等,判断源程序是否正确。必要时还可以使用开发系统的控制命令在程序中设置断点,通过带断点运行,进一步对目标程序实施监控、跟踪。如有错误,重复以上步骤,直到完成源程序的最终调试。,(6)运行,72,4.3 顺序和分支程序设计,4.3.1 顺序程序设计4.3.2 分支程序设计,73,4.3.1 顺序程序设计,顺序程序又称为简单程序,是一种顺序执行的程序。该类程序没有分支、循环或子程序。顺序程序虽然简单,但也能完成一定的功能,是构成复杂程序的基础。,74,应用项目中的主程序除去最后两条指令,余下的指令所构成的程序为顺序程序结构,其对应的程序流程图如图4-4所示。,【项目应用】,图4-4 应用项目主程序中的顺序程序流程图,75,;主程序:9 ORG0050H10 MAIN:MOV A,#03H;8155初始化命令字11 MOV DPTR,#8000H;8155命令口地址12 MOVX DPTR,A;向8155写入命令字13 MOV SP,#5AH;栈底移至5AH14 MOV 2BH,#60H;秒计数基制15 MOV 2CH,#60H;分计数基制16 MOV 2DH,#24H;时计数基制17 MOV TMOD,#01H;定时器工作方式118 MOV TH0,#3CH;置T0初值19 MOV TL0,#0B0H20 MOV IE,#87H;允许中断21 SETB TR0;启动定时器T0,【项目应用】源程序,76,4.3.2 分支程序设计,1单分支程序2双分支程序3多分支程序,77,1单分支程序,单分支程序的特点是,若判断条件满足,则执行程序段B,然后继续执行程序段A;若条件不满足,则不执行程序段B,直接执行程序段A,如图4-5(a)所示。根据实际也可以采用如图4-5(b)的结构,图中的“Y”,“N”也可以互换。,78,在应用项目中的中断服务程序中就包含了单分支程序结构,它的程序流程图如图4-6所示。下面以中断服务程序的源程序为例进行分析,【项目应用】,79,;中断服务程序:24 BREAK0:CLR EX0;关闭 中断25 JNB P3.2,$;消除按键抖动,等待按键释放26 INC 28H;分单元加127 MOV A,28H;十进制调整28 ADD A,#00H29 DA A30 MOV 28H,A31 SUBB A,#60H;不等于计数基制转NEXT132 JC NEXT133 MOV 28H,#00H;相等,分单元清034 NEXT1:LCALL DISP;调用显示子程序 SETB EX0;开放 中断36 RETI;中断返回,【项目应用】源程序,80,(1)该程序的功能是对自动打铃机的时钟分值进行调校,以保证时钟与标准时间一致。(2)调校的方法是,通过按下接在单片机P3.2引脚(外部中断外脚)上的按钮来进行。当按下按钮时会在P3.2引脚上出现低电平,于是引起中断,CPU响应中断后,会自动调用这个中断服务程序。考虑到按键时会出现抖动,所以使用JNB P3.2,$指令,起到消除按键抖动,等待按键释放的作用,只有释放了按键后,程序才往下执行。,说明:,81,(3)每按下并释放一次按钮,便视为是要增加1分钟的时间,于是程序控制分计时单元28H加1,并对加1后的数据进行十进制调整。因为放在计时单元中的数据要求是BCD码表示的十进制数,而十进制调整指令DA A 必须在加法指令之后执行才能获得正确结果,所以这里加进了一条ADD A,#00H 指令,否则,单看这条指令是没有意义的。,说明:,82,(4)进行十进制调整后的时间分值被送入分计时单元保存,同时,这个数据还要和分计时基制进行比较,判断是否等于60分,若等于60分,则要对分计时单元清0。(5)完成上述工作后,该程序调用显示子程序对调整后的时间进行显示,子程序返回后,该程序的任务才算完成。最后再次开放中断,并通过中断返回指令RETI回到原来的断点。,说明:,83,双分支程序的结构如图4-7所示,它的特点是,若判断条件满足,则执行程序段A;若条件不满足,则执行程序段B。,图4-7 双分支程序,2双分支程序,84,图4-8 应用项目T0中断服务程序流程图,【项目应用】,应用项目中的T0中断服务程序中就包含有类似的双分支程序结构,它的程序流程图如图4-8所示。,85,;T0中断服务程序:85CLOCK:PUSH PSW;保护现场86 PUSH ACC87 SETB RS0;选择工作寄存器组188 MOV TH0,#3CH;重装定时器T0初值89 MOV TL0,#0BDH 90 INC 26H;0.1s单元加191 MOV A,26H;取0.1s单元内容92 CJNE A,#0AH,DONE1;不等于10,转DONE193 MOV 26H,#00H;等于10,则清0,【项目应用】源程序,86,;T0中断服务程序:94MOV R0,#27H;指向秒计数单元95MOVRl,#2BH;指向秒计数基制单元96MOV R3,#03H;循环3次(秒、分、时)97CLOCK1:MOV A,R0;取计时单元的值98ADD A,#01H;计时单元加199DA A;十进制调整100MOV R0,A;送回计时单元101MOV 3BH,Rl;取计时基制102CJNE A,3BH,NEXT3;不等于计时基制,转出,【项目应用】源程序(续),87,103 MOV R0,#00H;相等,则计时单元清0104 INC R0;计时单元指针加1105 INC R1;时间基制单元指针加1106 DJNZ R3,CLOCK1;秒、分、时共3次循环107 NEXT3:ACALL CTRL;调用控制子程序108 DONE1:POP ACC;恢复现场109 POP PSW110 RETI;中断返回,【项目应用】源程序(续),88,多分支程序的结构如图4-9所示,它是根据散转条件的结果来决定转向的,所以也称为散转分支程序。使用指令:JMP A+DPTR,图4-9 多分支程序,3多分支程序,89,解:程序如下。MOV DPTR,#TAB;指向转移指令表首地址 MOV A,R2;取转移序号 ADD A,R2;序号值乘2 JNC NEXT;乘积小于256,转 INC DPH;大于等于256,DPTR高8位加1NEXT:JMP A+DPTR;散转TAB:AJMP PROG0;转移指令表 AJMP PROG1 AJMP PROGn,【例4-3】,请根据R2中存放的转移序号,编写出转向相应处理分支的程序。R2=0,转PROG0;R2=1,转PROG1;R2=n,转PROGn。,90,由于在该程序的转移指令表中,使用的是AJMP指令,这条指令的字节长度为2,各条转移指令的地址依次相差2个字节,所以编程时要进行地址修正。在执行散转指令前,需将R2中的序号乘以2。当R2中的转移序号大于等于128时,乘积产生进位,应将此进位加到DPTR的高8位。本例中的处理程序入口地址必须和相应的AJMP指令对应的PC当前值处在同一个2KB区内,也就是说,散转范围不能超出2KB。否则,可用LJMP指令代替AJMP指令,但此时,R2中的序号要乘以3,因为LJMP指令的长度为3个字节。,说明:,91,4.4 循环和查表程序设计,4.4.1 循环程序设计4.4.2 查表程序设计,92,4.4.1 循环程序设计,循环程序是指当循环条件满足时,能够重复执行某一段程序的程序结构。采用循环程序可以减少指令数量,缩短程序长度,节省存储单元,同时使程序的结构紧凑和可读性好。循环程序一般由以下4部分组成。,93,循环程序组成,(1)循环初始化:用于做循环前的准备工作。例如,给循环计数器、工作寄存器、地址指针等设置初值。(2)循环体:当循环条件满足时,需要反复循环执行的程序段,是循环程序的主体。(3)循环控制:用于控制循环程序的循环和结束。在重复执行循环体的过程中,不断修改循环变量,并根据循环变量对结束条件进行判断,直到满足结束条件,才结束循环程序的执行(4)循环结束:用于对循环程序执行的结果进行分析、处理和保存。,94,图4-10 循环程序结构,循环程序结构,95,如图4-10(a)所示。程序流程是,一进入循环程序,先执行循环体,然后根据循环结束条件判断是否结束循环。若不满足结束条件,则继续执行循环体;若满足结束条件,则进行结束处理,退出循环。其特点是循环体至少被执行一次。,1先执行后判断,96,如图4-10(b)。程序流程是将循环程序的控制部分放在循环的入口处,先根据循环结束条件判断是否结束循环。若满足结束条件,则直接进行结束处理,退出循环;若不满足结束条件,则反复执行循环体,其特点是:若一开始就满足循环结束条件,则一次也不执行循环体,即循环次数有可能为0。实际编程时,应根据具体情况选择合适的循环结构。循环程序中还可以包含循环程序,形成多重循环,即循环可以嵌套,但应避免从外循环跳入内循环。,2先判断后处理,97,4.4.2 查表程序设计,查表程序设计方法是预先通过伪指令DB或DW在ROM中构建一个常数表格,然后根据程序执行过程中得到的“表项序号”,通过运用查表指令进行查表,而获得对应表项的内容。MCS-51共有两条查表指令,使用方法如下。,98,(1)将待查表格首地址送入数据指针DPTR。(2)将需要找查的“表项序号”送入累加器A。(3)执行查表指令MOVC A,A+DPTR,即在A中得到查表结果。,1使用MOVC A,A+DPTR查表,99,(1)将需要找查的“表项序号”送入累加器A。(2)进行地址偏移量调整,即在MOVC A,A+PC指令前插入一条ADD A,#data指令。#data的值等于取出MOVC A,A+PC指令后的PC当前值(即该指令的下一条指令首地址)到待查表格首地址之间的距离(以字节数表示)。(3)执行查表指令MOVC A,A+PC,便可在A中得到查表结果。采用MOVC A,A+PC指令查表的好处是,不需要占用DPTR,使DPTR可以另作他用,但所查的表格只能存放在MOVC A,A+PC指令后的256字节之内。,2使用MOVC A,A+PC查表,100,应用项目中的显示子程序就包含了循环程序和查表程序,其程序流程图如图4-11所示。,【项目应用】,101,70 MOV R3,#00H;计数延时初值71 DISP2:DJNZ R3,DISP2;延时一段时间(1ms)由于71号指令被执行了255次,执行该指令所需的时间为2个机器周期,已知应用项目中应用的时钟频率为6MHz,所以机器周期是2s,于是以上程序实现的时间延时为:25522=1020(s)1ms,(1)实现“延时1ms”的循环程序:,102,;显示子程序:50DISP:MOVR0,#4FH;准备向缓冲区放数 57 MOVR0,#4AH;指向缓冲区首地址58 MOVR2,#0DFH;字位显示控制码59DISPl:;以下是显示一位数的程序 72 INCR0;修改显示缓冲区指针73 RRA;为显示下一位做准备74 MOVR2,A;存字位码75 JBACC.7,DISPl;不到最后一位,则继续76 RET;显示完6位,返回,(2)控制6个LED数码显示器依次显示时、分、秒时间值的循环程序(只要求理解程序结构即可):,103,4A4F为显示缓冲区,分别放置时、分、秒的时间数值。显示子程序需要将这些值逐个读出转换为字形码,并在LED上逐个显示出来。应用项目中共用6条字位控制线,字位码中为0的位表示被选中。当字位码为DFH,即1101 1111B时,表示最左边的一条字位线被选中,所以只要将字位码进行右移就可以控制字符逐位显示。当循环右移到最高位为0时,便表示已显示了一遍。,说明:,104,57 MOVR0,#4AH;指向显示缓冲区首地址 62 MOVA,R0;取显示缓冲区中的数63 MOVDPTR,#SEGTAB;指向字形码表首64 MOVC A,A+DPTR;查表,找字形码;字形码表:135 SEGTAB:DB 3FH,06H,5BH,4FH,66H,6DH,7DH136 DB 07H,7FH,6FH,(3)显示子程序中的查表程序:,将显示缓冲区4A4F中放置的时、分、秒的时间数值逐个读出并转换为字形码(字形码也就是控制LED进行8段显示的段码,将在第8章中介绍)。,105,4.5 子程序和中断程序设计,4.5.1 子程序设计4.5.2 中断程序设计,106,4.5.1 子程序设计,1子程序的编写2子程序的调用与返回3现场的保护和恢复4子程序调用时的参数传递,107,1子程序的编写,(1)子程序第一条指令的首地址称为子程序的入口地址。该指令前必须要用标号,此标号也就是该子程序的名称。最好以子程序的功能来命名,以达到顾名思义,一目了然的目的。(2)子程序的最后一定指令通常要设置为一条子程序返回指令RET,以便使子程序能正确返回主程序断点处,继续执行主程序。,108,子程序的调用通过使用子程序调用指令来实现。执行子程序调用指令时,CPU会自动把指向调用指令的下一条指令首地址的PC值(即断点地址)压入堆栈,把断点地址保护起来。如果是在同一个2KB区内调用,可以使用ACALL指令,使用LCALL指令则可以在64KB范围内调用。子程序的返回通过子程序返回指令RET实现。执行子程序返回指令时,CPU会自动把调用子程序时,压入堆栈的断点地址从堆栈弹出到PC中,从而使子程序能正确返回主程序。,2子程序的调用与返回,109,主程序在调用子程序时,仅仅对断点做了自动保护,对主程序正在使用的累加器A、工作寄存器R0R7、数据指针DPTR、程序状态字PSW以及有关的内存单元等都进行保护。如果这些单元中的内容在调用子程序结束后,主程序仍需使用,或子程序也要用到这些单元时,就会破坏这些单元在主程序中所存储的数据。因此,对于在主程