首页 > 编程知识 正文

51单片机定时器的使用原理及方法(单片机精准定时原理)

时间:2023-05-04 15:07:57 阅读:76904 作者:4154

计时器是单片机的重要功能模块之一,广泛应用于检测、控制领域。 计时器经常用作定时时钟以实现定时检测、定时响应和定时控制,并能够生成具有ms宽度的脉冲信号以驱动步进电机。 计时器和计数的最终功能都通过计数来实现,如果计数的事件源是周期性固定的脉冲,则可以实现计时器功能,否则只能实现计数功能。 因此,计时器功能和计数功能都可以在一个部件中实现。 通过下图,可以简单分析计时器的结构和工作原理。

一、定时器

1,51单片机计数器的脉冲输入脚。 的主要脉冲输入脚包括Px、y,也可以指对应于T0的P3.4和对应于T1的P3.5,主要用于检测片外脉冲。 针脚18和19相对于与石英振动对应输入脉冲,脉冲的频率和周期为

f=f/12=11.0592 m/12=0.9216 mhzt=1/f=1.085 us

2、计时器有计数模式和计时器模式两种工作模式。 将Px、y的输入脉冲计数为计数模式。 计时器模式将MCU的主时钟分频12分钟进行计数。 主时钟比较稳定,所以可以根据计数值推测计数经过时间。

3、51计数器的计数值保存在特殊功能寄存器中。 T0(TL0-0x8a、TH0-0x8C )、t1 (TH1-0x8D、TH1-0x8d ) )。

4、TLx与THx的组合关系

1 )、TLx和THx之间的32进制数。 也就是说,当TLx计数到32个脉冲时,在TLx变为0的同时THx进入1。 这也称为方式0。

2 )、TLx和THx之间的256进制数。 也就是说,当TLx计数到256个脉冲时,在TLx变为0的同时THx进入1。 这也称为方式1。 方式1时,最多65536脉冲发生溢出。 主频率为11.0592M时,每脉冲1.085us,因此溢出1次的时间为1.085usx65536=71.1ms。

3 )、THx用于保存TLx溢出后,TLx下次计数的起点。 这也称为方式2。

4 ) THx和TLx分别独立地计数自己的输入脉冲。 这也称为方式3。

5、定时器初始化

1 )确定定时器的计数模式。

2 )确定TLx与THx的组合关系。

3 )、确定计数开始值。 也就是TLx和THx的初始值。

4 )、是否开始计数。 TRx

(1)和(2)可以通过动作模式寄存器TMOD设定。 TMOD用于设定定时/计数器的动作模式,低位4位用于T0,高位4位用于T1。 其形式如下。

门)一个门位,用于设置计数器有无计数,是否受P3.2或P3.3电压状态的影响。 GATE=0时,表示计数器有无计数与两端口的电压状态无关; 如果GATA=1,则计数器是否计数参照管脚的状态进行计数,即P3.2为高时参照T0,P3.3为高时参照T1进行计数。

C/T:定时/计数模式选择位。=0是定时模式;=1是计数模式。

M1M0:动作模式设定位。 定时器/计数器有4种动作方式,用M1M0设定。

6、计数器溢出

计数器溢出时,THx和TLx都为0。 对应于特殊字段的溢出标志TFx写为1。

那么,理论到此为止。 让我们通过几个实验来看看计时器的使用方法。

连接到实验一、P1端口的8个指示灯以1秒钟的频率闪烁。

首先升级代码:

#include 'reg51.h'char c; voidtimeR0_init(/初始化计时器) { TMOD=0x01; //TH0=0; TL0=0; //计时器的计数起点为0 TR0=1; //启动计时器0}void main () {Timer0_Init ); while(1) if ) TF0==1)//检测计时器0是否溢出,每65535次({TF0=0; C; if(c==14 )/71ms14为1s(c=0; P1=~P1; } }上述代码的想法是,每计算14个溢出,就会反转P1端口的状态。 一个溢出发生的时间为71.1ms,14个约为1s。

每秒闪烁3358www.Sina.com/led指示灯。

#包含' re g51.h ' sbit ld1=P1 ^ 0; voidtimeR0_init(/初始化计时器) { TMOD=0x01; //TH0=0; TL0=0; //计时器的计数起点为0 TR0=1; //启动计时器0 )0}void Timer0_Overflow )//处理计时器0的溢出事件({静态char c; 检测if(TF0==1)//计时器0是否溢出,每65535次({TF0=0; C; if(c==14 )/71ms14为1s(c=0; LD1=! LD1; }}}void main () {Timer0_Init ); //初始化计时器0while(1) {Timer0_Overflow ); }与前面的例子相比,这里有两个不同。 首先,我们把timer0的溢出事件

作为子函数单独出来,其次是注意翻转一个led灯时候用的是“!”。

例子三、让连接到P1口的LED1和LED8灯每1秒钟闪烁。

#include "reg51.h"void Timer0_Init() //初始化定时器{ TMOD |= 0x01;//定时器0方式1,计数与否不受P3.2的影响 TH0 = 0; TL0 = 0; //定时器的计数起点为0 TR0 = 1;//启动定时器0}void Timer0_Overflow()//´处理定时器0的溢出事件{static char c;if(TF0 == 1) //检测定时器0是否溢出,每到65535次{TF0=0;c++;if(c==14) //71ms乘以14为1s{c=0;P1 ^= (1<<0);//LD1=!LD1;}}}void Timer1_Init(){TMOD|=0x10; //定时器1方式1,计数与否不受P3.3的影响TH1=0;TL1=0; //定时器1的计数起点为0TR1=1; //启动定时器1}void Timer1_Overflow()//处理定时器1的溢出事件{static char c;if(TF1==1) //软件查询,主循环每跑完一圈才会到这里。{TF1=0;c++;if(c==14){c=0;P1 ^= (1<<7);//LD8=!LD8;}}}void main(){Timer0_Init(); //初始化定时器0Timer1_Init(); //初始化定时器1while(1){Timer0_Overflow();Timer1_Overflow();}}相较于例二,例子三有几个点值得注意:

1、TMOD初始化为什么采用TMOD |= 0x01或0x10的形式?

      首先如果在定时器初始化函数中采用TMOD = 0x01和TMOD = 0x10,那么将造成LD1闪烁比LD8闪烁快8倍。分析一下,从main函数开始执行,先是初始化timer0,这时候定时器1设置为工作方式1。接着程序执行到Timer1_Init(),这时候TMOD=00010000,即选定了timer1在工作方式1,但同时timer0重新配置为工作方式0, 也就是32进制,所以产生快8倍现象。

     那为什么用|这个符号就可以做到互不影响呢?|是或运算符,即有1出1,全0出0。什么意思呢?举个例子,a是11110000,b是10101010,那么a|b就是11111010。通过引入这个符号,可以实现tmod对两个定时器的独立操作。


2、为什么使用P1 ^= (1<<0)可以实现LD1的控制呢?

      首先解释下^这个符号。^称为异或运算符,相同出0,不同出1。举个例子,a是11110000,b是10101010,那么a^b就是01011010。

      然后再来分析 x ^= (1<<i), 假设x为10101010,当i为1时, (1<<i)为00000010,那么x^ (1<<i)=10101010^00000010=10101000。当i为2时,(1<<i)为00000100,那么x^ (1<<i)=10101010^00000100=10101110,以此类推。我们发现,x ^= (1<<i)是在将x的第i位翻转而同时不影响其他位。

     因此P1 ^= (1<<0)实际是在翻转P0口第一位的值,因此也就是在闪烁LD1灯。


上面三个例子实际都是采用了软件查询法。即main函数会每次进入到溢出事件函数里去判断TF0或1是否等于1,这样就浪费了大量CPU时间。同时,实时性差,假如在执行Timer0_Overflow()的时候timer1也溢出了,这时候timer1的溢出事件就没有及时处理。因此下面我们要引入中断系统。


二、中断系统

中断系统是一套硬件电路,它可以在每个机器周期对所有的外设的标志位作查询。相比于前面的软件查询(if(xx==1)),中断系统也可以叫做硬件查询。51的中断系统可查询以下6个标志位。

IE0(TCON.1),外部中断0中断请求标志位。

IT1(TCON.2),外部中断1触发方式控制位。

IE1(TCON.3),外部中断1中断请求标志位。

TF0(TCON.5),定时/计数器T0溢出中断请求标志位。

TF1(TCON.7),定时/计数器T1溢出中断请求标志位。       

RI(SCON.0)或TI(SCON.1),串行口中断请求标志。当串行口接收完一帧串行数据时置位RI或当串行口发送完一帧串行数据时置位TI,向CPU申请中断。 

当中断系统查询到外设的标志位变为1时,中断系统可暂停当前的主循环,并且将程序跳转到用户预先指定的函数中执行。要启动中断系统,必须先进行中断初始化,其流程如下:

a、是否要查询外设标志(EA=0或EA=1,EA 也叫 CPU中断允许(总允许)位)

b、查询到标志1,是否要跳程序

c、跳转的目标函数,即中断服务子函数

所以在使用定时器中断时,我们只需要首先初始化中断系统,开启总中断(相当于总开关),开启定时器对应的控制位(相当于支路开关),再初始化定时器即可。中断系统作为单片机的外设,只有在某个中断产生时才会打断主循环,并由相应的中断号引入到相应的中断服务子函数。下图是6个中断标志位的信息。



实验四、使用中断系统实现LD1灯每1秒钟闪烁。

#include "reg51.h"void Timer0_Init(){TMOD|=0x01;TH0=56320/256; //计数起点为56320 ==10ms溢出一次TL0=56320%256;TR0=1;}void Timer1_Init(){}void ISR_Init() //初始化中断系统{EA=1; //启动中断系统EX0=0; //-->IE0ET0=1; //-->TF0 控制位置1,表明当TF0置1时,中断系统将介入 EX1=0; //-->IE1ET1=0; //-->TF1ES=0; //-->RI,TI}//以下中断服务子程序,我们希望中断系统来调用,而不是我们在main函数里面调用,因此使用interrupt. */void IE0_isr() interrupt 0{}/*void TF0_isr()interrupt 1 //71.1ms 进入一次,但如果要求10MS进来一次呢?{static char c;c++;if(c==14){P1^=(1<<0);c=0;}}*/void TF0_isr()interrupt 1 //10ms 进入一次{static char c;TH0=56320/256; //重装初值TL0=56320%256;c++;if(c==100){P1^=(1<<0);c=0;}}void IE1_isr()interrupt 2{}void TF1_isr() interrupt 3{}void RI_TI_isr() interrupt 4{}void main(){ Timer0_Init(); Timer1_Init(); ISR_Init(); while(1) { //... //发现溢出后,中断系统根据中断号寻找中断子服务函数,并强行暂停主循环并进入子函数 //... }}显然使用中断系统查询得到的1s更为精确。因为中断系统独立于main函数运行。另外本程序还预装了timer0的初值,这样的话就可以实现比71ms更小的时间片,比如要求10ms就进入中断。关于初值的设定,请参考下图。


实验五、用定时器实现数码管显示1234。

//数码管的定时扫描,每5ms显示一个数码管,也就是说相同的数码管,每20ms会被重新装入同样的数值,根据人眼的延迟效应,人眼观测到的数码管上的数值是静态的。 #include "reg51.h" unsigned int count; extern void load_smg(); void Timer0_Init() { TMOD|=0X01;TH0=60928/256;TL0=60928%256;//每5ms进入一次中断TR0=1; }void isr_Init() { EA=1;ET0=1; //TF0 如果这个标志为1,进入中断子函数 } void TF0_isr() interrupt 1 { TH0=60928/256;TL0=60928%256;//重装初值load_smg(); } void main() { Timer0_Init(); isr_Init();while(1){} } #include "reg51.h" //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90}; code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90}; char smgbuf[4]={1,2,3,4}; //从RAM的smgbuf这个地址开始连续存放4个数,并且每个数占一个单元。 extern unsigned int count;//外部无聊的热狗,表示并不在这里无聊的热狗void fill_smgbuf() //向LED缓冲区填充数据{smgbuf[0]=count/1000; //千位smgbuf[1]=(count%1000)/100; //百位smgbuf[2]=((count%1000)%100)/10; //十位smgbuf[3]=((count%1000)%100)%10; //个位}void load_smg() //将数码管显示缓冲区的数据,显示到数码管上 { static char i;fill_smgbuf();i++;if(i>=4){i=0;}P0=0xFF; //消除上一个循环的影子P2 = ~(1<<i);P0 = seg[smgbuf[i]]; }
实验六、实现按钮控制数码管上的数值加1或减1,并且当按住按钮不放时,能实现快速的增减。


这里的关键点在于如何实现快速增减,具体请详细分析代码。代码链接点击打开链接

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。