51单片机基础-详解中断系统
本文详细解析了8051单片机中断系统的核心概念与应用。主要内容包括:1)8051中断源的类型、向量地址及寄存器配置(IE、IP、TCON等);2)不同中断的触发方式与标志位清除规则,强调外部中断的电平/边沿触发差异及串口中断需手动清零标志位;3)中断优先级机制与嵌套规则,说明标准8051的两级优先系统;4)提供典型中断应用代码范例:毫秒定时器节拍、外部按键消抖处理、串口中断环形缓冲实现;5)介绍关
·
第三十二章 详解中断系统
1. 导入
中断让单片机“被动响应”外部/内部事件,无需主循环轮询即可在恰当时刻处理计时、按键、串口收发等任务。本章系统梳理 8051/8052 的中断源、向量与优先级、触发方式与标志位清除规则,并给出可靠的代码范式:毫秒节拍、外部按键(边沿/电平)、串口收发缓冲,以及中断嵌套与关键区保护。
2. 中断源、向量与寄存器
- 向量表(代码空间地址,C 编译器自动生成 RETI):
0x0003:INT0外部中断0(P3.2)0x000B:T0定时器0溢出0x0013:INT1外部中断1(P3.3)0x001B:T1定时器1溢出0x0023:串口(RI/TI)0x002B:T2定时器2(仅 8052/增强型)
- 使能/优先级寄存器(经典 8051):
IE:EA总开关;EX0 ET0 EX1 ET1 ES (ET2)各源使能IP:PX0 PT0 PX1 PT1 PS (PT2)各源优先级(1=高,0=低)- 增强型 51(如 STC)常有
IPH扩展更多优先级级别,具体查手册
- 触发与标志寄存器:
TCON:TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0IT0/IT1:外部中断触发方式(1=边沿触发下降沿,0=电平触发低电平)IE0/IE1:外部中断请求标志(由硬件置位)TF0/TF1:定时器溢出标志(硬件置位)
SCON(串口):SM0 SM1 SM2 REN TB8 RB8 TI RI
3. 触发与清除规则(高频考点)
- 外部中断
INT0/1ITx=1(下降沿触发):触发后硬件自动清IEx,对抖动不敏感ITx=0(低电平触发):只要引脚保持低就持续请求;退出前必须确保引脚回到高,否则会“连发”
- 定时器
TF0/TF1- 进入中断时硬件自动清除
TFx
- 进入中断时硬件自动清除
- 串口
RI/TI- 不会自动清除,必须在 ISR 中由软件清零对应标志位(通常按需分别判断)
4. 中断优先级与嵌套
- 两级优先系统(标准 8051):
- 高优先级可打断低优先级 ISR
- 同优先级之间不互相打断,响应顺序按内部轮询顺序:
INT0 > T0 > INT1 > T1 > 串口 (> T2)
- 嵌套注意:
- 不建议在 C 里人为
EA=1以允许同级互相嵌套,容易失控 - 如需更深层次控制,使用多级优先(带
IPH的增强型)或在高优先级 ISR 内短小处理、快速返回
- 不建议在 C 里人为
5. 代码范式与完整示例
5.1 毫秒系统节拍(T0 1ms)+ 软定时
#include <reg52.h>
/* 1ms 节拍 @11.0592MHz:TH0/TL0=0xFC66 */
volatile unsigned long sys_ms = 0;
void t0_init_1ms(void){
TMOD = (TMOD & 0xF0) | 0x01; // T0 方式1
TH0 = 0xFC; TL0 = 0x66;
ET0 = 1; EA = 1; TR0 = 1;
}
void t0_isr(void) interrupt 1 { // 向量 0x000B
TH0 = 0xFC; TL0 = 0x66;
sys_ms++;
}
/* 非阻塞延时:读取 sys_ms 轮询 */
void delay_ms_nb(unsigned int ms){
unsigned long t0 = sys_ms;
while ((unsigned long)(sys_ms - t0) < ms) { /* 允许主循环做其它事 */ }
}
要点:
- ISR 极短,仅维护节拍变量;避免在 ISR 内 printf/长延时
- 与主循环通信的变量用
volatile,防止优化器错误假定
5.2 外部中断按键(下降沿触发,硬消抖/软消抖)
#include <reg52.h>
sbit KEY_PIN = P3^2; // INT0 脚
volatile bit key_event = 0;
void int0_init_falling(void){
IT0 = 1; // 下降沿触发
EX0 = 1; EA = 1;
}
void ex0_isr(void) interrupt 0 { // 向量 0x0003
// 这里不做复杂逻辑,只置事件标志
key_event = 1;
}
/* 主循环中消抖+处理 */
void key_task(void){
if(key_event){
key_event = 0;
// 简单消抖:延时再读脚位(或用时间戳法更优)
{
unsigned int i,j; for(i=0;i<5;i++) for(j=0;j<125;j++);
}
if(KEY_PIN == 1){ // 下降沿后已回到高,确认一次有效按键
// TODO: 执行按键事件
}
}
}
若必须用电平触发(IT0=0),务必确保 ISR 结束前把输入拉回高电平(硬件/上拉/释放),否则会“黏住”反复进入。
5.3 串口中断收发(环形缓冲)——可靠不丢字
#include <reg52.h>
#define RX_BUF_SZ 32
volatile unsigned char rx_buf[RX_BUF_SZ];
volatile unsigned char rx_head=0, rx_tail=0;
void uart_init_9600(void){
TMOD |= 0x20; TH1=0xFD; TL1=0xFD; TR1=1;
SCON = 0x50; // 模式1,8N1,REN=1
ES = 1; EA = 1;
}
void uart_isr(void) interrupt 4 { // 向量 0x0023
if(RI){ // 接收中断
unsigned char nxt = (rx_head + 1) % RX_BUF_SZ;
unsigned char c = SBUF;
if(nxt != rx_tail){ rx_buf[rx_head] = c; rx_head = nxt; } // 不满则收
RI = 0; // 必须手动清
}
if(TI){ // 发送完成(若用阻塞发送可忽略)
TI = 0; // 清发送标志
}
}
bit uart_getc_nblk(unsigned char* pc){
if(rx_head == rx_tail) return 0;
*pc = rx_buf[rx_tail];
rx_tail = (rx_tail + 1) % RX_BUF_SZ;
return 1;
}
要点:
RI/TI必须由软件清零- 在 ISR 内不做打印,仅搬运数据到缓冲区
- 若需要发送中断驱动,可在 TI=1 时喂下一个字节
5.4 中断优先级设置与“关键区”保护
void intr_priority_setup(void){
// 设定高/低优先级:让串口(PS)高于T0(PT0),T0高于外部INT0(PX0)
IP = 0x10 /*PS*/ | 0x02 /*PT0*/ | 0x00 /*PX0*/;
// 若为增强型带 IPH,可进一步细化级别
}
/* 关键区保护:保存并恢复 EA */
void critical_section_example(void){
bit ea_save = EA;
EA = 0; // 关闭全局中断
// ... 修改与ISR共享的多字节变量 ...
EA = ea_save; // 恢复
}
说明:
- 两级优先:高优先能打断低优先 ISR;同级不嵌套
- 不建议长时间关闭
EA;保护窗口尽量小
5.5 使用备用寄存器组降低 ISR 进入/退出开销(Keil C51)
// 使用寄存器组1,可减少R0~R7保护开销与延迟
void ex0_isr(void) interrupt 0 using 1 {
// ...
}
注意:选择的 using 组需与主程序错开,避免抢占。
6. 定时器为中断源的常见配置
- T0/T1 方式选择(
TMOD):- 方式0:13位;方式1:16位常用;方式2:8位自动重装;方式3:拆分
- 自动重装(方式2)做固定速率:
/* T1 方式2自动重装,波特率/方波等稳定周期用途 */
void t1_mode2_init(unsigned char reload){
TMOD = (TMOD & 0x0F) | 0x20;
TH1 = reload; TL1 = reload;
ET1 = 1; EA = 1; TR1 = 1;
}
- 8052 的 T2(捕获/自动重装):
- 具备独立向量
0x002B与标志TF2,寄存器T2CON/RCAP2H/L - 具体位定义不同于 T0/T1,参考芯片手册
- 具备独立向量
7. 实战:把“毫秒节拍 + 按键 + 串口”整合成事件驱动
#include <reg52.h>
sbit LED = P1^0;
volatile unsigned long sys_ms = 0;
volatile bit key_event = 0;
/* --- T0 1ms --- */
void t0_init_1ms(void){
TMOD = (TMOD & 0xF0) | 0x01;
TH0 = 0xFC; TL0 = 0x66;
ET0 = 1; EA = 1; TR0 = 1;
}
void t0_isr(void) interrupt 1 { TH0=0xFC; TL0=0x66; sys_ms++; }
/* --- INT0 下降沿 --- */
void int0_init(void){ IT0=1; EX0=1; EA=1; }
void ex0_isr(void) interrupt 0 { key_event = 1; }
/* --- UART 9600 RX 缓冲 --- */
#define RXN 32
volatile unsigned char rbuf[RXN], rh=0, rt=0;
void uart_init(void){
TMOD |= 0x20; TH1=0xFD; TL1=0xFD; TR1=1;
SCON=0x50; ES=1; EA=1;
}
void uart_isr(void) interrupt 4 {
if(RI){ unsigned char n=(rh+1)%RXN, c=SBUF; if(n!=rt){ rbuf[rh]=c; rh=n; } RI=0; }
if(TI){ TI=0; }
}
bit uart_getc(unsigned char* c){ if(rh==rt) return 0; *c=rbuf[rt]; rt=(rt+1)%RXN; return 1; }
/* --- 主循环 --- */
void main(void){
unsigned long t = 0;
LED = 1;
t0_init_1ms();
int0_init();
uart_init();
while(1){
// 500ms 心跳
if(sys_ms - t >= 500){ t += 500; LED = !LED; }
// 按键处理(中断置位 + 主循环消抖)
if(key_event){
key_event = 0;
// 简单消抖
{ unsigned int i,j; for(i=0;i<10;i++) for(j=0;j<125;j++); }
if(P3^2){ /* 确认按键有效 */ /* TODO */ }
}
// 串口处理
{ unsigned char c;
if(uart_getc(&c)){
// 简单回显
SBUF = c; while(!TI); TI=0;
}
}
}
}
要点回顾:
- ISR 只做“放旗子/搬数据”
- 主循环消费事件、做相对耗时操作(打印、运算)
- 变量跨 ISR/主循环需
volatile;多字节访问用“关键区保护”
8. 性能、栈与时序
- 中断延迟与入口开销
- 与指令边界、是否使用寄存器组、编译器保存现场策略有关
- 需要极限实时性时,可将关键 ISR 设为高优先级 +
using专用寄存器组,并保持极短
- 栈深度
- 8051 栈在内部 RAM(默认从
SP=0x07向上增长),每次中断压栈 PC(2B)等 - 避免深层函数调用/可重入函数在 ISR 中使用;必要时调整
SP起点或使用 Keilreentrant谨慎管理
- 8051 栈在内部 RAM(默认从
- 结束指令
- ISR 必须以
RETI返回(C 编译器自动生成);不要自行RET
- ISR 必须以
9. 常见问题与排查
- 外部中断“连发/黏住”
- 使用电平触发但引脚未回高;改用边沿触发或保证释放;检查上拉/消抖
- 串口收不全/丢字
- 未在 ISR 清
RI;ISR 做了耗时操作;缓冲过小;波特率误差大
- 未在 ISR 清
- 定时不准/抖动
- 装载值不对;ISR 被长时间屏蔽;频繁在 ISR 内关中断
- 系统偶发跑飞
- 栈溢出(ISR 嵌套过多/递归);ISR 使用了非可重入库函数(如
printf)
- 栈溢出(ISR 嵌套过多/递归);ISR 使用了非可重入库函数(如
- 关键区冲突
- 主循环与 ISR 同时改同一多字节变量;用
EA保护并尽量缩短临界区
- 主循环与 ISR 同时改同一多字节变量;用
10. 小结
- 掌握 8051 中断的“源—向量—寄存器—清除规则—优先级”全链路
- 用范式代码实现:毫秒节拍、外部按键中断、串口 RX 环形缓冲
- 避免在 ISR 内做重活;用事件标志+主循环处理
- 需要更高实时性与更友好优先级,选用带
IPH/多级优先的增强型 51,并合理规划 ISR 使用寄存器组
更多推荐



所有评论(0)