【单片机技术】—— 89C52中断和定时器操作1

一、中断

1.1 基本概念

其实中断的概念很好理解:试想一下你正在教室里面搞单片机(这相当于与CPU正在执行主程序)这时,防空警报响了(一个中断信号),因此你得停下你手头的工作,去响应这个防空警报(中断响应),这个防空警报需要你执行一些行为(比如逃出教室或者躲在课桌底下),当你执行了一段时间之后,防空警报结束了(中断服务执行完了),那么你就从新回到教室继续搞单片机。

这个过程就是中断。

我们先看看中断都有哪几种:(1)外部中断0【中断号 0】,它的优先级是最高的。(2)定时器/计数器0 【中断号1】 (3)外部中断1【中断号 2】 (4)定时器/计数器 1【中断号 3】
(5)串口中断【中断号 4】 (6)定时器/计数器 2 【中断号 5】

先看看下面的图:其中,TCON, IE, IP 等都是与中断有关的寄存器。
在这里插入图片描述
要想在单片机中给一个中断(我们以外部中断0为例),看看需要什么条件:

  1. 我们看到上图的 EA,它是中断总允许,也就是说,我们得把 EA 置成 1 ,才可能输入中断信号
  2. EA=1 之后,我们就要看中断源允许,也就是说,我们要选择输入的中断种类。EX0 表示外部中断0,ET0 表示计数器/定时器 T0 中断、、、
  3. 如果我们想给外部中断0(其实外部中断0和外部中断1原理一样),那么,我们还需要给出 I T 0 IT0 ,当这个信号是0时,表示低电平触发、1表示下降沿触发。

1.2 代码和实验部分

下面,我们想实现这样一个效果:
【代码一】:实现数码管1,3,5亮,每隔一定时间同时亮一个数,然后我们在一些特定时刻给它外部中断0,中断时,让CPU点亮第一个LED灯,我们看看是什么效果:

#include<reg52.h>
#define unchar unsigned char
#define uint unsigned int

sbit WEI = P2^7;    //定义控制数码管的位选信号的单片机端口位(或者说定义单片机的P2.7口是WEI)
sbit DULA = P2^6;   //定义单片机P2.6口是DULA(控制数码管的段选信号)
sbit D1 = P1^0;     //定义控制第一个LED的单片机端口位
sbit int0 = P2^3;
uchar num;

uchar code table[] = {0x3f, 0x06,0x5b,0x4f,0x66,0x6d,
                      0x7d,0x07,0x7f,0x6f,0x77,0x7c,
                      0x39,0x5e,0x79,0x71};    //定义数码管的编码表,为了节约内存空间
void delay(uint);

void main()
{
	EA=1;  //先打开中断总允许
	EX0=1;  //打开中断源允许EX0,表示允许外部中断0输入
	IT0 = 1;  //表示下降沿触发(因为一上电默认单片机P3.2口是高电平的)
	
	//下面是数码管的显示:先位选
	WEI = 1;    //打开位选
	P0 = 0xea;
	WEI = 0;    //锁住位选信号
	
	while(1)
	{
		for(num=0;num<16;num++)
		{
			DULA = 1;      //打开段选
			P0 = table[num];
			DULA = 0;
			if(num == 3)
			{
				//如果num == 3那么就给一个下降沿,即让P3.2口变成低电平
				int0 = 0;
			}
		}
	}
}

void exter0() interrupt 0
{
	D1 = 0;    //中断响应就是要先让第一个LED亮
	delay(1000);  //延时
	D1 = 1;    //灭LED
	int0 = 1;   //把P3.2口变回高电平,用以准备下一次的中断。

}

void delay(uint z)
{
	//延时函数
	uint x,y;
	for(x=z;x>0;x--)
		for(y=1000;y>0;y--);
}

也就是说:我们想要给出一个中断信号(这里我们暂时先不管定时器和串口)就以外部中断0、外部中断1为例。那么我们就首先需要打开总中断允许(即令 EA=1);接下来,就要选取中断种类(即令EX0, ET0, EX1, ET1,ES),选哪个就把那个置1,最后一步就是选择触发方式:(IT0, IT1)置1表示下降沿触发、置0表示低电平触发

二、定时器

2.1 原理简析

这是单片机里面一个相当重要的概念:首先,51单片机里面有两个定时器 T0, T1。

定时器一但启动,它便在原来的数值上开始加1计数,若在程序开始时,我们没有设置 TH0 和TL0,它们的默认值都是0,假设时钟频率为12MHz,12个时钟周期为一个机器周期,那么此时机器周期为1us.

假如定时器工作在模式一:也就是我们先在 TL0 这8位里面记录时间,每当记满 TL0 的八位后,才向 TH0 进 1. 那么,在TH0、TL0的初始值都是0时,我们记满整个定时器需要的机器周期就是: ( 2 8 ) 2 = 256 (2^8)^2 = 256 x 256 256 = 65536 65536 个机器周期,再来多一个周期,那么计数器就溢出,那么随机向CPU 发出中断请求。

而上面的情况,从计数开始到向CPU发出中断,所花的时间是:1us x 65536 = 65536us =65.536ms
那么,假如我们给定计数器一个初始值,那么我们就可以控制计数器在经过规定的时间后产生中断。

要知道,在此之前,我们想要计时都是通过 d e l a y ( ) delay() 去做的,不仅要手工调参,而且定时精度不佳,但是利用定时器定时就非常精确!

2.2 调用定时器的步骤

首先,51单片机的定时器由两个寄存器 T M O D TMOD T C O N TCON 控制:
在这里插入图片描述
TMOD也是一个八位寄存器,低4位控制 T0 定时器,高四位控制 T1 定时器。
如果我们只是使用T0定时器,那么我们就直接把高四位置0即可。

我们看看:00表示模式0、01表示模式1、10表示模式2、11表示模式3.
我们分析一下模式1:模式1就是我们在上文提到的那种计数方式:也就是我们先在 TL0 这8位里面记录时间,每当记满 TL0 的八位后,才向 TH0 进 1.

=========================================
那么举个例子:假设我们要定时 50ms,也就是50000us.那么我们看一下TH0和TL0里面的值到底怎么算:

因为我们想要计数器精准记到50000us,之后这个计数器得告诉我们从开始到现在刚好走完了50000us,这个“告诉我们”的过程,就是计数器发出中断(即计数到65536的时候)。那么也就是说,这个计数器就不可能是从0开始的,它得是从某一个值开始计数,记了50000次刚好到65535.

那么,很简单,这个初始值就是 (65536-50000) = 15536

现在我们要做的,就是把这个15536分配到 TH0 和 TL0 中。我们会议一下模式一,是在 TL0 得8位都记满,即256个机器周期,才会向 TH0 进一位。因此,TH0我们可以这样表示:
T H 0 = ( 65536 50000 ) / 256 TH0 = (65536-50000)/256
这里,’/’ 表示取模,也就是商(整数)

TL0 我们可以这样表示: T L 0 = ( 65536 15536 ) % 256 TL0 = (65536-15536)\%256
这里的 ‘%’ 表示 取余数

在这里插入图片描述
对于 TCON的设置,我们不用把所有位的置一次,只需要定义 T R 0 TR0 (用于控制 T0 的启动和停止)
如果要同时用 T0 和 T1,那么我们就令 TR0 = 1; TR1 = 1; 即可。

由于定时器在计数满了溢出的时候也会向 CPU 发送中断请求,因此,我们也需要向调用外部中断0那样设置一些东西:比如打开中断总允许 EA(EA=1)、在打开 ET0(ET0=1)

重点:使用定时器的步骤!!

  1. 首先规定定时器的初始值 TH0 和 TL0 (如果是Mode 1 可以套用上面的公式)
  2. 然后我们设置控制定时器的第一个寄存器 TMOD
  3. 设置中断允许的信号
  4. 最后设置控制定时器的第二个寄存器 TCON

2.3 代码与实验

现在,我们还是希望点亮1、3、5三个数码管,每一次显示一个数字,但是闪烁的间隔时间我们不再使用之前的 d e l a y ( ) delay() 函数,而是采用定时器。我们看看代码:

注意:我们这里是假定了单片机的振荡频率是 12MHz,因为在12MHz时,晶振周期是 1/12M,而恰好1个机器周期等于 12 个晶振周期,那么一个机器周期也就是 (1/12)x12 = 1us。如果不是12MHz,那么一个机器周期的时间需要重新算一下。

#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int

sbit WEI = P2^7;     //定义位选信号端
sbit DULA = P2^6;    //定义段选信号端
uchar num,t;
uchar code table[] = {0x3f, 0x06,0x5b,0x4f,0x66,0x6d,
                      0x7d,0x07,0x7f,0x6f,0x77,0x7c,
                      0x39,0x5e,0x79,0x71};
                      
void main()
{
	TH0 = (65536-50000)/256;     //定义定时器初始高位
	TL0 = (65536-50000)%256;     //定义定时器初始低位

	TMOD = 0x01;                 //定义TMOD(也就是控制T0的第一个寄存器)

	EA=1;    //打开中断总允许
	ET0=1;   //打卡定时器T0的中断允许

	TR0 = 1//定时器T0启动
	
	WEI=1;
	P0=0Xea;
	WEI=0;
	for(num=0;num<8;num++)
	{
		if(t == 20)
		{
			DULA=1;
			P0=table[num];
			DULA=0;
		}
	}
}

void extraT0() interrupt 1
{
	TH0=(65536-50000)/256;
	TL0=(65536-50000)%256;   //因为在CPU执行这一段中断代码时,计数器已经溢出了,所以要把它的初始值置成一开始的那个
	t++;   //这里 t 每自增一次,代表已经经过了50ms,那么t = 20时也就是1s	
}
发布了140 篇原创文章 · 获赞 411 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_44586473/article/details/104775865
今日推荐