外部中断代码实现

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

039.4.3 外部中断代码实现

#include "reg52.h"
#include <intrins.h>

sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;

//sbit KEY1 = P3^2;
//sbit KEY2 = P3^3;

//sbit SUN = P3^7;

typedef unsigned char uchar;
typedef unsigned int uint;

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;
    EX0 = 1;
    IT1 = 1;
    EX1 = 1;

    EA = 1;

    while (1)
    {
        //Delay_xms(1000);
    }
}

void Int0_Routine(void) interrupt 0
{
    Delay_xms(10);
    LED3 = ~LED3;
}

void Int1_Routine(void) interrupt 2
{
    Delay_xms(10);
    LED4 = ~LED4;
}

一、外部中断代码实现

这段程序使用 STC89C52 的两个外部中断完成按键控制:按下 SW3,P3.2/INT0 产生下降沿,进入 Int0_Routine() 并翻转 LED3;按下 SW4,P3.3/INT1 产生下降沿,进入 Int1_Routine() 并翻转 LED4。主循环不轮询按键,CPU 只在外部事件出现时转去执行中断服务程序(ISR)。

SW3 → P3.2/INT0 → interrupt 0 → LED3(P1.2)翻转
SW4 → P3.3/INT1 → interrupt 2 → LED4(P1.3)翻转

一次外部中断能被响应,需要同时满足四类条件:引脚出现匹配的电气变化、请求标志有效、局部允许和总允许均打开、优先级仲裁允许 CPU 响应。IT0 = 1 只选择触发方式,不能代替 EX0 = 1EA = 1

1.1 相关寄存器的配置

本例显式使用 TCONIE 中的五个位:

代码寄存器位本例中的作用
IT0 = 1TCON.0INT0 选择下降沿触发
EX0 = 1IE.0允许外部中断0请求进入中断系统
IT1 = 1TCON.2INT1 选择下降沿触发
EX1 = 1IE.2允许外部中断1请求进入中断系统
EA = 1IE.7打开 CPU 中断总允许开关

初始化代码写在 main()while (1) 之前,只执行一次。通常先配置触发方式和各中断源,再打开局部允许位,最后设置 EA = 1,这样可以避免初始化尚未完成时进入 ISR。

1.1.1 中断允许寄存器

IE 是地址为 A8H 的 8 位、可位寻址中断允许寄存器。它相当于中断系统的“总闸门 + 分闸门”:EA 是总闸门,EX0EX1 等是各中断源的分闸门。

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 的含义
B7EA开放 CPU 总中断允许;各中断仍需自己的允许位为 1屏蔽所有可屏蔽中断
B6保留数据手册用 x 表示,程序不要依赖其读值不作为功能位使用
B5ET2允许定时器2中断禁止定时器2中断
B4ES允许串口中断禁止串口中断
B3ET1允许定时器1中断禁止定时器1中断
B2EX1允许外部中断1禁止外部中断1
B1ET0允许定时器0中断禁止定时器0中断
B0EX0允许外部中断0禁止外部中断0

本例将 EAEX1EX0 置 1,其余有效允许位保持 0:

IE有效位 = 1x00 0101B
           │    │ │
           EA   EX1 EX0

B6 是保留位,因此不应把整个寄存器严格描述成固定的 1000 0101B。若忽略保留位,常用字节写法 IE = 0x85 能得到相同的有效控制状态,但它会同时重写其他允许位;本例逐位赋值更直观,也不容易误伤其他已经启用的中断源。

1.1.2 定时器/计数器中断控制寄存器

TCON 位于 88H,可位寻址。它的高四位控制定时器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
名称完整含义典型控制者
B7TF1定时器1溢出请求标志,溢出后为 1硬件置位;CPU 响应 T1 中断时硬件清零,也可由软件清零
B6TR1定时器1运行控制,1启动、0停止软件
B5TF0定时器0溢出请求标志,溢出后为 1硬件置位;CPU 响应 T0 中断时硬件清零,也可由软件清零
B4TR0定时器0运行控制,1启动、0停止软件
B3IE1外部中断1请求标志,对应 P3.3/INT1外部条件成立时由硬件置位
B2IT1INT1 触发类型;0低电平、1下降沿软件配置
B1IE0外部中断0请求标志,对应 P3.2/INT0外部条件成立时由硬件置位
B0IT0INT0 触发类型;0低电平、1下降沿软件配置

ITxIExEXx 的名字相近,但职责不同:

ITx:Trigger Type,规定“什么变化算触发”
IEx:Interrupt Request Flag,记录“请求已经发生”
EXx:Enable External Interrupt,决定“是否允许响应”

在下降沿模式下,外部请求得到响应后,IE0IE1 由硬件清零。在低电平模式下,即使响应时标志被清除,只要引脚仍保持低电平,请求条件就会继续存在,CPU 可能再次进入 ISR。因此本例使用下降沿模式,更符合“一次按下产生一次事件”的预期。

执行 IT0 = 1IT1 = 1 后,在没有定时器运行和其他请求的前提下:

TCON = 0000 0101B = 0x05
              │ │
             IT1 IT0

1.2 任务信息资源

程序从按键轮询工程整理而来:保留 LED 定义和毫秒延时函数,注释不再使用的 KEY1KEY2 与光敏输入定义,把一次性的外部中断初始化放到 while (1) 之前,再在 main() 外编写两个 ISR。

外部中断硬件直接监视 P3.2、P3.3,所以主循环可以为空。while (1) 仍然必须保留,使主程序持续运行;中断发生时 CPU 暂停当前主程序流程,执行 ISR 后再返回被打断的位置,而不是由 while 主动调用 ISR。

1.2.1 外部中断代码过程

完整实现可以按以下顺序理解:

  1. 建立并重命名 4.2外部中断 工程,保留 LED 与延时相关代码。
  2. 删除按键轮询和光敏传感器等与本实验无关的业务代码。
  3. main() 中配置 INT0、INT1 的触发方式和允许位。
  4. 最后打开 EA 总中断允许。
  5. main() 外定义 interrupt 0interrupt 2 两个 ISR。
  6. 编译、下载并分别按下 SW3、SW4,观察 LED3、LED4 翻转。

若编译器提示某一行语法错误,还要检查它的前一行。例如输入法产生的中文分号 不能代替 C 语言半角分号 ;,编译器常常会到下一行才发现上一条语句没有正确结束。

1.2.1.1 外部中断零配置

INT0 的初始化由三层配置组成:

IT0 = 1;  // TCON.0:P3.2/INT0 下降沿触发
EX0 = 1;  // IE.0:打开 INT0 的局部允许
EA = 1;   // IE.7:打开总中断允许

相应的 ISR 为:

void Int0_Routine(void) interrupt 0
{
    Delay_xms(10);
    LED3 = ~LED3;
}

interrupt 0 告诉 Keil C51 编译器:这个函数服务于中断号0,即 INT0 的 0003H 向量。Int0_Routine 只是便于阅读的函数名,硬件不靠函数名匹配中断源。

按下 SW3 后的执行过程是:

P3.2:1 → 0
   │ IT0=1,下降沿匹配
   ▼
IE0由硬件置1
   │ EX0=1,局部允许
   │ EA=1,总允许
   ▼
CPU响应0003H向量
   ▼
Int0_Routine():延时10 ms,翻转LED3
   ▼
RETI返回主程序,继续执行空循环

只配置 INT0 时,SW3 能翻转 LED3,SW4 不会触发本程序,因为 INT1 的触发方式和局部允许尚未配置。

ISR 中的 Delay_xms(10) 是便于入门观察的简单按键消抖:机械触点闭合时可能在几毫秒内多次通断,产生多个下降沿,延时可以滤掉一部分抖动。但 ISR 内延时会占用 CPU,并阻塞同级和更低优先级中断;工程项目更适合让 ISR 只记录事件或暂时关闭该中断,再由定时器或主循环完成延时确认。

1.2.1.2 外部中断一配置

INT1 与 INT0 对称配置:

IT1 = 1;  // TCON.2:P3.3/INT1 下降沿触发
EX1 = 1;  // IE.2:打开 INT1 的局部允许

它的 ISR 必须使用 interrupt 2

void Int1_Routine(void) interrupt 2
{
    Delay_xms(10);
    LED4 = ~LED4;
}

interrupt 1 属于定时器0,不属于外部中断1。STC89C52 的八个中断号和向量如下:

C51中断号中断源请求标志允许位向量地址
0INT0IE0EX00003H
1Timer0TF0ET0000BH
2INT1IE1EX10013H
3Timer1TF1ET1001BH
4UARTRITIES0023H
5Timer2TF2EXF2ET2002BH
6INT2IE2EX20033H
7INT3IE3EX3003BH

如果把两个 ISR 声明中的 interrupt 0interrupt 2 对调,INT0 向量就会进入翻转 LED4 的函数体,INT1 向量则会进入翻转 LED3 的函数体,两个按键的控制对象随之交换。这进一步说明:函数名服务于程序员阅读,interrupt n 决定中断向量与函数体的绑定。一个工程中不能让两个 ISR 使用同一个中断号。

1.2.1.3 外部中断配置回顾

reg52.h 已经把特殊功能寄存器及其可位寻址位定义成名称。在 Keil 中对 IT0EX0 等名称使用 “Go To Definition”,可以看到类似定义:

sfr TCON = 0x88;
sbit IE1 = TCON^3;
sbit IT1 = TCON^2;
sbit IE0 = TCON^1;
sbit IT0 = TCON^0;

sfr IE = 0xA8;
sbit EA  = IE^7;
sbit EX1 = IE^2;
sbit EX0 = IE^0;

sfr 用于声明特殊功能寄存器,sbit 用于给可位寻址寄存器中的某一位命名。例如 sbit IT0 = TCON^0 表示 IT0 就是 TCON 的 B0;这和 sbit LED3 = P1^2 给 P1.2 起名为 LED3 的原理相同。赋值 IT0 = 1 最终只改变 TCON.0,不会改动同一寄存器的其他位。

本例完成初始化、且没有外部请求正在等待时,各寄存器的关键状态为:

TCON               = 0000 0101B = 0x05
IE有效位            = 1x00 0101B
IP有效位(B5~B0)  = 00 0000B
IPH                 = 0000 0000B

程序没有设置 IPIPH,因此 INT0、INT1 使用复位后的优先级0。没有配置优先级不代表中断不能运行,优先级寄存器只负责多个请求之间的仲裁。

另外,P3 口复位后准双向口锁存值为 1,P3.2、P3.3 处于释放的输入状态,所以本例无需再写 GPIO 模式切换代码。如果其他代码曾把 P3.2 或 P3.3 输出锁存为 0,应先写 1 将引脚释放,否则外部按键无法形成正常的高低电平变化。

1.2.2 中断系统结构图

以 INT0 为例,中断系统结构可以简化为:

P3.2 / INT0
     │
     ├── IT0=0:检测低电平
     └── IT0=1:检测下降沿(本例)
                 │
                 ▼
         TCON.IE0 请求标志
                 │
                 ▼
        IE.EX0 局部允许开关
                 │
                 ▼
         IE.EA 总允许开关
                 │
                 ▼
  [IPH.PX0H, IP.PX0] 优先级仲裁
                 │
                 ▼
        0003H → interrupt 0 ISR

INT1 的路径完全对应:P3.3 → IT1 → IE1 → EX1 → EA → [PX1H,PX1] → 0013H → interrupt 2。允许外部中断0的位是 EX0,不是 ESES 只允许串口中断。

STC89C52 中与中断系统直接相关的寄存器总览如下,位名均按 B7 到 B0 排列:

寄存器地址B7 → B0主要职责
IEA8HEA、保留、ET2ESET1EX1ET0EX0总允许与各中断源允许
IPB8H保留、保留、PT2PSPT1PX1PT0PX0INT0~Timer2 的优先级低位
IPHB7HPX3HPX2HPT2HPSHPT1HPX1HPT0HPX0H八个中断源的优先级高位
TCON88HTF1TR1TF0TR0IE1IT1IE0IT0T0/T1 控制及 INT0/INT1 请求与触发方式
SCON98HSM0/FESM1SM2RENTB8RB8TIRI串口控制及收发请求标志
T2CONC8HTF2EXF2RCLKTCLKEXEN2TR2C/T2CP/RL2定时器2控制及请求标志
XICONC0HPX3EX3IE3IT3PX2EX2IE2IT2INT2/INT3 的优先级低位、允许、标志和触发方式

IETCON 的每一位已在前文展开。其余寄存器各位含义如下。

XICON:INT2、INT3 控制

名称含义
B7PX3INT3 优先级低位,与 IPH.PX3H 组成四级优先级
B6EX3INT3 局部允许,1允许、0禁止
B5IE3INT3 请求标志,外部条件成立时硬件置 1
B4IT3P4.2/INT3 触发类型,0低电平、1下降沿
B3PX2INT2 优先级低位,与 IPH.PX2H 组成四级优先级
B2EX2INT2 局部允许,1允许、0禁止
B1IE2INT2 请求标志,外部条件成立时硬件置 1
B0IT2P4.3/INT2 触发类型,0低电平、1下降沿

IP 与 IPH:优先级的低位和高位

中断源IPXICON 中的低位 LIPH 中的高位 H
INT0IP.B0 = PX0IPH.B0 = PX0H
Timer0IP.B1 = PT0IPH.B1 = PT0H
INT1IP.B2 = PX1IPH.B2 = PX1H
Timer1IP.B3 = PT1IPH.B3 = PT1H
UARTIP.B4 = PSIPH.B4 = PSH
Timer2IP.B5 = PT2IPH.B5 = PT2H
INT2XICON.B3 = PX2IPH.B6 = PX2H
INT3XICON.B7 = PX3IPH.B7 = PX3H

IP.B6IP.B7 是保留位。每一组 [H,L] 都按二进制编码优先级:00 为0级最低,01 为1级,10 为2级,11 为3级最高。高优先级中断可以打断低优先级 ISR,同级中断不能互相打断。若同级请求同时出现,固定查询顺序为 INT0 → Timer0 → INT1 → Timer1 → UART → Timer2 → INT2 → INT3

SCON:串口控制与中断标志

名称含义
B7SM0/FE作为 SM0 时与 SM1 选择串口方式;在相应控制条件下作为帧错误标志 FE
B6SM1串口工作方式选择位
B5SM2多机通信及接收筛选控制
B4REN串口接收允许,1允许接收
B3TB8方式2、3发送的第9位数据
B2RB8方式2、3接收的第9位;方式1中可反映停止位
B1TI发送中断标志,硬件置位,ISR 中需软件清零
B0RI接收中断标志,硬件置位,ISR 中需软件清零

TIRI 共用 IE.ES、同一个 UART 中断向量以及 interrupt 4,所以串口 ISR 要判断是哪一个标志发起请求并分别清零。

T2CON:定时器2控制与中断标志

名称含义
B7TF2定时器2溢出标志,通常由软件清零;作为波特率发生器时行为受 RCLK/TCLK 影响
B6EXF2T2EX 外部事件标志,满足捕获或重装条件时置位,需软件清零
B5RCLK选择定时器2作为串口接收波特率时钟
B4TCLK选择定时器2作为串口发送波特率时钟
B3EXEN2允许 T2EX 负跳变触发捕获或重装
B2TR2定时器2运行控制,1启动、0停止
B1C/T20选择内部时钟定时,1选择外部事件计数
B0CP/RL2选择捕获或自动重装方式,并受串口时钟相关位共同影响

TF2EXF2 共用 IE.ET2、Timer2 中断向量和 interrupt 5。它们不会因为 CPU 进入 ISR 就自动清零,服务程序需要判断来源并软件清零。

从结构图可以归纳出三个不能混淆的概念:请求标志负责报告事件,允许位负责开关通道,优先级位只负责仲裁。打开 EX0EA 不会凭空产生中断;出现下降沿但 EX0 = 0 时,请求也不能进入 CPU。

1.2.3 按键触发中断

开发板上用于外部中断实验的是 SW3 和 SW4:

                         STC89C52
内部弱上拉 ── P3.2 / INT0 ── SW3 ── GND
内部弱上拉 ── P3.3 / INT1 ── SW4 ── GND

按键松开时,P3 口的内部弱上拉使引脚保持高电平;按下后触点闭合,引脚被直接接到 GND,产生 1 → 0 的下降沿。机械按键的四脚中,同侧两脚通常内部相连,按下后把两侧导通;看原理图时应沿网络标号确认信号端和 GND 端,不能只凭按键外形判断。

本例输出电路是低电平点亮:

+5V ──► LED3 ── R10(10 kΩ)── P1.2
+5V ──► LED4 ── R11(10 kΩ)── P1.3

电阻与 LED 串联,用来限制电流。P1.2 输出 0 时,电流路径为 +5V → LED3 → R10 → P1.2 内部下拉管 → GND,LED3 点亮;P1.2 输出 1 时,两端电位接近,LED3 熄灭。LED4 与 P1.3 的原理相同。

P1 口复位后通常为高电平,所以 LED 初始熄灭。第一次进入 ISR,LED3 = ~LED3LED4 = ~LED4 将端口位由 1 翻转为 0,对应 LED 点亮;下一次按键又翻转为 1,对应 LED 熄灭。

完整的软硬件对应关系如下:

按下SW3
  │
  ├─ 电路:P3.2被SW3接地,形成下降沿
  ├─ 寄存器:IT0检测 → IE0置位 → EX0与EA放行
  ├─ CPU:进入interrupt 0
  └─ 输出:P1.2翻转,LED3改变亮灭状态

按下SW4
  │
  ├─ 电路:P3.3被SW4接地,形成下降沿
  ├─ 寄存器:IT1检测 → IE1置位 → EX1与EA放行
  ├─ CPU:进入interrupt 2
  └─ 输出:P1.3翻转,LED4改变亮灭状态

经典 8051 常讲 INT0、INT1 两个外部中断,本开发板也只把两个独立按键接到 P3.2、P3.3。但本板使用的 STC89C52 系列还提供 P4.3/INT2P4.2/INT3,因此芯片本身共有四个外部中断源。INT2、INT3 由 XICON 控制,不要把“板上只有两个外部中断按键”误解成“芯片只有两个外部中断源”。

这类 8051 端口没有 STM32 式的 GPIO/复用功能选择矩阵。本例五条语句的作用分别是选择触发方式、打开两个局部允许和一个总允许;它们使外部中断硬件开始响应 P3.2、P3.3,但不是把引脚从某个独立的 GPIO 模式切换到 EXTI 模式。

1.3 STM32思维导图

STM32 的外部中断/事件控制器通常称为 EXTI。多数 GPIO 引脚可以经由 AFIO 或 SYSCFG 选择到 EXTI0~EXTI15,但同编号引脚共享同一条 EXTI 线:例如 PA0、PB0、PC0 都竞争 EXTI0,通常只能选择其中一个作为该线的输入。具体可用引脚和中断向量分组还要以对应 STM32 型号的参考手册为准。

对比项STC89C52 外部中断STM32 EXTI 的常见实现
GPIO对应关系INT0=P3.2、INT1=P3.3、INT2=P4.3、INT3=P4.2多数 GPIO 可映射到 EXTI0~EXTI15,同编号端口脚共享一条线
触发方式低电平或下降沿常见为上升沿、下降沿或双边沿;部分系列还支持更多事件配置
请求标志IE0IE3EXTI pending 标志
允许路径EXx + EAEXTI 屏蔽/使能 + NVIC 使能
优先级IP/IPH 四级NVIC 可编程优先级,位数因芯片而异
ISR处理边沿模式请求标志在响应时由硬件清除通常要在 ISR 中按芯片规定清除 pending 标志

STM32 中的三种边沿触发可以用波形理解:

低 ─────┌──────── 高      上升沿:0 → 1
高 ─────┐________ 低      下降沿:1 → 0
双边沿:上述两种变化都产生请求

两类单片机的底层思路相通:外部引脚变化被检测,硬件置请求标志,允许与优先级系统决定 CPU 是否响应,CPU 进入 ISR 处理事件,最后清理请求状态并返回主程序。先把 STC89C52 的 ITx → IEx → EXx → EA → 优先级 → ISR 链路理解清楚,再学习 STM32 的 GPIO 映射、EXTI 和 NVIC,就能把大量寄存器配置归入同一套逻辑。