前言
中断是单片机非常重要的概念,类似于软件中回调的作用。前面几章我们都是主动驱动元器件工作,但如果单片机需要被动地去响应外部的信息时,中断的重要性就凸显出来了。从本章节开始,我们要开始直接操作中断相关的寄存器,由于偏底层,学起来可能没有之前几章那么轻松。
中断系统概述
中断涉及的概念比较多,先阐释如下:
- 轮询:依次询问每一个IO设备,如果检测到需要提供服务,立刻执行服务,再询问下一个设备。
- 适用场景:频繁需要服务的外设
- 特点
- 优点:实现简单
- 缺点:消耗大量CPU资源。慢速的IO交互无需大量频繁的检测。
- 中断:当外部设备需要服务时,CPU接收到中断请求,立刻停止当前程序,转而执行中断服务程序,执行结束后,回到原先处继续执行。
- 适用场景:慢速交互的情况
- 特点
- 优点:实时性高、即刻响应、节约CPU资源
- 缺点:实现稍复杂
- 中断源:发生中断的源头。传统51单片机中一般至少有5个基础的中断源(按自然优先级排列)。
- 外部中断0(
INT0
) - 定时器0中断(
T0
) - 外部中断1(
INT1
) - 定时器1中断(
T1
) - 串口中断(
UART
)
- 外部中断0(
- 中断优先级:
- 自然优先级:当几个中断同时向CPU发出请求,CPU会根据中断自然优先级依次处理。
- 抢占优先级:当进入某个中断时,发生了优先级更高的中断,CPU会打断低优先级的中断,转而执行高优先级中断,形成中断嵌套。同级不会打断。传统51单片机只有2个优先级,设置寄存器
IP
即可更改默认排序,一般保持默认即可。
- 中断嵌套:在执行中断服务函数的过程中,有优先级更高的中断发生,会暂停当前中断执行,转而执行更高级的中断程序。即形成嵌套。
- 中断请求:中断源向CPU申请中断。
- 中断响应:CPU接收到中断请求转去执行中断服务程序的行为。
- 中断服务程序:由开发者预先定义(关键字
interrupt
+ 中断号),处理相应的中断事件。原则上,不要在中断中写任何阻塞程序,以保证其实时性。 - 中断号:是CPU进入对应中断服务程序的重要标志,不同的中断源对应不同的中断号。
- 中断返回:执行完对应中断服务程序后,返回中断点处继续往下执行。
- 中断点:主函数被中断的地方。
中断源 | 优先级 | 中断请求标志位 | 中断允许标志位 | 中断号 |
---|---|---|---|---|
I N T 0 ‾ \overline{INT0} INT0 | 0 | IE0 | EX0/EA | 0 |
T 0 T0 T0 | 1 | TF0 | ET0/EA | 1 |
I N T 1 ‾ \overline{INT1} INT1 | 2 | IE1 | EX1/EA | 2 |
T 1 T1 T1 | 3 | TF1 | ET1/EA | 3 |
U A R T UART UART | 4 | RI/TI | ES/EA | 4 |
T 2 T2 T2 | 5 | TF2 | ET2/EXF2/EA | 5 |
I N T 2 ‾ \overline{INT2} INT2 | 6 | IE2 | EX2/EA | 6 |
I N T 3 ‾ \overline{INT3} INT3 | 7 | IE3 | EX3/EA | 7 |
注:T2定时器,外部中断INT2、INT3为STC89C52RC系列新增外设。其中,INT2、INT3仅44脚封装才有。
以上那么多概念,其实都是很重要的,所以建议大家好好读一读,能对中断系统有一个总体的感知。虽然理论上,上述寄存器都会用到,但也没有必要强行记忆,多写两次,你就会看这些寄存器非常眼熟了,实在记不清也可以来我的博客再回忆一遍哈~
下面对中断系统做一个小结:
中断系统主要是解决高速CPU和慢速人机交互所造成的CPU资源浪费问题。适用于被动响应场景。试想,对于按键检测,如果将它放在循环里不断检测,人按下按键的过程至少是毫秒级别的,而等待按下的时间更是漫长,而此刻CPU已经循环检测了上万次,这显然是一种无意义的检测。
而这时中断的优势就体现出来了,将按键响应的代码放在中断服务函数里,只有当按键被按下时,才会执行。这样大大节约了CPU的资源。
那么我们怎么去利用中断呢,需要介绍以下几种寄存器。
寄存器说明
中断优先级寄存器(IP)
寄存器 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
IP | - | - | PT2 | PS | PT1 | PX1 | PT0 | PX0 |
- 中断优先级寄存器(IP):控制中断的抢占优先级。字节地址B8H。
- PX0:外部中断INT0中断优先级控制位,置
1
为高优先级,置0
为低优先级。 - PT0:外部定时器T0中断优先级控制位
- PX1:外部中断INT1中断优先级控制位
- PT1:外部定时器T1中断优先级控制位
- PS:串口中断优先级控制位
- PT2:外部定时器T2中断优先级控制位(新增)
- PX0:外部中断INT0中断优先级控制位,置
注:STC89C52单片机可以通过配置 IP寄存器和 IPH寄存器实现4个抢占优先级。详见芯片手册,本文不做介绍。
中断允许控制寄存器(IE)
寄存器 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
IE | EA | - | ET2 | ES | ET1 | EX1 | ET0 | EX0 |
- 中断允许控制寄存器(IE):控制中断的使能开关。字节地址A8H。
- EX0:外部中断INT0允许位,置
1
(高)开,置0
(低)关。 - ET0:外部定时器T0允许位
- EX1:外部中断INT1允许位
- ET1:外部定时器T1允许位
- ES:串口中断允许位
- ET2:外部定时器T2允许位(新增)
- EA:关总中断允许位,是控制所有中断使能的总开关。
- EX0:外部中断INT0允许位,置
中断请求标志寄存器(TCON)
寄存器 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|
TCON | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
- 中断请求标志寄存器(TCON):设置中断的发生方式。字节地址88H。
- IT0:外部中断INT0的触发方式控制位,置0为低电平触发,置1为下降沿触发(常用)
- IE0:外部中断INT0的请求标志位,发生中断请求时,由硬件自动置1,响应后自动置0。
- IT1: 外部中断INT1的触发方式控制位 。
- IE1:外部中断INT1的请求标志位
- TR0:定时器T0的运行控制位,置1为开始运行,置0停止运行。
- TF0:定时器T0的溢出中断请求标志位,发生溢出中断请求时,硬件自动置1,响应后自动置0。
- TR1:定时器T1的运行控制位。
- TF1:定时器T1的溢出中断请求标志位。
外部中断配置
STM32系列单片机每个引脚都可以配置中断,而传统的51单片机只有2个引脚有外部中断(STC89C52单片机共有4个外部中断引脚,分别为P3.2–INT0、P3.3–INT1、P4.3–INT2、P4.2–INT3,其中,外部中断2和3的配置还需要用到XICON
寄存器)。
了解了上述3个寄存器之后,配置引脚外部中断其实很简单。具体配置流程如下(以INT0为例):
- 设置外部中断触发方式为下降沿触发
IT0 = 1;
- 使能外部中断
EX0 = 1;
- 打开关总中断
EA = 1;
软件实现
按键事件的外部中断实现
实验现象为,按下第四个独立按键(P3.3
),第一个LED(P2.0
)亮灭翻转。
interrupt.h
#ifndef __INTERRUPT_H__
#define __INTERRUPT_H__
#include "delay.h"
void INTx_init(u8);
#endif
interrupt.c
#include "interrupt.h"
/**
** @brief 外部中断封装
** @author QIU
** @data 2023.08.31
**/
/*-------------------------------------------------------------------*/
/**
** @brief 配置外部中断x
** @param x:对应外部中断编号
** @retval 无
**/
void INTx_init(u8 x){
switch(x){
case 0:
IT0 = 1; // 设置外部中断0触发方式,下降沿触发
EX0 = 1; // 使能外部中断0
break;
case 1:
IT1 = 1; // 设置外部中断1触发方式,下降沿触发
EX1 = 1; // 使能外部中断1
break;
}
EA = 1; // 使能总中断
}
// 外部中断0的中断服务程序模板
//void INT1_serve() interrupt 0{
// ;
//}
// 外部中断1的中断服务程序模板
//void INT1_serve() interrupt 2{
// ;
//}
main.c
#include "delay.h"
#include "interrupt.h"
#include "led.h"
/**
** @brief 以外部中断方式响应按键事件
** @author QIU
** @data 2023.08.31
**/
/*-------------------------------------------------------------------*/
// 定义引脚
sbit btn4 = P3^3;
void main(){
INTx_init(1);
while(1);
}
void INT1_serve() interrupt 2{
if(btn4 == 0){
delay_ms(10);
// 按下消抖
if(btn4 == 0){
led_turn(1);
}
}
}
本例简单演示了如何使能外部中断,其意义在于,将慢速的按键响应放在中断中而非主程序中检测处理,这显然比轮询方法更加高效,节约了CPU的资源。
但与此同时,我们知道,在中断服务程序中不宜处理耗时的程序,这也是本例存在的缺陷,仅仅使用外部中断不能完美解决按键检测的问题,因为按键存在抖动这一客观事实。在下一个章节,我们将使用定时器完成按键消抖。它比延时函数更加优雅,且进一步提升由于延时所消耗的性能。
遇到的问题
- 如果使能中断就必须书写中断服务程序,并正确书写中断号。否则程序进入中断卡死。
- 开发板上
P3.2
引脚为外部中断INT0
,除了接独立按键,还接了红外接收装置,可能会产生串扰。建议查看开发板原理图。
总结
外部中断使得CPU可以免于无谓等待的消耗,从而集中处理更加重要事情上。单片机的性能得以充分利用。