单片机学习之十四:定时器应用(模式1)
开机后程序作如下的灯光变换:第一次led0、led2亮;第二次led1、led3亮;第三次led4、led6亮;第四次led5、led7亮;第五次led0、led2、led4、led6亮;第六次led1、led3、led5、led7亮;第七次8个二极管全亮;第八次8个二极管全灭。然后重头开始循环。每次状态转换间隔时间是50ms。
二、实验目的
掌握内部定时/计数器的作定时功能(模式1)的应用
三、实验任务分析:
看到这个实验题目,大家可能会说,这个试验难度不大,我们只要把这几种灯光对应的输出存到一个表里面,然后查表就可以啦,灯光转换之间调用50ms的延时程序即可。 这样的思路当然可以,但是,如果这样作,我们就没有必要写这个试验啦。今天,我们要老瓶装新酒,看看这个50ms的定时时间除了用延时程序完成,还有没有别的方法。以前的定时,我们常常采用延时程序来完成。其实这种方法不是很完美,由于程序的指令中还包含其他的判断指令,所以延时程序作定时不是非常精确。由于日常的电子系统常常需要定时和计数的功能,所以MCS-51单片机内部就自带了两个16位的寄存器,用来作定时/计数器,我们给它们起了名字,T0、T1。(我们的AT89S52里面有3个定时器,一般的程序两个就够啦,我们先介绍这两个吧。)
现在就来学习这两个定时/计数器的用法。这个试验的实质,就是利用自带的定时/计数器来产生50ms的定时。
先来看看这两个定时/计数器的结构吧,如下图所示。
1、定时器的结构:
从图上可以看出,定时/计数器的核心是一个加1计数器(注:16位,计数范围从0000h~ffffh),根据K0开关的不同方向,这个计数器可以对两个脉冲来源计数,一个是系统的时钟振荡器,另外一个是外部的脉冲源。
2、定时器的工作方式
当计数器对系统时钟脉冲计数的时候,由于系统时钟是一个时间基准,所以脉冲数×脉冲周期就是一段固定时间,因此工作于定时方式;当计数器对外部的脉冲进行计数的时候(也就是对TX端计数,TX端我们用到的时候再解释吧),就可用于对外部事件计数,工作于计数方式。我们这个试验要产生50ms的定时,用的是定时功能,在后面的试验里我们还要作计数功能的试验。
我们发现,当k0向上打的时候,工作于定时方式,向下打的时候工作于计数方式。那么k0又是由什么控制的呢?
K0是一位模拟开关,方向受 这一位的控制,当 =0时,K0向上,工作于定时方式;当 =1时,K0向下,工作于计数方式。(注:数电教材的《AD转换》一章有模拟开关的原理,大家可以查阅),
那么, 又是什么呢,它是特殊功能寄存器tmod中的一位。tmod是用来控制定时/计数器的工作方式的,是一个8位的寄存器,它的各位情况如下图所示:
(注:tmod是我们又新接触的一个特殊功能寄存器,和别的特殊功能寄存器一样,在内部RAM的特殊功能寄存器区。)
看到了吧, 是tmod寄存器的D2和D6位,所以,如果我们想让T0工作于定时方式,就应该把D2置0;如果要让T0工作于计数方式,就应该把D2置1。对T1的控制也是一样的。
注意,在这里我要特别说明一点,我们不能用setb指令给tmod的某一位置1,也不能用clr指令给某一位置0。为什么呢?记得我们以前对ie寄存器可以这样作:setb ea(开中断),clr ea(关中断),那么tmod寄存器为什么不行呢?
这是因为tmod寄存器是不能够“位寻址”的,也就是说,我们不能单独的对其中一位进行操作,而必须对整个寄存器进行赋值。而以前我们学过的ie、psw、tcon、acc这几个特殊功能寄存器却是可以“位寻址”的。具体的哪些寄存器可以位寻址,而哪些寄存器不行,大家可以参照相关教材,有很详细的说明。
再看一下k1,它也是一个模拟开关,当与门输出是1的时候,开关闭合,启动计数器;当与门输出是0的时候,开关打开,计数器停止。至于它如何控制我们稍后介绍。
3、定时时间的计算
好啦,知道了怎样选择定时还是计数方式,我们就要考虑一下定时时间的计算了。要计算定时时间,我们就要知道计数器的输入脉冲周期是多少。实际上,计数器是对机器周期计数的,而我们也知道,1个机器周期=12×振荡周期,所以,计数器的输入脉冲周期是:12×(1/12MHZ)=1us。
可见,要产生50ms的定时,只要计数器记50000个脉冲就可以啦。那么,怎样让计数器在记入了50000个脉冲后,向单片机发出一个消息,表示定时时间到呢?这就要用到我们以前学习过的概念-中断啦。我们以前说过,单片机有5个中断源,两个是外部中断 和 ,两个是T0和T1的溢出中断,还有一个是串行口中断。我们以前用过外部中断 ,这个试验我们要用到T0的溢出中断。
16位的计数器从0开始,记入216=65536个脉冲的时候,会向外面产生溢出,这个溢出把TFX置一,(注:TF0是T0的溢出中断标志,TF1是T1的溢出中断标志),从而向CPU申请中断,在进入中断处理程序后,由硬件对TFX清0,不需要我们操作。(注:顺便说一下TF0和TF1,它是我们以前接触过的tcon特殊功能寄存器中的两位,tcon是一个可以“位寻址”的特殊功能寄存器,我们在试验七中用这个寄存器设置过外部中断的触发方式,想不起来的话,回头看看前面的实验吧。)
4、计数初值的计算
继续刚才的问题,16位的计数器要记入65536个脉冲才会产生溢出中断,那么怎样让它在记入50000个脉冲后产生中断呢?这就要用到我们在数电中间学习过的内容啦。我们可以给计数器置一个初值65536-50000=15536,这样计数器在记入50000个脉冲之后就会产生溢出中断了。
那么,怎样给计数器置初值呢?T0和T1是两个16位的计数器,我们可以把它们分为高8位THX和低8位TLX。Cpu和T0、T1之间的关系如下图所示(注:其中,P3.4和P3.5用作第二功能,是工作于计数方式的时候,外部的计数脉冲输入,也就是定时/计数器结构图10-1中的TX端。)
我们以T0为例,要给它置入初值15536,就要对高8位的TH0和低8位的TL0分别置数。15536变换成16进制的数是3CB0(注:转换方法可以查阅数电教材《进制转换》一章),所以我们就这样给T0置初值:mov th0,#3ch ;mov tl0,#0b0h,就可以啦。
5、计数器的启动和停止
在给T0置入初值之后,并且在允许T0溢出中断(注:由IE寄存器中的ET0控制,详细说明可见试验7),和cpu开启中断的前提下,我们就要启动T0开始定时了。我们前面说过T0的启动和停止是由图10-1中与门的输出决定的。当与门输出是1的时候,T0启动;当与门输出是0的时候,T0停止。
那么什么时候与门输出是1呢?从图上可知有两种情况。
(1)、当gate=0时,只需要tr0=1,即可启动T0计数
(2)、当gate=1,并且tr0=1的时候,还需要int0=1才能启动T0计数
在该试验中,我们用到第一种情况,也就是我们把gate赋值0,然后通过对tr0的置1和置0来启动和停止计数器。这是一种常用的方式,至于第二种方式,我们以后通过试验给大家分析它们之间的不同。
Gate在哪里呢?大家再看看上面给出的tmod寄存器图,就是d3和d7位。所以我们在对T0初始化的时候,要给d3赋值0。至于d4~d7都和T0没有关系,可以是任何状态。
再说说tr0的问题,它也是tcon寄存器中的一位,它的各位功能如下图所示,我们把用到的作个简单介绍。
1、TF0:T0的溢出中断,当T0溢出的时候由硬件置位,在进入中断服务程序后,由硬件清0,不用我们操作;TF1类似。(注:如果我们采用查询方式,通过查询TFx的状态判断是否到达定时,而不进入中断服务程序,那就要对这1位用软件清0啦!可不要忘啦!)
TR0:当gate=0时,置一启动T0计数,置0停止T0计数;TR1类似
好啦,现在基本上分析清楚了,我们来看看主程序和中断服务程序的流程图吧。
四、程序流程图:
五、实验程序
(注意:在作这个试验的时候,不要忘了把JMP0跳线置于1、2的位置,选择二极管显示单元)
org 0000h
ajmp main
org 000bh ;T0溢出中断入口地址
ajmp time0
org 0020h
main: clr p1.5
mov r1,#0ffh
mov sp,#70h ;设置堆栈
mov tmod,#01h ;T0初始化,工作于定时方式,详细解释见注释
mov th0,#3ch ;T0置计数初值
mov tl0,#0b0h
setb et0 ;允许T0溢出中断
setb ea ;cpu开中断
setb tr0 ;启动T0计数
ajmp $ ;等待
time0:inc r1 ;查表求灯光,输出到p0口,详细解释见多位数码显示试验
mov a,r1
mov dptr,#tab
movc a,@a+dptr
mov p0,a
cjne a,#0ffh,next
mov r1,#0ffh
next: mov th0,#3ch ;由于计数器已经溢出,所以需要重设计数初值
mov tl0,#0b0h
reti
tab: db 0fah,
db 0aah,55h,00h,0ffh
end
六、几点说明
我们来看看T0初始化的语句mov tmod,#01h,现在我们把tmod的各位功能详细说明一下。
(1) d2是定时/计数方式选择,这里应该把d2置0,选择T0工作于定时方式
(2)d3也应该置0,这样通过控制tr0即可启动T0
(3)d1d0(M
M
M
M
M
(2) 在这个试验中,由于定时50ms需要16位的计数器,故d1d0=01
从上面的分析可见,我们给tmod可以赋值:XXXX0001,可以是01h,也可以是
现在把这个程序下载到学习板上,看看效果吧。我们发现,灯光变换的速度太快啦,几乎没有办法看清楚,原因很简单,因为50ms的定时太短了。16位的计数器定时最多能够达到65.536ms,那么如果我们需要更长时间的定时,比如仍然是刚才的试验,但是时间要求是1s,该怎么办呢?
其实也很简单,我们设一个计数器,初值是0,每次T0溢出中断的时候,给这个计数器加一。在主程序里,我们反复检测这个计数器的值,达到20的时候调用灯光子程序就可以啦。
下面就是这个定时1s的程序,其中,我们用r2作这个计数器。
org 0000h
ajmp main
org 000bh ;T0溢出中断入口地址·
ajmp time0
org 0020h
main: clr p1.5
mov r1,#0ffh
mov r2,#00h ;给计数器r2赋初值0
mov sp,#70h ;设置堆栈
mov tmod,#01h ;设置T0工作方式
mov th0,#3ch ;T0置计数初值
mov tl0,#0b0h
setb et0 ;允许T0溢出中断
setb ea ;cpu开中断
setb tr0 ;启动T0开始计数
wait: cjne r2,#20,wait ;定时时间未到,继续查询等待
acall light ;定时时间到,调用查表求灯光子程序
ajmp wait
;以下是查表求灯光子程序
light: mov r2,#00h ;计数器重新赋初值
inc r1 ;查表求灯光,详细解释见试验5“多位数码显示”
mov a,r1
mov dptr,#tab
movc a,@a+dptr
mov p0,a
cjne a,#0ffh,next
mov r1,#0ffh
next: ret ;子程序返回
;以下是中断服务程序
time0: inc r2 ;计数器加1
mov th0,#3ch ;重置计数初值
mov tl0,#0b0h
reti ;中断返回
tab: db 0fah,
db 0aah,55h,00h,0ffh
end
好啦,把这个程序下载到学习板上,就会很清楚的看到灯光的变换方式啦。
在这个试验里面,由于我们的定时时间较长,所以采用了16位的计数器,假如定时时间短的话,定时/计数器还可以采用别的工作方式,例如13位,或者8位等。
评论