江科大51单片机学习笔记之定时器与中断系统

一、定时器概念

定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成

定时器作用:

(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作

(2)替代长时间的Delay,提高CPU的运行效率和处理速度

二、STC89C52定时器资源

•定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机(STC89C52)增加的资源

•注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的

三、定时器框图

定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行

51-定时器框图
四、定时器工作模式

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

模式0:13位定时器/计数器

模式1:16位定时器/计数器(常用)

模式2:8位自动重装模式

模式3:两个8位计数器

模式1较为常用,本文主要讲解模式1,定时器0的模式1的原理图如下

在这里插入图片描述

工作原理:TL0和TH0是定时器T0的计数器部分(TH0和TL0是两个8位寄存器),系统产生时钟脉冲,每来一个脉冲信号,计数器就+1,当计数器加到最大值65535(模式1,两个8位计数器合并为一个16位计数器,最大值65535)时,就会产生中断,同时定时器0的中断标志位也会置1(TF0置1),计数器重新置为0。类似于沙漏。

时钟部分:SYSclk:系统时钟,即晶振周期,以12MKZ晶振为例,接入系统时钟后,可以选择12分频或者6分频,例如12分频后,脉冲频率就为1MHz,而C/T是寄存器中的一位(TMOD中的一位),C/T=1时,接入系统时钟,为定时器,C/T为0,通过T0引脚接入外部时钟,为计数器

注:本开发板上的晶振为11.0592MHz

五、中断系统
中断系统介绍

当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。微型机的中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。

简单来说,就是程序执行过程中,有个更紧急的事件请求执行,CPU转去执行这个中断程序,执行完毕后再返回到原来的程序继续执行

image-20230803153225452

如果在某个中断程序执行过程中,又有个更高级别的中断请求(可以理解为更紧急的事件)响应,则CPU会转去执行这个级别更高的中断程序,执行完后再返回原来的中断程序继续执行,这叫中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。

(中断具体执行过程可见计算机组成原理或操作系统)

STC89C52中断资源

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

中断优先级个数:4个

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

本单片机是多级中断系统。高优先级的中断请求可以打断低优先级的中断,反之,低优先级的中断请求不可以打断高优先级及同优先级的中断。当两个相同优先级的中断同时产生时,将由查询次序来决定系统先响应哪个中断。

STC89C52的各个中断查询次序如下表所示:

image-20230803153956344
中断号

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

image-20230803154102217

注:这里的中断服务函数的名称是可以自行改变的,但是中断号是固定的

发生中断时,程序会自动执行上面对应某个中断的中断服务函数,执行完这个函数后,继续回到主程序执行后续程序

中断寄存器

与中断相关的寄存器如下:

image-20230803160213075

1.中断允许寄存器IE
单片机CPU对中断源的开放或屏蔽,每一个中断源是否被允许中断,是由内部的中断允许寄存器IE和XICON控制的。寄存器IE的格式如下:

image-20230803154630501

EA:CPU的总中断允许控制位,EA=1,CPU开放中断,EA=0,CPU屏蔽所有的中断申请。EA的作用是使中断允许形成两级控制。即各中断源首先受EA控制;其次还受各中断源自己的中断允许控制位控制。(要允许某个中断,必须将EA置为1)

**ET2:**定时/计数器T2的溢出中断允许位。ET2=1,允许T2中断;ET2=0,禁止T2中断。

**ES:**串行口1中断允许位。ES=1,允许串行口1中断;ES=0,禁止串行口1中断。

**ET1:**定时/计数器T1的溢出中断允许位。ET1=1,允许T1中断;ET1=0,禁止T1中断。

**EX1:**外部中断1中断允许位。EX1=1,允许外部中断1中断;EX1=0,禁止外部中断1中断。

**ET0:**T0的溢出中断允许位。ET0=1,允许TO中断;ET0=0禁止TO中断。

**EX0:**外部中断0中断允许位。EX0=1,允许中断;EX0=0禁止中断。

2.定时器/计数器0/1控制寄存器TCON

image-20230803160336855

上面两个是与定时器0/1紧密相关的两个寄存器

其余寄存器见参考手册的中断章节

六、定时器相关的寄存器

定时器0/1的相关寄存器如下:

image-20230803155319241
1.定时器/计数器控制寄存器TCON
image-20230803155429124
2.定时器/计数器工作模式寄存器TMOD
image-20230803155543748

TL0和TH0是两个8为寄存器,是定时器0的计数值存放的位置,TL1和TH1也是同理

七、定时器与中断系统
image-20230803155717990

为了方便讲解,这里使用的中断系统图是传统51单片机的图(以定时器0为例),实际的图见下图。

C/T是用来选择内部系统时钟还是外部时钟,内部时钟又可以选择6分频或者12分频(本单片机已经固定为了12分频),之后由TR0、GATE和INT0三个位来决定定时器0是否能有效工作。如果GATE为0,取反为1,因为GATE和INT0是用或门连接,所以GATE经过非门为1时,与门的一端一定为1,则与门的输出由TR0决定,TR0为1时,定时器0能工作。如果GATE为1,取反为0,则只有当TR0和INT0都为1时定时器0才能正常工作。详细可见上面的寄存器的各个位的描述。

定时器0正常工作后,计数溢出后会产生定时器0中断T0,此时中断标志位TF0会被置1,若中断使能位ET0和EA都为1,则中断允许执行,设置一个中断优先级后便可进入中断入口,执行中断服务函数。

上图的中断优先级只有0和1,实际本单片机的中断优先级有4级,如下图

实际的STC89C52的中断结构图

image-20230803150915313
八、实验一:按键控制LED流水灯方向

使用定时器T0的模式1,计数范围为0-65535,本单片机为12分频,则时钟频率为1MHz(以12MHz为例),即每隔1us计一次数。计数器在65535时溢出,则,如果想定时1ms,则需要计数1000次,故可设置计数器初始值为64535(江科大写法),但这会有1us的误差,故计数器初始值可以设置为64536

定时器0初始化

TMOD不可位寻址,只能直接操作寄存器,SMOD可以位寻址,直接操作对应的位

本实验设置GATE为0,根据上述分析,则定时器是否开始由TR0决定

void timer0_init()
{
	//TMOD = 0x01;//模式1 0000 0001 
	//上面这一行代码可以替换为下面两行代码,这样只处理TMOD的低四位的T0部分,而高四位的T1部分不变,从而不影响定时器1
	TMOD &= 0xF0;//按位与,将TMOD的低四位清零,高四位不变
	TMOD |= 0x01;//按位或,将TMOD的最低位置1,其余位不变
	
	TF0 = 0;//也可以不设置,默认为0
	TR0 = 1;//开启定时器
	//计数器
	TH0 = 64535/256;//高8位
	TL0 = 64535%256;//低8位
	//中断配置
	ET0 = 1;//使能T0中断
	EA = 1;//所有中断使能
	PT0 = 0;//设置中断优先级,为低优先级
}
定时器0中断服务函数
unsigned int count = 0;//也可以用静态局部变量
//定时器0的中断处理函数
void Timer0_Routine() interrupt 1
{
	//重新赋值计数器,因为计数器溢出后会变为0,要1ms产生一次中断,需要重新给计数器赋值为64535
	TH0 = 64535/256;
	TL0 = 64535%256;
	//1ms产生一次中断,每次中断count+1,1000次后再进行处理,此时处理的时间间隔就为1s
	count++;
	if(count >= 1000)
	{
		count = 0;
		P2_0 = ~P2_0;
	}
	
}
测试

测试LED1每隔一秒状态发生转换

#include <REGX52.H>
unsigned int count = 0;//也可以用静态局部变量
void timer0_init()
{
	//TMOD = 0x01;//模式1 0000 0001 
	//上面这一行代码可以替换为下面两行代码,这样只处理TMOD的低四位的T0部分,而高四位的T1部分不变,从而不影响定时器1
	TMOD &= 0xF0;//按位与,将TMOD的低四位清零,高四位不变
	TMOD |= 0x01;//按位或,将TMOD的最低位置1,其余位不变
	
	TF0 = 0;//也可以不设置,默认为0
	TR0 = 1;//开启定时器
	//计数器
	TH0 = 64535/256;//高8位
	TL0 = 64535%256;//低8位
	//中断配置
	ET0 = 1;//使能T0中断
	EA = 1;//所有中断使能
	PT0 = 0;//设置中断优先级,为低优先级
}

void main()
{
	timer0_init();
	while(1)
	{
		
	}
}

//定时器0的中断处理函数
void Timer0_Routine() interrupt 1
{
	//重新赋值计数器,因为计数器溢出后会变为0,要1ms产生一次中断,需要重新给计数器赋值为64535
	TH0 = 64535/256;
	TL0 = 64535%256;
	//1ms产生一次中断,每次中断count+1,1000次后再进行处理,此时处理的时间间隔就为1s
	count++;
	if(count >= 1000)
	{
		count = 0;
		P2_0 = ~P2_0;
	}
	
}
软件生成定时器初始化函数

定时器的初始化函数也可以通过STC-ISP软件直接生成,如图所示。选择定时器计算器,以12MHz为例,选择对应的定时器、定时长度、定时器模式。选择定时器时钟为12T,因为本单片机使用的12分频,如果想使用6分频,可选择左侧的使能6T(双倍速)模式,进行软件上的倍频。(注:本单片机是11.0592MKz,但是12MKz也可以用,毕竟这里的实验对时间精度要求不高)

51-定时器计算器修改版2

AUXR |= 0x7F;这行删除,因为单片机内部已经设置为12分频了,且没有这个AUSR寄存器。再加上中断的配置即可

void Timer0Init(void)		//1毫秒@12.000MHz
{
	//AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时

	//中断
	ET0 = 1;//使能T0中断
	EA = 1;
	PT0 = 0;//设置中断优先级,为低优先级
}
使用定时器总结

使用定时器主要分为三步

1)利用ISP生成对应定时器的初始化函数,加上中断的配置

2)编写对应的中断处理函数

3)调用定时器初始化函数

实验最终逻辑

接下来是按键控制LED流水灯流动方向,按下K1,可以改变流水灯的方向

main.c

#include <REGX52.H>
#include "Timer.h"
#include "Key.h"
#include <INTRINS.H>

unsigned char keyNum = 0, LEDMode = 0;
void main()
{
	Timer0Init();
	P2 = 0xFE;
	while(1)
	{
		keyNum = GetKeyNum();
		if(keyNum)
		{
			if(keyNum == 1)//按下K1,改变流水灯的方向
			{
				LEDMode= LEDMode == 0?1:0;
			}
		}
	}
}

//定时器0的中断处理函数
void Timer0_Routine() interrupt 1
{
	static unsigned int count = 0;
	//重新赋值计数器,因为计数器溢出后会变为0,要1ms产生一次中断,需要重新给计数器赋值为64535
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	//1ms产生一次中断,每次中断count+1,1000次后再进行处理,此时处理的时间间隔就为1s
	count++;
	if(count >= 1000)
	{
		count = 0;
        //LEDMode为0,左移 LEDMode为1,右移 _crol_()为循环左移函数  _cror_()为循环右移函数
		P2 = LEDMode == 0?_crol_(P2,1):_cror_(P2,1);
	}
	
}

Key.c

#include <REGX52.H>
#include "Delay.h"
/**
  * @brief  获取独立按键的值
  * @param  无
  * @retval 返回独立按键的值,若没有按键按下,返回0
  */
unsigned char GetKeyNum()
{
	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.c

#include <REGX52.H>

/**
  * @brief  定时器0初始化,1ms
  * @param  无
  * @retval 无
  */
void Timer0Init(void)		//1毫秒@12.000MHz
{
	//AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时

	//中断
	ET0 = 1;//使能T0中断
	EA = 1;
	PT0 = 0;//设置中断优先级,为低优先级
}
九、实验二:定时器时钟
#include <REGX52.H>
#include "Timer.h"
#include "LCD1602.h"

unsigned char second = 0, minute = 0, hour = 0; 
void main()
{
	Timer0Init();
	LCD_Init();
	LCD_ShowString(1,1,"clock:");
	LCD_ShowString(2,1,"  :  :");
	
	while(1)
	{
		LCD_ShowNum(2,1,hour,2);
		LCD_ShowNum(2,4,minute,2);
		LCD_ShowNum(2,7,second,2);
	}
}

//定时器0的中断处理函数
void Timer0_Routine() interrupt 1
{
	static unsigned int count = 0;
	//重新赋值计数器,因为计数器溢出后会变为0,要1ms产生一次中断,需要重新给计数器赋值为64535
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	//1ms产生一次中断,每次中断count+1,1000次后再进行处理,此时处理的时间间隔就为1s
	count++;
	if(count >= 1000)
	{
		count = 0;
		second++;
		if(second == 60)
		{
			second = 0;
			minute++;
			if(minute == 60)
			{
				minute = 0;
				hour++;
				if(hour == 24)
				{
					hour = 0;
				}
			}
		}
	}
	
}

更多51单片机笔记见主页

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值