1. 从零开始理解51单片机定时器
作为一名刚接触51单片机的初学者,第一次看到数据手册上关于定时器的描述时,我完全是一头雾水。那些寄存器名称、位定义、工作模式,看起来就像天书一样。经过反复实践和查阅资料,我终于搞明白了定时器的基本原理和配置方法。下面就把我的学习心得分享给大家,希望能帮助其他初学者少走弯路。
定时器和计数器本质上都是对脉冲进行计数,区别在于脉冲来源不同。定时器的脉冲来自系统时钟,而计数器的脉冲来自外部引脚。以STC89C52RC为例,它的定时器工作过程是这样的:系统每隔一定时间产生一个脉冲,计数器就加1,当计数器达到上限溢出时,就会触发中断。
2. 定时器相关寄存器详解
2.1 控制寄存器TCON
TCON是一个8位寄存器,地址为88H,可以位寻址。它包含了定时器0和1的控制位以及外部中断标志位。我们需要重点关注的是定时器相关的几个位:
- TF1/TF0:定时器1/0溢出标志位
- TR1/TR0:定时器1/0运行控制位
- IE1/IE0:外部中断1/0请求标志
- IT1/IT0:外部中断1/0触发方式控制位
对于定时器0的配置,我们主要操作TR0位。将其置1,定时器0就开始计数;置0则停止计数。TF0是中断标志位,当定时器溢出时会自动置1,进入中断服务程序后会由硬件自动清零。
注意:TCON寄存器中既有定时器控制位又有外部中断控制位,配置时要注意区分,不要搞混了。
2.2 工作模式寄存器TMOD
TMOD也是一个8位寄存器,地址为89H,但不能位寻址。它用于设置定时器0和1的工作模式:
code复制高4位:定时器1控制
低4位:定时器0控制
每个定时器的4位定义如下:
- GATE:门控位
- C/T:定时器/计数器选择位
- M1/M0:工作模式选择位
常用的模式1配置是:
- GATE=0(内部TRx控制)
- C/T=0(定时器模式)
- M1=0,M0=1(模式1,16位定时器)
因此,如果只使用定时器0,TMOD可以配置为0x01。
3. 定时器初始化与中断配置
3.1 定时器初始化步骤
完整的定时器0初始化流程如下:
- 设置TMOD寄存器,配置定时器0为模式1
- 计算并设置定时器初值(TH0和TL0)
- 开启定时器中断(ET0=1)
- 开启总中断(EA=1)
- 启动定时器(TR0=1)
对应的C语言代码示例:
c复制void Timer0_Init(void)
{
TMOD &= 0xF0; //清除定时器0控制位
TMOD |= 0x01; //设置定时器0为模式1
TH0 = 0xFC; //设置定时初值
TL0 = 0x18;
ET0 = 1; //开启定时器0中断
EA = 1; //开启总中断
TR0 = 1; //启动定时器0
}
3.2 定时器初值计算
定时器初值的计算是关键步骤。以12MHz晶振为例:
- 机器周期 = 12 / 12MHz = 1μs
- 模式1是16位定时器,最大计数值65536
- 如果要定时1ms,需要计数1000次
- 初值 = 65536 - 1000 = 64536 = 0xFC18
因此:
- TH0 = 0xFC
- TL0 = 0x18
如果需要其他定时时间,可以按相同方法计算。
4. 中断服务程序编写
定时器溢出后会进入中断服务程序,需要在程序中定义中断函数:
c复制void Timer0_ISR(void) interrupt 1
{
static unsigned int count = 0;
TH0 = 0xFC; //重新装载初值
TL0 = 0x18;
count++;
if(count >= 1000) //1秒到
{
count = 0;
//执行定时任务
}
}
重要提示:在模式1下,中断服务程序中必须重新装载定时器初值,因为模式1不会自动重装。
5. 常见问题与解决方案
5.1 定时不准怎么办?
可能原因及解决方法:
- 晶振频率不准确 - 更换质量好的晶振
- 初值计算错误 - 重新检查计算公式
- 中断服务程序执行时间过长 - 优化代码,减少中断服务程序中的操作
5.2 定时器不工作怎么办?
排查步骤:
- 检查TR0/TR1是否置1
- 确认TMOD寄存器配置正确
- 检查中断是否开启(ET0/ET1和EA)
- 使用示波器或逻辑分析仪检查定时器引脚输出
5.3 如何实现更长时间的定时?
对于超过定时器最大定时时间的需求,可以采用以下方法:
- 使用静态变量在中断中累计计数
- 结合软件计数器扩展定时范围
- 使用定时器级联方式
6. 实际应用案例
6.1 精确延时函数实现
利用定时器可以实现精确的延时函数:
c复制void Delay_ms(unsigned int ms)
{
unsigned int i,j;
for(i=0; i<ms; i++)
{
for(j=0; j<120; j++); //粗略延时1ms
}
}
void Timer_Delay_ms(unsigned int ms)
{
TMOD &= 0xF0;
TMOD |= 0x01;
TH0 = 0xFC;
TL0 = 0x18;
ET0 = 0; //不使用中断
TR0 = 1;
while(ms--)
{
while(!TF0); //等待溢出
TF0 = 0;
TH0 = 0xFC;
TL0 = 0x18;
}
TR0 = 0;
}
6.2 PWM信号生成
通过定时器可以生成PWM信号控制LED亮度或电机速度:
c复制void PWM_Init(void)
{
TMOD &= 0xF0;
TMOD |= 0x01;
TH0 = 0xFF;
TL0 = 0xCE; //100us中断
ET0 = 1;
EA = 1;
TR0 = 1;
}
void Timer0_ISR(void) interrupt 1
{
static unsigned char pwm_count = 0;
static unsigned char pwm_duty = 50; //占空比50%
TH0 = 0xFF;
TL0 = 0xCE;
pwm_count++;
if(pwm_count >= 100) pwm_count = 0;
if(pwm_count < pwm_duty)
LED = 0; //LED亮
else
LED = 1; //LED灭
}
7. 进阶技巧与优化
7.1 定时器模式选择
51单片机定时器有4种工作模式:
- 模式0:13位定时器
- 模式1:16位定时器(最常用)
- 模式2:8位自动重装定时器
- 模式3:两个8位定时器(仅定时器0)
模式2特别适合需要精确定时且频繁进入中断的场合,因为它会自动重装初值。
7.2 低功耗设计
在电池供电应用中,可以通过以下方式降低功耗:
- 在不需要定时器时关闭它(TR0=0)
- 使用定时器唤醒MCU从空闲模式
- 适当降低定时器中断频率
7.3 定时器与看门狗
在关键应用中,可以同时使用定时器和看门狗:
- 定时器处理常规定时任务
- 看门狗作为系统运行监控
- 注意避免看门狗在定时器中断中被频繁喂狗
经过多次实践,我发现理解定时器的关键在于弄清楚寄存器配置与硬件行为之间的对应关系。建议初学者可以:
- 先用示波器观察定时器引脚输出
- 从简单应用开始,逐步增加复杂度
- 多参考芯片数据手册中的定时器相关章节
- 尝试用不同模式实现相同功能,比较优缺点
