一、中断系统
中断系统是单片机实现实时处理的核心机制,用于响应内外紧急事件,核心包含定义、源、流程、嵌套、向量表及控制寄存器六大模块。

1. 中断与中断源
- 中断:为使CPU具备对外界紧急事件的实时处理能力而设计的机制。当CPU执行当前程序时,被内外事件触发,暂停当前任务转去处理该事件(执行中断服务程序),处理完后返回原程序继续执行,类似"工作时接紧急电话"。
- 中断源 :触发中断的"事件来源",即导致CPU暂停的具体原因。结合STC89C51RC/RD+系列,常见中断源如下:
- 外部中断:INT0(P3.2)、INT1(P3.3)、INT2、INT3
- 定时器中断:Timer0、Timer1、Timer2(溢出触发)
- 串口中断:UART(接收/发送完成触发)
2. 中断处理流程
遵循"请求-判断-处理-恢复"逻辑,核心步骤共6步,需经过CPU内核的多层判断:
- 中断请求:中断源(如Timer0溢出、INT0按键)向CPU发送触发信号;
- 中断判断:CPU内核执行完当前指令后,先检查全局中断使能(EA)及该中断是否被屏蔽,
- 确认优先级:确认中断优先级(如高优先级中断优先响应);
- 保护现场:保存当前CPU寄存器(如累加器A、程序计数器PC)的值,避免后续处理覆盖原数据;
- 执行中断服务程序:通过中断向量表找到对应服务函数地址,跳转执行(如读取传感器、翻转IO口);
- 恢复现场:将保存的寄存器值还原,恢复CPU执行原程序的初始状态;
- 中断返回:跳回原程序被暂停的位置(恢复PC值),继续执行原任务。
3. 中断嵌套
指高优先级中断可打断正在执行的低优先级中断服务程序,处理完高优先级中断后,返回继续执行低优先级中断,最终恢复原程序,本质是"优先级抢占"机制。
- 示例:CPU正在处理"INT1按键中断"(低优先级),此时"Timer0溢出中断"(高优先级)触发,CPU会暂停INT1处理,优先执行Timer0服务程序,执行完后回到INT1中断,最后返回原程序。
- 优先级控制:通过IP(中断优先级寄存器)、IPH(高优先级寄存器) 配置,STC89C51默认优先级从高到低为:INT0 > Timer0 > INT1 > Timer1 > UART > Timer2 > INT2 > INT3。
4. 中断向量表
是存储"中断号(查询次序号)"与"中断服务程序入口地址"的指针数组,相当于CPU的"中断导航目录",用于快速定位服务程序。
-
51单片机C语言编程中,中断号直接对应
interrupt
关键字后的参数,具体映射如下:中断类型 中断号 服务函数示例 外部中断0(INT0) 0 void Int0_Routine(void) interrupt 0;
定时器0(Timer0) 1 void Timer0_Routine(void) interrupt 1;
外部中断1(INT1) 2 void Int1_Routine(void) interrupt 2;
定时器1(Timer1) 3 void Timer1_Routine(void) interrupt 3;
串口(UART) 4 void UART_Routine(void) interrupt 4;
定时器2(Timer2) 5 void Timer2_Routine(void) interrupt 5;
外部中断2(INT2) 6 void Int2_Routine(void) interrupt 6;
外部中断3(INT3) 7 void Int3_Routine(void) interrupt 7;
5. 中断控制寄存器
中断的使能、类型(电平/边沿触发)需通过专用寄存器配置,核心寄存器包括IE(中断允许寄存器) 和TCON(定时器/中断控制寄存器)。
(1)IE寄存器(中断允许,地址:A8H,可位寻址)
位序号 | 位名称 | 功能描述 |
---|---|---|
B7 | EA | 全局中断使能位:1=允许全局中断;0=禁止所有中断 |
B6 | - | 保留位,无实际功能 |
B5 | - | 保留位,无实际功能 |
B4 | ES | 串口中断使能位:1=允许;0=禁止 |
B3 | ET1 | Timer1中断使能位:1=允许;0=禁止 |
B2 | EX1 | INT1中断使能位:1=允许;0=禁止 |
B1 | ET0 | Timer0中断使能位:1=允许;0=禁止 |
B0 | EX0 | INT0中断使能位:1=允许;0=禁止 |
(2)TCON寄存器(定时/中断控制,地址:88H,可位寻址)
位序号 | 位名称 | 功能描述 |
---|---|---|
B7 | TF1 | Timer1溢出标志:计数溢出时硬件置1,CPU响应中断后硬件清0(也可软件清0) |
B6 | TR1 | Timer1运行控制位:1=启动Timer1;0=停止Timer1 |
B5 | TF0 | Timer0溢出标志:功能同TF1,对应Timer0 |
B4 | TR0 | Timer0运行控制位:功能同TR1,对应Timer0 |
B3 | IE1 | INT1请求标志:INT1触发时硬件置1,CPU响应后硬件清0 |
B2 | IT1 | INT1触发类型位:1=下降沿触发;0=低电平触发 |
B1 | IE0 | INT0请求标志:功能同IE1,对应INT0 |
B0 | IT0 | INT0触发类型位:功能同IT1,对应INT0 |
二、定时器系统
51单片机定时器本质是"可编程计数器",基于机器周期(1机器周期=12晶振周期)实现定时或计数功能,核心包含工作原理、控制寄存器及实战代码。
1. 定时器工作原理
定时器核心是16位计数器(由高8位THx和低8位TLx组成) ,支持两种工作模式,需通过TMOD
寄存器配置模式,TRx
启动计数:
- 定时模式 :计数器对内部机器周期 计数。从预设初值(THx/TLx)开始加1,计数到65535(0xFFFF)后溢出,触发
TFx
标志,向CPU请求中断(如定时1ms); - 计数模式 :计数器对外部引脚脉冲计数。引脚(T0=P3.4、T1=P3.5)每检测到1个脉冲上升沿/下降沿,计数加1,溢出后触发中断(如统计按键次数)。
2. 核心控制寄存器
除上述TCON
外,定时器模式由TMOD
配置,初值由THx/TLx
设置:
(1)TMOD寄存器(定时器模式,地址:89H,不可位寻址)
位序号 | 位名称 | 功能描述(高4位对应Timer1,低4位对应Timer0) |
---|---|---|
B7/B3 | GATE | 门控位:1=由INTx引脚和TRx共同控制启动;0=仅由TRx控制启动 |
B6/B2 | C/T | 模式选择位:1=计数模式(外部脉冲);0=定时模式(内部机器周期) |
B5~B4/B1~B0 | M1~M0 | 工作模式位:00=13位定时/计数;01=16位定时/计数;10=8位自动重装;11=双8位 |

(2)THx/TLx寄存器(定时器初值寄存器)
TH0
(地址8CH)/TL0
(地址8AH):Timer0的高8位/低8位初值寄存器;TH1
(地址8DH)/TL1
(地址8BH):Timer1的高8位/低8位初值寄存器;- 初值计算:若定时t ms,晶振频率f_{\\text{osc}}(如11.0592MHz),则初值 = 65536 - \\frac{t \\times 10\^{-3} \\times f_{\\text{osc}} \\times 10\^{6}}{12}。
3. 实战代码示例
(1)定时器0控制P2口LED闪烁(定时500ms翻转)
c
(1)定时器0控制P2口LED闪烁(定时500ms翻转)
#include <reg52.h>
// 定时器0初始化:16位定时模式,允许中断
void init_timer0(void)
{
IE |= (1 << 7) | (1 << 1); // 使能全局中断(EA=1)和Timer0中断(ET0=1)
TCON |= (1 << 4); // 启动Timer0(TR0=1)
TMOD &= ~(3 << 2); // 清除Timer0模式位(M1~M0)
TMOD &= ~(1 << 1);
TMOD |= (1 << 0); // Timer0设为16位定时模式(M1=0,M0=1)
TH0 = 64535 >> 8; // 加载初值(定时约1ms,晶振11.0592MHz)
TL0 = 64535;
}
// Timer0中断服务函数:计数500次(500ms)翻转P2口
void timer0_handler(void) interrupt 1
{
static int t = 0;
++t;
if (t >= 500) { // 累计500ms
P2 ^= 0xFF; // 翻转P2所有引脚(LED闪烁)
t = 0;
}
TH0 = 65035 >> 8; // 重新加载初值(避免溢出后计数不准)
TL0 = 65035;
}
int main(void)
{
init_timer0();
P2 = 0xFF; // 初始P2口高电平(LED灭)
while (1)
{
} // 主循环空转,等待中断
return 0;
}
(2)定时器控制和五个按键控制蜂鸣器不同频率的声音
cs
#include <reg52.h>
#include "key.h" // 需确保该头文件中实现了init_key()(按键初始化)和key_pressed()(按键检测)
// 蜂鸣器频率对应的定时器0初值(晶振频率默认11.0592MHz,1机器周期=12/11059200≈1.085μs)
// 计算逻辑:定时时间=(65536-初值)×机器周期,蜂鸣器方波频率=1/(2×定时时间)(翻转一次为半个周期)
#define Hz200 63035 // 200Hz频率对应的定时器初值
#define Hz400 64285 // 400Hz频率对应的定时器初值
#define Hz800 64910 // 800Hz频率对应的定时器初值
#define Hz1000 65035 // 1000Hz频率对应的定时器初值
#define Hz2000 65285 // 2000Hz频率对应的定时器初值
unsigned short n = Hz200; // 蜂鸣器初始频率对应的定时器初值(默认200Hz)
/**
* @brief 定时器0初始化函数(16位定时模式,用于产生蜂鸣器驱动方波)
* 配置说明:
* 1. 使能全局中断(EA=1)和定时器0中断(ET0=1)
* 2. 定时器0工作模式:模式1(16位定时,需手动重装初值)
* 3. 初始加载默认频率(200Hz)的初值,定时器暂不启动(TR0=0,由按键控制启动)
*/
void init_timer0(void)
{
IE |= (1 << 7) | (1 << 1); // EA=1(全局中断使能),ET0=1(定时器0中断使能)
// TCON |= (1 << 4); // 注释:定时器0运行控制位TR0暂不置1,由按键触发启动
// 配置定时器0工作模式(模式1:16位定时)
TMOD &= ~(3 << 2); // 清除定时器1的模式位(TMOD高4位,避免干扰)
TMOD &= ~(3 << 0); // 清除定时器0的模式位(TMOD低4位:M1M0初始化为00)
TMOD |= (1 << 0); // 定时器0模式1:M1=0,M0=1(16位定时模式)
TH0 = n >> 8; // 加载定时器0高8位初值(n为16位,右移8位取高字节)
TL0 = n; // 加载定时器0低8位初值
}
/**
* @brief 定时器0中断服务函数(核心:翻转P2.1引脚,产生蜂鸣器驱动方波)
* 逻辑说明:
* 1. 每次定时器0溢出(达到定时时间),触发中断
* 2. 翻转P2.1电平(高→低/低→高),产生方波信号
* 3. 重新加载初值(16位模式无自动重装,需手动补初值,确保定时精度)
*/
void timer0_handler(void) interrupt 1
{
P2 ^= (1 << 1); // 翻转P2.1引脚电平(蜂鸣器接此引脚,方波驱动发声)
TH0 = n >> 8; // 重新加载定时器高8位初值(避免下一次定时时间偏差)
TL0 = n; // 重新加载定时器低8位初值
}
/**
* @brief 主函数(按键检测+频率切换+定时器启停控制)
* 功能流程:
* 1. 初始化定时器0和按键
* 2. 循环检测按键:
* - 按键1~5:设置对应频率的初值,启动定时器0(蜂鸣器按对应频率发声)
* - 无按键(key=0):停止定时器0(蜂鸣器静音)
*/
int main(void)
{
int key; // 优化:将key定义在函数开头,兼容部分51编译器的局部变量处理规则
init_timer0(); // 初始化定时器0
init_key(); // 初始化按键(需在key.h中实现,如配置按键引脚、消抖等)
while(1) // 主循环:持续检测按键
{
key = key_pressed(); // 读取按键值(返回0=无按键,1~5=对应按键)
// 按键1:蜂鸣器频率设为200Hz,启动定时器0
if(key == 1)
{
n = Hz200;
TCON |= (1 << 4); // TR0=1(启动定时器0,开始产生方波)
}
// 按键2:蜂鸣器频率设为400Hz,启动定时器0
else if(key == 2)
{
n = Hz400;
TCON |= (1 << 4);
}
// 按键3:蜂鸣器频率设为800Hz,启动定时器0
else if(key == 3)
{
n = Hz800;
TCON |= (1 << 4);
}
// 按键4:蜂鸣器频率设为1000Hz,启动定时器0
else if(key == 4)
{
n = Hz1000;
TCON |= (1 << 4);
}
// 按键5:蜂鸣器频率设为2000Hz,启动定时器0
else if(key == 5)
{
n = Hz2000;
TCON |= (1 << 4);
}
// 无按键:停止定时器0(蜂鸣器静音)
else if(key == 0)
{
TCON &= ~(1 << 4); // TR0=0(停止定时器0,方波中断,蜂鸣器不发声)
}
}
return 0; // 主函数返回(实际51单片机中此句可省略,循环永不退出)
}
key.c
cs
#include <reg52.h>
void init_key(void)
{
P1 |= (0x0f << 4);
}
int key_pressed(void)
{
int ret = 0;
if((P1 & (1 << 4)) == 0)
{
ret = 1;
}
else if((P1 & (1 << 5)) == 0)
{
ret = 2;
}
else if((P1 & (1 << 6)) == 0)
{
ret = 3;
}
else if((P1 & (1 << 7)) == 0)
{
ret = 4;
}
else if((P3 & (1 << 5)) == 0)
{
ret = 5;
}
return ret;
}