一、延时程序的设计方法
在编程时经常需要在程序的执行过程中插入一段延迟时间,对此有两种方案可供选择:一是利用片内的硬件资源——可编程定时器TCC;二是采用软件手段——插入一段延时程序。在此先介绍后一种方法。如果延迟时间较短,可以连续插入几条空操作指令NOP;如果延迟时间较长,可以插入一段循环结构的(单一循环或者多重循环的)延时程序。
在编写延时程序之前,必须对EM78P447S单片机指令系统中的每一条指令的执行时间以及指令周期T\(_{INS}\)有所了解。在58条指令构成的指令系统中,6条无条件跳转指令(即JMP,CALL,RET,RETL,RETI和TBL)占用2个指令周期;6条条件跳转指令(即DJZ,DJZA,JZ,JZA,JBC和JBS),当条件为真发生跳转时需要占用2个指令周期,当条件为假不发生跳转时仅占用1个指令周期;其余指令都仅占用1个指令周期。另外,还应搞清单片机时基振荡器的工作频率f\(_{osc}\)(即用户所选用的晶振频率),以便确定时钟周期(T\(_{OSC}\)=1/f\(_{osc}\))和指令周期(默认状态为T\(_{INS}\)=4T\(_{OSC}\),也可以改设为T\(_{INS}\)=2T\(_{OSC}\),设定方法以后在烧写器的用法中介绍),以及程序执行时间的累计办法。
[例1] 本例中提供了设计手法不同、延迟时间也不同的3个延时子程序。首先定义两个循环控制变量N和M,用于保存决定延时长短的时间常数。为了便于计算,每条指令后面的注释部分中都给出了指令周期数(括号内的数字)。
N == 20H ; 定义寄存器变量N
M == 21H ;定义寄存器变量M
DELAY1:
MOV A,@X ;(1)循环变量初始值X(待定)经A转送N
MOV N,A ;(1)
LOOP: DJZ N ;(1\2)N-1送N并判断结果=0?是!跳出循环
JMP LOOP ;(2)否!循环回去
RET ;(2)返回调用程序
;——————————————
;0.8ms延时子程序
;——————————————
DELAY2:
MOV A,@100 ;(1)循环变量初始值100经A转送N
MOV N,A ;(1)
LOOP: NOP ;(1)填充空操作
NOP ;(1)
NOP ;(1)
NOP ;(1)
NOP ;(1)
DJZ N ;(1/2)N-1送N并判断结果=0?是!跳出循环
JMP LOOP ;(2)否!循环回去
RET ;(2)返回调用程序
;——————————————
;100ms延时子程序
;——————————————
DELAY3:
MOV A,@133 ;(1)外循环变量初始值经A转送M
MOV M,A ;(1)
LOOP1:MOV A,@251 ;(1)内循环变量初始值经A转送N
MOV N,A ;(1)
LOOP2: DJZ Z ;(1/2)N-1=0?是!跳出内层循环
JMP LOOP2 ;(2)否!循环回去
DJZ M ;(1/2)M-1=0?是!跳出外层循环
JMP LOOP1 ;(2)否!循环回去
RET ;(2)返回调用程序
执行上述延时子程序DELAY1所需指令周期个数=1+1+[(1+2)×(X-1)+2]+2。式中的“1+1”对应两条MOV指令:“1+2”对应DJZ(非跳转)和JMB指令;“X”是循环变量递减的次数,“X-1”是循环次数,由于DJZ指令的执行过程是先递减后判断再跳转,所以循环的次数比递减的次数小1;接下来的“2”对应DJZ(成功跳转)指令;最后的“2”对应RET指令。当时钟晶振选用4MHz、指令周期设定为“4”个时钟周期时,每个指令周期T\(_{INS}\)为1μs(T\(_{INS}\)=4T\(_{OSC}\)=4/f\(_{osc}\))。在上面的计算式中,当时间常数X=1时,延时=1+1+2+2=6T\(_{INS}\)=6μs;当X=99时,延时=1+1+(1+2)×(99-1)+2+2=300T\(_{INS}\)=300μs=0.3ms。
在延时子程序DELAY1中,由于保存X的是一个RAM单元,X的取值范围只能为0~255,这就使得最大延迟时长受到限制。可以利用在循环体内填充NOP指令的方法来加大延时,从而构成延时子程序DELAY2,不过程序长度会明显增加。它的延时=1+1+(5+1+2)×(100-1)+2+2=798TINS=798μs≈0.8ms。
如果希望既要加大延迟时间又要程序尽量短小,最好采用多重循环程序结构。延时子程序DELAY3就是一个二重循环结构,其延时=2+[2+(1+2)×(251-1)+2+1+2]×(133-1)+2+2=99930TINS=99.930ms≈100ms。
在利用上述各子程序实现延时时,若将调用它的CALL指令的执行时间也考虑在内的话,则延迟时间就又多了2个指令周期。
二、查表程序的设计方法
在单片机的开发应用中,经常用到查表程序,来实现代码转换、索引或翻译等。下面就以7段LED数码管显示驱动程序设计作为讲解的范例。LED数码管内部包含8只发光二极管,其中7只发光二极管构成字型笔段(a~g),1只发光二极管构成小数点(dp)。对于任何一只发光二极管,只要阳极为高电平、阴极为低电平,并且电位差高于其阈值(约为1.7~2.1V)就会被点亮。根据各二极管公共端连接方式的不同,又有共阴极和共阳极LED数码管之分,如图1所示。驱动LED点亮的笔段码和LED所显字符之间的关系如表1所示。需要进一步说明的是,笔段码的内容不仅会随着数码管共阴或共阳类型的不同而变化,还与数码管连接单片机端口引脚的顺序有关。


EM78P447S单片机的查表程序可以利用其“子程序带值返回指令RETL”来实现。思路是,采用若干条携带着笔段码的RETL指令按索引值的顺序排列在一起,来构成一张数据表。然后以表头为参照,以被显数字为索引值(或叫偏移量),到表中查找对应被显数字的笔段码。具体方法是,采用带有入口参数和出口参数的子程序结构,用数据表来构成子程序的主体部分。在子程序的开头安放一条修改程序计数器PC值的指令,来实现子程序内部的跳转,以实现索引。
查表过程是,在主程序中先把被显数字——索引值,作为笔段码在数据表中的地址偏移量存入A,以便向子程序传递参数,接着调用子程序。子程序的第一条指令将A中的地址偏移量取出并与程序计数器PC的当前值叠加,则程序就自动会跳到携带着所需笔段码的RETL指令处。由该指令将笔段码装入A中,以便向主程序传递参数,然后返回主程序。
[例2]
假设用8位端口P6作为一只“共阴极”LED数码管的驱动端口(连接顺序同图1)。现在要求把寄存器单元20H中的低音字节作为一位十六进制数送到LED显示。欲采用端口P6作驱动,需要将该端口的各引脚全部设置为输出,方法是将P6端口的“方向控制寄存器IOC6”的各位全部清零。另外,从主程序到子程序传递参数(就是被显数字,也是用于查表的地址偏移量)以及从子程序到主程序返回参数(即查到的被显数字的笔段码)用的都是累加器A。程序如下:
PC == 02H ;声明寄存器PC的地址为02H
STATUS == 03H ;声明寄存器STATUS的地址为03H
P6 == 06H ;声明寄存器P6的地址为06H
IOC6 == 06H ;声明寄存器IOC6的地址为06H
;——————————————
;主程序
;——————————————
ORG 0FFFH ;设置复位矢量
JMP MAIN ;跳转到主程序
ORG 003H ;设置主程序起始
地址
MAIN:
MOV A,@00H
;把P6口脚方向控制数据00H送A
IOW IOC6 ;经过A设定P6口
各脚全为输出
MOV A,20H ;把20H单元的
数据送A
AND A,@0FH ;屏蔽掉高4位后作为查表地址偏移量
CALL CONVERT ;调用数码转
换子程序
MOV A,P6 ;将查得结果送
P6端口显示
STOP:
JMP STOP ;停机,原地踏步
;——————————————
;查表(即转换)子程序
;——————————————
CONVERT:
;子程序名称,也是
子程序入口地址标号
TBL ;把A内容叠加到PC的
低8位上形成跳转
TABLE:
RETL 3FH ;“0”的笔段码
RETL 06H ;“1”的笔段码
RETL 5BH ;“2”的笔段码
RETL 4FH ;“3”的笔段码
RETL 66H ;“4”的笔段码
RETL 6DH ;“5”的笔段码
RETL 7DH ;“6”的笔段码
RETL 07H ;“7”的笔段码
RETL 7FH ;“8”的笔段码
RETL 6FH ;“9”的笔段码
RETL 77H ;“A”的笔段码
RETL 7CH ;“b”的笔段码
RETL 39H ;“C”的笔段码
RETL 5EH ;“d”的笔段码
RETL 79H ;“E”的笔段码
RETL 71H ;“F”的笔段码
END;程序全部结束
数据表可以看成是由16条RETL指令构成,表头为“TABLE”。当程序跳转到子程序,便开始执行“TBL”指令,同时,程序计数器的当前值已经指向表头。在此基础上再叠加预存在A中的表内地址偏移量。假设该偏移量为8,则叠加后的PC值指向“RETL 7FH”指令,其中7FH即为数字8的显示笔段码。使程序跳转到该指令并执行它,执行后便返回到主程序,同时将7FH装入A中。
本刊今年第6期介绍的MASM78汇编器,读者可到本刊网站上下载:www.radio.com.cn。
大海创作室