51单片机基础-定时器中断
第十六章 定时器中断
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
#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 定时器中断服务函数
unsigned char timer_count = 0; // 记录中断次数void timer0_isr() interrupt 1 {TH0 = 0x4B; // 重新加载初值TL0 = 0xE6;timer_count++;if (timer_count >= 20) { // 50ms × 20 = 1sP1 = ~P1; // 每秒翻转P1口(LED闪烁)timer_count = 0;}
}
interrupt 1
表示T0中断的中断号。
3.3 完整示例:非阻塞LED闪烁
void main() {timer0_init();while(1) {// 主程序可执行其他任务// 如:按键扫描、电机控制、数据处理等}
}
此时LED每秒闪烁一次,但主程序无需延时,可并行处理其他任务。
3.4 应用1:动态数码管扫描(非阻塞)
将数码管扫描放入定时器中断,每1ms执行一次:
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 = 0xFC66TH0 = 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:实时时钟(秒计时)
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
。
void timer1_init() {TMOD |= 0x10; // T1方式1TH1 = 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之间的数据交互。