定时器基本理论
040.5.1 定时器基本理论
#include "reg52.h"
#include <intrins.h>
typedef unsigned char uchar;
typedef unsigned int uint;
uint tim_count = 0;
sbit LED1 = P1^0;
sbit LED2 = P1^1;
void Delay_xms(uint xms) // @11.0592MHz
{
uchar data i, j;
while (xms)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
TMOD &= 0xF0; //TMOD低四位清零
TMOD |= 0x01; //定时器0配置成工作模式1
TL0 = 0x00;
TH0 = 0xDC; //装载计数初值0xDC00 10ms触发一次中断
TF0 = 0; //标志位清零
TR0 = 1; //开始运行定时器
ET0 = 1; //允许定时器中断
EA = 1; //总中断打开
while (1)
{
Delay_xms(1000);
}
}
/*****************
定时器0的中断处理函数
*****************/
void Timer0_Routine(void) interrupt 1
{
TL0 = 0x00;
TH0 = 0xDC; //重装初值
tim_count++; //定时器中断每触发一次,count值+1
tim_count = tim_count % 100; //让count值在0-99之间变化
if (0 == tim_count)
{
LED1 = ~LED1; //count每计数100次,改变一下灯的状态
LED2 = ~LED2; //10ms*100 = 1000ms = 1s 每隔一秒灯的状态改变一次
}
}
一、定时器基本理论
定时器可以理解成单片机内部的一只硬件闹钟。外部中断等待的是引脚电平变化,定时器中断等待的是内部计数器溢出;两者都会产生中断请求,并在允许条件满足时让 CPU 暂停主程序、转去执行相应的中断服务程序(ISR)。
本例使用定时器0的模式1,每约 10 ms 产生一次中断。软件累计 100 次中断后翻转 LED1、LED2,因此两个 LED 每约 1 s 改变一次亮灭状态。
11.0592 MHz系统时钟
│ 12T模式:每12个时钟形成一个计数脉冲
▼
Timer0从0xDC00向上计数
│ 计满溢出
▼
TF0置1 ── ET0局部允许 ── EA总允许
▼
000BH向量 → Timer0_Routine() interrupt 1
│ 重装0xDC00,并累计中断次数
▼
每累计100次翻转LED1、LED2
1.1 实验演示
烧录程序后,LED1 和 LED2 同步闪烁。它们的状态每约 1 s 翻转一次:亮变灭需要约 1 s,灭变亮再需要约 1 s,所以从一次点亮到下一次点亮的完整周期约为 2 s。
主循环中虽然保留了 Delay_xms(1000),但没有任何 LED 操作:
while (1)
{
Delay_xms(1000);
}
LED 仍然能够按照定时器节拍变化,是因为硬件定时器独立计数。即使 CPU 正在执行延时循环,只要 Timer0 溢出且 ET0、EA 已打开,CPU 就会中断当前延时程序,进入 Timer0_Routine(),执行完 RETI 后再回到延时程序被打断的位置。
这也说明“定时器不占用 CPU 计时”并不等于整个功能完全不消耗 CPU:计数过程由硬件完成,不需要 CPU 在循环中反复加一;但定时器初始化、每次 ISR 以及 ISR 中的软件处理仍然需要 CPU 时间。
本实验涉及两组关键硬件电路。
LED1、LED2 输出电路
+5V ──► LED1 ── R8(10 kΩ)── P1.0
+5V ──► LED2 ── R9(10 kΩ)── P1.1
LED 与电阻串联,R8、R9 用于限制电流,避免 LED 和单片机端口过流。两个 LED 都是低电平点亮:
- P1.0 输出 0 时,电流沿
+5V → LED1 → R8 → P1.0内部下拉管 → GND流动,LED1 点亮。 - P1.0 输出 1 时,LED1 两端电压差很小,LED1 熄灭。
- P1.1 与 LED2 的工作过程完全相同。
P1 口复位后通常为高电平,所以 LED 初始熄灭。LED1 = ~LED1 与 LED2 = ~LED2 每执行一次,就把对应端口位在 0、1 之间翻转一次,从而改变灯的亮灭状态。
系统时钟电路
11.0592 MHz晶振
XTAL2 ─────────────[ X1 ]───────────── XTAL1
│ │
C9 22 pF C10 22 pF
│ │
GND GND
晶振与单片机内部振荡放大电路共同产生稳定的系统时钟。C9、C10 是晶振的负载电容,它们与走线、引脚寄生电容共同满足晶体所需的负载条件,并帮助振荡器稳定启动。定时器最终统计的是由系统时钟分频得到的计数脉冲,因此晶振频率或 6T/12T 设置变化时,相同的 TH0/TL0 初值会得到不同的定时时间。
实验操作时,应在下载软件中选对 STC89C52RC/LE52RC 型号和串口,再打开工程生成的 HEX 文件进行下载。程序现象与普通延时闪灯相似,但产生时间基准的方法不同:延时闪灯依靠 CPU 执行空循环,定时器闪灯依靠硬件计数和中断。
1.2 定时器相关寄存器
阅读定时器程序时,可以先把寄存器按职责分类,而不是把每一行当成互不相关的配置:
| 职责 | 寄存器或位 | 回答的问题 |
|---|---|---|
| 选择工作方式 | TMOD | 使用定时还是外部计数?模式0~3中的哪一种?是否由外部引脚门控? |
| 保存当前计数值 | TH0、TL0 | Timer0 当前从什么数开始、已经数到哪里? |
| 启停与溢出标志 | TCON.TR0、TCON.TF0 | 计数器是否运行?是否已经溢出? |
| 中断允许 | IE.ET0、IE.EA | Timer0 请求能否进入 CPU? |
| 中断入口 | interrupt 1 | 溢出请求获准后执行哪个 ISR? |
本例初始化语句可以按信号流逐项对照:
| 代码 | 直接作用 | 最终效果 |
|---|---|---|
TMOD &= 0xF0 | 将 TMOD 低四位清零 | 清除 Timer0 的 GATE0、C/T0、M1_0、M0_0,同时保留 Timer1 高四位 |
| `TMOD | = 0x01` | 将 TMOD.B0 置 1 |
TH0=0xDC; TL0=0x00 | 写入 16 位初值 0xDC00 | 规定本轮计数的起点 |
TF0=0 | 清除 Timer0 溢出标志 | 避免带着待处理请求开始运行 |
TR0=1 | 打开 Timer0 运行开关 | Timer0 开始接收计数脉冲 |
ET0=1 | 打开 Timer0 局部中断允许 | 允许 TF0 请求通过分闸门 |
EA=1 | 打开 CPU 总中断允许 | 允许所有已单独开放的中断源进入仲裁 |
查阅数据手册时,可以先在“定时器/计数器0/1”章节找到寄存器总表,再分别查看 TCON、TMOD 和工作模式图。读图时始终追踪这条路径:时钟或外部脉冲从哪里进入、由哪个开关放行、TH/TL 怎样组合、溢出后哪个标志置位、请求经过哪些允许位、最后进入哪个向量。
1.3 定时器中断原理
延时函数与硬件定时器的区别
软件延时的本质是让 CPU 执行大量无业务意义的循环指令。在这段时间里,主程序不能继续处理轮询按键、通信协议或其他普通任务;如果系统已经开启中断,CPU 仍可响应中断,但中断执行时间还会使软件延时变长。因此软件延时适合上电等待、简单实验或很短的阻塞,不适合复杂系统中的长期时间管理。
硬件定时器在后台根据计数脉冲递增,主程序可以同时执行其他任务。只有初始化和中断到达后的处理需要 CPU。典型的非阻塞结构是:
主循环:持续处理按键、通信和状态机
│
│ Timer0每10 ms中断一次
▼
ISR:只更新软件计时标志或计数器,然后迅速返回
“闹钟”的三个组成部分
| 闹钟概念 | Timer0中的对应内容 |
|---|---|
| 用什么时间基准 | 11.0592 MHz 系统时钟经 12 分频得到计数脉冲 |
| 从现在到响铃要多久 | 模式1使用 TH0:TL0 保存 16 位初值 0xDC00 |
| 时间到后做什么 | 溢出使 TF0=1,CPU 进入 interrupt 1 ISR |
10 ms 的计算过程
本例假定芯片按 12T 方式运行 Timer0。晶振频率为 11.0592 MHz,每 12 个晶振周期增加一次计数:
计数频率 = 11.0592 MHz ÷ 12 = 921.6 kHz
单次计数时间 = 1 ÷ 921600 Hz ≈ 1.085069 μs
模式1是 16 位向上计数器,最大状态数为 2^16 = 65536。初值是 TH0:TL0 = 0xDC00:
0xDC00 = 56320
溢出前所需计数次数 = 65536 - 56320 = 9216
定时时间 = 9216 × 1.085069 μs = 10000 μs = 10 ms
计数关系可以画成:
装载0xDC00 → 0xDC01 → ... → 0xFFFE → 0xFFFF → 0x0000
│
└── TF0由硬件置1
模式1不会自动恢复 0xDC00。溢出后硬件从 0x0000 继续计数,因此 ISR 一开始重新写入 TL0=0x00、TH0=0xDC,为下一轮 10 ms 计时做准备。由于从溢出到软件完成重装之间存在中断响应和指令执行时间,实际间隔会比理论 10 ms 略长;需要更高精度时,应补偿 ISR 延迟或选用具有自动重装能力的工作方式。
由 10 ms 得到约 1 s
tim_count++;
tim_count = tim_count % 100;
if (0 == tim_count)
{
LED1 = ~LED1;
LED2 = ~LED2;
}
tim_count 依次在 0~99 之间循环。每次约 10 ms 中断加一,累计 100 次回到 0,于是:
10 ms/次 × 100次 = 1000 ms ≈ 1 s
Timer0 对应 C51 中断号1和向量地址 000BH。Timer0_Routine 是便于阅读的函数名,真正把函数与 Timer0 向量绑定的是 interrupt 1。
1.4 定时器工作模式
标准 8051/89C51 包含 Timer0、Timer1;本开发板使用的 STC89C52 还包含 Timer2。T0、T1 与传统 8051 兼容,本课程重点掌握它们;T2 由 T2CON 等寄存器控制,不能简单套用 T0/T1 的全部配置。
Timer0、Timer1 都可以工作为“定时器”或“计数器”:
C/T | 工作方式 | 脉冲来源 | 常见用途 |
|---|---|---|---|
| 0 | 定时 | 系统时钟分频 | 周期任务、超时、波形节拍 |
| 1 | 计数 | 外部引脚脉冲;T0=P3.4,T1=P3.5 | 统计外部事件或脉冲数量 |
TMOD 的高四位控制 Timer1,低四位控制 Timer0:
TMOD(89H) B7 B6 B5 B4 B3 B2 B1 B0
GATE1 C/T1 M1 M0 GATE0 C/T0 M1 M0
└────── Timer1 ──────┘ └────── Timer0 ──────┘
GATE=0:只要TRx=1,定时器就能运行,本例采用这种方式。GATE=1:除了TRx=1,还要求对应的 INTx 引脚为高电平,适合测量外部高电平脉宽等场景。C/T=0:使用内部时钟分频进行定时。C/T=1:统计 T0 或 T1 引脚上的外部脉冲。M1、M0:共同选择模式0~3。
四种模式的正确对应关系如下:
M1 M0 | 模式 | 计数结构 | 计数状态数 | 是否自动重装 | 特点 |
|---|---|---|---|---|---|
00 | 模式0 | 13位,由 THx 8位与 TLx 低5位组成 | 2^13=8192 | 否 | 为兼容早期 8048 保留,较少使用 |
01 | 模式1 | 16位,THx 与 TLx 全部参与 | 2^16=65536 | 否 | 范围大、直观,本例使用 |
10 | 模式2 | 8位自动重装;TLx 计数,THx 保存重装值 | 2^8=256 | 是 | 周期稳定,不必在每次 ISR 中软件重装 |
11 | 模式3 | Timer0 拆成两个独立8位计数器 | 每个 2^8=256 | 否 | TL0 使用 TR0/TF0,TH0 借用 TR1/TF1;Timer1 在模式3下停止 |
四种模式的本质差异,是 THx、TLx 怎样组合以及溢出后是否自动重装,不是四种不同的中断类型。
本例的两条位运算值得单独理解:
TMOD初始或当前值:aaaa bbbb
TMOD &= 0xF0: aaaa 0000 清除Timer0配置,保留Timer1配置
TMOD |= 0x01: aaaa 0001 Timer0设为GATE=0、C/T=0、模式1
直接写 TMOD = 0x01 也能让 Timer0 进入模式1,但会把 Timer1 的高四位一起清零。使用先与后或的掩码操作,可以只更新 Timer0 的低四位。
STC89C52 的 T0/T1 定时速率可在烧录设置中选择 12T 或 6T:12T 每 12 个系统时钟计数一次,6T 每 6 个系统时钟计数一次。若把本例从 12T 切换为 6T 而不更改初值,计数速度会加倍,单次溢出约为 5 ms,LED 状态也会更快翻转。
1.5 定时器相关寄存器
本例涉及 IE、TCON、TMOD、TH0 和 TL0。其中 IE、TCON 可以位寻址,TMOD 不可位寻址,因此代码通常通过按位与、按位或设置 TMOD。
IE:中断允许寄存器
IE(A8H) B7 B6 B5 B4 B3 B2 B1 B0
EA - ET2 ES ET1 EX1 ET0 EX0
| 位 | 含义 |
|---|---|
EA | CPU 总中断允许,1开放总闸门,0屏蔽全部可屏蔽中断 |
| B6 | 保留位,不应作为程序状态依据 |
ET2 | Timer2 中断允许 |
ES | 串口中断允许 |
ET1 | Timer1 中断允许 |
EX1 | 外部中断1允许 |
ET0 | Timer0 中断允许,本例置 1 |
EX0 | 外部中断0允许 |
Timer0 的中断请求要进入 CPU,必须同时满足 TF0=1、ET0=1、EA=1。TR0=1 只是让计数器运行,不能代替中断允许位。
TCON:定时器/计数器0、1控制寄存器
TCON(88H) B7 B6 B5 B4 B3 B2 B1 B0
TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0
| 位 | 含义 | 置位或清零方式 |
|---|---|---|
TF1 | Timer1 溢出请求标志 | 溢出时硬件置 1;CPU 响应 T1 中断时硬件清 0,也可软件清 0 |
TR1 | Timer1 运行控制 | 软件置 1启动、清 0停止;还受 TMOD.GATE1 影响 |
TF0 | Timer0 溢出请求标志 | 溢出时硬件置 1;CPU 响应 T0 中断时硬件清 0,也可软件清 0 |
TR0 | Timer0 运行控制 | 软件置 1启动、清 0停止;还受 TMOD.GATE0 影响 |
IE1 | 外部中断1请求标志 | 外部条件成立时硬件置位 |
IT1 | 外部中断1触发类型 | 软件配置,0低电平、1下降沿 |
IE0 | 外部中断0请求标志 | 外部条件成立时硬件置位 |
IT0 | 外部中断0触发类型 | 软件配置,0低电平、1下降沿 |
TF0 与 TR0 不能混淆:TF0 的 F 可记作 Flag,是溢出标志;TR0 的 R 可记作 Run,是运行开关。正常过程是软件先清 TF0、再置 TR0,随后由硬件计数并在溢出时置 TF0。
软件:TF0=0 → TR0=1
│
硬件:持续计数 → 溢出 → TF0=1 → 请求中断
│
CPU响应:进入interrupt 1,并由硬件清TF0
虽然复位值已经使 TF0=0,初始化时显式执行 TF0=0 仍是一种稳妥做法:它能清除程序此前阶段遗留的溢出请求,保证 Timer0 从已知状态开始。
TMOD:工作方式寄存器
| 位 | Timer1含义 | 位 | Timer0含义 |
|---|---|---|---|
B7 GATE1 | Timer1 外部门控选择 | B3 GATE0 | Timer0 外部门控选择 |
B6 C/T1 | Timer1 定时/计数选择 | B2 C/T0 | Timer0 定时/计数选择 |
B5 M1_1 | Timer1 模式选择高位 | B1 M1_0 | Timer0 模式选择高位 |
B4 M0_1 | Timer1 模式选择低位 | B0 M0_0 | Timer0 模式选择低位 |
本例最终的 Timer0 低四位是 0001B:GATE0=0、C/T0=0、M1_0=0、M0_0=1,即由 TR0 直接控制的内部时钟、模式1定时器。
TH0、TL0:16位计数值
TL0 地址为 8AH,保存低8位;TH0 地址为 8CH,保存高8位。在模式1中二者级联:
高8位 低8位
TH0:TL0 = 1101 1100 : 0000 0000 = 0xDC00
TL0 从 0xFF 再加一时向 TH0 进位;TH0 从 0xFF 再加一时整个 16 位计数器溢出,并由硬件置位 TF0。TH0/TL0 保存的是“向上计数的起点”,不是直接保存毫秒数;必须结合晶振频率、6T/12T 设置和所需计数次数计算初值。
定时器初始化与中断处理的完整分工如下:
初始化阶段(只执行一次)
TMOD配置模式 → TH0/TL0装初值 → TF0清零 → TR0启动
→ ET0允许 → EA总允许
运行阶段(硬件循环)
计数 → 溢出 → TF0置位 → CPU响应interrupt 1
ISR阶段(每约10 ms执行一次)
重装TH0/TL0 → tim_count加1 → 每100次翻转LED → RETI
这条链路把“时间基准、计时长度、时间到后的动作”分别落到了系统时钟、TH0/TL0 和 Timer0_Routine() 上。只要这三个部分以及 TR0/TF0/ET0/EA 的职责不混淆,后续配置 Timer1、周期采样、按键消抖和通信超时都会沿用同一套思路。