51单片机之中断(interrupt)
中断概念
现在假设一个情况,单片机在循环点亮LED,也就是实现了一个流水灯,现在有一个按键作为输入,我们希望通过这个按键来实现不同的流水灯效果,当然了,你可以把点亮LED和检测按键放在同一个循环里面,假设这时候你又通过串口发送字符串,随着主程序任务量的增大,循环一次的时间也会越来越长,这就会导致按键检测失效,或者有时主程序没有循环,按键检测根本就塞不进去,这个时候就需要使用中断的方式。
很多单片机教程里面都会举这样一个例子,假设你在家里写作业(主程序),厨房里正在烧着水,这时候有人来敲门。我们不可能一边写作业,一边看水烧开没有,一边看有没有人敲门,这样效率太低了。用中断的方式就是:当你听见水烧开了(中断信号),立马去关煤气,把热水灌暖壶里(中断服务);当你听见敲门声也是同理。处理完这些之后安安静静的写作业。这样效率才是最高的。
51单片机的中断源
51单片机的中断源如下图所示
中断源 | 默认中断级别 | 序号(C语言用) | 入口地址(汇编用) |
---|---|---|---|
INT0外部中断0 | 1 | 0 | 0003H |
T0定时器/计数器0中断 | 2 | 1 | 000BH |
INT1外部中断1 | 3 | 2 | 0013H |
T1定时器/计数器1中断 | 4 | 3 | 001BH |
TI/RI-串行口中断 | 5 | 4 | 0023H |
T2定时器/计数器2中断(89C52系列有) | 6 | 5 | 002BH |
可以将上面的六个中断源分为三类:
外部中断
定时器中断
串行口中断
在说一说它们的触发条件:
外部中断的触发和某一个IO的电平有关系,可以通过设置相关寄存器,实现低电平或者是下降沿触发。
定时器中断的触发和相关的计数寄存器有关,溢出时便会触发中断。
串行口中断的触发条件为当串口发送或者接收到数据时。这里需要注意的是,发送和接收都会共用这一个中断服务函数。所以当触发中断时,在中断服务函数里面需要查询相关标志位来判断到底是发送触发的还是接收触发。
中断优先级
当设置有多个中断在工作时,必然涉及到中断优先级的问题。在上面的表格里已经列出了默认的中断级别,数字越小,优先级越高。
举个例子,当执行优先级为1 的中断时(INT0外部中断0),优先级为3的中断(INT1外部中断1)来了,这时候只能等待前者执行完毕才能执行后者。反过来,执行优先级为3 的中断时来了优先级为1的中断,这时会立即执行后者,执行完后又会返回执行前者。简单说,优先级高的可以打断优先级低的。
51单片机设置优先级的方法是这样的,假设上面所说的中断都在一个默认的梯队里面,里面有先后之说。那么其实还有另外一个高优先级的梯队,对51各个中断优先级的设置,其实就是放在哪一个梯队的问题。但无论放在哪一个梯队,相对位置不变,也就是说,某些优先级组合是不可能做到的。
一般来说,一个中断服务函数里不会放太多需要执行的内容,这是一条应该遵循的原则。所以中断执行的很快,很少会有两个中断同时到达或者执行的情况,所以大多数情况下,我们并不需要太在意中断优先级的问题。
相关寄存器
中断允许寄存器IE
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | EA | \ | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
D0至D5分别与上面表格的中断源对应,设置相应位置为1即打开相应中断。EA(enable all)为总开关,使用中断必须打开。
中断优先级寄存器IP
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | \ | \ | \ | PS | PT1 | PX1 | PT0 | PX0 |
通过设置相应的位置为1,把该中断设置为高优先级序列。
定时器/计数器控制寄存器TCON
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
高4位(D4到D7)与定时器/计数器0、1有关,详细介绍请查看博主的定时器一文
低4位(D0到D3)与外部中断0、1有关,具体为:
位 | 功能 |
---|---|
IT0 | 外部中断0触发方式选择位,0低电平触发,1下降沿触发 |
IE0 | 外部中断0请求标志位,触发中断置1,进入中断函数硬件置0 |
IT1 | 外部中断1触发方式选择位,同IT0 |
IE1 | 外部中断1请求标志位,同IE0 |
外部中断0(INT0)连接在引脚 P3^2 上
外部中断1(INT1)连接在引脚 P3^3 上
关于定时器/计数器和串口中断的配置,我会在后面的博客中再详细介绍。下图展示了51的中断系统的大致结构。
编程
一个中断服务函数(中断函数 )应该类似于下面这样:
void interrupt_service(void) interrupt x [using y]
{
//your code
}
函数名可以自定义,但是参数和返回值必须是 void 。
interrupt 指明了中断源,x可以取0~4( 89C52可以用到5 )。
using 指明了中断函数使用的寄存器组,取值0~3。这个可以不用理会。
下面这个程序展示了外部中断0和1的两种不同用法
#include "reg52.h"
void main()
{
IT0=0; //INT0 低电平触发
IT1=1; //INT1 下降沿触发
EX0=1; //INT0 中断使能
EX1=1; //INT1 中断使能
EA=1; //总中断使能
while(1)
{
P1=0x55; //假设P1连接了一组LED
}
}
void int0() interrupt 0
{
P1= 0xf0;
}
void int1() interrupt 2
{
P1= 0x0f;
}
如果你把上面的代码下载到单片机里,你会发现效果并不是特别满意。(真的不是在捉弄你)
主要是由于:
1 选择低电平触发,由于按键按下后会持续一段时间,中断执行的次数无法保证,这是不合理的。
2 由于按键的抖动,电平下降沿也会执行数次,这个也是不合理的。
外部中断并不适合连接按键,通常是用来连接一些模块的IRQ引脚使用。如红外接收,超声波HC-04,无线模块NRF24L01。