第十六章 定时器中断
1. 导入
在前面章节中,我们使用软件延时函数(如delay_ms
)控制LED闪烁、数码管扫描等。但软件延时会阻塞CPU ,无法同时执行其他任务。为实现精确、非阻塞的时间控制 ,本章学习51单片机的定时器/计数器 模块,并通过定时器中断实现周期性任务调度。
51单片机内置两个16位定时器:T0 和 T1 ,可配置为定时器(计时)或计数器(计外部脉冲)。本章重点使用定时器模式,结合中断机制,实现:
- 精确毫秒级延时;
- 非阻塞LED闪烁;
- 动态数码管扫描;
- 实时时钟基础;
- 为多任务系统打下基础。
2. 硬件设计
2.1 定时器工作原理
定时器通过对机器周期 进行计数实现定时。
51单片机一个机器周期 = 12个时钟周期。
若晶振为 11.0592MHz,则:
机器周期=11.0592×10612≈1.085μs
定时器从初值开始递增,当计满(溢出)时触发中断。
2.2 相关寄存器
寄存器 | 功能说明 |
---|---|
TMOD | 模式控制寄存器,设置T0/T1工作方式 |
TH0/TL0 | 定时器0高8位/低8位 |
TH1/TL1 | 定时器1高8位/低8位 |
TCON | 控制寄存器,含TR0、TF0等 |
IE | 中断使能寄存器 |
TMOD格式(高4位为T1,低4位为T0):
GATE | C/T | M1 | M0 | 功能 |
---|---|---|---|---|
0 | 0 | 1 | 0 | 定时器模式,方式2(8位自动重装) |
0 | 0 | 1 | 1 | 方式1(16位定时器,常用) |
本章使用方式1:16位定时器,最大计数值65536。
3. 软件设计
3.1 定时器初始化(以T0为例)
目标:每50ms中断一次(常用于数码管扫描或实时时钟)。
计数值=1.085×10−650×10−3≈46082
初值 = 65536−46082=19454=0x4BE6
c
#include <reg52.h>
void timer0_init() {
TMOD = 0x01; // T0工作方式1(16位定时器)
TH0 = 0x4B; // 高8位
TL0 = 0xE6; // 低8位
ET0 = 1; // 使能T0中断
EA = 1; // 开启总中断
TR0 = 1; // 启动定时器
}
3.2 定时器中断服务函数
c
unsigned char timer_count = 0; // 记录中断次数
void timer0_isr() interrupt 1 {
TH0 = 0x4B; // 重新加载初值
TL0 = 0xE6;
timer_count++;
if (timer_count >= 20) { // 50ms × 20 = 1s
P1 = ~P1; // 每秒翻转P1口(LED闪烁)
timer_count = 0;
}
}
interrupt 1
表示T0中断的中断号。
3.3 完整示例:非阻塞LED闪烁
c
void main() {
timer0_init();
while(1) {
// 主程序可执行其他任务
// 如:按键扫描、电机控制、数据处理等
}
}
此时LED每秒闪烁一次,但主程序无需延时,可并行处理其他任务。
3.4 应用1:动态数码管扫描(非阻塞)
将数码管扫描放入定时器中断,每1ms执行一次:
c
unsigned char display_pos = 0;
unsigned char show_num[4] = {1, 2, 3, 4}; // 显示内容
unsigned char code seg_code[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void timer0_init_1ms() {
TMOD = 0x01;
// 1ms = 1000μs → 计数 ≈ 922
// 初值 = 65536 - 922 = 64614 = 0xFC66
TH0 = 0xFC;
TL0 = 0x66;
ET0 = 1;
EA = 1;
TR0 = 1;
}
void timer0_isr() interrupt 1 {
TH0 = 0xFC;
TL0 = 0x66;
P0 = 0x00;
P2 = (P2 & 0xF0) | 0x0F; // 关闭所有位选
P0 = seg_code[show_num[display_pos]];
P2 = (P2 & 0xF0) | (0x0F & ~(1 << display_pos));
display_pos++;
if (display_pos > 3) display_pos = 0;
}
数码管自动扫描,主程序无需干预。
3.5 应用2:实时时钟(秒计时)
c
unsigned char sec = 0, min = 0, hour = 0;
void timer0_isr() interrupt 1 {
static unsigned int count = 0;
TH0 = 0x4B;
TL0 = 0xE6;
count++;
if (count >= 20) { // 1秒
count = 0;
sec++;
if (sec >= 60) {
sec = 0;
min++;
if (min >= 60) {
min = 0;
hour++;
if (hour >= 24) hour = 0;
}
}
}
}
可结合数码管显示时间。
3.6 定时器1的使用(扩展)
定时器1配置方式与T0类似,中断号为 interrupt 3
。
c
void timer1_init() {
TMOD |= 0x10; // T1方式1
TH1 = 0x4B;
TL1 = 0xE6;
ET1 = 1;
EA = 1;
TR1 = 1;
}
void timer1_isr() interrupt 3 {
TH1 = 0x4B;
TL1 = 0xE6;
// 执行T1任务
}
可用于串口波特率发生器或第二路定时任务。
3.7 编译与下载
- Keil中创建工程;
- 确保定时器初值计算正确;
- 编译生成HEX;
- 下载至单片机;
- 观察LED是否定时闪烁,数码管是否稳定显示。
若中断不触发:
- 检查
EA
、ET0
是否开启;- 确认
TR0=1
已启动;- 检查初值是否正确(可使用工具自动计算)。
4. 小结
本章通过学习定时器中断,掌握了非阻塞时间控制技术,主要内容包括:
- 定时原理:理解机器周期与计数关系;
- 寄存器配置:掌握TMOD、TH0/TL0、TCON、IE等设置;
- 中断服务:编写定时器中断函数,实现周期性任务;
- 应用实践:实现LED闪烁、数码管扫描、实时时钟;
- 系统优化:解放主程序,提升系统响应能力与稳定性。
4.1 常见问题与解决
问题 | 原因 | 解决方法 |
---|---|---|
中断不执行 | EA或ET0未开启 | 检查中断使能 |
定时不准确 | 初值计算错误 | 重新计算或使用定时器计算器 |
程序跑飞 | 中断函数过长或嵌套 | 保持中断函数简洁 |
溢出错误 | 未重装初值 | 在中断中重新赋值TH0/TL0 |
4.2 下一步学习建议
- 使用定时器实现PWM输出;
- 结合外部中断实现多事件响应;
- 设计多任务调度器;
- 应用于电子钟 、数据采集系统等项目。
本章标志着你已掌握精准时间控制能力,下一章将进入串口通信(UART) 的学习,实现单片机与PC之间的数据交互。