【51单片机】中断&定时器原理解析 + 使用

学习使用的开发板:STC89C52RC/LE52RC
编程软件:Keil5
烧录软件:stc-isp

开发板实图:

文章目录

  • 中断
  • 定时器
  • 定时器工作模式
  • 定时器相关寄存器
  • 编码
  • 初始化定时器
  • 独立按键改变LED流水灯方向
  • 在介绍定时器前,需要先对中断有所了解,定时器的核心机制之一就是使用了中断

    中断

    中断是为使CPU具有对外界紧急事件的实时处理能力而设置的
    当CPU正在处理某件事时,外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完后,再回到原来被中断的地方,继续原来的工作,这个过程被称为中断


    中断程序流程

    左侧图:在主程序执行期间,发现有中断请求到来,会先打个断点,然后进行中断响应,执行中断处理程序,完成后返回断点,继续执行主程序
    右侧图:以程序地址空间的形式展示处理中断的过程,发生中断时,会跳转到中断处理程序的地址空间,执行完再跳转回主程序的地址空间


    中断资源
    中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)

    中断优先级个数:4个

    如果使用C语言编程,中断查询次序号就是中断号

    其中 Timer0_Routine(void) 为定时器T0的中断处理函数,中断号为1,Timer1_Routine(void)为定时器T1的中断处理函数,中断号为3,以此类推

    注意:中断资源和单片机型号是关联在一起的,不同型号可能会有不同的中断资源,例如中断资源个数不同,中断优先级个数不同等

    如下是一个较为简单的中断结构图,只有两个优先级

    左侧的INT0INT1 为外部中断0和1,T0T1 为定时器中断0 和 1
    可以看到,要想让中断启用,还需要连通电路。例如想让定时器中断0启动,还需要将ET0 = 1EA = 1PT0选择一个优先级,0或1都可


    定时器

    我们对中断已经有了一些了解,接下来我们介绍定时器

    定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
    引入如下场景:独立按键控制LED流水灯的方向

  • 对于LED流水灯,我们可以控制每隔1秒改变亮灯
  • 对于独立按键,我们不光要循环检测是否有按键按下,还需要等待按下的按键松开
  • LED灯的操作是周期性的,独立按键的检测则是时刻不停的,我们无法将两个任务线性组合在一起。
    例如检测到按键按下,还需要等待按键松开,那么此时线性的执行会被卡在等待独立按键松开,而无法让LED灯呈现流水状。此时就可以使用 定时器 完成该需求


    定时器作用:

  • 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
  • 替代长时间的Delay,提高CPU的运行效率和处理速度
  • 定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0T1 是所有51单片机所共有的。
    STC89C52 共有 3 个定时器(T0、T1、T2),T0和T1与传统的51单片机兼容,T2 是此型号增加的资源


    定时器框图

    定时器在单片机内部就像一个小闹钟,根据时钟输出信号,每隔"一定时间",计算单元的数值就增加1,当计数单元数值增加到"设定的闹钟提醒时间时",计算单元就会向中断系统发出中断请求,产生"响铃提醒",使程序跳转到对应的中断处理函数中


    定时器工作模式

    STC89C52的 T0 和 T1均有四种工作模式

  • 模式0:13位定时器/计数器
  • 模式1:16位定时器/计数器(常用)
  • 模式2:8位自动重装模式
  • 模式3:两个8位计算器
  • 工作模式1框图:

    该框图由时钟系统、计数系统和中断系统三部分组成

    计数系统

  • 中间的TH0(time high)TL0(time low)是计数单元,TH0为高8位,TL0为低8位,所以总共能计数 0 ~ 65535。当时钟系统发出一个脉冲周期后,计数系统每收到一个脉冲周期,计数单位就会 + 1,当超出最大值后溢出,发出中断请求
  • 时钟系统
    时钟系统有两个来源,SYSclkT0 Pin引脚

  • SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHz。该时钟系统有两条线路,分别为12T分频和6T分频。如果为12T分频,则输出为1MHz,周期为1微秒,即每隔1微秒输出到计数系统一次,进行一次计数
  • 控制部分

  • C/T=0,多路开关连接到系统时钟的分频输出,T0对时钟周期计数,T0工作在定时方式C/T=1,多路开关连接到外部脉冲输入P3.4/T0,即工作在计数方式
  • GATE=0(TMOD.3)时,如TR0=1,则定时器计数。GATE = 1,允许由外部输入INT0控制定时器0,这样可以实现脉宽测量

  • 定时器和中断系统工作方式如下

    定时器相关寄存器

    寄存器是连接软硬件的媒介

    在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式

    寄存器相当于一个复杂机器的“操作按钮”


    因为TCON涉及到TMOD,我们先讲解TMOD
    TMOD定时器/计数器工作模式寄存器

    1. TMOD高4位为控制定时器1,低四位为控制定时器0

    2. 定时和计数功能由特殊功能寄存器TMOD的控制位C/T进行选择;

    3. M1和M0组合决定定时器/计数器的工作模式,共4种模式,其中模式0、1、2两个定时器相同,模式3不同

    后续操作使用定时器T0,所以着重讲解定时器T0,定时器T1配置基本相同

    符号 功能
    TMOD.3(第3位) GATE TMOD.3控制定时器0,置1时只有在INT0脚为高及TR0控制位置1时才可打开定时器/计数器0
    TMOD.2 C/T TMOD.2控制定时器0用作定时器或计数器清0用作定时器(从内部系统时钟输入),置1用作计数器(从T0/P3.4脚输入)

    GATE的逻辑可参看下图

  • 当GATE = 0时,经过非门取反为1,则或门一定为1,此时只要TR0 = 1即可开启定时器0
  • 当GATE = 1时,经过非门取反为0,需要INT0 = 1,或门才为1,还需要TR0 = 1才可开启定时器0
  • M1M0用作选择工作模式


    TCON
    TCON为定时器/计数器T0、T1的控制寄存器,同时也锁存T0、T1溢出中断源和外部请求中断源等,TCON格式如下:

    TF1,TR1TF0,TR0作用相同,只是前者是控制定时器T1,后者是控制定时器T0。后续操作使用定时器T0,所以讲解TF0和TR0

  • TF0定时器/计数器T0溢出中断标志。T0被允许计数后,从初值开始计数+1,当最高位产生溢出后,由硬件置“1”TF0向CPU请求中断,一直保持CPU响应该中断时,才由硬件清零“0”TF0(也可由程序查询清“0”)
  • TR0定时器T0的运行控制位。该位由软件置位和清零。当GATE(TMOD.3) = 0TR0 = 1就允许T0开始计数,TR0 = 0禁止计数;GATE(TMOD.3) = 1,TR0 = 1且INT0输入高电频,才允许T0计数
  • IE0外部中断0请求源(INT0P3.2)标志。IE0 = 1外部中断0向CPU请求中断,当CPU响应外部中断时,由硬件清“0”IE0(边沿触发方式)
  • IT0外部中断0触发方式控制位IT0 = 0时,外部中断0为低电平触发方式,当INT0(P3.2)输入低电频时,置位IE0。采用低电频触发方式时,外部中断源(输入到INT0)必须保持低电频有效,直到该中断被CPU响应,同时在该中断服务程序执行完之前,外部中断源必须被清除(P3.2要变高),否则将产生另一次中断。当IT0 = 1时,则外部中断0(INT0)端口由“1” -> “0” 下降沿跳变,激活中断请求标志位IE0,向主机请求中断处理
  • 编码

    初始化定时器

    初始化定时器主要分为三部分:定时器选择,中断系统,计数单元

    定时器选择

    1. 首先我们要选择定时器,而非计数器。通过TMODC/T = 0
    2. 接下来,选择定时器工作模式——16位定时器/计数器,即TH0和TL0全用。通过TMODM0 = 0, M1 = 1

    因为TMOD是不可位寻址,只能8位一起赋值,不能向P2I/O口那样,可以使用P2_1 = 1,单独给第一位赋1

    因为我们使用定时器T0,所以保持定时器T1部分配置不变

    TMOD &= 0xF0; 	//高4位保持不变,低4位清零
    TMOD |= 0x01; 	//高4位保持不变,低4位为0001
    

    计数单元
    系统频率为12MHz,我们选择12T模式,则到达计数单元的时钟为1MHz,即1us 计数加1
    16位定时器范围为0 ~ 65535,我们想实现1ms的周期,可以设置初值为64535,距离溢出为1000us,即1ms

    TH0 = 64535 / 256;		//高8位
    TL0 = 64535 % 256;		//低8位
    

    但其实定时器溢出是达到65536才会发出中断请求,上述设置会有1us的误差,追求准确的话,TL0再加1即可

    TH0 = 64535 / 256;		//高8位
    TL0 = 64535 % 256 + 1;		//低8位
    

    中断系统

  • 首先要打开定时器的中断开关。通过TCONTF0和TR0
  • TF0 = 0; 				//溢出标志位,初始化为0,硬件置1
    TR0 = 1;				//允许中断
    
  • 其次打开中断系统开关
  • //中断开关
    ET0 = 1;				//定时器0的中断开关
    EA = 1;					//中断总开关
    PT0 = 0;				//中断优先级为低
    

    到此定时器的初始化就完成了,完整代码如下:

    #include <REGX52.h>
    /**
      * @brief  初始化定时器0
      * @parm	无
      * @retval	无
      */
    void Timer0Init()
    {
    	//TMOD选择定制器0,不可位寻址,只能一起赋值
    	//高4位是定时器1,低4位是定时器0
    	//从高到底分别为GATE,C/T,M1,M0,详细看说明手册
    	//M1, M0 为01,选择16位定时器
    	TMOD &= 0xF0; 	//高4位保持不变,低4位清零
    	TMOD |= 0x01; 	//高4位保持不变,低4位为0001
    	//定时器初值,每当超过65535,会发生一次中断,1us + 1
    	//设置初值为64535,距离超时有1000us,即1ms
    	TH0 = 64535 / 256;		//高8位
    	TL0 = 64535 % 256 + 1;		//低8位
    	//定时器中断
    	TF0 = 0; 				//溢出标志位,初始化为0,硬件置1
    	TR0 = 1;				//允许中断
    	//中断开关
    	ET0 = 1;				//定时器0的中断开关
    	EA = 1;					//中断总开关
    	PT0 = 0;				//中断优先级为低
    }
    

    也可以使用STC-ISP直接生成定时器初始化方法


    注意上述配置都要选择正确,不然无法达到预期效果
    其中代码中的AUXR &= 0x7F是较高版本的51单片机的新配置,本型号不支持,需要将该行代码删除

    定时器时钟只有1T和12T模式,要选择6T,可在左侧设置


    中断处理函数
    定时器0的中断号为1, 中断处理函数如下:

    void Timer0_Rountine(void) interrupt 1
    {
    	
    }
    

    我们要实现每隔1s进行的任务,可使用额外的变量辅助

    void Timer0_Rountine(void) interrupt 1
    {
    	static unsigned int timeCount = 0;
    	timeCount++;
    	if(timeCount >= 1000)
    	{
    		timeCount = 0;
    	}
    }
    

    注意:每次发送中断,还需要对计数单元进行初始化,否则计数单元溢出就不是1us了

    void Timer0_Rountine(void) interrupt 1
    {
    	TH0 = 64535 / 256;			//高8位
    	TL0 = 64535 % 256 + 1;		//低8位
    	static unsigned int timeCount = 0;
    	timeCount++;
    	if(timeCount >= 1000)
    	{
    		timeCount = 0;
    	}
    }
    

    独立按键改变LED流水灯方向

    使用模块化编程
    延迟器模块——用于等待按键松开
    Delay.h

    #ifndef __DELAY_H__
    #define __DELAT_H__
    
    void Delayms(unsigned char xms);//等待指定毫秒
    
    #endif
    

    Delay,c

    //等待指定时间,单位是毫秒
    void Delayms(unsigned char xms)		//@12.000MHz
    {
    	while(xms--)
    	{
    		unsigned char i, j;
    		i = 2;
    		j = 239;
    		do
    		{
    			while (--j);
    		} while (--i);
    	}	
    }
    

    独立按键模块——检测哪个按键按下
    SoleKey.h

    #ifndef __SOLEKEY_H__
    #define __SOLEKEY_H__
    
    unsigned char soleKey();
    
    #endif
    

    SoleKey.c

    #include <REGX52.h>
    #include "Delay.h"
    /**
      * @brief  获取独立按键的键码(哪个被按下)
      * @parm	无
      * @retval	独立按键的键码
      */
    unsigned char soleKey()
    {
    	unsigned char keyNum = 0;
    	if(P3_1 == 0){Delayms(20);while(P3_1 == 0);Delayms(20);keyNum = 1;}
    	if(P3_0 == 0){Delayms(20);while(P3_0 == 0);Delayms(20);keyNum = 2;}
    	if(P3_2 == 0){Delayms(20);while(P3_2 == 0);Delayms(20);keyNum = 3;}
    	if(P3_3 == 0){Delayms(20);while(P3_3 == 0);Delayms(20);keyNum = 4;}
    	return keyNum;
    }
    

    定时器模块——初始化定时器,提供定时器中断处理函数模板
    Timer.h

    #ifndef __TIMER_H__
    #define __TIMER_H__
    
    void Timer0Init();
    
    #endif
    

    Timer.c

    #include <REGX52.h>
    /**
      * @brief  初始化定时器0
      * @parm	无
      * @retval	无
      */
    void Timer0Init()
    {
    	//定时器模式
    	//TMOD选择定制器0,不可位寻址,只能一起赋值
    	//高4位是定时器1,低4位是定时器0
    	//从高到底分别为GATE,C/T,M1,M0
    	//GATE = 0,只要TR0为1就可以打开定时器
    	//C/T = 0,选择定时器
    	//M1, M0 为01,代表选择模式1——16位定时器
    	//选择定时器T0
    	//所以低四位为0001
    	TMOD &= 0xF0; 	//高4位保持不变,低4位清零
    	TMOD |= 0x01; 	//高4位保持不变,低4位为0001
    	//计数单元中断
    	TF0 = 0; 				//溢出标志位,初始化为0,硬件置1
    	TR0 = 1;				//允许中断
    	//中断开关
    	ET0 = 1;				//定时器0的中断开关
    	EA = 1;					//中断总开关
    	PT0 = 0;				//中断优先级为低
    	//定时器初值,每当超过65535,会发生一次中断,1us + 1
    	//设置初值为64535,距离超时有1000us,即1ms
    	TH0 = 64535 / 256;		//高8位
    	TL0 = 64535 % 256 + 1;		//低8位
    	
    }
    
    定时器0中断函数模板
    //void Timer0_Rountine(void) interrupt 1
    //{
    //	TH0 = 64535 / 256;		//高8位
    //	TL0 = 64535 % 256 + 1;		//低8位
    //	static unsigned int timeCount = 0;
    //	timeCount++;
    //	if(timeCount >= 1000)
    //	{
    //		timeCount = 0;
    //	}
    //}
    

    主程序
    主函数循环检测按键按下,定时器0中断处理函数根据按键按下情况改变LED流水灯方向
    main.c

    #include <REGX52.h>
    #include <INTRINS.h>
    #include "Timer.h"
    #include "SoleKey.h"
    
    unsigned int timeCount = 0, mode = 0, keyNum = 0;
    /**
      * @brief  根据独立按键改变LED流水灯的方向
      * @parm	无
      * @retval 无
      */
    void Timer0_Routine(void) interrupt 1
    {
    	//注意每次中断都要初始化定时值
    	TH0 = 64535 / 255;		//高8位
    	TL0 = 64535 % 255;		//低8位
    	timeCount++;
    	if(timeCount >= 1000)
    	{
    		timeCount = 0;
    		if(mode == 0)//模式一,LED向右
    			P2 = _crol_(P2, 1);
    		else//模式二,LED向左
    			P2 = _cror_(P2, 1);
    	}
    }
    
      
    void main()
    {
    	Timer0Init();
    	P2 = 0xFE;//初始化LED灯为只有D1亮
        while(1)
        {
            keyNum = soleKey();
    		if(keyNum == 1)
    		{
    			mode++;
    			if(mode >= 2)	
    				mode = 0;
    		}
        }
    }
    
    

    补充:改变LED流水灯方向使用_cror__crol_这两个方法,其作用为循环右移和左移,不用考虑溢出导致的全0的情况,其函数声明在头文件<INTRINS.h>


    以上就是本篇博客的所有内容,感谢你的阅读
    如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。

    作者:好想有猫猫

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【51单片机】中断&定时器原理解析 + 使用

    发表回复