AT89C51定时器实战:从方波到PWM脉冲的三种经典信号生成方案
1. AT89C51定时器基础与信号生成原理
第一次接触AT89C51定时器时,我被它既能定时又能计数的双重特性惊艳到了。这就像你家里有个多功能料理机,既能榨果汁又能绞肉馅。定时器本质上是个16位的加法计数器,当它接收单片机内部的时钟脉冲时就是定时器模式,而接收外部引脚(P3.4/P3.5)的脉冲时就变身计数器。
我实测过,在12MHz晶振下,每个机器周期正好1微秒。这意味着定时器每计一个数就是1μs,这种精确度对生成周期性信号特别有用。记得初学时常搞混TMOD和TCON这两个寄存器,后来发现TMOD像定时器的"身份证",决定工作模式;而TCON则是"开关面板",控制启动停止。
定时器有四种工作模式,但生成信号最常用的是模式1(16位定时)。比如要产生2ms方波,相当于需要定时1ms后翻转电平。通过公式计算初值:65536 - 1000(因为1ms=1000μs),这个数值会被拆成高8位和低8位分别存入TH1和TL1。
2. 周期2ms方波的两种实现方案
2.1 查询法实现
查询法就像不断看手表等下课的学生。下面这段代码我调试了三次才成功:
#include <reg51.h> sbit P1_0 = P1^0; // 定义输出引脚 void main() { TMOD = 0x10; // 设置T1为模式1 TR1 = 1; // 启动定时器 while(1) { TH1 = (65536-1000)/256; TL1 = (65536-1000)%256; // 装载1ms初值 while(!TF1); // 死等溢出标志 TF1 = 0; // 必须手动清零 P1_0 = !P1_0; // 电平翻转 } }实测中发现两个坑点:一是忘记清零TF1会导致只翻转一次,二是初值计算错误会产生非对称波形。用示波器测量时,建议先检查周期是否准确,再观察占空比是否50%。
2.2 中断法实现
中断法就像设置闹钟,到点才处理:
#include <reg51.h> sbit P1_0 = P1^0; void timer1_isr() interrupt 3 { P1_0 = !P1_0; TH1 = (65536-1000)/256; // 重装初值 TL1 = (65536-1000)%256; } void main() { TMOD = 0x10; TH1 = (65536-1000)/256; TL1 = (65536-1000)%256; EA = 1; // 总中断允许 ET1 = 1; // T1中断允许 TR1 = 1; while(1); // 主循环空转 }中断法的优势是CPU不用一直查询,可以执行其他任务。但要注意中断服务程序要尽量精简,我有次在中断里做复杂计算导致信号抖动。两种方法对比如下:
| 特性 | 查询法 | 中断法 |
|---|---|---|
| CPU占用率 | 100% | <1% |
| 代码复杂度 | 简单 | 需处理中断 |
| 定时精度 | 较高 | 受中断延迟影响 |
| 适用场景 | 单一定时任务 | 多任务系统 |
3. 长周期2s方波的实现技巧
当需要2秒的长周期时,直接定时会超出16位计数范围(最大65.536ms)。我的解决方案是"化整为零":用20ms作为基础定时单元,累计100次就是2秒。
3.1 中断法实现方案
#include <reg51.h> sbit P1_0 = P1^0; unsigned char count = 0; void timer1_isr() interrupt 3 { TH1 = (65536-20000)/256; // 20ms初值 TL1 = (65536-20000)%256; if(++count == 100) { // 计满100次 count = 0; P1_0 = !P1_0; // 翻转电平 } } void main() { TMOD = 0x10; TH1 = (65536-20000)/256; TL1 = (65536-20000)%256; EA = ET1 = 1; TR1 = 1; while(1); }这里count变量要用unsigned char类型,如果定时更长时间建议改用int。我曾遇到count溢出导致信号异常的问题,后来加了volatile修饰符解决。
4. PWM脉冲生成实战
PWM(脉冲宽度调制)就像用开关水龙头控制水流大小。要产生周期20ms、占空比20%的矩形波,意味着高电平4ms,低电平16ms。
4.1 精确占空比控制
#include <reg51.h> sbit P1_0 = P1^0; unsigned char pwm_count = 0; void timer1_isr() interrupt 3 { TH1 = (65536-4000)/256; // 4ms基础单元 TL1 = (65536-4000)%256; switch(++pwm_count) { case 1: P1_0 = 0; break; // 第1次中断变低电平 case 5: // 第5次中断 P1_0 = 1; pwm_count = 0; break; } } void main() { TMOD = 0x10; TH1 = (65536-4000)/256; TL1 = (65536-4000)%256; EA = ET1 = 1; TR1 = 1; P1_0 = 1; // 初始高电平 while(1); }调试PWM时,建议先用LED观察亮度变化,再用示波器测量具体参数。占空比调整只需修改case判断条件,比如30%占空比就是3:7的比例。
5. 工程实践中的经验分享
在实际项目中,我总结出几个关键点:
- 晶振稳定性决定定时精度,普通项目用12MHz足够,高精度场合建议换11.0592MHz
- 中断服务程序中避免浮点运算,我有次因计算耗时导致波形畸变
- 多定时器协同工作时,要注意优先级设置,TF标志位可能被意外清除
- 使用示波器调试时,建议先看整体波形,再放大观察边沿细节
对于更复杂的信号生成,可以结合多个定时器。比如用T0做时间基准,T1控制占空比。当需要可变占空比时,可以引入外部按键输入来调整计数值。
