外部中断理论部分
037.4.1 外部中断理论部分
#include "reg52.h"
#include <intrins.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit LED1 = P1^0;
sbit LED2 = P1^1;
void Delay_xms(uint xms) // @11.0592MHz
{
uchar data i, j;
while (xms)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
IT0 = 1; //配置外部中断0为下降沿触发
EX0 = 1; //打开外部中断0允许位
IT1 = 1; //配置外部中断1为下降沿触发
EX1 = 1; //打开外部中断1允许位
EA = 1; //开总允许中断
while (1)
{
Delay_xms(1000);
}
}
void Int0_Routine(void) interrupt 0 //外部中断0 触发函数 注意是interrupt 0
{
Delay_xms(10);
LED1 = ~LED1;
}
void Int1_Routine(void) interrupt 2 //外部中断1 触发函数 注意是interrupt 2
{
Delay_xms(10);
LED2 = ~LED2;
}
一、外部中断理论部分
51 单片机的常用中断包括外部中断、定时器中断和串口中断。中断的核心作用不是让 CPU 同时执行两段程序,而是让 CPU 在主程序运行期间,能够暂停当前任务,优先处理突发事件,处理完成后再返回断点继续执行。
本节使用 STC89C52RC 的外部中断 0 和外部中断 1控制两个 LED。实验现象与轮询按键相同,都是“按一次改变一次灯的状态”,但 CPU 发现按键事件的方式不同:
轮询方式:主程序主动反复读取按键
中断方式:中断硬件监视引脚,事件发生后通知 CPU 执行中断服务程序
中断能显著改善长延时期间的事件响应,但要准确理解它的能力:本程序的 Delay_xms(1000) 仍是占用 CPU 的忙等待,并没有因为使用中断而节省这 1 秒的计算时间;外部中断只是能够在延时循环执行期间暂时打断它,避免 CPU 必须等到延时结束才读取按键。若要真正降低 CPU 占用,应进一步使用定时器、状态机或空闲/掉电模式。
1.1 实验演示
实验使用开发板上的 SW3、SW4 和 LED1、LED2:
SW3 ──► P3.2 / INT0 ──► interrupt 0 ──► LED1(P1.0)翻转
SW4 ──► P3.3 / INT1 ──► interrupt 2 ──► LED2(P1.1)翻转
两只按键的一端分别连接 P3.2、P3.3,另一端接 GND。P3 口复位后具有内部弱上拉,因此按键松开时引脚为高电平,按下时被开关拉到低电平。按下瞬间产生的 1 → 0 变化就是下降沿,可用于触发外部中断。
1.1.1 烧录之前写好的外部中断代码
打开配套代码中的 4.1外部中断_实验演示 工程,编译后在 Objects 目录生成 HEX 文件,再通过烧录软件下载到 STC89C52RC。烧录前应核对以下内容:
- 当前打开的是 4.1 外部中断工程,而不是前面的按键轮询工程。
- 工程编译无错误,
Objects中 HEX 文件的生成时间与本次编译一致。 - 烧录软件选择了正确的芯片型号、串口和下载文件。
- 开发板与下载器共地,下载完成后重新上电运行。
代码在进入无限循环之前先完成中断初始化:
IT0 = 1;
EX0 = 1;
IT1 = 1;
EX1 = 1;
EA = 1;
这五个符号均由 reg52.h 映射到特殊功能寄存器中的某一位。它们不是五个普通变量,而是直接控制芯片内部中断硬件的位地址。
1.1.2 外部中断实验演示
程序运行后:
- 按一下 SW3,P3.2 上出现下降沿,外部中断 0 被请求,LED1 状态翻转。
- 再按一下 SW3,LED1 再次翻转。
- 按一下 SW4,P3.3 上出现下降沿,外部中断 1 被请求,LED2 状态翻转。
- 主程序即使正在执行
Delay_xms(1000),外部中断仍可在当前机器指令结束后获得响应。
按键与中断引脚的实际电路为:
P3 口内部弱上拉
│
VCC
│
P3.2 / INT0 ─────────────●──── SW3 ──── GND
P3.3 / INT1 ─────────────●──── SW4 ──── GND
松开:内部弱上拉使引脚为 1
按下:开关闭合,引脚由 1 变 0,产生下降沿
释放:开关断开,引脚由 0 变 1,产生上升沿
源程序设置 IT0 = 1、IT1 = 1,所以只在按下产生的下降沿申请中断,释放按键产生的上升沿不会触发本例的外部中断。
机械按键在闭合和断开时会抖动,一个肉眼看来只有一次的按下动作,电气上可能产生多个下降沿。中断函数中的 10 ms 延时是一种简单的演示性消抖,但它没有等待按键完全释放,也会延长中断服务时间。更稳妥的工程做法是让外部中断只记录事件或暂时屏蔽该中断,再由定时器在若干毫秒后确认按键电平并恢复中断。
1.1.3 外部中断代码演示
主循环只执行 1 秒延时,没有查询 P3.2 或 P3.3:
while (1)
{
Delay_xms(1000);
}
当 INT0 或 INT1 产生有效下降沿时,CPU 暂停延时函数,跳转到相应的中断服务程序:
void Int0_Routine(void) interrupt 0
{
Delay_xms(10);
LED1 = ~LED1;
}
void Int1_Routine(void) interrupt 2
{
Delay_xms(10);
LED2 = ~LED2;
}
LED1 = ~LED1 和 LED2 = ~LED2 对单独的位变量取反,使低电平有效的 LED 在亮、灭之间切换。中断服务程序结束后,Keil C51 编译器生成 RETI 中断返回指令,CPU 回到被打断的 Delay_xms() 内部继续运行。
中断响应并不是“完全不影响主程序时间”。中断服务程序执行期间,主程序处于暂停状态,所以这 10 ms 消抖及进出中断的时间会叠加到主循环这次延时的实际墙钟时间中。中断的优势是响应及时,而不是让 ISR 的执行时间凭空消失。
1.2 中断原理
一个中断请求要真正进入中断服务程序,必须同时满足以下条件:
中断事件成立
│
▼
请求标志位置 1
│
├── 本中断允许位 = 1
├── 总允许位 EA = 1
└── 当前优先级允许响应
│
▼
CPU 跳转到中断向量
│
▼
执行中断服务程序
可以把中断响应条件简化为:
能够响应 = 请求标志 × 本地允许 × EA总允许 × 优先级条件满足
任意一项为 0,CPU 都不会进入相应的中断服务程序。例如只写 EX0 = 1 而忘记 EA = 1,INT0 即使检测到下降沿也不会被 CPU 响应。
1.2.1 中断原理分析
用“打游戏时水壶响了”理解中断,可以对应为:
| 生活场景 | 单片机场景 |
|---|---|
| 正在打游戏 | CPU 正在执行 while (1) 主程序 |
| 水壶响了 | 中断源产生请求 |
| 暂停游戏并记住进度 | 完成当前指令,把返回地址压入堆栈 |
| 去倒水 | 执行中断服务程序 ISR |
| 倒完水回来 | 执行 RETI |
| 从暂停位置继续游戏 | 从主程序断点继续运行 |
更准确的硬件流程如下:
- 中断源产生请求并置位相应标志。
- CPU 完成当前正在执行的机器指令,不会在一条指令执行到一半时跳走。
- 中断控制器检查本地允许位、
EA、当前中断状态和优先级。 - CPU 把返回地址 PC 压入堆栈,并装载对应的中断向量地址。
- Keil C51 生成的 ISR 入口代码保存本函数需要使用的工作寄存器。8051 硬件并不会自动保存所有通用寄存器。
- 执行中断服务程序。
- 恢复保存的寄存器并执行
RETI,从断点继续主程序。
RETI 不能简单换成普通函数返回指令 RET。两者虽然都会从堆栈恢复 PC,但 RETI 还会通知中断控制器“当前中断服务已经结束”,解除相应的中断优先级占用状态。
中断服务程序应尽量短:读取或清除必要标志、保存少量数据、设置事件标志后立即返回。长延时、等待按键释放、大量打印和复杂计算都会延迟其他中断,严重时会造成数据丢失或系统实时性下降。
1.3 实现过程
STC89C52RC 的中断实现可以按四步理解:
- 确定使用哪个中断源及其触发条件。
- 配置触发方式、清理可能遗留的请求标志。
- 打开本中断允许位,最后打开
EA。 - 编写与中断编号匹配的中断服务程序。
1.3.1 中断源
STC89C52 系列提供 8 个中断源。前五项是传统 8051 学习中最常用的外部中断、定时器和串口中断,STC89C52 还包括定时器 2以及扩展外部中断 2、3。
| 查询顺序/C51中断号 | 中断源 | 产生请求的条件 | 请求标志 | 本地允许位 | 中断向量 |
|---|---|---|---|---|---|
| 0 | INT0,外部中断0 | P3.2 下降沿或低电平 | IE0 | EX0 | 0003H |
| 1 | Timer0 | T0 溢出 | TF0 | ET0 | 000BH |
| 2 | INT1,外部中断1 | P3.3 下降沿或低电平 | IE1 | EX1 | 0013H |
| 3 | Timer1 | T1 溢出 | TF1 | ET1 | 001BH |
| 4 | UART | 发送完成或接收完成 | TI 或 RI | ES | 0023H |
| 5 | Timer2 | T2 溢出或 T2EX 事件 | TF2 或 EXF2 | ET2 | 002BH |
| 6 | INT2,外部中断2 | P4.3 下降沿或低电平 | IE2 | EX2 | 0033H |
| 7 | INT3,外部中断3 | P4.2 下降沿或低电平 | IE3 | EX3 | 003BH |
表中的 0~7 同时是同一优先级下的固定查询顺序和 Keil C51 的 interrupt n 编号。数字越小,同一时刻、同一优先级的多个请求中越先被查询。它不是用户配置的优先级等级;真正的可编程优先级由 IP、IPH 和 XICON 中的优先级位决定。
外部中断的两种触发方式如下:
ITx | 触发方式 | 特点 |
|---|---|---|
0 | 低电平触发 | INTx 只要保持低电平,请求条件就持续存在;ISR 返回前应确保外部信号已经释放,否则可能再次进入中断 |
1 | 下降沿触发 | 只记录一次 1 → 0 变化;适合本节按键演示,但需要考虑按键抖动产生多个边沿 |
INT0、INT1 的触发位是 TCON.IT0、TCON.IT1;INT2、INT3 的触发位是 XICON.IT2、XICON.IT3。
1.3.2 中断触发函数
每个中断源都有固定向量和固定 C51 中断编号:
void Int0_Routine(void) interrupt 0; // INT0
void Timer0_Routine(void) interrupt 1; // 定时器0
void Int1_Routine(void) interrupt 2; // INT1
void Timer1_Routine(void) interrupt 3; // 定时器1
void UART_Routine(void) interrupt 4; // 串口
void Timer2_Routine(void) interrupt 5; // 定时器2
void Int2_Routine(void) interrupt 6; // INT2
void Int3_Routine(void) interrupt 7; // INT3
因此,外部中断 1虽然名称中带有数字 1,C51 声明却必须使用 interrupt 2,因为查询序列中还有一个 Timer0 位于 INT0 和 INT1 之间。interrupt 2 表示第三个中断向量 0013H,并不表示“优先级为 2”。函数名可以自行命名,interrupt n 后面的编号必须正确。
多个中断同时出现时先比较可编程优先级。STC89C52 每个中断源可由一对高、低优先级位编码为 0~3 共四级:
| 高位 H | 低位 L | 优先级 |
|---|---|---|
| 0 | 0 | 0,最低 |
| 0 | 1 | 1,较低 |
| 1 | 0 | 2,较高 |
| 1 | 1 | 3,最高 |
高优先级中断可以打断正在执行的低优先级 ISR;同级中断不能相互嵌套。若多个待处理中断处于同一优先级,则按 INT0 → Timer0 → INT1 → Timer1 → UART → Timer2 → INT2 → INT3 的固定顺序查询。
1.3.3 中断相关寄存器
与中断直接相关的寄存器可以先按职责分类:
| 寄存器 | 地址 | 主要职责 | 是否可位寻址 |
|---|---|---|---|
IE | A8H | 总允许以及 INT0、T0、INT1、T1、UART、T2 的本地允许 | 是 |
XICON | C0H | INT2、INT3 的触发方式、请求标志、本地允许和低优先级位 | 是 |
IP | B8H | INT0~INT1、T0~T2、UART 的优先级低位 | 是 |
IPH | B7H | 八个中断源的优先级高位 | 否,只能按字节读写 |
TCON | 88H | T0/T1 运行与溢出标志;INT0/INT1 请求标志与触发方式 | 是 |
SCON | 98H | 串口工作方式、收发控制以及 TI、RI 中断标志 | 是 |
T2CON | C8H | 定时器2运行方式及 TF2、EXF2 中断标志 | 是 |
1. IE:中断允许寄存器
IE(A8H) B7 B6 B5 B4 B3 B2 B1 B0
EA - ET2 ES ET1 EX1 ET0 EX0
复位值: 0 x 0 0 0 0 0 0
| 位 | 含义 | 置 1 的作用 | 清 0 的作用 |
|---|---|---|---|
EA | CPU 总中断允许 | 开放中断总闸门 | 屏蔽全部可屏蔽中断 |
| B6 | 保留位 | 不使用,应保持规定状态 | 不使用 |
ET2 | 定时器2中断允许 | 允许 TF2/EXF2 请求进入中断系统 | 禁止定时器2中断 |
ES | 串口中断允许 | 允许 RI/TI 请求串口中断 | 禁止串口中断 |
ET1 | 定时器1中断允许 | 允许 T1 溢出中断 | 禁止 T1 中断 |
EX1 | 外部中断1允许 | 允许 INT1 请求 | 禁止 INT1 中断 |
ET0 | 定时器0中断允许 | 允许 T0 溢出中断 | 禁止 T0 中断 |
EX0 | 外部中断0允许 | 允许 INT0 请求 | 禁止 INT0 中断 |
EA 是总闸门,各中断源的允许位是分闸门。两级都打开才能响应:
INT0请求 ──► [EX0分闸门] ──► [EA总闸门] ──► 优先级仲裁 ──► CPU
本例复位后所有有效允许位均为 0。执行 EX0 = 1、EX1 = 1、EA = 1 后,有效控制位得到:
IE = 1x00 0101B
│ │ │
EA EX1 EX0
其中 B6 是保留位,数据手册用 x 表示其值不应作为程序依据;按常用写法直接装入 IE = 0x85 时,有效控制位与上图相同。按位赋值只改变目标位,不会无意覆盖同一寄存器中的其他中断允许状态。若直接写入整个 IE,则会同时改写其他中断允许位,工程中应根据整体需求选择写法。
2. TCON:定时器0/1与外部中断0/1控制寄存器
TCON(88H) B7 B6 B5 B4 B3 B2 B1 B0
TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0
复位值: 0 0 0 0 0 0 0 0
| 位 | 含义 | 详细作用 |
|---|---|---|
TF1 | 定时器1溢出标志 | T1 溢出时由硬件置 1;CPU 响应 T1 中断时硬件清 0,也可由软件清 0 |
TR1 | 定时器1运行控制 | 置 1 启动 T1,清 0 停止 T1;它本身不是中断允许位 |
TF0 | 定时器0溢出标志 | T0 溢出时由硬件置 1;CPU 响应 T0 中断时硬件清 0,也可由软件清 0 |
TR0 | 定时器0运行控制 | 置 1 启动 T0,清 0 停止 T0 |
IE1 | 外部中断1请求标志 | INT1 条件成立时置 1;下降沿模式响应后由硬件清 0 |
IT1 | INT1 触发方式 | 0 为 P3.3 低电平触发,1 为 P3.3 下降沿触发 |
IE0 | 外部中断0请求标志 | INT0 条件成立时置 1;下降沿模式响应后由硬件清 0 |
IT0 | INT0 触发方式 | 0 为 P3.2 低电平触发,1 为 P3.2 下降沿触发 |
在低电平触发模式中,即使响应过程清除了请求状态,只要 INTx 引脚仍为低电平,请求条件仍然成立,ISR 返回后就可能再次进入。因此低电平触发的外设必须在返回前撤销请求,或者由软件屏蔽并正确处理。
本例设置 IT0 = 1、IT1 = 1。在其他 TCON 位均保持复位值的前提下:
TCON = 0000 0101B = 0x05
│ │
IT1 IT0
3. XICON:扩展外部中断控制寄存器
XICON(C0H) B7 B6 B5 B4 B3 B2 B1 B0
PX3 EX3 IE3 IT3 PX2 EX2 IE2 IT2
复位值: 0 0 0 0 0 0 0 0
| 位 | 含义 | 详细作用 |
|---|---|---|
PX3 | INT3 优先级低位 | 与 IPH.PX3H 组合决定 INT3 的 0~3级优先级 |
EX3 | INT3 允许位 | 置 1 允许 P4.2/INT3 中断,清 0 禁止 |
IE3 | INT3 请求标志 | INT3 触发条件成立时硬件置 1,响应后硬件清 0 |
IT3 | INT3 触发方式 | 0 为 P4.2 低电平触发,1 为下降沿触发 |
PX2 | INT2 优先级低位 | 与 IPH.PX2H 组合决定 INT2 的 0~3级优先级 |
EX2 | INT2 允许位 | 置 1 允许 P4.3/INT2 中断,清 0 禁止 |
IE2 | INT2 请求标志 | INT2 触发条件成立时硬件置 1,响应后硬件清 0 |
IT2 | INT2 触发方式 | 0 为 P4.3 低电平触发,1 为下降沿触发 |
INT2、INT3 的控制位集中在 XICON,但它们仍受 IE.EA 总允许位控制。只设置 EX2 或 EX3 而没有设置 EA,同样不能响应。
4. IP 与 IPH:中断优先级寄存器
IP(B8H) B7 B6 B5 B4 B3 B2 B1 B0
- - PT2 PS PT1 PX1 PT0 PX0
IPH(B7H) B7 B6 B5 B4 B3 B2 B1 B0
PX3H PX2H PT2H PSH PT1H PX1H PT0H PX0H
IP 中每一位是相应中断的优先级低位:
IP 位 | 对应中断 |
|---|---|
| B7、B6 | 保留位,不作为优先级控制位 |
PT2 | Timer2 |
PS | UART |
PT1 | Timer1 |
PX1 | INT1 |
PT0 | Timer0 |
PX0 | INT0 |
IPH 中每一位是相应中断的优先级高位:
IPH 位 | 对应中断 |
|---|---|
PX3H | INT3;低位为 XICON.PX3 |
PX2H | INT2;低位为 XICON.PX2 |
PT2H | Timer2;低位为 IP.PT2 |
PSH | UART;低位为 IP.PS |
PT1H | Timer1;低位为 IP.PT1 |
PX1H | INT1;低位为 IP.PX1 |
PT0H | Timer0;低位为 IP.PT0 |
PX0H | INT0;低位为 IP.PX0 |
以 INT0 为例,优先级由 [PX0H, PX0] 两位共同决定:00 为 0级,01 为 1级,10 为 2级,11 为 3级。其他中断完全按照同样的方法组合各自的 H、L 位。
IP 可位寻址,可以写 PX0 = 1;IPH 不可位寻址,通常要用掩码按字节修改,避免覆盖其他中断的高位。例如需要把 PX0H 置 1时,可使用读—改—写方式保留其余位。复位后 IP 的有效优先级位和 IPH 均为 0,本例没有改动它们,因此 INT0、INT1 都处于 0级;如果两个请求同时到来,INT0 因固定查询顺序靠前而先被处理。
5. SCON:串口控制寄存器
SCON(98H) B7 B6 B5 B4 B3 B2 B1 B0
SM0/FE SM1 SM2 REN TB8 RB8 TI RI
| 位 | 含义 | 详细作用 |
|---|---|---|
SM0/FE | 模式位0/帧错误标志 | 当 PCON.SMOD0=0 时与 SM1 组合选择串口模式;当 SMOD0=1 时用于帧错误检测标志 FE |
SM1 | 串口模式位1 | 与 SM0 组合选择方式0~3 |
SM2 | 多机通信控制 | 在方式2、3中可用于多机通信筛选;不同模式下含义应结合串口章节判断 |
REN | 接收允许 | 置 1 允许串口接收,清 0 禁止接收 |
TB8 | 发送第9位 | 在方式2、3中保存要发送的第9位数据 |
RB8 | 接收第9位/停止位 | 方式2、3中保存接收的第9位;方式1中可反映停止位状态 |
TI | 发送中断标志 | 一帧发送到规定位置后由硬件置 1;必须由软件清 0 |
RI | 接收中断标志 | 一帧接收完成并满足条件后由硬件置 1;必须由软件清 0 |
UART 的发送与接收共用一个中断向量,ES = 1 后,TI 或 RI 任意一个为 1都会申请串口中断。进入串口 ISR 后必须分别判断并清除标志:
if (RI)
{
RI = 0;
// 读取接收数据
}
if (TI)
{
TI = 0;
// 处理发送完成
}
CPU 响应串口中断时不会自动清除 TI、RI。若软件不清零,退出 ISR 后中断请求仍然存在,程序可能不断重新进入串口中断。
6. T2CON:定时器2控制寄存器
T2CON(C8H) B7 B6 B5 B4 B3 B2 B1 B0
TF2 EXF2 RCLK TCLK EXEN2 TR2 C/T2 CP/RL2
| 位 | 含义 | 详细作用 |
|---|---|---|
TF2 | 定时器2溢出标志 | T2 溢出时置 1;当 RCLK 或 TCLK 为 1时不置位;需要软件清 0 |
EXF2 | 定时器2外部标志 | EXEN2=1 时,T2EX 负跳变触发捕获或重装并置位;需要软件清 0 |
RCLK | 串口接收时钟选择 | 置 1 时用 T2 溢出作为串口方式1、3的接收时钟;清 0 时使用 T1 |
TCLK | 串口发送时钟选择 | 置 1 时用 T2 溢出作为串口方式1、3的发送时钟;清 0 时使用 T1 |
EXEN2 | T2 外部事件允许 | 置 1 后允许 T2EX 负跳变触发捕获或重装,具体行为由 CP/RL2 等位决定 |
TR2 | 定时器2运行控制 | 置 1 启动 T2,清 0 停止 T2 |
C/T2 | 定时/计数选择 | 0 使用内部时钟作定时器,1 对外部下降沿计数 |
CP/RL2 | 捕获/自动重装选择 | 置 1 选择捕获,清 0 选择自动重装;作为串口波特率发生器时受其他位共同约束 |
Timer2 的 TF2、EXF2 共用 interrupt 5。与 Timer0、Timer1 的 TF0、TF1 不同,TF2、EXF2 需要在软件中根据实际中断原因清零。
7. 本节五条配置语句的寄存器结果
| 代码 | 寄存器位 | 作用 |
|---|---|---|
IT0 = 1 | TCON.0 | INT0 选择下降沿触发 |
EX0 = 1 | IE.0 | 允许 INT0 中断 |
IT1 = 1 | TCON.2 | INT1 选择下降沿触发 |
EX1 = 1 | IE.2 | 允许 INT1 中断 |
EA = 1 | IE.7 | 打开 CPU 中断总闸门 |
从复位状态出发,执行完初始化后:
TCON = 0000 0101B = 0x05
IE有效位 = 1x00 0101B
IP有效位(B5~B0) = 00 0000B ┐
IPH = 0000 0000B ┴─ 所有中断保持优先级0
最终信号链可以完整写成:
按下 SW3
│
▼
P3.2/INT0:1 → 0
│ IT0=1,识别下降沿
▼
IE0 请求标志置 1
│ EX0=1,INT0分闸门打开
│ EA=1,总闸门打开
│ [PX0H,PX0]=00,优先级0
▼
CPU 跳到向量 0003H
│
▼
执行 Int0_Routine(interrupt 0)
│
▼
LED1 翻转,RETI 返回主程序
掌握这条链路后,面对任何 51 单片机中断都可以依次回答五个问题:事件怎样产生、请求标志在哪里、本地允许位在哪里、优先级由哪些位决定、对应哪个中断编号和向量地址。这样就不会把触发位、标志位、允许位和优先级位混为一谈。