蓝桥杯单片机学习6:深入理解定时器和计数器原理

定时器

  • 定时器/计数器
  • 1.工作原理
  • 2.相关寄存器
  • 3.工作模式
  • 4.定时器中断配置
  • 实战环节
  • 1.任务要求
  • 2.实现思路
  • 3.代码实现
  • 总结
  • 上一期我们学习了外部中断的相关内容,现在我接着来学习定时器。

    定时器/计数器

    1.工作原理

    定时器/计数器是一种能够对内部时钟信号或者外部输入信号进行计数,当计数值达到设定要求时,向CPU提出中断请求,从而实现定时或计数功能的外设。定时器的基本工作原理是进行计数。

    举个栗子:你可以把定时器比喻成一个装了水的瓶子,每一次计数理解成向瓶子里面丢一个石子,当丢的石子足够多时,瓶子里面的水就会溢出,产生中断请求。

    当作为定时器使用时,计数信号的来源是周期性的内部时钟频率,在单片机的内部,有一个频率为12MHZ的晶振,可以稳定的产生的产生一个周期为12MHZ的时钟信号,那么定时器就可以实现在每隔一段时间加一,实现定时。我们可以来简单的计算一下一个定时器的溢出周期:
    单片机的时钟周期:T=1/f(sys) = 1/12MHZ = 0.083us 这是一个时钟周期
    单片机的指令周期:单片机的指令周期 = 12*时钟周期=12 * 0.083 = 1us;即定时器在工作的状态下每1us计数器就会加一(每1us向瓶子里面丢一个石子)。

    那么最大可以加到多少呢?我们这里以定时器0为例:在单片机内部有两个寄存器TH0和TL0,用来给定时器0计数,那么计数的最大值就是2^16-1 = 65535(最多丢65535个石子,瓶子里的水就会溢出),也就是说定时器可以对时钟信号从0开始计数一直到65535,然后溢出产生中断请求。那么最大的定时周期就是1us*65535 =65.535ms;

    当然:65.535ms的定时时间对于我们也许有一些鸡肋,但是我们可以认为的设置定时器初始的值(即可以设置这个瓶子里原来有多少石子),来控制定时的时间。

    举个栗子:我们需要定时1ms,则我们可以设置:
    TH0 = (65535-1000)/256 = 0xFC;
    Tl0 = (65535-1000)%256 = 0x18;
    这样,我们就可以控制定时器每1ms溢出一次

    PS:STC15系列单片机有两种计数模式,一种是12T模式,每12个时钟周期加一,与传统的8051单片机相同;另一种时1T模式,每一个时钟信号加一,速度时12T模式的12倍,但一般默认是12T模式。

    作为计数器使用时计数信号的来源是非周期性的外部输入信号。外部引号可以有对应的引脚的脉冲信号输入,其对应关系如下:
    定时器0—>P3.4; 定时器1—>P3.5;定时器2—>P3.1;

    2.相关寄存器

    1.定时器/计数器0/1控制寄存器TCON(可位寻址)

    TR0/TR1分别为定时器0/1的运行控制位,TR0/TR1为1时表示打开定时器0/1,为0时表示关闭

    TF0/TF1为定时器0/1溢出中断标志,当定时器0/1溢出时由硬件置1,在CPU处理中断请求后自动清0,也可以由软件清0。(知道就行,不用管)

    2.定时器/计数器工作模式寄存器TMOD(不可位寻址)

    该寄存器的前四位控制定时器1,后四位控制定时器0。
    GATE控制定时器打开方式。
    C/T可以选择定时器的工作方式:为0时工作为定时器,为1时工作为计数器。
    M1/M0控制定时器的工作模式:一共有四种。

    3.辅助寄存器AUXR(不可位寻址)
    主要负责对定时器0/1的速度控制,以及定时器2的相关配置

    T0x12/T1x12/T2x12:控制定时器0/1/2的速度模式,为0时为12T模式,为1时为1T模式,默认是12T模式。
    T2R::定时器2允许控制位,为1时表示打开定时器2,反之则关闭。
    T2_C/T :为0表示工作为定时器,为1表示工作为计数器。

    4.定时器T0heT1的中断控制寄存器IE和IP(可位寻址)

    ET0/ET1:定时器T0/T1的中断允许位,为1时表示允许中断,反之则不允许
    PT0/PT1:设置定时器T0/T1的中断优先级,为1设置为高优先级,为0设置低优先级。

    5.定时器0/1/2高八位/低八位寄存器TLH0/TL0/TH1/TL1/T2H/T2L

    前面讲过,定时器的本质是一个加法计数器,定时器的高八位/低八位寄存器的作用就是用来存放这个计数器的值,当计数器的值到达最大值溢出时,产生中断请求。

    3.工作模式

    1.16位自动重装载

    在M1 = M0 = 0时,定时器工作在工作模式0(16位自动重装载)。
    在定时器溢出之后,会自动装载为原来设置的TL0/TH0。

    2.16位不可重装载

    在M1 =0; M0 = 1时,定时器工作在工作模式1(16位不可重装载),
    在定时器溢出之后,需要在中断服务函数中设置TL0和TH0。不可自动重装载。

    3.八位自动重装载

    当M1 = M0 = 1时,定时器工作在工作模式2(八位自动重装载)
    此时定时器的计数范围位0~255,在定时器溢出时,TL0=TH0,将Th0的值装载的TL0中,实现八位自动重装载。可以通过改变TH0的值去控制定时器溢出的时间。

    4.不可屏蔽中断16位自动重装载
    由于这个模式几乎用不到,所以我们不讲。

    PS:定时器2的工作模式固定位16位自动重装载,不可更改。

    4.定时器中断配置

    配置定时器中断,一般有一下几个步骤:

    初始化函数

    1.设置定时器工作模式
    2.设置定时器高八位/低八位寄存器的值(设置定时器溢出时间)
    3.开启中断允许
    4.设置定时器中断优先级(定时器2不可以设置优先级)
    5.开启定时器

    具体代码如下:

    //定时器0初始化函数,1ms进来一次
    void Timer0_Init(void)
    {
        TMOD |= 0x00;    //设置定时器0工作模式0,16位自动重装载
    //    TMOD |= 0x01;    //设置定时器0工作模式1
    //    TMOD |= 0x02;    //设置定时器0工作模式2,8位自动重装载
        TL0 = 0x18;     //设置定时器溢出时间
        TH0 = 0xFC;
        ET0 = 1;        //开启定时器0中断允许
        EA=1;           //开启总中断允许
        TR0 = 1;        //开启定时器0
        PT0 = 0;        //设置优先级为低优先级,为1时设置为高优先级
    }
    //定时器0初始化函数,1ms进来一次
    void Timer1_Init(void)
    {
        TMOD |= 0x00;    //设置定时器1工作模式0,16位自动重装载
    //    TMOD |= 0x10;    //设置定时器1工作模式1
    //    TMOD |= 0x20;    //设置定时器1工作模式2,8位自动重装载
        TL1 = 0x18;     //设置定时器溢出时间
        TH1 = 0xFC;
        ET1 = 1;        //开启定时器0中断允许
        EA=1;           //开启总中断允许
        TR1 = 1;        //开启定时器0
        PT1 = 0;        //设置优先级为低优先级,为1时设置为高优先级
    }
    //定时器2初始化函数,1ms进来一次
    //定时器2工作模式固定为16位自动重装载模式
    void Timer2_Init(void)
    {
       AUXR |= 0x10;        //将T2R设置为1,打开定时器2
        IE2 |= 0x04;     //开启定时器2中断允许
        T2H = 0xFC;     //设置定时器2中断溢出时间
        T2L = 0x18;     
        EA = 1;         //开启中断总允许
    }
    

    中断服务函数

    6.编写定时器中断服务函数
    具体中断号与定时器对于关系如下:
    定时器0 interrupt 1
    定时器1 interrupt 3
    定时器2 interrupt 12

    具体代码如下:

    //定时器0服务函数
    void External_Hander0() interrupt 1
    {
        //函数内容
    }
    
    //定时器1服务函数
    void External_Hander2() interrupt 3
    {
        //函数内容
    
    }
    
    //定时器2服务函数
    void External_Hander2() interrupt 12
    {
        //函数内容
    
    }
    

    实战环节

    1.任务要求

    2.实现思路

    首先我们有一个Tick值,可以通过Tick得到数码管需要显示的数字,每50msTick++,数码管需要显示的数字也随之改变,就可以实现模拟秒表的走时功能,
    然后通过外部中断0/S5按下,将Tick清0,数码管上的数字也随之清0.
    通过外部中断1/S4按下,控制定时器的开启和关闭,实现暂停和启动的功能

    3.代码实现

    1.mian.c

    #include <STC15F2K60S2.H>
    #include "Interrupt.h"
    #include "LS138.h"
    
    unsigned char arr_NixieTube[8]; //用于存放数码管显示的内容
    unsigned long int Tick = 0;     //用于计算数码管所需要显示的内容
    
    //通过数码管显示的内容计数的到Tick的初始值
    void arr_NixieTube_Init(void)
    {
        arr_NixieTube[0]=0;
        arr_NixieTube[1]=8;
        arr_NixieTube[2]=21;
        arr_NixieTube[3]=2;
        arr_NixieTube[4]=6;
        arr_NixieTube[5]=21;
        arr_NixieTube[6]=1;
        arr_NixieTube[7]=8;
        Tick = arr_NixieTube[7] + 10*arr_NixieTube[6] + arr_NixieTube[4]*60 + 
        arr_NixieTube[3]*600 + arr_NixieTube[1]*3600 +arr_NixieTube[0]*360000;
    }
    
    //通过Tick的值,得到数码管需要显示的内容
    void Tick_To_arr(void)
    {
        arr_NixieTube[0] = Tick/12000;
        arr_NixieTube[1] = Tick/1200%10;
        arr_NixieTube[3] = Tick/200%10;
        arr_NixieTube[4] = Tick/20%10;
        arr_NixieTube[6] = Tick/10%2;
        arr_NixieTube[7] = Tick%10;
    }
    //初始化函数
    void Init(void)
    {
        Timer0_Init();
        LS138_Init();
        IT0_Init();
        IT1_Init();
        arr_NixieTube_Init();
    }
    
    void main()
    {
        Init();
    	while(1)
    	{
            Tick_To_arr();  //在数码管显示之前,通过Tick的值,得到数码管需要显示的内容
    		SEG_Write(arr_NixieTube);   //数码管显示内容
    	}
    }
    
    
    //外部中断0服务函数
    void External_Hander0() interrupt 0
    {
        Tick = 0;       //将Tick清0
    }
    
    //外部中断1服务函数
    void External_Hander2() interrupt 2
    {
        
        TR0 = ~TR0;     //开启/关闭定时器
    }  
    
    void External_Hander1() interrupt 1
    {
        static unsigned char Timer0_Count=0;
    //    TL0 = 0x18;
    //    TH0 = 0xFC;
        Timer0_Count++;
        if(Timer0_Count>=50)
        {
            Timer0_Count=0;     //每50msTick++
            Tick++;
        }
    }    	
    

    PS: void SEG_Write(unsigned char* p) 这个函数出现在“蓝桥杯单片机学习——数码管静态显示”,

    2.Interrupt.c

    #include "Interrupt.h"
    
    //外部中断0初始化函数
    void IT0_Init(void)
    {
        EX0 = 1;    //开启外部中断0允许
        EA=1;       //开启中断总允许
        IT0=1;      //设置触发方式为下降沿触发,为0时触发方式为下降沿和上升沿都触发
        PX0 = 0;    //设置低优先级,为1时为高优先级
    }
    //外部中断1初始化函数
    void IT1_Init(void)
    {
        EX1 = 1;    //开启外部中断0允许
        EA=1;       //开启中断总允许
        IT1=1;      //设置触发方式为下降沿触发,为0时触发方式为下降沿和上升沿都触发
        PX1 = 0;    //设置低优先级,为1时为高优先级
    }
    
    //定时器0初始化函数,1ms进来一次
    void Timer0_Init(void)
    {
        TMOD |= 0x00;    //设置定时器0工作模式0,16位自动重装载
    //    TMOD |= 0x01;    //设置定时器0工作模式1
    //    TMOD |= 0x02;    //设置定时器0工作模式2,8位自动重装载
        TL0 = 0x18;     //设置定时器溢出时间
        TH0 = 0xFC;
        ET0 = 1;        //开启定时器0中断允许
        EA=1;           //开启总中断允许
        TR0 = 1;        //开启定时器0
        PT0 = 0;        //设置优先级为低优先级,为1时设置为高优先级
    }
    

    3.Interrupt.h

    #ifndef __INTERRUPT_H_
    #define __INTERRUPT_H_
    #include <STC15F2K60S2.H>
    
    //外部中断0初始化函数
    void IT0_Init(void);
    
    //外部中断1初始化函数
    void IT1_Init(void);
    
    //定时器0初始化函数,1ms进来一次
    void Timer0_Init(void);
    
    
    外部中断0服务函数
    //void External_Hander0() interrupt 0
    //{
    //    
    //}
    
    外部中断1服务函数
    //void External_Hander2() interrupt 2
    //{
    //    
    //} 
    
    定时器0服务函数
    //void External_Hander0() interrupt 1
    //{
    //    
    //}
    
    
    
    #endif  /*__INTERRUPT_H_*/
    

    总结

    定时器在我们的程序编写工程中应用比较广泛,其中定时器0/1的使用频率最高,工作模式中16位自动重装载使用较多,需要重点掌握,使用方法并不难,但需要理解清楚定时器的工作原理,其他的就没有了,好好学习吧。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 蓝桥杯单片机学习6:深入理解定时器和计数器原理

    发表评论