1. 定时器基础回顾你的单片机闹钟刚接触51单片机的定时器时我总喜欢把它比作厨房里的多功能计时器。想象一下你正在煮一锅汤同时还要烤面包。如果一直盯着时钟看时间其他事情就做不了了。这时候定时器就像你的厨房助手到点就会提醒你该做什么。51单片机通常配备2-3个定时器T0/T1/T2它们本质上都是16位的计数器。以最常见的12MHz晶振为例经过12分频后定时器每1微秒会计数一次。就像沙漏里的沙子当沙子漏完计数溢出就会触发中断告诉CPU时间到了这里有个关键公式要记住定时时间 (65536 - 初值) × 机器周期比如要实现50ms定时计算过程是这样的// 12MHz晶振下 初值 65536 - 50000 15536 // 因为50ms50000us TH0 15536 / 256; // 高8位 TL0 15536 % 256; // 低8位2. 精准延时告别低效的Delay函数很多新手最常犯的错误就是滥用Delay函数。我曾经调试一个项目发现LED闪烁总是不准最后发现是Delay函数被中断打断了。来看个典型对比传统Delay方式void delay_ms(unsigned int ms) { while(ms--) { for(i0; i114; i); // 空循环 } }这种方式的三大致命伤CPU完全被占用不能做其他事时间精度受中断影响不同编译器优化效果不同定时器中断方案volatile unsigned int timer_count 0; void Timer0_Init() { TMOD 0x01; // 模式1 TH0 0x3C; // 50ms初值 TL0 0xB0; ET0 1; EA 1; TR0 1; } void Timer0_ISR() interrupt 1 { TH0 0x3C; // 重装初值 TL0 0xB0; timer_count; } // 使用时 while(timer_count 20); // 等待1秒 timer_count 0;实测下来定时器方案的精度可以控制在±0.1%以内而Delay函数可能会有5%以上的误差。3. 多任务调度时间片轮转实战我曾用51单片机同时控制LED流水灯、数码管显示和按键扫描秘诀就是时间片轮转。原理就像幼儿园老师分糖果给每个任务分配固定时间轮流执行。任务调度器框架#define MAX_TASKS 3 unsigned char task_delay[MAX_TASKS]; // 任务延时计数器 void Timer0_ISR() interrupt 1 { static unsigned char task_idx 0; TH0 0xFC; // 1ms中断 TL0 0x66; // 任务调度 if(--task_delay[task_idx] 0) { switch(task_idx) { case 0: task_led(); break; case 1: task_display(); break; case 2: task_key(); break; } task_delay[task_idx] task_interval[task_idx]; } task_idx (task_idx 1) % MAX_TASKS; }典型任务配置任务执行间隔执行时间优先级LED控制10ms0.5ms低数码管扫描2ms1ms高按键检测50ms0.2ms中实际项目中我踩过的一个坑某个任务执行时间超过了分配的时间片导致其他任务饿死。解决方法要么优化代码要么增加超时检测if(P3_4 0) { // 按键检测 static unsigned char hold_time 0; if(hold_time 100) { // 超时保护 hold_time 0; return; } }4. 进阶技巧定时器的创造性应用除了基础功能定时器还能玩出很多花样。去年我做的一个智能花盆项目就用T0和T1实现了这些功能PWM调光用定时器模拟void Timer1_ISR() interrupt 3 { static unsigned char pwm_cnt 0; TH1 0xFF; // 高频中断 TL1 0x00; if(pwm_cnt 100) pwm_cnt 0; LED (pwm_cnt duty_cycle) ? 1 : 0; }外部事件计数将定时器设为计数器模式TMOD 0x05; // T0作为计数器 TH0 0x00; // 从0开始计数 TL0 0x00; TR0 1; // 启动计数 // 通过P3.4(T0)引脚输入脉冲硬件消抖比软件延时更可靠if(key_pressed) { TR0 1; // 启动20ms定时器 } void Timer0_ISR() interrupt 1 { TR0 0; // 停止定时器 if(key_stable) { process_key(); } }定时器就像单片机的瑞士军刀用的好能让你的代码既高效又优雅。刚开始可能会觉得寄存器配置很复杂但掌握后会发现它比软件延时可靠得多。建议从简单的1ms定时开始逐步尝试更复杂的应用你会爱上这种设置好就忘掉的编程方式。
51单片机从零开始入门教程 第七章(定时器实战:精准延时与多任务调度)
发布时间:2026/7/15 20:39:04
1. 定时器基础回顾你的单片机闹钟刚接触51单片机的定时器时我总喜欢把它比作厨房里的多功能计时器。想象一下你正在煮一锅汤同时还要烤面包。如果一直盯着时钟看时间其他事情就做不了了。这时候定时器就像你的厨房助手到点就会提醒你该做什么。51单片机通常配备2-3个定时器T0/T1/T2它们本质上都是16位的计数器。以最常见的12MHz晶振为例经过12分频后定时器每1微秒会计数一次。就像沙漏里的沙子当沙子漏完计数溢出就会触发中断告诉CPU时间到了这里有个关键公式要记住定时时间 (65536 - 初值) × 机器周期比如要实现50ms定时计算过程是这样的// 12MHz晶振下 初值 65536 - 50000 15536 // 因为50ms50000us TH0 15536 / 256; // 高8位 TL0 15536 % 256; // 低8位2. 精准延时告别低效的Delay函数很多新手最常犯的错误就是滥用Delay函数。我曾经调试一个项目发现LED闪烁总是不准最后发现是Delay函数被中断打断了。来看个典型对比传统Delay方式void delay_ms(unsigned int ms) { while(ms--) { for(i0; i114; i); // 空循环 } }这种方式的三大致命伤CPU完全被占用不能做其他事时间精度受中断影响不同编译器优化效果不同定时器中断方案volatile unsigned int timer_count 0; void Timer0_Init() { TMOD 0x01; // 模式1 TH0 0x3C; // 50ms初值 TL0 0xB0; ET0 1; EA 1; TR0 1; } void Timer0_ISR() interrupt 1 { TH0 0x3C; // 重装初值 TL0 0xB0; timer_count; } // 使用时 while(timer_count 20); // 等待1秒 timer_count 0;实测下来定时器方案的精度可以控制在±0.1%以内而Delay函数可能会有5%以上的误差。3. 多任务调度时间片轮转实战我曾用51单片机同时控制LED流水灯、数码管显示和按键扫描秘诀就是时间片轮转。原理就像幼儿园老师分糖果给每个任务分配固定时间轮流执行。任务调度器框架#define MAX_TASKS 3 unsigned char task_delay[MAX_TASKS]; // 任务延时计数器 void Timer0_ISR() interrupt 1 { static unsigned char task_idx 0; TH0 0xFC; // 1ms中断 TL0 0x66; // 任务调度 if(--task_delay[task_idx] 0) { switch(task_idx) { case 0: task_led(); break; case 1: task_display(); break; case 2: task_key(); break; } task_delay[task_idx] task_interval[task_idx]; } task_idx (task_idx 1) % MAX_TASKS; }典型任务配置任务执行间隔执行时间优先级LED控制10ms0.5ms低数码管扫描2ms1ms高按键检测50ms0.2ms中实际项目中我踩过的一个坑某个任务执行时间超过了分配的时间片导致其他任务饿死。解决方法要么优化代码要么增加超时检测if(P3_4 0) { // 按键检测 static unsigned char hold_time 0; if(hold_time 100) { // 超时保护 hold_time 0; return; } }4. 进阶技巧定时器的创造性应用除了基础功能定时器还能玩出很多花样。去年我做的一个智能花盆项目就用T0和T1实现了这些功能PWM调光用定时器模拟void Timer1_ISR() interrupt 3 { static unsigned char pwm_cnt 0; TH1 0xFF; // 高频中断 TL1 0x00; if(pwm_cnt 100) pwm_cnt 0; LED (pwm_cnt duty_cycle) ? 1 : 0; }外部事件计数将定时器设为计数器模式TMOD 0x05; // T0作为计数器 TH0 0x00; // 从0开始计数 TL0 0x00; TR0 1; // 启动计数 // 通过P3.4(T0)引脚输入脉冲硬件消抖比软件延时更可靠if(key_pressed) { TR0 1; // 启动20ms定时器 } void Timer0_ISR() interrupt 1 { TR0 0; // 停止定时器 if(key_stable) { process_key(); } }定时器就像单片机的瑞士军刀用的好能让你的代码既高效又优雅。刚开始可能会觉得寄存器配置很复杂但掌握后会发现它比软件延时可靠得多。建议从简单的1ms定时开始逐步尝试更复杂的应用你会爱上这种设置好就忘掉的编程方式。