第16章 定时器中断
第十六章 定时器中断
1. 导入
通过学习外部中断,这一章应该比较简单了。STC89C5X 含有 3 个定时器:定时器 0、 定时器 1、 定时器 2( 注意: 51 系列单片机一定有基本的 2 个定时器( 定时器 0 和定时器 1) 。 本章以定时器 0 为例进行讲解, 让大家学会 51 单片机定时器的使用, 定时器 1 的使用方法与定时器 0 一样。
本章要实现的功能是: 通过定时器 0 中断控制 D1 指示灯间隔 1 秒闪烁。
2. 定时器介绍
2.1 CPU 时序的有关知识
-
振荡周期:为单片机提供定时信号的振荡源的周期( 晶振周期或外加振荡周期)。
-
状态周期:2 个振荡周期为 1 个状态周期, 用 S 表示。 振荡周期又称 S 周期或时钟周期。
-
机器周期:2 个振荡周期为 1 个状态周期, 用 S 表示。 振荡周期又称 S 周期或时钟周期。
-
指令周期:完成 1 条指令所占用的全部时间, 它以机器周期为单位。
更多可以参考:时钟周期、振荡周期、机器周期、CPU周期、状态周期、指令周期、总线周期、任务周期_一个时钟周期等于【 】 振荡周期。(2分)a1b2c3d4-CSDN博客
2.2 51单片机定时器原理
STC89C5X 单片机内有两个可编程的定时/计数器 T0、 T1 和一个特殊功能定时器 T2。 定时/计数器的实质是加 1 计数器( 16 位) , 由高 8 位和低 8 位两个寄存器 THx 和 TLx 组成。 它随着计数器的输入脉冲进行自加 1, 也就是每来一个脉冲, 计数器就自动加 1, 当加到计数器为全 1 时, 再输入一个脉冲就使计数器回零, 且计数器的溢出使相应的中断标志位置 1, 向 CPU 发出中断请求( 定时/计数器中断允许时) 。
如果定时/计数器工作于定时模式, 则表示定时时间已到;如果工作于计数模式, 则表示计数值已满。 可见, 由溢出时计数器的值减去计数初值才是加 1 计数器的计数值。
更多可以参考:51单片机定时/计数器详解(工作原理及模式、应用)-电子发烧友网
还有51单片机定时/计数器结构参考:【51单片机系列】C51中的定时计数器 - Zoya23 - 博客园 (cnblogs.com)
51单片机入门 - 定时/计数器原理及结构(T0和T1)_51单片机定时器t0是什么-CSDN博客
2.3 51单片机定时/计数器的工作方式
- 方式0
方式 0 为 13 位计数, 由 TL0 的低 5 位( 高 3 位未用) 和 TH0 的 8 位组成。TL0 的低 5 位溢出时向 TH0 进位, TH0 溢出时, 置位 TCON 中的 TF0 标志, 向 CPU发出中断请求。 其结构图如下所示:
门控位 GATE 具有特殊的作用。 当 GATE=0 时, 经反相后使或门输出为 1, 此时仅由 TR0 控制与门的开启, 与门输出 1 时, 控制开关接通, 计数开始; 当 GATE=1时, 由外中断引脚信号控制或门的输出, 此时控制与门的开启由外中断引脚信号和 TR0 共同控制。 当 TR0=1 时, 外中断引脚信号引脚的高电平启动计数, 外中断引脚信号引脚的低电平停止计数。
这种方式常用来测量外中断引脚上正脉冲的宽度。 计数模式时, 计数脉冲是 T0 引脚上的外部脉冲。 计数初值与计数个数的关系为: X=213-N。
- 方式1
方式1的技术位数是16位,由TL0作为低18位,TH0作为高8位,组成了16位加一计时器。其结构如下图所示:
计数初值与计数个数的关系为: X=216-N。
- 方式2
方式 2 为自动重装初值的 8 位计数方式。 工作方式 2 特别适合于用作较精确的脉冲信号发生器。 其结构图如下所示:
计数初值与计数个数的关系为: X=28-N。
- 方式3
方式 3 只适用于定时/计数器 T0, 定时器 T1 处于方式 3 时相当于 TR1=0,停止计数。 工作方式 3 将 T0 分成为两个独立的 8 位计数器 TL0 和 TH0。 其结构如下所示:
这几种工作方式中应用较多的是方式 1 和方式 2。 定时器中通常使用定时器方式 1, 串口通信中通常使用方式 2。
关于这几种工作方式详细可以参考:51单片机定时器/计数器T0|T1的四种工作方式_单片机的定时计数器t0的工作在方式2下计数寄存器是()。-CSDN博客
3. 定时器配置
了解了一些关于定时器的基础知识后,在使用定时器时,应=应该如何配置使其工作,步骤如下:
-
对TMOD赋值,用来确定T0和T1的工作方式,如果使用定时器 0 即对 T0 配置, 如果使用定时器 1 即对 T1 配置。
-
根据所要定时的时间计算初值,并将其写入 TH0、 TL0 或 TH1、 TL1。
-
如果使用中断, 则对 EA 赋值, 开放定时器中断。
-
使 TR0 或 TR1 置位, 启动定时/计数器定时或计数。
3.1 时间初值计算
上述中有一个定时/计数器初值的计算, 下面我们来看下如何计算定时/计数器初值。
前面我们介绍过机器周期的概念, 它是 CPU 完成一个基本操作所需要的时间。其计算公式是: 机器周期=1/单片机的时钟频率。
51 单片机内部时钟频率是外部时钟的 12 分频, 也就是说当外部晶振的频率输入到单片机里面的时候要进行 12分频。
比如说你用的是 12MHZ 晶振, 那么单片机内部的时钟频率就是 12/12MHZ,当你使用 12MHZ 的外部晶振的时候, 机器周期=1/1M=1us。 如果我们想定时 1ms的初值是多少呢? 1ms/1us=1000。 也就是要计数 1000 个, 初值=65535-1000+1 ( 因为实际上计数器计数到 65536( 2 的 16 次方) 才溢出, 所以后面要加 1) =64536=FC18H, 所以初值即为 THx=0XFC, TLx=0X18。
知道了如何计算定时/计数器初值, 那么想定时多长时间都可以计算出, 当然由于定时计数器位数有限, 我们不可能直接通过初值定时很长时间, 如果要实现很长时间的定时, 比如定时 1 秒钟。 可以通过初值设置定时 1ms, 每当定时 1ms结束后又重新赋初值, 并且设定一个全局变量累计定时 1ms 的次数, 当累计到1000 次, 表示已经定时 1 秒了。 需要其他定时时间类似操作, 这样我们就可以使用定时器来实现精确延时来替代之前的 delay 函数。
这里以定时器 0 为例介绍配置定时器工作方式 1、 设定 1ms 初值, 开启定时器计数功能以及总中断, 如下:
void time0_init // 初始化定定时器0
{
TMOD |= 0x01; // 选择为定时器 0 模式,工作方式 1
THO = 0xFC; // 给定时器赋初值,定时1ms
TL0 = 0x18;
ETO = 1; // 打开定时器0中断允许
EA = 1; // 打开总中断
TR0 = 1; // 打开定时器
}
4. 硬件设计
本实验使用到硬件资源如下:
-
LED 模块( D1)
-
定时器 0
本章硬件电路非常简单, 只使用到开发板上 LED 模块的 D1, 至于定时器 0 它属于 51 单片机内部资源, 只需通过软件配置即可使用。
5. 软件设计
本章我们要实现的是:通过定时器0中断控制D1指示灯间隔1秒闪烁
#include <REGX52.H>
// 定义LED1管脚
sbit LED1 = P2^0;
// 定时器0中断配置函数,通过设置TH和TL即可确定定时时间
void time0_init() // 定时器初始化函数
{
TMOD |= 0X01; // 选择为定时器0模式,工作方式1
TH0 = 0XFC; // 给定时器赋初值,定时1ms
TL0 = 0X18;
ET0 = 1; // 打开定时器0中断允许
EA = 1; // 打开总中断
TR0 = 1; // 打开定时器
}
// 定时器0中断函数
void time0() interrupt 1
{
static unsigned int i; // 定义静态变量i
TH0 = 0XFC; // 给定时器赋初值,定时1ms
TL0 = 0X18;
i++;
if(i == 1000) // 计时1秒
{
i=0; // 重新开始计时
LED1=!LED1; // LED状态翻转
}
}
// 主函数
int main()
{
time0_init(); // 定时器0中断配置
while(1)
{
}
}
这个程序还是很好理解的,主要是配置定时器和写定时器函数。下面简单解释一下怎么实现功能的:
-
选择定时器工作模式 0、 工作方式 1、 设置定时 1ms 初值、 打开定时器计数功能和开启总中断功能。
-
然后进入 while 循环, 在循环体内没有执行任何功能程序。当定时时间到达即会进入定时器 0 中断。
-
在中断服务函数内, 重新赋初值准备下次计数, 并且定义一个静态变量来累计定时 1ms 次数, 当变量等于 1000 时, 表示定时时间达 1 秒, 然后清零变量以及控制 LED 状态翻转。
-
执行完成后退出中断返回主函数, 当时间到达又进入中断, 如此循环。
6. 小结
为什么要使用关键字 static 将 i 定义为静态变量呢? 我们希望每次进入中断函数时, i 保存的是上次累加值, 使用了 static 关键字, 就可以让变量 i 实现这种功能, 即不会每次进入中断函数后被初始化为 0。 假如去掉 static 关键字, 那么变量 i 就是一个局部变量, 每次进入中断函数后, 变量 i 初始值都是 0,也就是说它的值永远也不会递增到 1000, 从而实现不了 1s 定时。 可以这样理解,使用了 static 关键字就相当于将 i 变成了一个全局变量功能。
本质相比于马上要来的串口通信应该是简单的,主要是配置定时器还有中断函数,正确配置好定时器,然后在中断函数里面写我们想要的功能就行了。
代码中也出现几个新的寄存器,我们来好好分析一下:
void time0_init() // 定时器初始化函数
{
TMOD |= 0X01; // 选择为定时器0模式,工作方式1
TH0 = 0XFC; // 给定时器赋初值,定时1ms
TL0 = 0X18;
ET0 = 1; // 打开定时器0中断允许
EA = 1; // 打开总中断
TR0 = 1; // 打开定时器
}
TMOD |= 0X01;
TMOD
寄存器配置定时器的模式。0x01
表示将定时器0配置为工作方式1(16位定时器)。这是16位定时器模式,计数范围从0到65535。
TH0 = 0XFC; TL0 = 0X18;
TH0
和TL0
是定时器0的高位和低位寄存器。0xFC18
是定时器的初值。定时器0的计数值从0xFC18
开始计数,计数到0xFFFF
后溢出,触发中断。
计算定时时间需要根据系统时钟频率。假设你的系统时钟频率是12 MHz(常见的51单片机时钟),定时器的计数周期是 1 / (12 MHz / 12 / 256) = 1 ms
。
- 计算初值:定时器0的计数周期为1 ms,所以要设置的初值为
65536 - 1000
(因为每秒1000次中断)。65536 - 1000 = 64536
,对应的十六进制为0xFC18
。
ET0 = 1;
- 打开定时器0中断允许。
EA = 1;
- 打开全局中断,使能所有中断。
TR0 = 1;
- 启动定时器0开始计数。
void time0() interrupt 1
{
static unsigned int i; // 定义静态变量i
TH0 = 0XFC; // 给定时器赋初值,定时1ms
TL0 = 0X18;
i++;
if(i == 1000) // 计时1秒
{
i = 0; // 重新开始计时
LED1 = !LED1; // LED状态翻转
}
}
static unsigned int i;
- 使用
static
关键字声明i
变量,使其在多次调用中保持其值。每次定时器中断触发时,i
不会被重新初始化,而是保留上次中断的值。
TH0 = 0XFC; TL0 = 0X18;
- 每次定时器中断时,重新设置定时器0的初值。这样做是为了确保定时器继续以1 ms为周期计时。注意,如果你希望中断周期完全一致,可以考虑是否有必要在中断中重新加载初值。
i++;
- 每次定时器中断触发时,
i
的值增加1。
if(i == 1000)
- 当
i
达到1000时,说明已经经过了1000次中断,即1秒钟。此时,执行LED状态翻转操作,并将i
重新设置为0,开始新的计时周期。
LED1 = !LED1;
- 翻转LED的状态。如果LED是通过引脚控制的,确保
LED1
正确配置为你用来控制LED的引脚。这个操作通常用来测试LED是否正常工作或者作为一种视觉反馈。
2024.7.25 第一次修订
2024.8.20 第二次修订,后期不再维护