前言
在一些教程里面,定时器被放到了比较靠后的地方,一般会放在数码管之后,这里我们先来介绍定时器,因为我们比赛时要用到的代码的大的框架是基于定时器来写的,所以我们先来介绍定时器中断的相关部分,后面再去介绍其他外设。
定时器&中断是是什么
首先,我们来说,定时器是什么,看名字,定时器是一个用来定时的的工具,我们可以认为他是单片机上的一个电子时钟,根据设定的时间间隔触发各种操作,如果不用定时器的话,我们就会用延时进行操作,延时的话就会占用单片机资源,这样的话,我们单片机的资源利用率就会大幅降低,并且可能会出现什么一些“幽灵现象”
但是我们的定时器只能做一个计时的作用,这个时候我们就要请出他的好搭档——中断,中断是什么,简单来说,就是打断当前在做的事件去处理另一个事件,处理好另一件事件再返回来处理当前事件。
定时器和中断配合起来,我们简称为定时器中断,组合起来的意思就是,定时器工作到某一个定时的位置,触发中断,中断去处理什么事情,对,就是这样。
数据手册
我们来看一下我们数据手册里面的介绍,这是手册里面给出我们中断的概念

在这里还涉及到了中断优先级的问题,我们在这里暂时不涉及
下面,我们将从几个方面来介绍我们的中断系统
1.中断结构
我们所使用的单片机的中断系统结构可以看作一个由中断源、中断标志、中断允许、中断优先级和硬件查询逻辑组成的协同工作网络。
我们首先知道中断源是什么,中断源就是能发出中断请求的信号来源,我们接受到这个信号后,就表示我们要进行中断这个任务了,下面我们详细说明一下中断的相应流程
中断请求--->中断允许--->中断查询--->中断响应--->执行中断服务程序--->中断返回
以上就是一个中断响应的全部流程,上面的写法又响又不臭,我们用人话说,就是一个中断的过程,我们需要先允许这个中断发生,然后按照优先级顺序去查询所有已开启的中断标志,如果查询到了有效的中断,CPU执行完当前指令后,产生一个指令,硬件的跳转到对应的固定中断向量地址,CPU 开始执行位于中断向量地址处的任务,任务执行完毕,返回主程序,CPU 恢复之前的现场并继续执行的一个大的过程。
2.中断寄存器
中断允许寄存器(IE)
这里我们来看下面的图
我们看到有一个EA贯彻到底,这个是总中断允许位,1就是开放所有的中断,0就是关闭所有的中断,这就相当于是一个大门,有中断工作这个必须要打开
然后EA的左边就有一大串的寄存器,这个就是对应一个单独的中断工作的选择开关,下面我们通过一个表格来看
| 符号 | 功能说明 |
|---|---|
| EA | 总中断允许位,1=开放所有中断,0=关闭所有中断 |
| ET2 | 定时器2中断允许位 1 = 允许;0 = 禁止 |
| ES | 串口中断允许位 1 = 允许;0 = 禁止 |
| ET1 | 定时器1中断允许位 1 = 允许;0 = 禁止 |
| EX1 | 外部中断1允许位 1 = 允许;0 = 禁止 |
| ET0 | 定时器0中断允许位 1 = 允许;0 = 禁止 |
| EX0 | 外部中断0允许位1 = 允许;0 = 禁止 |
然后我们再来看一下中断优先级所用到的寄存器,我们也是通过表格来看一下
中断优先级寄存器(IP)
| 符号 | 功能说明 |
|---|---|
| PS | 串口中断优先级1 = 高优先级;0 = 低优先级 |
| PT1 | 定时器1中断优先级 1 = 高优先级;0 = 低优先级 |
| PX1 | 外部中断1优先级 1 = 高优先级;0 = 低优先级 |
| PT0 | 定时器0中断优先级 1 = 高优先级;0 = 低优先级 |
| PX0 | 外部中断0优先级 1 = 高优先级;0 = 低优先级 |
3.中断优先级
51 单片机的中断优先级系统分为两层:分别是自然优先级和抢占优先级
这次我们都分开介绍一下,首先是自然优先级,当多个中断同时发生,且它们在 IP 寄存器中的优先级位都被设置为相同(同为高或同为低)时,CPU 会按照一个固定的顺序来响应。这个顺序由硬件决定,不可更改,称为自然优先级,顺序从高到低为:INT0 -> T0 -> INT1 -> T1 -> UART -> INT2 -> INT3就比如说,我们同时设置的INT0和INT1的中断,CPU会优先响应INT0;
然后我们来看抢占优先级,抢占优先级是通过设置IP寄存器来改变中断的优先级,怎么说呢,高优先级的中断可以打断正在执行中的低优先级的中断,实现中断嵌套;而低优先级的中断不能打断高优先级或者同优先级的中断,相当于在排队;同优先级的中断直接不能嵌套
我们还可以来看一下我们的表格

从上倒下的顺序也就是我们的自然优先级的顺序,然后我们可以通过表格内优先级设置的寄存器的赋值设置不同级别的优先级
总结一下,我们可以归类出下面三点
//高优先级中断可打断低优先级中断。
//同级或低优先级中断需等待当前中断执行完毕。
//同时发生时,先比较 IP 设置,若相同则按自然优先级响应。
4.中断处理
在CPU响应中断后,会自动跳转到一个固定的地址,也就是我们的中断向量。这些地址是固定不变的,通常为8字节,下面来看一下我们的中断向量表(了解即可)
| 中断源 | 中断向量地址 |
|---|---|
| INT0 | 0x0003H |
| T0 | 0x000BH |
| INT1 | 0x0013H |
| T1 | 0x001BH |
| UART | 0x0023H |
| INT2 | 0x0033H |
然后就是我们的中断服务函数的编写(ISR)
首先我们需要一个入口,在C语言中,通常使用void 函数名() interrupt 中断编号来声明,编译器会自动处理跳转;在这里,函数名我们可以随便命名,但是我们的中断编号要根据我们所需要响应的中断来写,下面我们来看一下不同中断响应对应的中断编号
| 中断源 | 中断编号 |
|---|---|
| INT0 | interrupt0 |
| T0 | interrupt1 |
| INT1 | interrupt2 |
| T1 | interrupt3 |
| UART | interrupt4 |
| T2 | interrupt5 |
| INT2 | interrupt6 |
在编写我们的中断服务函数时,我们尽量做到代码简短一些,只处理最紧急的任务,长时间的任务我们最好交给主循环处理
中断介绍到上面我们介绍的差不多了,下面我们来讲一下定时器
5.定时器
虽然我们之前没有介绍到延时什么的骚操作,但是我决定还是简单的提一嘴,在刚开始学单片机的“正常路线”里面,大家大多数都会先接触延时,再去接触定时器,延时是什么,我们简单一提,延时就是我们通过让单片机执行空指令(什么都不做)达到等待的作用,但是由于我们单片机内部资源有限,延时会大量占用我们的单片机资源,并且延时是通过单片机内部的周期运行计时计数,所以没有那么的准确,所以我们这里就要用到定时器;首先定时器是什:定时器/计数器可以用于精确的定时,还可以对外部脉冲进行计数。这么说官方是吧,看字面意思,定时器就是一个计时计数的工具。
下面我们来详细讲解介绍一下定时器
6.定时器核心概念
定时器模式
对单片机内部的机器周期时钟脉冲进行计数。一个机器周期等于 12 个时钟周期(在 1T 单片机中为 1 个时钟周期)。通过计算累计的机器周期个数,实现定时功能。
公式:定时时间 = (计数次数 × 机器周期) = (2ⁿ - 初值) × (12 / 频率)
计数器模式
对来自单片机外部引脚(T0/P3.4, T1/P3.5)的脉冲信号进行计数。每来一个下降沿,计数值加 1。
核心寄存器
THx/TLx (x=0,1,2):16 位计数寄存器的高8位和低8位。用户写入初始值,计数过程中其值不断递增。
TMOD:定时器模式寄存器,用于设置 T0 和 T1 的工作模式
TCON:定时器控制寄存器,包含运行控制位 (TRx) 和溢出标志位 (TFx)。
了解上面我们的一些概念后,下面我们来看我们定时器的工作模式
7.定时器/计数器 T0 和 T1 的工作模式
T0 和 T1 的功能几乎完全相同,通过 TMOD 寄存器 来设置其工作模式。TMOD 的低 4 位用于 T0,高 4 位用于 T1。
TMOD 寄存器结构

GATE:门控位
0 = 仅由软件位 TRx 控制定时器启停
1 = 由 TRx 和外部中断引脚 INTx 同时为高电平时才启动定时器(用于测量外部脉冲宽度)
M1, M0:工作模式选择位
M1 M0 工作模式 说明
0 0 模式 0 13 位定时器/计数器
0 1 模式 1 16 位定时器/计数器
1 0 模式 2 8 位自动重装定时器/计数器
1 1 模式 3 双 8 位定时器/计数器(仅 T0)
以上共有四种工作模式,这样看还是很懵是吧,你先别懵,往下看
8.四种工作模式详解
模式 0:13 位定时器/计数器模式
这种模式不常用,但是可以了解一下
我们先来看一下这种模式下的工作原理,使用 TLx 的低 5 位 和 THx 的 8 位,共同组成一个 13 位的计数器(2¹³ = 8192);TLx 的高 3 位无效,可忽略。他的计数范围就是0到8191对应16进制就是(0X0000~0X1FFF),我们给他设置的计数初值就是初值 = 8192 - 所需计数次数,操作模式如下
设置 TMOD 选择模式 0。
计算初值,并写入 THx 和 TLx(注意 TLx 只使用低5位)。
置位 TRx 启动定时器。
定时器从初值开始加 1 计数,溢出时置位 TFx,并向 CPU 请求中断。
溢出后,计数值回到 0,若需循环定时,必须在中断服务程序中手动重装初值。
这种模式计数比较怪,13位,怪怪的,不推荐使用
模式 1:16 位定时器/计数器模式
这是最常用、最标准的模式,提供了最大的计数范围和灵活性。
他的工作原理跟上面的13位计数相似,不同的地方就是这个是16位, TLx 的8位全部有效,使用 TLx 的 8 位 和 THx 的 8 位,共同组成一个 16 位的计数器。这个的计数范围就比上面的要大了,计数范围:0 ~ 65535 (0x0000 ~ 0xFFFF),我们单片机所用的晶振是12MHZ,机器周期是1ms,在这个模式下,最大的定时时间是65.535ms,计数的初值和上面一样,计数范围改变,那就是初值 = 65536 - 所需计数次数,他的特点是计数范围大,使用灵活,但需要软件重装,在重装期间可能产生微小误差。操作流程和上面的13位计数模式一样,注意TMOD模式选择模式1,其余就改变初值即可。
模式 2:8 位自动重装定时器/计数器模式
这种模式适用于需要高精度、固定间隔定时的场合,如产生串口通信的波特率
这里的工作原理就稍有不同了,我们选用TLx 作为 8 位计数器,THx 作为常数重装寄存器,保存着自动重装的初值,这个模式下的计数范围就小了,计数范围为0 ~ 255 (0x00 ~ 0xFF),初值的话就是初值 = 256 - 所需计数次数,这个的操作流程和上面的稍有不同,这里我们单独拿出来看一下
设置 TMOD 选择模式 2。
计算初值,并将相同的值写入 THx 和 TLx。
置位 TRx 启动定时器。
TLx 从初值开始加 1 计数,溢出时不仅置位 TFx,还会自动将 THx 中的值重新装入 TLx,然后立即开始新一轮计数。
用户无需在中断中重装初值。(历程中单独讲)
这种模式的特点就是自动重装,避免了因软件重装带来的定时误差,精度高。但计数范围较小
双八位我们用的比较少,这里我们就不举例了
在我们的使用中,我给大家一些下面的建议
绝大多数通用定时需求:优先选择模式 1。
串口通信或高精度固定间隔定时:优先选择模式 2。
除非有特殊兼容性要求,否则避免使用模式 0。
下面给大家展示一个代码,就用16位的模式,大家可以参考理解一下
#include <reg52.h>
// 定义LED指示灯
sbit LED = P1^0;
unsigned int ms_count = 0; // 毫秒计数器
/*
定时器0模式1初始化
定时50ms @11.0592MHz
机器周期 = 12 / 11.0592MHz ≈ 1.085μs
16位最大计数值 = 65536
所需计数值 = 50000μs / 1.085μs ≈ 46082
初值 = 65536 - 46082 = 19454 = 0x4BFE
*/
void Timer0_Mode1_Init(void)
{
TMOD &= 0xF0; // 清除T0的设置位
TMOD |= 0x01; // 设置T0为模式1 (M1=0, M0=1)
// 16位模式下的初值设置
TH0 = 0x4B; // 高8位初值
TL0 = 0xFE; // 低8位初值
ET0 = 1; // 允许T0中断
EA = 1; // 开启总中断
TR0 = 1; // 启动T0
}
/*
定时器0中断服务程序
在模式1下,溢出后必须手动重装16位初值
*/
void Timer0_ISR() interrupt 1
{
// 手动重装16位初值
TH0 = 0x4B;
TL0 = 0xFE;
ms_count += 50; // 累计时间(毫秒)
if(ms_count >= 1000) // 达到1秒
{
ms_count = 0;
LED = ~LED; // 每秒LED状态翻转一次
}
}
void main(void)
{
Timer0_Mode1_Init();
while(1)
{
}
}
代码如上,本节大概就介绍完了,大家有问题可以在评论区讨论,或者进入主页获取大家一起讨论相关内容,需要源程序的同学可以通过主页方式获取

1万+

被折叠的 条评论
为什么被折叠?



