通过上一节,我们知道了中断是怎么回事,懂得了如何去操作外部中断了,那么,这节缘缘就给你说说中断中最头疼的一个东西——定时器/计数器,又称特殊功能寄存器,是不是在别人的程序中经常看到类似这样的程序,
那么它们是什么意思呢,在弄懂这个之前,我们就在当今中国房价居高不下的时代里,拿房子说说事吧:
通过这们的情况,我们可以得出下面这种关系:
假设我们造了一个这样的小区,这个小区有高低两个区域,其中一个是用大房子做的,称为高级区,只有10个房子(房价贵,地皮更贵),一个是小房子做的,也有10个房子:
那么,这么些房子有多少个空间,究竟能住多少人呢?
通过上面的说明,我们得知,其实这个小区也就是个能住10×10=100人的房子,我们装每个房子定义成一个空间,那么就是它最大就是100个空间了,那么这些人是如何住进这个房子(空间)的,又有何种规定呢?
通过这个,我们知道了这么一种情况,我们要让房子腾空一次,就必须要住100人,那么,我们把全部房子腾空做为一个参考事件,假设让这些房子全部腾空时,全都来听缘缘讲故事,目前我们要得到房子腾空一次,那就得让100个人去住,如果有63个就腾空一次该怎么办呢:
我们得知了,在定义的情况下,就产生一个腾房子的事件,如果我们把这个腾房子记成一个时间,即一个人住一次就是1分钟,那么刚才的63个人住了并腾出来就是63分钟了,如果我们的这些房子是一个特殊的存储空间,那么怎么去算或者如何去定义时间呢:
假设这是个只能装100个数的存存储空间,当数值超过100时会产生一个中断响应,并将空间置空。
10个数的空间为一个位,有两个位,高位和低位,高位装的是低位的全部,低位装的是单个的数,假设低位每一位装的是1,那么高位的一位就装的是10,如果把数据装满,便如下表一样:
这里有个问题,就是装数是必须从低位依次向高位装,而且顺序是不能变的,其*面的数字代表的是占用的空间情况,不存在其数值意义,也就是说10也占用着一个空间,1也占用着一个空间,其代表着一个空间,但实际上,每个位都会空着一个空间,最多只能装99个数,即高位9个,低位9个,这时,只要再装进来一个数,低位的空间就会先装满,然后全部溢出去,再把高位的全部填满,高位的也溢出去,这样就会产生一次中断。
如果我们只要装63个数就要产生一次响应,该怎么做呢?
解答如下:因为这个空间必须要装满100个才能产生一次响应,那么我们就要在装这63个数之前把其它的空间要装上,不让其空着,那么装63个数就会产生一次中断响应,之前要装多少数呢?答案就是100-63=37,于是我们就先要装37个数的空间,然后再进行63个数空间的计算,那么这63个数怎么装呢,我们就先对这100个数的空间的高位先装,因为这100个数的空间的空间是由两个10位的高低空间组成的,那么装数时,我们对要装的数进行求整,装入高位,把剩下的(也称求余),装到低位里面,其装法如下(以下有颜色的代表是装了数的空间,没有颜色的是空着的空间):
我们要一个装63个数的中断响应,那么我们要装的数就是100-63=37了;
算出了这个,该怎么装呢,我们先对它的高位进行装数,因位高位装的数都是10低位的倍数,只有低位有10个,所以装数的公式为:(100-63)/10=3.7,对3.7取整,就是取小数点前的那个计算的值,取整就是3,因为我们是以10为基数算的,因为高位最前面的那个空间满了会直接溢出去产生中断,所以在装数时空着的,我们就与那个挨着,把这3个高位(每位是低位的全部,即为10)装满,如下表所示:
我们将剩下的数怎么装呢,因为是10为基数算的,那么剩下的0.7要乘上10就是我们要装的数了,0.7*10=7,与高位挨上,装在低位上:
我们看到,有问号的红色空间是空着的,在高位有6个空着的,那么就是6×10=60了,低位有3个空着的,总共就是63个空间了。
那么对它如何进行装数运算,其进行方式如下表所示:
我们先从它的低位的最后一位向高位装数:
到这里是低位的空间满了,它会溢出去,低位就全部腾空了,把溢出去的这10个数装在高位的一个空间上:
当高位溢出时,就会产生中断响应了,因这这个空间只能装100个数,在运算前我们就把37个空间填满了,所以只在装63次数时就会产生一个中断响应,这达到了我们要装63个数就产生一次中断响应的要求。
我们做到了用63个数产生一次中断响应的要求了,那么我们要以630个数产生一次中断响应该怎么办呢?
前面说过,当产生一次中断响应时,数据空间会清空,继续装入初值,再进行63个中断的过程,依次循环,那么,我们让这个循环进行10次,那不就是我们要得到的63个数的中断了吗,这该怎么做呢,我们引入一个数记这个中断,当这个中断产生时,就会计数一次,其基本结构如下:
那么在程序中该如何表达呢,如下所示:
这时在定时器函数里面:
{
高位=(100-63)/10;前面已述
低位=(100-63)%10;%是求余的意思;
M++;这是引入的一个值;
}
在主函数中只做个判断就行了:
If(M==10)因为我们要用的是630个数的中断,所以当M=10时,正好就是63*10=630个数的中断。
{
M=0; 意思是只要10次够了就置0重新计数,要不然就一直加,
这样就失去了630个数产生一次中断的效果了。
********这里写上处理的事件,如灯亮了,开关打开了等处理事件。
}
看了是不是很简单,明白了这个,那么我们回过头来看看这是开头的这个是怎么回事,先来看看我们的定时器/计数器吧是个啥概念吧:
以下所有时间的计算为了方便,用12M的晶震计算,其它的计算方法与此一样。
我们知道,单片机在处理一个事件是会占用一个机器周期,即在寄存器中装入一个数时,它就会占用一个机器周期,通过上图我们得知,装满这个寄存器时,需要装65536个数值,这样就会占用65536个机器周期。
在前面说过,一个机器周期是晶震的12分频,如果晶震是12MHZ,那么一个机器周期就是12*1/12=1uS,那么65536个机器周期就是65536uS=65.536mS=0.065536S。
现在我们得知了寄存器全部装满时需要的时间了,当寄存器装满时,就会产生一个中断响应,这个响应是我们在程序中定义的处理事件。
但是,我们一般不会以65536这样的时间去定时,因为这样定时不好算,比如我们要定一秒钟,结果是1÷0.065536=15.2587(次)了,这样就产生了小数,对于定时时,不太精确了,假设是我们定时1秒采取20次计算是多少呢,就是1÷20=0.05S了,把这个换算成US就是50000了,那么我们的定时器是如何做到的呢:
特殊寄存器 |
TH0 |
TL0 |
因为通过前面的图介绍,我们知道T1和T0都有两个8位的特殊寄存器,共计16位,分为高位TH0与低位TL0,我们就以T0做说明,T1与其相同:
低位是8位,用二进制制表示装的数,如果8位全部装满就是1111 1111,那换算成16进制就是0xff,这个数换算成10进制等于多少呢,就是255,但是这个空间还少一个,如果是256才会向上溢,所以记为256。
现在问题来了,因为在这个存储器的定义上,高位的一个数是低位的全部总和,即把高位的一个数装一次就是256个低位数,如果把高位的全部装满应该是多少呢,那就是256*256,这个值是多少呢,算下后即为65536,也就是说当T0中的这两个寄存器装65536个数时,T0会发生一次中断,那么T0是怎么定义时间的呢?
我们知道,一个机器周期是是12个时钟周期,那么机器周期就是晶震的12分之1了,如果以12M的晶震算的话,那么单片机的时钟周期就是12M*1/12=1US(微秒),T0中的寄存器每装入一个数的时间就是一个机器周期,即1US,那么把T0的寄存器全部装满就是65536US,如果换算成MS(毫秒)就是65536US/1000=65.536MS,如果换算成秒的话就是65.536MS/1000=0.065536 S(秒)
假设,我们要定时50mS,应该怎么办呢,很简单,我们让65536先把50000(我们要定时50MS的数)减去,将减去的值先装入寄存器,装值时我们要对高位进行256的求模(整)操作,(为什么是256呢,因为这个低位是256的存储空间,高位的一个数就是低位的所有数,所以高位装的全是低位的倍数,)把剩下的数装到低位里面,那么这个数倒底是怎么装的呢:
首先,我们让65536先把要定时的这50000减去,就变成了15536了。
接下来,我们给高位装,因为高位是256的倍数,所以,这里就成了15536÷256=60.6875,我们对这个数先取整,就是60了,即我们在T0的高位的256个空间装了60个空间(每个空间包含256个数)。
再接着,我们对60.6875取余,就是0.6875了,我们是以256为基数取余的,所以在装低位时,我们用这个余数与256相乘,即为0.6875×256=176,这个数正好能装在256的低位空间中。
所以,经过上面的过程,我们对T0空间装数的情况是:
高位:60,低位:176,那么,我们这样装合适吗?
我们反验证一下,就是看高位与低位的数加到一起是多少:
算法是高位×256+低位,即为60×256+176=15360+176=15536,那么我们让65536把这个数减去,看是不是50000了,不用算就能看出来了,就是50000了,这样,我们先给T0用上述方法装15536个数,把空间先占上,然后对剩下的空间进行50000的填数而产生中断响应操作,即可得到50MS的中断,算过了50000US一次的中断,那么我们再来算算10000US是怎么装初值的:
高位:(65536-10000)/256,即为55536÷256=216.9375,取整后即为216个空间;
低位:(65536-10000)%256,即为55536÷256=216.9375,取余后为0.9375,我们让这个数与256相乘,即0.9375*256=240,也正好装下了。
我们再来反验证一下,看是不是能达到10000US的定时要求:
高位×256+低位,即为256×216+240=55296+240=55536,65536-55536=10000US,所以算法是没有错的,那么,这个初值如何在程序中表现呢,为了方便说明,仍以50000US为例说明:
void timer0( ) interrupt 1
{
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
***********这里写上中断时产生的响应就行了。
}
我们知道了一个50MS的中断是怎么产生的,那么我要定时1秒该怎么办呢,我们就引入一个值,让它计算这个中断多少次会达到1秒这个时间,通过S(秒)与MS(毫秒)的换算我们得知,1S=1000MS,那么1S的中断就是1000/50=20(次),这时我们引入一个值来让它计算这个中断的次数,如果到20次了(可以定义多个计数中断的数,以完成各种不同的任务),就会产生中断响应,下面是完整程序,晶震为12M的,可以充分地说明这一点:
#include < reg52.h >
sbit LED=P1^5;//接继电器、LED或蜂鸣器
sbit teep=P1^6;//接继电器、LED或蜂鸣器
int i=0,j=0;//定义计算中断次数的数
void timer0() interrupt 1 ////定时50MS的中断函数
{
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
i++;
j++;
}
void main()
{
TMOD = 0x01;//定时器的工作模式
TH0 =(65536-50000)/256;
TL0 =(65536-50000)%256;
EA=1;
ET0=1;
TR0=1;
TR1=0;
while(1)
{
if(j==20)//1秒的时间(50MS*20=1000MS=1S)
{
j=0;
LED=!LED;//1秒亮1秒灭循环
}
if(i==40)//2秒的时间(50MS*40=2000MS=2S)
{
i=0;
teep=!teep;//2秒开2秒关循环
}
}
}
怎么样,是不是很简单,现在看到开头的那种程序还觉得难吗?当然也有写不一样的,它们是把这个10进制数换算成十六进制赋值的,但进无论如何,其原理还是一样的。
以上就是对定时器的说明,计数器的原理是一样的,只不过把定时当计数使用罢了。
以上就是对定时器的啰嗦解释,如果不啰嗦,这块很抽象,很难理解,所以啰嗦点,希望与我一样有困难的朋友一个帮助,对于中断定时,一定要先算好一个机器周期的时间,才能做好对定时器的定时操作,如果机器周期没算合适,那么肯定定出来的时间不准,时间怎么算,就是晶震/12就行了,如11.0592M的,他的机器周期就是11.0592/12=0.9216,那么定时50MS就是概是(50+(50-50*0.9216))*0.9216=49.9999999MS,所以算这个,如果是低于12就用加的,如是果高于12就用减的(红色标记处)。
本片篇只是将学习过程中的经验汇总分享,由于本人才浅学孰,还望各位发现问题后及时给予指正。
0