当前位置: 首页 > article >正文

【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位)GATETMOD.3控制定时器0,置1时只有在INT0脚为高及TR0控制位置1时才可打开定时器/计数器0
TMOD.2C/TTMOD.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>


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


http://www.kler.cn/a/373791.html

相关文章:

  • 电商直播带货乱象频出,食品经销商如何规避高额损失?
  • KOA——基于Node的Web框架
  • ASPICE 4.0引领自动驾驶未来:机器学习模型的特点与实践
  • 瑞格智慧心理服务平台 NPreenSMSList.asmx sql注入漏洞复现
  • Ollama接口文档中文版
  • git:将多个提交合并为一个
  • 论文阅读-A gated cross-domain collaborative network for underwater object detection
  • 【热门主题】000015 大数据治理:开启数据价值新纪元
  • DEVOPS: 认证与调度
  • muduo库TcpConnection类源码解析——链接管理
  • 数学建模学习(130):使用Python基于模糊TOPSIS算法的多准则决策分析
  • 聆听用户声音的3个方法,挖掘客户真实潜在需求
  • 24年10月Google Play政策更新通知
  • 字符串逆序(c语言)
  • 在Android开发中实现静默拍视频
  • 数智时代:以低代码开发为催化剂 加速中国制造转型升级
  • 适配器模式适用的场景
  • PostgreSQL用load语句加载插件
  • Spring 设计模式之装饰器模式
  • Vue组件学习 | 八、 v-bind指令
  • Spring Boot 集成 RabbitMQ
  • linux上使用scp从windows往linux传数据
  • 易优cms webshell
  • STM32 第17章 EXIT--外部中断/事件控制器
  • 【福建医科大学附属第一医院-注册安全分析报告】
  • nvm 版本管理工具