中断是51单片机最核心的功能之一,它允许处理器在执行主程序的同时,能够及时响应外部或内部的突发事件。想象一下你正在看书(主程序),突然电话响了(中断请求),你会先做个书签标记当前阅读位置(保护现场),接完电话后再回到原位置继续阅读(恢复现场)。51单片机的中断机制也是类似的原理。
51单片机通常包含5个中断源:
这些中断源通过四个特殊功能寄存器进行控制:
提示:不同厂商的51内核单片机可能扩展了更多中断源,使用前需查阅具体型号的数据手册。
以INT0(P3.2引脚)为例,硬件连接通常有三种方式:
c复制// 典型电路示例
// P3.2 ----[10K上拉]---- VCC
// |
// [按键]
// |
// GND
完整的初始化流程应包含以下步骤:
设置中断触发方式(TCON寄存器):
c复制IT0 = 1; // 设置INT0为下降沿触发
开启外部中断(IE寄存器):
c复制EX0 = 1; // 允许INT0中断
EA = 1; // 开启总中断
(可选)设置中断优先级(IP寄存器):
c复制PX0 = 1; // 设置INT0为高优先级
注意:使用低电平触发时,必须确保中断信号持续时间足够短(通常<3个机器周期),否则会重复触发中断。
在Keil C51中,中断函数通过interrupt关键字和中断号定义:
c复制void INT0_ISR() interrupt 0 // INT0中断号为0
{
// 中断处理代码
IE0 = 1; // 清除中断标志(某些型号需软件清零)
}
关键注意事项:
为防止中断破坏主程序环境,应手动保护关键寄存器:
c复制#pragma OPTIMIZE(6) // 设置最高优化级别
void INT0_ISR() interrupt 0 using 1 // 使用寄存器组1
{
/* 编译器会自动生成现场保护代码 */
/* 用户处理逻辑 */
}
经验:使用using指定专用寄存器组可显著减少现场保护时间,但要注意避免与主程序冲突。
以Timer0模式1(16位定时器)为例:
设置工作模式(TMOD寄存器):
c复制TMOD &= 0xF0; // 清零Timer0控制位
TMOD |= 0x01; // 设置为模式1
计算并装入初值:
c复制#define FOSC 11059200UL // 晶振频率
#define TIMER_MS 1 // 定时1ms
TH0 = (65536 - FOSC/12/1000*TIMER_MS) >> 8;
TL0 = (65536 - FOSC/12/1000*TIMER_MS) & 0xFF;
启动定时器(TCON寄存器):
c复制TR0 = 1; // 启动Timer0
ET0 = 1; // 允许Timer0中断
EA = 1; // 开启总中断
通过中断实现微秒级延时:
c复制volatile uint16_t timer0_count;
void Timer0_ISR() interrupt 1
{
TH0 = 0xFC; // 重装1ms初值
TL0 = 0x66;
if(timer0_count) timer0_count--;
}
void delay_ms(uint16_t ms)
{
timer0_count = ms;
while(timer0_count);
}
实测技巧:在12MHz晶振下,上述代码实际误差<±0.5%,比软件延时更精确。
影响中断响应时间的因素:
优化建议:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 中断不触发 | 中断未使能(EA/EX) | 检查IE寄存器配置 |
| 多次意外触发 | 未清除标志位 | 检查TCON中的TF/IE位 |
| 执行结果异常 | 现场保护不完整 | 使用using或手动保护ACC/PSW |
| 定时不准 | 初值计算错误 | 核对晶振频率和分频系数 |
c复制volatile uint8_t adc_ready = 0;
void ADC_ISR() interrupt 5
{
adc_ready = 1; // 仅设置标志
}
void main()
{
while(1){
if(adc_ready){
adc_ready = 0;
// 实际处理ADC数据
}
}
}
低功耗设计中的典型应用:
c复制void enter_sleep()
{
PCON |= 0x01; // 置位IDL进入空闲模式
_nop_(); // 等待中断唤醒
}
void INT0_ISR() interrupt 0
{
// 唤醒后首先执行此处
PCON &= ~0x01; // 清除休眠标志
}
实现两级中断嵌套:
c复制void config_interrupts()
{
IP = 0x04; // 设置Timer0为高优先级
IE = 0x8B; // 开启INT0、Timer0、Timer1中断
}
void Timer0_ISR() interrupt 1 // 高优先级
{
// 可打断低优先级中断
}
void INT0_ISR() interrupt 0 // 低优先级
{
// 执行时可能被Timer0中断
}
在实际项目中,我习惯为每个中断编写详细的注释说明,包括:
这种文档习惯在后期维护和团队协作中能节省大量调试时间。对于复杂的多中断系统,建议绘制中断关系流程图,明确标注各中断的优先级和可能的嵌套情况。