定时器及其应用
为什么要使用定时器
在前面的案例中,我们都是使用一个Delay函数实现延时,而Delay函数却会造成很多的不方便,因为在Delay的延时过程中,我们无法同时进行其他的事情,就比如我们使用洗衣机洗个衣服,如果我们啥也不干,就只在洗衣机前干等着就相当于单片机中使用Delay函数,但是我们知道,现实生活中我们还有很多别的事可以做,所以我们就可以定个闹钟,先干别的事情,等闹钟响了我们再来取衣服,而单片机中的定时器就是这个闹钟,有了闹钟的加入,我们的单片机的效率就可以大大提升。
怎么使用定时器
我们在学习单片机的时候,一定要记住两句话,单片机通过配置寄存器来控制内部线路的连接 和 寄存器是连接软硬件的媒介,我们使用定时器也不例外,首先我们需要知道,定时器有哪些寄存器,通过查看手册可以知道分别有控制寄存器TCON和工作模式寄存器TMOD,所以我们只需要对这两个寄存器分别配置即可使用定时器。
在我们等待洗衣机洗衣服的时候,我们在做别的事情,但是当闹钟响时我们需要先放下手中的事情,去洗衣机收衣服,收完衣服后我们要继续做没有完成的事情,这个过程在单片机中就由中断系统来实现,因此我们需要对中断系统由一定的了解并配置中断系统的寄存器。
所以我们只需要分别配置定时器的工作模式、控制方式、中断系统三个部分,即可实现定时器工作
(我们目前只对定时器0进行配置)
定时器控制
其中寄存器TCON为可位寻址(可以单独对其某一位赋值),各位如下:
SFR name | Address | bit | B7 | B6 | B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|---|---|---|---|---|
TCON | 88H | name | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
TCON我们只对定时器0的相关位进行赋值 定时器范围为0~65535,每隔1us计数加一,总共定时65535us即65.535ms不足一秒 1s:1ms执行1000次,给定定时器初值64535,理溢出差值就为1000,实现计时1ms,所以我们还需要对定时器的初值进行设置,赋值为:
TF0=0; TR0=1; TH0=64535/256; //高位赋值 TL0=64535%256; //低位赋值
中断系统
中断系统结构如图:
我们在使用定时器0即Timer0时,我们想要其进入中断程序,就需要对其各个寄存器(红线圈出来的位置)进行配置,具体参数配置为:
ET0=1; //配置中断 EA=1; //配置中断 PT0=0; //配置中断
定时器工作模式选择
寄存器TMOD为不可位寻址(只能整体赋值),各位如下:
GATE | C/T | M1 | M0 | GATE | C/T | M1 | M0 |
---|---|---|---|---|---|---|---|
定时器1 | 定时器1 | 定时器1 | 定时器1 | 定时器0 | 定时器0 | 定时器0 | 定时器0 |
TMOD的低四位为定时器0的位
M1和M0决定定时器工作在哪种模式,定时器有四种模式,分别为
模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器
C/T选择定时器或计数器,门控端控制工作 我们想要让定时器工作在模式一,那么就给TMOD最后两位,分别给M1和M0 0和1 选择定时器模式,则给倒数第三位C/T位0 门控端给0
赋值如下:
TMOD=0x01; //0000 0001
但是这样会对定时器1的值造成影响,而在我们不能对其进行位赋值的情况下,我们可以使用按位与和按位或来只对寄存器的低四位进行赋值,实现如下:
TMOD&=0xF0; //将TMOD的低四位全置为0,高四位不变 TMOD|= 0x01; //将TMOD的最低位置为1,其余位不变
综上所述,对定时器0的初始化进行模块化,封装为一个函数,得到:
void Timwer0_Init() { TMOD&=0xF0; //将TMOD的低四位全置为0,高四位不变 TMOD|= 0x01; //将TMOD的最低位置为1,其余位不变 TF0=0; TR0=1; //让定时器开始工作 TH0=64535/256; //高位赋值 TL0=64535%256; //低位赋值 ET0=1; //配置中断 EA=1; //配置中断 PT0=0; //配置中断 }
当我们使用这个“闹钟”的时候,我们要设置好,”闹钟“响后到底要干什么,所以,我们需要写一个中断子程序,来执行中断后到底要进行什么工作,中断子程序的模板为:
void Timer0_Routine() interrupt 1//中断子程序 { static unsigned int T0_Count;//static使T0_Count在函数结束后的值仍然保持不变 TH0=64535/256; TL0=64535%256; //定时器需要重新赋初值 if(T0_Count++ >= 1000) //定时器的时长为1ms,执行一千次得到1s { //每隔1s执行一次if的函数体内的内容 T0_Count=0; P2_0=~P2_0; //此处为改变LED0的状态 } } //以上是一个使LED0进行闪烁的子程序,设置的间隔为1s
定时器的应用示例
使用定时器实现控制流水灯
#include <REGX52.H> #include "Timer0.h" #include "Key.h" #include <INTRINS.H> unsigned char KeyNum,LEDMODE;//KeyNum记录按键编号 LEDMODE记录流水灯模式 void main() { P2=0xFE; Timer0_Init(); //定时器初始化 while(1) { KeyNum=Key(); //监测按键按下 if(KeyNum) { if(KeyNum==1) //按了一下按键1 { LEDMODE++; //LEDMODE加一 if(LEDMODE>=2) LEDMODE=0;//LEDMODE只会取值0和1 } } } } void Timer0_Routine() interrupt 1//中断子程序 { static unsigned int T0_Count;//static使T0_Count在函数结束后的值仍然保持不变 TH0=64535/256; TL0=64535%256; //定时器需要重新赋初值 if(T0_Count++ >= 500) { T0_Count=0; if(LEDMODE==0) //LEDMOED为0时流水灯左流 { P2=_crol_(P2,1);//函数crol包含在头文件<INTRINS.H>中实现循环右移 } if(LEDMODE==1) //LEDMODE为1时流水灯右流 { P2=_cror_(P2,1);//cror循环右移 } } }
程序运行分析:主函数中第一步将LED0点亮,之后调用第一个函数定时器初始化函数Timer0_Init此时相当于已经设置好了闹钟,中断程序1在闹钟每隔500个闹钟周期(0.5s)就会执行一次if内的语段(判断LEDMODE并实现一次流水)。主函数始终运行在while循环中,不断收集判断按键情况,每按下一次按键LEDMODE就会改变,从而改变流水灯的流水方向
使用定时器实现时钟显示
#include <REGX52.H> #include "Delay.h" #include "LCD1602.h" #include "Timer0.h" unsigned char Sec=55,Min=59,Hour=23; void main() { LCD_Init(); Timer0_Init(); LCD_ShowString(1,1,"CLOCK:"); LCD_ShowString(2,1," : :"); while(1) { LCD_ShowNum(2,1,Hour,2); LCD_ShowNum(2,4,Min,2); LCD_ShowNum(2,7,Sec,2); } } void Timer0_Routine() interrupt 1 { static unsigned int T0_Count; TH0=64535/256; TL0=64535%256; if(T0_Count++ >= 1000) { T0_Count=0; Sec++; if(Sec>=60) { Sec=0; Min++; if(Min>=60) { Min=0; Hour++; if(Hour>=24) Hour=0; } } } }
程序运行方式类似流水灯