找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 13950|回复: 1
打印 上一主题 下一主题
收起左侧

51单片机定时器的原理与使用(22页详细的word格式教程下载)

[复制链接]
跳转到指定楼层
楼主
ID:325686 发表于 2018-5-9 10:08 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
定时器是单片机的重要功能模块之一,在检测、控制领域有广泛应用。定时器常用作定时时钟,以实现定时检测,定时响应、定时控制,并且可以产生ms宽的脉冲信号,驱动步进电机。定时和计数的最终功能都是通过计数实现,若计数的事件源是周期固定的脉冲则可实现定时功能,否则只能实现计数功能。因此可以将定时和计数功能全由一个部件实现。通过下图可以简单分析定时器的结构与工作原理。
一、定时器

1、51单片机计数器的脉冲输入脚。主要的脉冲输入脚有Px,y, 也指对应T0的P3.4和对应T1的P3.5,主要用来检测片外来的脉冲。而引脚18和19则对应着晶振的输入脉冲,脉冲的频率和周期为

F = f/12 = 11.0592M/12 = 0.9216MHZ      T = 1/F = 1.085us
2、定时器有两种工作模式,分别为计数模式和定时模式。对Px,y的输入脉冲进行计数为计数模式。定时模式,则是对MCU的主时钟经过12分频后计数。因为主时钟是相对稳定的,所以可以通过计数值推算出计数所经过的时间。
3、51计数器的计数值存放于特殊功能寄存器中。T0(TL0-0x8A, TH0-0x8C), T1(TL1-0x8B, 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.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用于设置定时/计数器的工作方式,低四位用于T0,高四位用于T1。其格式如下:
GATE:门控位,用于设置计数器计数与否,是否受P3.2或P3.3电压状态的影响。GATE=0时,表示计数器计数与否与两端口电压状态无关;GATA=1时,计数器是否计数要参考引脚的状态,即P3.2为高时T0才计数,P3.3为高时T1才计数。
C/T:定时/计数模式选择位。      =0为定时模式;    =1为计数模式。
M1M0:工作方式设置位。定时/计数器有四种工作方式,由M1M0进行设置。
6、计数器的溢出
计数器溢出后,THx与TLx都归0。并将特殊功能区中对应的溢出标志位TFx写为1。
好了,理论就讲述到这。现在我们通过一些实验来看看怎么使用定时器。
实验一、P1口连接的8个LED灯以1秒钟的频率闪烁。
首先上代码:
  1. #include "reg51.h"  
  2.        char c;  
  3.          
  4.        void Timer0_Init() //初始化定时器  
  5.        {  
  6.           TMOD = 0x01;     //  
  7.           TH0 = 0;  
  8.           TL0 = 0;   //定时器的计数起点为0  
  9.           TR0 = 1; //启动定时器0  
  10.    }  
  11.      
  12.    void main()  
  13.    {  
  14.        Timer0_Init();  
  15.        while(1)  
  16.        {  
  17.            if(TF0 == 1) //检测定时器0是否溢出,每到65535次  
  18.            {  
  19.                TF0=0;  
  20.                c++;  
  21.                if(c==14)    //71ms乘以14为1s  
  22.                {  
  23.                    c=0;  
  24.                    P1=~P1;  
  25.                }  
  26.            }  
  27.        }  
  28.    }  
复制代码

上述代码的思路是每计算14个溢出,则翻转P1口状态。产生一个溢出的时间是71.1ms,14个则约为1s。

  1. #include "reg51.h"  
  2.        sbit LD1 = P1^0;  
  3.          
  4.        void Timer0_Init() //初始化定时器  
  5.        {  
  6.           TMOD = 0x01;     //  
  7.           TH0 = 0;  
  8.           TL0 = 0;   //定时器的计数起点为0  
  9.           TR0 = 1; //启动定时器0  
  10.    }  
  11.      
  12.    void Timer0_Overflow()  //处理定时器0的溢出事件  
  13.    {  
  14.        static char c;  
  15.        if(TF0 == 1) //检测定时器0是否溢出,每到65535次  
  16.            {  
  17.                TF0=0;  
  18.                c++;  
  19.                if(c==14)    //71ms乘以14为1s  
  20.                {  
  21.                    c=0;  
  22.                    LD1=!LD1;  
  23.                }  
  24.            }     
  25.    }  
  26.      
  27.    void main()  
  28.    {  
  29.        Timer0_Init();    //初始化定时器0  
  30.        while(1)  
  31.        {  
  32.            Timer0_Overflow();  
  33.        }  
  34.    }  
复制代码


相比于上个例子,这里有两个区别,首先是将timer0的溢出事件作为子函数单独出来,其次是注意翻转一个led灯时候用的是“!”。

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

  1. #include "reg51.h"  
  2.          
  3.        void Timer0_Init() //初始化定时器  
  4.          
  5.        {  
  6.           TMOD |= 0x01;        //定时器0方式1,计数与否不受P3.2的影响  
  7.           TH0 = 0;  
  8.           TL0 = 0;   //定时器的计数起点为0  
  9.           TR0 = 1; //启动定时器0  
  10.    }  
  11.      
  12.    void Timer0_Overflow()  //´处理定时器0的溢出事件  
  13.    {  
  14.        static char c;  
  15.        if(TF0 == 1) //检测定时器0是否溢出,每到65535次  
  16.            {  
  17.                TF0=0;  
  18.                c++;  
  19.                if(c==14)    //71ms乘以14为1s  
  20.                {  
  21.                    c=0;  
  22.                    P1 ^= (1<<0);//LD1=!LD1;  
  23.                }  
  24.            }     
  25.    }  
  26.      
  27.    void Timer1_Init()  
  28.    {  
  29.        TMOD|=0x10; //定时器1方式1,计数与否不受P3.3的影响  
  30.        TH1=0;  
  31.        TL1=0; //定时器1的计数起点为0  
  32.        TR1=1; //启动定时器1  
  33.    }  
  34.      
  35.    void Timer1_Overflow()  //处理定时器1的溢出事件  
  36.    {  
  37.        static char c;  
  38.        if(TF1==1) //软件查询,主循环每跑完一圈才会到这里。  
  39.        {  
  40.            TF1=0;  
  41.            c++;  
  42.            if(c==14)  
  43.            {  
  44.                c=0;  
  45.                P1 ^= (1<<7);//LD8=!LD8;  
  46.            }  
  47.        }     
  48.    }  
  49.      
  50.    void main()  
  51.    {  
  52.        Timer0_Init();    //初始化定时器0  
  53.        Timer1_Init();     //初始化定时器1  
  54.        while(1)  
  55.        {  
  56.            Timer0_Overflow();  
  57.            Timer1_Overflow();  
  58.        }  
  59.    }  
复制代码


相较于例二,例子三有几个点值得注意:

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秒钟闪烁。

  1. #include "reg51.h"  
  2.          
  3.        void Timer0_Init()  
  4.        {  
  5.            TMOD|=0x01;  
  6.            TH0=56320/256;   //计数起点为56320 ==10ms溢出一次  
  7.            TL0=56320%256;  
  8.            TR0=1;  
  9.        }  
  10.      
  11.    void Timer1_Init()  
  12.    {  
  13.          
  14.    }  
  15.      
  16.    void ISR_Init()    //初始化中断系统  
  17.    {  
  18.        EA=1; //启动中断系统  
  19.        EX0=0; //-->IE0  
  20.        ET0=1; //-->TF0 控制位置1,表明当TF0置1时,中断系统将介入   
  21.        EX1=0; //-->IE1  
  22.        ET1=0; //-->TF1  
  23.        ES=0; //-->RI,TI  
  24.      
  25.    }  
  26.      
  27.    //以下中断服务子程序,我们希望中断系统来调用,而不是我们在main函数里面调用,因此使用interrupt. */  
  28.      
  29.    void IE0_isr() interrupt 0  
  30.    {  
  31.      
  32.    }  
  33.      
  34.    /*void TF0_isr()    interrupt 1  //71.1ms 进入一次,但如果要求10MS进来一次呢?
  35.    {
  36.        static char c;
  37.        c++;
  38.        if(c==14)
  39.        {
  40.            P1^=(1<<0);
  41.            c=0;
  42.        }
  43.    }
  44.    */  
  45.    void TF0_isr()  interrupt 1  //10ms 进入一次  
  46.    {  
  47.        static char c;  
  48.        TH0=56320/256;   //重装初值  
  49.        TL0=56320%256;  
  50.        c++;  
  51.        if(c==100)  
  52.        {  
  53.            P1^=(1<<0);  
  54.            c=0;  
  55.        }  
  56.    }  
  57.      
  58.    void IE1_isr()  interrupt 2  
  59.    {  
  60.      
  61.    }  
  62.      
  63.    void TF1_isr() interrupt 3  
  64.    {  
  65.          
  66.    }  
  67.      
  68.    void RI_TI_isr() interrupt 4  
  69.    {  
  70.      
  71.    }  
  72.      
  73.    void main()  
  74.    {  
  75.         Timer0_Init();  
  76.         Timer1_Init();  
  77.         ISR_Init();  
  78.      
  79.         while(1)  
  80.         {  
  81.             //...  
  82.             //发现溢出后,中断系统根据中断号寻找中断子服务函数,并强行暂停主循环并进入子函数  
  83.             //...  
  84.         }  
  85.    }  
复制代码

显然使用中断系统查询得到的1s更为精确。因为中断系统独立于main函数运行。另外本程序还预装了timer0的初值,这样的话就可以实现比71ms更小的时间片,比如要求10ms就进入中断。关于初值的设定,请参考下图。

实验五、用定时器实现数码管显示1234。
  1. //数码管的定时扫描,每5ms显示一个数码管,也就是说相同的数码管,每20ms会被重新装入同样的数值,根据人眼的延迟效应,人眼观测到的数码管上的数值是静态的。  
  2.         #include "reg51.h"  
  3.         unsigned int count;  
  4.         extern void load_smg();  
  5.         void Timer0_Init()  
  6.         {  
  7.            TMOD|=0X01;  
  8.            TH0=60928/256;  
  9.            TL0=60928%256;//每5ms进入一次中断  
  10.        TR0=1;  
  11.     }  
  12.      
  13.    void isr_Init()  
  14.     {  
  15.        EA=1;  
  16.        ET0=1; //TF0 如果这个标志为1,进入中断子函数  
  17.     }  
  18.      
  19.     void TF0_isr() interrupt 1  
  20.     {  
  21.        TH0=60928/256;  
  22.        TL0=60928%256;//重装初值  
  23.        load_smg();  
  24.     }  
  25.      
  26.     void main()  
  27.     {  
  28.        Timer0_Init();  
  29.        isr_Init();  
  30.        while(1)  
  31.        {  
  32.          
  33.        }  
  34.      
  35.     }  
  36.      
  37.     #include "reg51.h"   
  38.      //char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};  
  39.     code char seg[10]={0xC0,0XF9,0XA4,0XB0,0X99,0X92,0X82,0XF8,0X80,0X90};  
  40.     char smgbuf[4]={1,2,3,4}; //从RAM的smgbuf这个地址开始连续存放4个数,并且每个数占一个单元。  
  41.     extern unsigned int count; //外部申明,表示并不在这里申明  
  42.      
  43.    void fill_smgbuf() //向LED缓冲区填充数据  
  44.    {  
  45.        smgbuf[0]=count/1000;  //千位  
  46.        smgbuf[1]=(count%1000)/100;  //百位  
  47.        smgbuf[2]=((count%1000)%100)/10;   //十位  
  48.        smgbuf[3]=((count%1000)%100)%10;   //个位  
  49.    }  
  50.      
  51.    void load_smg()   //将数码管显示缓冲区的数据,显示到数码管上  
  52.     {  
  53.        static char i;  
  54.        fill_smgbuf();  
  55.        i++;  
  56.        if(i>=4)  
  57.        {  
  58.            i=0;  
  59.        }  
  60.        P0=0xFF;   //消除上一个循环的影子  
  61.        P2 = ~(1<<i);  
  62.        P0 = seg[smgbuf[i]];      
  63.     }  
复制代码

实验六、实现按钮控制数码管上的数值加1或减1,并且当按住按钮不放时,能实现快速的增减。

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

https://zhidao.baidu.com/question/561457023576622684.html

单片机中TCON和TMOD寄存器,无论是汇编程序还是C语言程序,都可以直接赋值。具体赋值多少,根据以上两寄存器各位具体功能确定。

https://blog.csdn.net/zn2016/article/details/53353818

51定时器使用

2016年11月26日 18:56:03

阅读数:1020

1.设置特殊功能寄存器TMOD,配置好工作模式。
TMOD中M0/M1的配置决定定时器(0或1)的工作模式。
M1  = 0,M0 = 0,工作模式0,由THn的8位和TLn的5位组成一个13位的定时器。
M1 = 0,M0 = 1,工作模式1,由THn和TLn组成1个16位的定时器。
M1 = 1,M0 = 0,工作模式2,8位自动重装模式,定时器溢出后由THn重装的TLn中。
M1 = 1,M0 = 1,工作模式3,禁用定时器1,定时器0变成两个8位的定时器。
2.设置计数寄存器TH0,TL0的初值。
3.设置TCON,通过TR0置1,来让定时器开始计数。
4.判断TCON寄存器的TF0位,检测定时器是否溢出。
注意:定时器计数溢出后,TF0会置位,如果没有开定时器中断则需要软件清零TF0位。如果开定时器中断则TF0位由硬件清理。谨记:定时器溢出后给TH0,TL0 重装载值。
定时器在每一个机器周期计数向上加1。


完整的Word格式文档51黑下载地址:

51单片机定时器的原理与使用.docx (1.34 MB, 下载次数: 52)



评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享淘帖 顶2 踩
回复

使用道具 举报

沙发
ID:128463 发表于 2019-3-30 17:16 | 只看该作者
xiexie!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表