跳转到内容

四、定时/计数器(流水灯延时、定时)

下面讲复习提纲第四部分:四、定时/计数器。这一部分常考定时器功能、工作方式、TMOD/TCON、方式1和方式2、初值计算、定时器中断程序,也是流水灯延时、秒表、交通灯倒计时的核心。


复习提纲中写:

定时功能:对系统机器周期计数,实现精准延时、定时
计数功能:对外部引脚脉冲计数,用于测速、计数

这句话可以拆成两层。

定时器对单片机内部的机器周期脉冲计数。

比如 12MHz 晶振下,标准 51 单片机:

1个机器周期 = 1us

如果定时器每 1us 加 1,那么:

计数1000次 ≈ 1ms
计数50000次 ≈ 50ms
计数1000000次 ≈ 1s

当然 16 位定时器一次最多只能计 65536 次,所以长时间定时通常要多次中断累加。

定时器常用于:

LED定时闪烁
流水灯定时切换
秒表
电子钟
交通灯倒计时
按键扫描
定时采样传感器

计数器对外部引脚输入的脉冲计数。

T0 和 T1 的外部输入引脚是:

T0:P3.4
T1:P3.5

例如传送带上每经过一个物体,传感器输出一个脉冲,计数器加 1,就能统计产品数量。

计数器常用于:

外部事件计数
产品计数
转速测量
频率测量
脉冲计数

考试答法:

定时器/计数器既可对内部机器周期脉冲计数实现定时,也可对外部引脚输入脉冲计数实现事件计数。

2. 定时器和计数器本质上是同一个东西

Section titled “2. 定时器和计数器本质上是同一个东西”

很多同学会以为“定时器”和“计数器”是两个完全不同的硬件。其实在 51 单片机里,它们本质上都是加 1 计数器,只是计数脉冲来源不同

定时器模式:计内部脉冲
计数器模式:计外部脉冲

可以这样理解:

定时器:数时间的脉冲
计数器:数外部事件的脉冲

模式选择由 TMOD 中的 C/T 位决定:

C/T = 0:定时器模式
C/T = 1:计数器模式

TMOD 用来设置 T0、T1 的工作方式和工作模式。

它的特点:

字节地址89H
不可位寻址
高4位控制T1
低4位控制T0

结构可以理解为:

高4位:GATE C/T M1 M0 -> 控制T1
低4位:GATE C/T M1 M0 -> 控制T0
GATE=0:只由TRx控制启动和停止
GATE=1:由TRx和INTx引脚电平共同控制

一般初学程序都用:

GATE=0

也就是只靠程序中的 TR0=1TR1=1 启动。

C/T=0:定时器模式,对内部机器周期计数
C/T=1:计数器模式,对外部输入脉冲计数

定时器程序里通常是 C/T=0

M1 M0工作方式特点
00方式013位计数器,较少使用
01方式116位计数器,最常用
10方式28位自动重装,考试常考
11方式3T0拆成两个8位计数器

如果 T0 设置为方式1定时器模式:

GATE=0, C/T=0, M1=0, M0=1
低4位 = 0001B = 1
TMOD = 0x01

所以常见代码:

TMOD = 0x01; // T0方式1定时

如果 T1 设置为方式2定时器模式:

高4位 = 0010B
TMOD = 0x20

常见于串口波特率:

TMOD = 0x20; // T1方式2

TCON 中既有定时器控制位,也有外部中断控制位。这里重点看高 4 位:

名称作用
TF1T1溢出标志T1溢出后硬件置1
TR1T1运行控制1启动T1,0停止T1
TF0T0溢出标志T0溢出后硬件置1
TR0T0运行控制1启动T0,0停止T0

启动 T0:

TR0 = 1;

停止 T0:

TR0 = 0;

查询 T0 是否溢出:

if (TF0 == 1)
{
TF0 = 0;
// 溢出处理
}

如果用中断方式,进入 T0 中断后,TF0 通常由硬件自动清零。


方式0 是 13 位计数器,由 THx 的 8 位和 TLx 的低 5 位组成。

它主要是为了兼容早期 MCS-48 系列,实际使用较少。考试通常只要求知道:

方式0是13位定时/计数方式。

一般程序题不会优先让你用方式0。


方式1由 THx 和 TLx 组成 16 位计数器。

THx:高8位
TLx:低8位

最大计数范围:

0000H -> FFFFH
共65536个状态

从装入初值开始加 1,一直加到溢出。

公式:

定时时间 = (65536 - 初值) × 机器周期
初值 = 65536 - 定时时间 / 机器周期

例题:12MHz 晶振,T0方式1定时1ms

Section titled “例题:12MHz 晶振,T0方式1定时1ms”

已知:

晶振 = 12MHz
机器周期 = 12 / 12MHz = 1us
定时时间 = 1ms = 1000us

需要计数:

N = 1000us / 1us = 1000

初值:

初值 = 65536 - 1000 = 64536

转十六进制:

64536 = FC18H

所以:

TH0 = 0xFC;
TL0 = 0x18;

方式1溢出后,计数器会回到 0。如果你想每 1ms 都中断一次,就要在中断服务程序中重新装初值:

void timer0_isr(void) interrupt 1
{
TH0 = 0xFC;
TL0 = 0x18;
}

5.3 方式2:8位自动重装,考试最爱

Section titled “5.3 方式2:8位自动重装,考试最爱”

复习提纲说:

方式2优点:自动重装初值,无需程序手动赋值,定时精准、适合做波特率发生器。

方式2中:

TLx:真正计数
THx:保存重装初值

当 TLx 溢出时,硬件自动把 THx 的值重新装入 TLx。

可以理解为:

THx = 备份初值
TLx = 正在跑的计数器

方式2是 8 位计数,最大计数值是 256。

定时时间 = (256 - 初值) × 机器周期
初值 = 256 - 定时时间 / 机器周期

例如 12MHz 晶振下,想让 T0 每 100us 溢出:

机器周期 = 1us
计数次数 = 100
初值 = 256 - 100 = 156 = 9CH

所以:

TMOD = 0x02; // T0方式2
TH0 = 0x9C;
TL0 = 0x9C;
TR0 = 1;

串口通信需要稳定周期产生发送/接收节拍。方式2自动重装,减少了中断重装初值造成的误差,所以常用 T1 方式2做波特率发生器。

例如 11.0592MHz 晶振,9600bps 常见设置:

TMOD = 0x20; // T1方式2
TH1 = 0xFD;
TL1 = 0xFD;
TR1 = 1;

方式3只适用于 T0。它把 T0 拆成两个独立的 8 位计数器:

TL0:一个8位计数器
TH0:另一个8位计数器

这种方式较特殊,T1 的某些控制资源会被占用。考试一般只要求知道:

方式3把T0分成两个8位定时/计数器,T1通常不作为普通中断定时器使用,可作为串口波特率发生器。

定时器最常见的应用不是单纯查询 TF0,而是使用中断。

典型结构:

初始化定时器
装初值
开定时器中断
开总中断
启动定时器
主程序循环
定时器溢出时自动进入中断
#include <reg51.h>
unsigned int ms_count = 0;
sbit LED = P1^0;
void main(void)
{
TMOD = 0x01; // T0方式1,定时器模式
TH0 = 0xFC; // 12MHz下1ms初值
TL0 = 0x18;
ET0 = 1; // 开T0中断
EA = 1; // 开总中断
TR0 = 1; // 启动T0
while (1)
{
// 主程序可以做其他事情
}
}
void timer0_isr(void) interrupt 1
{
TH0 = 0xFC; // 重装初值
TL0 = 0x18;
ms_count++;
if (ms_count >= 500)
{
ms_count = 0;
LED = !LED; // 约500ms翻转一次
}
}

这个程序可以理解为:

每1ms进一次中断
累计500次就是500ms
每500ms翻转一次LED

7. 流水灯延时:软件延时和定时器延时

Section titled “7. 流水灯延时:软件延时和定时器延时”

前面 I/O 口中流水灯常用软件延时:

void delay(unsigned int x)
{
unsigned int i, j;
for (i = 0; i < x; i++)
for (j = 0; j < 120; j++);
}

优点:

简单
容易写
适合初学演示

缺点:

不精确
CPU一直空转
延时时不能及时处理其他任务
受晶振和编译器影响

定时器延时用硬件计数,CPU 不必一直空转。

例如流水灯每 200ms 移动一次:

#include <reg51.h>
#include <intrins.h>
unsigned int tick = 0;
void main(void)
{
P1 = 0xFE;
TMOD = 0x01;
TH0 = 0xFC;
TL0 = 0x18;
ET0 = 1;
EA = 1;
TR0 = 1;
while (1)
{
// 等待定时器中断改变P1
}
}
void timer0_isr(void) interrupt 1
{
TH0 = 0xFC;
TL0 = 0x18;
tick++;
if (tick >= 200)
{
tick = 0;
P1 = _crol_(P1, 1);
}
}

这样主程序不会被长延时卡住,更适合复杂系统。


这是定时器最重要的计算题。

机器周期 = 12 / 晶振频率

如果晶振是 12MHz:

机器周期 = 1us

如果晶振是 6MHz:

机器周期 = 2us
N = 定时时间 / 机器周期

例如 12MHz 下定时 10ms:

N = 10000us / 1us = 10000

方式1:

初值 = 65536 - N

方式2:

初值 = 256 - N

方式1得到 16 位初值后:

THx = 初值高8位
TLx = 初值低8位

例:12MHz,方式1,定时 10ms:

初值 = 65536 - 10000 = 55536 = D8F0H
TH0 = D8H
TL0 = F0H

答案:

定时器模式对内部机器周期脉冲计数,用于产生定时;计数器模式对外部引脚输入脉冲计数,用于统计外部事件。两者本质都是加1计数器,只是计数脉冲来源不同。

答案:

方式0为13位计数器,方式1为16位计数器,方式2为8位自动重装计数器,方式3为T0拆分成两个8位计数器。

答案:

方式2具有自动重装初值功能,溢出后硬件自动把THx中的初值装入TLx,减少软件重装时间,提高定时精度,常用于周期定时和串口波特率发生器。

题目:

12MHz晶振,T0方式1定时1ms,求初值。

答案:

机器周期=1us,1ms需计数1000次,初值=65536-1000=64536=FC18H,所以TH0=FCH,TL0=18H。

答案示例:

TMOD = 0x01;
TH0 = 0xFC;
TL0 = 0x18;
ET0 = 1;
EA = 1;
TR0 = 1;

TMOD:选择工作方式和定时/计数模式
TCON:控制启动停止,保存溢出标志
TR0:启动/停止T0
TF0:T0溢出标志
方式1:16位,范围大,但溢出后需重装初值
方式2:8位,范围小,但自动重装

4. 定时器中断开关要同时开 EA 和 ET0

Section titled “4. 定时器中断开关要同时开 EA 和 ET0”

只写:

ET0 = 1;

不够,还要:

EA = 1;

5. 12MHz下机器周期不是 1/12us,而是 1us

Section titled “5. 12MHz下机器周期不是 1/12us,而是 1us”

因为:

时钟周期 = 1/12MHz
机器周期 = 12个时钟周期 = 1us

51单片机的定时器/计数器本质上是加1计数器。定时器模式对内部机器周期脉冲计数,用于实现延时和定时;计数器模式对外部T0、T1引脚输入脉冲计数,用于外部事件计数。T0外部输入引脚为P3.4,T1外部输入引脚为P3.5。
TMOD用于设置定时器/计数器的工作模式和工作方式,高4位控制T1,低4位控制T0;TCON中的TR0、TR1用于启动或停止定时器,TF0、TF1为溢出标志。定时器有方式0、方式1、方式2、方式3,其中方式1为16位计数器,最常用;方式2为8位自动重装计数器,溢出后自动重装初值,适合周期定时和串口波特率发生器。
定时器初值计算公式为:定时时间=(最大计数值-初值)×机器周期。方式1最大计数值为65536,方式2最大计数值为256。12MHz晶振下,标准51单片机1个机器周期为1us。