9.1 中断系统知识介绍
在单片机中有两个重要的概念分别叫做中断、中断系统,那么他们分别又代表什么意义呢?当单片机 CPU 正在运行主程序时外界发生了紧急事件请求,要求单片机停止当前的工作,而去处理这个紧急事件,处理完成之后,在回到主程序原来的地方继续执行,这样的过程称之为中断,实现中断功能的部件称为中断系统。
一般单片机中都有多个中断源,当多个中断源同时发出请求的时候,单片机应该先响应哪一个呢?在单片机中可以设置中断源的优先级,同时出现时单片机将优先响应优先级高的中断源。另外一种情况,当单片机 CPU 正在响应某个中断时,一个更高优先级的中断请求产生了,这是 CPU 将暂停响应当前低优先级的中断,转而去响应高优先级的中断源,响应完成后继续处理低优先级中断,之后再回到主程序继续运行,这个过程称之为中断嵌套。
我们这里举一个生活中的例子来解释中断嵌套的过程。当你在家中正在打扫卫生的时候,家里电话响了,这时你便会先放下手中的活,然后拿起电话接电话,在打电话的时候有人按门铃了,这是你暂时先放下电话去开门,开完门之后你接着拿起电话继续讲,当挂断电话后,你紧接着刚才继续打扫卫生了。例子中打扫卫生相当于单片机 CPU 执行的主程序,电话相当于低优先级的中断,门铃相当于高优先级的中断。在打电话的时候先去开门相当于中断嵌套。
STC89C52 单片机提供了 8 个中断请求源,分别为:外部中断 0(INT0)、定时器 0 中断,外部中断 1(INT1)、定时器 1 中断、串口(UART)中断、定时器 2 中断、外部中断 2(INT2)、外部中断 3(INT3)。8 个中断请求源可以设置为 4 个中断优先级,每一个中断源都可以通过软件设置为四级优先级的任一级。高优先级的中断可以打断低优先级的中断,反之则不可。同一优先级的中断同时来临时,则根据中断查询的顺序进行中断响应。
如上表所示,在相同优先级内 ,外部中断 0 的查询次序最高,外部中断 3 的查询次序最低。传统 51 系列单片机一般值配置上表所示的前 5 个中断源,而且只有两级中断优先级。传统 51 点单片机通过寄存器 IP 设置优先级,STC89C52 通过新增加的寄存器 IPH 与 IP 寄存器共同设置 4 级优先级,与传统两级中断优先级完全兼容。我们可以将上述 8 种中断源分为 3 大类:
第一种: 外部中断(INT0、1、2、3)
第二种: 定时器中断(Timer0,1,2)
第三种: 串口中断(UART)
每种类型中断的使用方式都是相同的,只不过设置的寄存器不相同罢了。前面第六章讲解的就是第二种的定时器 0 中断。所有中断的控制寄存器如下表所示,包括 7 个八位的寄存器。如下表所示,IP、IPH 寄存器在单片机复位后值均为 0,结合上表可知,在不对这两个寄存器进行重新设置时,所有中断均为优先级 0。
单片机中断系统结构图如下图所示,每一个中断都有一个单独中断允许开关,INT0 开关为 EX0,Timer0 为 ET0,之后有一个中断允许总开关 EA,EA 关闭是才允许中断,当 EA 断开时,所有中断将被屏蔽。在设置后中断后,首先需要允许中断。这一章我们将重点讲解外部中断的使用。
在 C 语言编程中,中断又是怎么来实现的呢?在主程序中首先设置后中断的工作方式,然后允许中断,接着程序执行主程序,当中断来临时,程序跳转到对应的中断子函数中执行相应的操作,执行完之后继续回到主程序。在 C 语言中,8 个中断源对应这 8 个中断子程序函数,如下图所示,将中断要实现各功能编辑到相应的函数中即可。
如图 9-2 所示,前面函数的名字可以根据需要进行更改,关键字 interrupt x 决定了这个行数属于哪个中断,在第六章中讲到的定时器 0 中断,即 Timer0 中断,那么它对应的为”interrupt 1”。
9.2 外部中断应用
STC89C52 系列单片机对中断源可开放或屏蔽,是有内部的特殊功能寄存器 IE(中断允许寄存器)进行控制的,IE 寄存器格式如下:
中断允许寄存器 IE 各位功能定义如下表所示:
单片机 STC89C52 复位后会将寄存器 IE 清零,需要有用户对 IE 中相应的位清“0”或置“1”来实现中断请求的允许或屏蔽。
传统 51 单片机具有两级中断优先级,可实现中断嵌套。通过中断优先级寄存器 IP 可实现优先级的控制。IP 寄存器格式如下:
中断优先级控制寄存器 IP 各位功能定义如下表所示:
单片机复位后,优先级控制寄存器 IP 将清零,即所有中断请求均为低优先级。
TCON 寄存器在第六章定时器中断时讲解过,其中寄存器的高 5 位为与定时器/计数器相关的功能位,这里不再赘述。这里介绍与外部中断相关的看控制位:
特殊功能寄存器 TCON 低 4 位功能介绍:
介绍了各控制寄存器的功能之后,下面我们讲解外部中断的实现。为了便于观察我们设计的外部中断 0 的功能为:当外部中断 0(P3.2)引脚出现下降延时,即设置为下降沿触发模式,进入中断后流水灯流动一次。在 RY-51 开发板中外部中断 0 的 P3.2 引脚与独立按键 K19 相连接,因此当按键按下一次给管脚 P3.2 一个下降沿,即产出一次中断。实现的功能呢为按下一次按键,流水灯流动一次,设计程序如下:
/*----------------------------------------------------
** 外部中断0试验,功能:按下按键K19,流水灯流动一次。
----------------------------------------------------*/
#include<reg52.h>
#define uint unsigned int
uint Move = 0;
void main()
{
IT0 = 1; //设置外部中断0为下降沿触发方式
EX0 = 1; //允许外部中断0中断
EA = 1;//开全局中断
while(1);
}
//外部中断0中断函数
void int0_r(void) interrupt 0
{
P1 = ~(0x01<<Move);//流水灯
if(Move>=8) Move=0;
else Move++;
}
将程序下载到单片机中,每按一次按键 K19 观察流水灯的变化情况。这个例子为外部中断 0 的基本写法,依此类推只要改变相应的寄存器设置,同样可以对外部中断 1,2,3 进行应用。
这里讲解的只是单个中断的应用,下面我们结合前面讲过的定时器 0 中断来看一个中断嵌套的例子。我们将第 8 章最后的数码管显示的程序进行稍微的改造,将变量 Sec 每秒增加一次改为来一次外部中断增加一次,即相当于上面的按键按一次 Sec 增加一次,那么数码管显示的将是按键的按下次数。程序的主要变化如下所示:
/*----------------------------------------------------
** 数码管显示任意数值
**
** case语句应用
----------------------------------------------------*/
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
#define FOSC 11059200 //单片机晶振频率
#define T_1ms (65536 - FOSC/12/1000) //定时器初始值计算
uint count = 0;
uint flag = 0;
uint Move = 0;
uint T_count = 0;
uint Sec = 0;
sbit DU = P2^7;
sbit WE = P2^6;
uchar Buf_LED[8] ={0};
//共阳型(0~9,A,b,C,d,E,F,全亮,全灭),字码组
uchar code table_D[]={0xC0,0xF9,0xA4,0xB0,
0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,
0xC6,0xA1,0x86,0x8E,
0x00,0xFF};
//位选数组
uchar code table_W[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0xFF,0x00};
void LED_disp(unsigned char Num_DU,unsigned char Num_WE);
void main()
{
TMOD = 0x01; //定时器工作模式配置
TL0 = T_1ms; //装载初始值
TH0 = T_1ms>>8;
TR0 = 1; //启动定时器
ET0 = 1; //允许定时器中断
IT0 = 1; //设置外部中断0为下降沿触发方式
EX0 = 1; //允许外部中断0中断
EA = 1; //开总中断
while(1) //循环
{
Buf_LED[7]= Sec%10;
Buf_LED[6]= Sec/10%10;
Buf_LED[5]= Sec/100%10;
Buf_LED[4]= Sec/1000%10;
Buf_LED[0]= 17;
Buf_LED[1]= 17;
Buf_LED[2]= 17;
Buf_LED[3]= 17;
}
}
//定时器0中断函数
void timer0() interrupt 1
{
TL0 = T_1ms;//重装初始值
TH0 = T_1ms>>8;
count++;
if(count>= 2)//每一毫秒进入一次中断,达到2次则为2ms更新一次数码管。
{
count = 0;
switch(flag)
{
case 0:LED_disp(Buf_LED[0],flag++);break;
case 1:LED_disp(Buf_LED[1],flag++);break;
case 2:LED_disp(Buf_LED[2],flag++);break;
case 3:LED_disp(Buf_LED[3],flag++);break;
case 4:LED_disp(Buf_LED[4],flag++);break;
case 5:LED_disp(Buf_LED[5],flag++);break;
case 6:LED_disp(Buf_LED[6],flag++);break;
case 7:LED_disp(Buf_LED[7],flag);flag=0;break;
default:break;
}
}
}
//外部中断0中断函数
void int0_r(void) interrupt 0
{
int i=0;
Sec++;
for( i =0;i<=5000;i++);
}
/*----------------------------------------------------
** 单个数码管显示函数
**
** Num_DU:显示的字符
** Num_WE:显示的位
----------------------------------------------------*/
void LED_disp(unsigned char Num_DU,unsigned char Num_WE)
{
//关闭所有数码管,消隐处理
P0 =table_W[9];
WE = 1;
WE = 0;
//锁存字符
P0 = table_D[Num_DU];
DU = 1;
DU = 0;
//锁存位
P0 = table_W[Num_WE];
WE = 1;
WE = 0;
}
如上述代码所示,增加了外部中断 0 特殊功能寄存器的设置。
将 Sec 自加放到外部中断函数中,并且在函数中增加了一个延时,让中断函数处理的时间长一点方便中断嵌套现象的观察。其它的代码与数码管显示代码相同,这里不再赘述。
将程序编译下载到单片机中,观察显示效果。当按下一次按键 K19,数码管显示数字加 1。在观察现象的时候,能发现数码管显示会出现闪烁,显示受到干扰。下面分析一下产生闪烁的原因。
我们在程序中没有对外部中断 0 和定时器 0 中断进行中断优先级设置,所以均为低优先级,根据前面介绍在同一优先级条件下外部中断 0 的查询次序高于定时器 0 中断,当定时器 0 中断和外部中断 0 同时产生时,将优先进入外部中断 0 中断函数。所以当按键按下和定时器中断同时到来时,程序会先进入外部中断函数,而造成没有正常的进入定时器中断刷新数码管的显示,因此数码管显示的闪烁。
那么,在程序中将定时器 0 中断设置为高优先级中断,外部中断为低优先级中断。因此,当定时器中断到来时,不管程序有没有正在执行外部中断 0 函数,程序都会进入到定时器中断函数中,保证了数码管的刷新,因此消除了数码管闪烁的现象,增加的程序代码如下代码所示:
void main()
{
TMOD = 0x01; //定时器工作模式配置
TL0 = T_1ms; //装载初始值
TH0 = T_1ms>>8;
TR0 = 1; //启动定时器
ET0 = 1; //允许定时器中断
IT0 = 1; //设置外部中断0为下降沿触发方式
EX0 = 1; //允许外部中断0中断
PT0 = 1;//定时器0设置为高优先级 -----------------增加设置优先级代码1
PX0 = 0;//外部中断0设置为低优先级 -----------------增加设置优先级代码2
EA = 1; //开总中断
while(1) //循环
9.3 本章小节
本章详细介绍了单片机中断系统的工作原理以及部分中断寄存器功能的定义。介绍了外部中断 0 功能 C 语言程序的编写,介绍了中断嵌套的应用。