第二节 定时器
1 、概述
C51中的定时器和计数器是一个硬件电路支持的,通过对寄存器的不同配置,我们就可以使用定时器。
确切的说定时器和计数器的区别是导致它们加一的信号不同。
当我们配置为定时器时,每经过一个计数周期,计数存储器的值就加一。
当我们配置为计数器时,每来一个负跳变信号(信号从P3.4或P3.5引脚输入输入),就加一,这样就可以达到计数的目的。
总结一下
定时器和计数器电路一样
定时或者计数的本质上就是让单片机某个部件数数
当定时器用的时候,靠内部震荡电路数数
当计数器用的时候,数外面的信号,读取针脚的数据
这里再来区分一下晶振,时钟周期和机器周期
晶振(晶体振荡器),是数字电路的“心脏”。数字电路的所有工作都离不开晶振,晶振的快慢与好坏会影响整个系统的稳定性。
时钟周期:在一个时钟周期内,CPU只完成一个最基本的动作。是计算机中最小的,最基本的时间单位。也叫振荡周期,定义为时钟频率的倒数。
机器周期:机器周期也叫CPU周期。一个机器周期完成一条指令。在计算机中,为了方便管理,将一条指令分为若干阶段(取指、译码、执行),每一个阶段完成一个基本操作。每个基本操作完成所需要的时间称为机器周期。一个机器周期由若干时钟周期组成。
下面我们来举一个例子:
当芯片晶振频率是11.0592MHz的时候,我们的定时器加一时间是多少?
周期 = 1 / 频率
当晶振频率是11.0592MHz 的时候,等于 11059.2KHz = 11059200Hz机器周期 = 12 x 时钟周期 =12 x (1/ 时钟频率 ) = 12 / 时钟频率 s = 12 / 11059200 s = 12 000 000 / 11059200 us = 1.085 us所以说当我们的定时器(12T模式)加一时,时间过去了 1.085us,这在之后有很多用处。
2、如何配置定时器
STC89C52RC单片内部设置了两个定时器/计数器。
TCON寄存器:
TCON寄存器的第5位(TF0)和第7位(TF1)分别表示定时器计数值是否溢出,溢出为1,未溢出为0。
bit6(TR1)和bit4(TR0)是定时器的开关,为1时开始计时
TMOD寄存器
TMOD中我们一般使用16位计数,设置M1M0两位为01,再设置TL0、TH0(定时器0存放计数值的寄存器)
这样就配置好了一个定时器。
3、通过定时器来控制LED
以下代码通过定时器来实现灯一秒亮一次。
void main() { int cnt = 0; led = 1; // 1.配置定时器0工作模式为16位计时 TMOD = 0x01; // 2.给初值,定一个10ms来计时 TL0 = 0x00; TH0 = 0xDC; // 3.开始计时 TR0 = 1; TF0 = 0; //当爆表时,硬件会修改bit5(TF0)位上面的数据,改成1(置一),如果不用中断,就要用代码清零 while(1){ if( TF0 == 1 ){ TF0 = 0;//代码手动清零 cnt++;//统计爆表的次数 TL0 = 0x00;//重新给初值 TH0 = 0xDC; } // 4.爆表了,操作led吗,累积到1s,再操作led // 爆表了,变量加一,加个100次就是1s,每隔1s转换led状态 // if( !(cnt % 500) ) if(cnt == 100){ led = !led; cnt = 0;//当100次表示1s,cnt重新从0开始计算下一次的一秒 } } }
这里需要注意:
(1) 通过前面我们可以了解到,定时器加一(计的是机器时间,上面已经算过了),时间过去1.085us,我们的定时器模式设置为16位,最大可以计数到
=65536,65536*1.085us=71106.56us 约定等于71ms。也就是说该模式下定时器最大可以定时71ms。
现在我们想让灯一秒亮一秒灭,可以用软件延时,但是不是很准确。这个时候定时器就派上用场了。
我们先让定时器每次定10ms,当过去100个10ms时,1s钟的时间就到啦。所以我们可以设置定时器的初值,使得初值到最大值刚好过去10ms,那么该怎么算呢?
假设初值为x,那么(65536- x)*1.085 us= 10000 us 就可以算出初值x 约等于56320,十六进制0xDC00。
或者我们还有更简单的方法:
用STC-ISP来直接计算:
可以看到初值存放在TL0和TH0中,为0xDC00,与我们的手算结果一致。
配置好了定时器的模式以及初值,我们可以打开定时器计时,通过TF0/TF寄存器判断是否溢出,当有溢出时,我们设置变量cnt++,cnt计数到100,说明过去了1s,可以控制灯的开关。
(2)TF寄存器由硬件置1,在中断中使用时,当请求cpu被响应时由硬件清零,也可以由程序查询清零
(3)在编写这个代码时,在判断 cnt是否到100时我出现了一个错误,导致灯一闪随即又灭了。 if( !(cnt%100) ),这里当cnt 是0或者100的时候都是成立的,所以有小bug。
4、通过定时器中断的方式来控制Led
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行
STC89C51的有8个中断源,4个外部中断,3个定时器中断,1个串口中断
中断控制的相关寄存器:
这里我们只用到了两个寄存器
EA是总中断开关,在使用时需要置1
ET0是定时器零溢出的中断允许位,在使用时也要置1
这里我们只使用这俩个寄存器便可以开始定时器零溢出的中断
XICON寄存器:
void Timer0Init(void) //10毫秒@11.0592MHz { AUXR &= 0x7F; //定时器时钟12T模式 TMOD &= 0xF0; //设置定时器模式 TL0 = 0x00; //设置定时初值 TH0 = 0xDC; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0 = 1; //开启Time0中断 EA = 1; //开启总中断开关 } void main() { led = 1; led1 = 1; Timer0Init(); //10毫秒@11.0592MHz //当爆表时,硬件会修改bit5(TF0)位上面的数据,改成1(置一),如果不用中断,就要用代码清零 while(1){ led1 = 0; Delay500ms(); led1 = 1; Delay500ms(); } } //执行中断处理函数 void Time0Handler() interrupt 1 { cnt++;//统计爆表的次数 TL0 = 0x00;//重新给初值 TH0 = 0xDC; // 4.爆表了,操作led吗,累积到1s,再操作led // 爆表了,变量加一,加个100次就是1s,每隔1s转换led状态 // if( !(cnt % 500) ) if(cnt == 100) { led = !led; cnt = 0;//当100次表示1s,cnt重新从0开始计算下一次的一秒 } }
代码中出现了AUXR &=0X7F;这里简单说明一下,是为了降低单片机时钟对于外界电磁辐射的干扰。
在这里再整理一下自己对于中断不太明白的点,首先:
(1)中断函数不同于子函数,不需要主函数调用就可以执行
(2)C51中断处理函数也叫中断函数,在写中断函数时必须用interrupt n 来设定终端号,编译时就会自动的确定入口地址
(3)例如中断函数 void timer0_sev(void) interrupt 1
这里timer0_sev(void)是中断函数名,可以随意写
interrupt1 是中断号,不能随意写