51单片机定时器/计数器中断详解(T0和T1)——从入门到精通

定时器中断是51单片机最常用的外设之一,它能让单片机"一心二用",在不占用CPU时间的前提下精准计时。本文将结合代码实例、寄存器图解和定时原理,带你彻底搞懂定时器0和定时器1的中断编程。

一、为什么要用定时器中断?

很多初学者刚开始都是用 Delay 函数实现延时,比如 Delay_xms(1000)。这种方式的缺点是:延时期间CPU啥也干不了,就像一个员工在"死等",浪费了处理能力。

而定时器是一个独立于CPU的硬件外设,它像一个小闹钟:

  • 设置好时间长度(初值)

  • 启动后,硬件自动计时

  • 时间一到,触发中断,通知CPU去处理

  • CPU处理完中断,回去继续干原来的活

这样,CPU就不需要一直盯着时间,效率大大提高。

二、51单片机定时器资源概览

型号 定时器数量 说明
STC89C51 2个(T0, T1) 常用
STC89C52 3个(T0, T1, T2) 多一个T2,功能更丰富

每个定时器/计数器都可以通过寄存器配置为 定时模式 (对内部时钟计数)或 计数模式(对外部引脚脉冲计数)。

本文主要讲解 T0T1定时中断应用。

三、核心寄存器详解(配图说明)

定时器编程需要操作以下几个特殊功能寄存器(SFR):

1. TMOD ------ 定时器工作模式寄存器(地址89H,不可位寻址)

符号 功能说明
TMOD.7 / TMOD.3 GATE 门控位:0 → 仅由TRx启动;1 → 需INTx引脚为高且TRx=1才启动
TMOD.6 / TMOD.2 C/T 功能选择:0 → 定时器(对内部时钟计数);1 → 计数器(对T0/T1引脚脉冲计数)
TMOD.5,4 / TMOD.1,0 M1,M0 模式选择(见下表)

模式选择表(M1 M0)

M1 M0 模式 说明 常见使用
0 0 0 13位定时器(THx+TLx低5位) 较少用
0 1 1 16位定时器(THx+TLx全8位) 最常用
1 0 2 8位自动重装初值 常用
1 1 3 T0分成两个8位;T1停止计数 T0专用

本文示例均采用模式1(16位定时器,需手动重装初值)

2. TCON ------ 定时器控制寄存器(地址88H,可位寻址)

符号 功能
TCON.7 TF1 T1溢出标志:计满溢出时由硬件置1,进入中断后自动清零(也可软件查询清零)
TCON.6 TR1 T1运行控制位:软件置1启动,清零停止
TCON.5 TF0 T0溢出标志,类似TF1
TCON.4 TR0 T0运行控制位,类似TR1

另外低4位用于外部中断,这里不展开。

3. IE ------ 中断允许寄存器(地址A8H,可位寻址)

符号 功能
IE.7 EA 总中断允许:1=开,0=关
IE.4 ET1 定时器T1中断允许
IE.3 EX1 外部中断1允许
IE.2 ET0 定时器T0中断允许
IE.1 EX0 外部中断0允许

4. THx / TLx ------ 计数初值寄存器

  • T0:TH0(高8位)、TL0(低8位)

  • T1:TH1、TL1

  • 模式1时,THx和TLx拼接成一个16位计数器,从初值加1到65535,溢出触发中断。

四、定时器计数原理(重点!)

要理解初值计算,必须先搞清楚时钟周期、机器周期、指令周期的关系。

1. 几个重要概念

  • 时钟周期 = 1 / 晶振频率。例如11.0592MHz晶振,时钟周期 ≈ 0.0904μs

  • 机器周期 = 12个时钟周期(标准51模式,也叫12T模式)。这是CPU执行一个基本操作的时间单位。

  • 定时器计数 :每经过一个机器周期 ,定时器的计数值自动加1。

    (如果是6T模式,则每6个时钟周期加1,速度加倍)

2. 定时时间计算公式(模式1,12T)

定时时间(s)=65536−初值晶振频率(Hz)×12定时时间(s)=晶振频率(Hz)65536−初值​×12

或者写成微秒形式:

初值=65536−定时时间(μs)×晶振频率(MHz)12初值=65536−12定时时间(μs)×晶振频率(MHz)​

实例:晶振11.0592MHz,想得到10ms = 10000μs 定时

初值=65536−10000×11.059212初值=65536−1210000×11.0592​

先算 10000×11.059212=11059212=92161210000×11.0592​=12110592​=9216

再算 65536−9216=5632065536−9216=56320

转换为十六进制:56320 = 0xDC00

所以 TH0 = 0xDC, TL0 = 0x00 ✅(与代码一致)

3. 为什么代码中是 TH0 = 0xDC; TL0 = 0x00

因为0xDC00 高8位是0xDC,低8位是0x00。注意这里使用的是16位初值直接拆分

4. 补充:定时器计数范围

  • 模式1(16位):计数范围 0 ~ 65535

  • 模式2(8位自动重装):计数范围 0 ~ 255,溢出后自动从THx取数重装,适合产生精确的短延时。

五、完整编程步骤(以定时器0为例)

参照提供的代码 main.c(第一个文件),步骤如下:

cpp 复制代码
// 1. 设置TMOD ------ 选择定时器0、模式1、定时功能
TMOD &= 0xF0;   // 低4位清零
TMOD |= 0x01;   // M1=0, M0=1 => 模式1;C/T=0 => 定时器;GATE=0

// 2. 装入初值(10ms)
TH0 = 0xDC;
TL0 = 0x00;

// 3. 清空溢出标志位(可选,上电默认0)
TF0 = 0;

// 4. 打开定时器0中断允许
ET0 = 1;

// 5. 打开总中断
EA = 1;

// 6. 启动定时器(TR0置1)
TR0 = 1;

然后编写中断服务函数:

cpp 复制代码
void Timer0_Routine(void) interrupt 1   // 中断号1对应定时器0
{
    // 重装初值(模式1不会自动重装)
    TH0 = 0xDC;
    TL0 = 0x00;
    // 用户代码:例如累计100次得到1秒,翻转LED
    tim_count++;
    if(tim_count >= 100) {
        LED1 = ~LED1;
        tim_count = 0;
    }
}

注意 :中断服务函数必须使用 interrupt 关键字,并指定正确的中断号

  • 外部中断0 → 0

  • 定时器0 → 1

  • 外部中断1 → 2

  • 定时器1 → 3

  • 串口中断 → 4

六、代码实战分析

代码1 ------ 定时器0,1秒翻转LED1和LED2

cpp 复制代码
uint tim_count = 0;   // 全局变量,累计中断次数

void main()
{
    TMOD &= 0xF0; TMOD |= 0x01;
    TL0 = 0x00; TH0 = 0xDC;
    TF0 = 0; TR0 = 1;
    ET0 = 1; EA = 1;
    while(1)
    {
        Delay_xms(1000);   // 主循环里随便干点别的,这里延时1秒
    }
}

void Timer0_Routine(void) interrupt 1
{
    TL0 = 0x00; TH0 = 0xDC;
    tim_count++;
    tim_count = tim_count % 100;   // 0~99循环
    if(0 == tim_count)              // 每100次(1秒)翻转
    {
        LED1 = ~LED1;
        LED2 = ~LED2;
    }
}

效果 :两个LED同步1秒闪烁。

注意 :主循环中的 Delay_xms(1000) 是软件延时,会阻塞CPU,但因为中断仍然会按时触发,所以LED闪烁依然准确。但更好的写法是在主循环中做其他事情,完全依赖定时器产生延时。

代码2 ------ 定时器1,LED4每500ms翻转

cpp 复制代码
void main()
{
    TMOD &= 0x0F; TMOD |= 0x10;   // 高4位设T1模式1,低4位保留
    TH1 = 0xFC; TL1 = 0x66;       // 初值0xFC66,定时多久?见后文计算
    TF1 = 0; TR1 = 1;
    ET1 = 1; EA = 1;
    while(1) Delay_xms(1000);
}

void Timer1_Routine(void) interrupt 3
{
    TH1 = 0xFC; TL1 = 0x66;
    tim_count++;
    tim_count = tim_count % 500;
    if(0 == tim_count)
        LED4 = ~LED4;
}

初值计算验证

0xFC66 = 64614

定时时间=65536−6461411.0592×12=92211.0592×12≈83.33×12≈1000μs=1ms定时时间=11.059265536−64614​×12=11.0592922​×12≈83.33×12≈1000μs=1ms

所以该定时器每1ms触发一次中断,tim_count 计数500次后翻转LED4 → 500ms翻转一次。

七、实验现象与效果

定时器 中断周期 累计次数 翻转周期 对应LED
T0 10ms 100 1s LED1, LED2
T1 1ms 500 500ms LED4

实际烧录到开发板,可以看到LED1和LED2同步1秒闪一次,LED4以两倍频率(0.5秒)闪动。

八、进阶补充知识

1. 6T模式与12T模式

默认情况下,51单片机是12T模式,即一个机器周期=12个时钟周期。但一些增强型51(如STC系列)可以设置成6T模式,定时器计数速度加倍。如果烧录时选了6T,同样初值定时时间会减半,需要重新计算。

2. 使用模式2自动重装

模式2下,THx存放重装值,TLx从初值加到255溢出,然后自动装入THx,非常适合产生精确的PWM或串口波特率。示例:

cpp 复制代码
TMOD |= 0x02;   // T0模式2
TH0 = 0x38;     // 自动重装值
TL0 = 0x38;
ET0 = 1; EA = 1; TR0 = 1;

3. 定时器用作计数器(外部脉冲计数)

将C/T位设为1,T0引脚(P3.4)或T1引脚(P3.5)上的负跳变会使计数器加1。例如:

cpp 复制代码
TMOD |= 0x04;   // T0计数器模式,C/T=1
TH0 = 0; TL0 = 0;
TR0 = 1;
while(1) {
    if(TF0) {
        // 计数溢出处理
        TF0 = 0;
    }
}

4. 门控位GATE测量脉宽

当GATE=1时,定时器启动由INTx引脚控制。可以测量INT0引脚上高电平的宽度:

cpp 复制代码
TMOD |= 0x08;   // GATE=1
TH0 = 0; TL0 = 0;
while(INT0 == 0);   // 等待高电平到来
TR0 = 1;            // 启动,但实际计数受INT0控制
while(INT0 == 1);   // 等待低电平
TR0 = 0;
// 此时TH0,TL0中的数值 = 高电平宽度对应的机器周期数

九、常见问题与排错

现象 可能原因
定时器不工作 忘记 TR0=1EA=1ET0=1
时间不准 晶振频率与初值计算不匹配
中断只触发一次 模式1忘记重装初值
主函数死循环中不响应中断 中断优先级问题,但通常不会;检查总中断
编译报错"undefined symbol" 忘记包含 reg52.h 或关键字写错

十、总结

51单片机的定时器中断是嵌入式入门的核心知识点。掌握以下几点即可熟练应用:

  1. TMOD 选模式(最常用模式1和模式2)

  2. THx/TLx 装初值,用公式 初值=65536−时间(μs)×晶振(MHz)12初值=65536−12时间(μs)×晶振(MHz)​

  3. TCON 的TRx控制启停,TFx查询/自动清零

  4. IE 开总中断EA和对应的ETx

  5. 中断服务函数用 interrupt n 指定正确的中断号

  6. 模式1需手动重装初值,模式2自动重装

希望这篇博客能让你彻底看懂定时器中断,并能自己写出精确的定时程序。如果有疑问,欢迎在评论区交流!

相关推荐
飞猿_SIR1 小时前
RK3288 Android11平台移植RTL8733BU-WiFi模组
android·嵌入式硬件
国产化创客1 小时前
嵌入式视觉完整技术体系--ESP32/K230/RDK-X5/树莓派四层架构全解析
嵌入式硬件·物联网·架构·开源·智能硬件
cft56200_ln2 小时前
TDA4时间同步3 网卡添加虚拟时间戳
c语言·开发语言·arm开发·驱动开发·嵌入式硬件·网络协议
HAPPY酷2 小时前
STM32 两种烧录方式对比:Keil Load vs FlyMCU 串口下载
stm32·单片机·嵌入式硬件
清风6666662 小时前
基于单片机的汽车胎压与温度监控系统
单片机·嵌入式硬件·汽车·毕业设计·课程设计·期末大作业
济6172 小时前
ROS开发专栏---ROS2 机械臂应用入门(2)---机械臂自动抓取物品实验---适配Ubuntu 22.04
嵌入式硬件·嵌入式·ros2·机器人开发·机器人方向
✎ ﹏梦醒͜ღ҉繁华落℘2 小时前
产品研发----点型光电感烟火灾探测器(三)
单片机
济6172 小时前
ROS开发专栏---家庭服务机器人饮料递送实验---适配Ubuntu 22.04
嵌入式硬件·嵌入式·ros2·机器人方向
点灯小铭2 小时前
基于单片机控制的多模式智能冰箱设计—冷藏、速冷、省电与自动化霜功能实现
单片机·mongodb·自动化·毕业设计·课程设计·期末大作业