【51单片机】中断定时器原理解析 + 使用
学习使用的开发板:STC89C52RC/LE52RC
编程软件:Keil5
烧录软件:stc-isp
开发板实图:
文章目录
- 中断
- 定时器
- 定时器工作模式
- 定时器相关寄存器
- 编码
- 初始化定时器
- 独立按键改变LED流水灯方向
在介绍定时器前,需要先对中断有所了解,定时器的核心机制之一就是使用了中断
中断
中断是为使CPU具有对外界紧急事件的实时处理能力而设置的
当CPU正在处理某件事时,外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完后,再回到原来被中断的地方,继续原来的工作,这个过程被称为中断
中断程序流程
左侧图:在主程序执行期间,发现有中断请求到来,会先打个断点,然后进行中断响应,执行
中断处理程序
,完成后返回断点,继续执行主程序
右侧图:以程序地址空间的形式展示处理中断的过程,发生中断时,会跳转到中断处理程序的地址空间
,执行完再跳转回主程序的地址空间
中断资源
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
如果使用C语言编程,中断查询次序号就是中断号
其中 Timer0_Routine(void)
为定时器T0的中断处理函数,中断号为1,Timer1_Routine(void)
为定时器T1的中断处理函数,中断号为3,以此类推
注意
:中断资源和单片机型号是关联在一起的,不同型号可能会有不同的中断资源,例如中断资源个数不同,中断优先级个数不同等
如下是一个较为简单的中断结构图
,只有两个优先级
左侧的INT0
和 INT1
为外部中断0和1,T0
和 T1
为定时器中断0 和 1
可以看到,要想让中断启用,还需要连通电路。例如想让定时器中断0启动,还需要将ET0 = 1
, EA = 1
,PT0选择一个优先级,0或1都可
定时器
我们对中断已经有了一些了解,接下来我们介绍定时器
定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
引入如下场景:独立按键控制LED流水灯的方向
- 对于LED流水灯,我们可以控制每隔1秒改变亮灯
- 对于独立按键,我们不光要循环检测是否有按键按下,还需要等待按下的按键松开
LED灯的操作是周期性的,独立按键的检测则是时刻不停的,我们无法将两个任务线性组合在一起。
例如检测到按键按下,还需要等待按键松开,那么此时线性的执行会被卡在等待独立按键松开,而无法让LED灯呈现流水状。此时就可以使用 定时器
完成该需求
定时器作用:
- 用于计时系统,可实现软件计时,或者使程序
每隔一固定时间完成一项操作
- 替代长时间的Delay,提高CPU的运行效率和处理速度
定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0
和 T1
是所有51单片机所共有的。
STC89C52
共有 3 个定时器(T0、T1、T2
),T0和T1与传统的51单片机兼容,T2 是此型号增加的资源
定时器框图
定时器在单片机内部就像一个小闹钟,根据时钟输出信号,每隔"一定时间",计算单元的数值就增加1,当计数单元数值增加到"设定的闹钟提醒时间时"
,计算单元就会向中断系统发出中断请求,产生"响铃提醒",使程序跳转到对应的中断处理函数中
定时器工作模式
STC89C52
的 T0 和 T1均有四种工作模式
- 模式0:13位定时器/计数器
- 模式1:16位定时器/计数器(常用)
- 模式2:8位自动重装模式
- 模式3:两个8位计算器
工作模式1框图:
该框图由时钟系统、计数系统和中断系统三部分组成
计数系统
- 中间的
TH0(time high)
和TL0(time low)
是计数单元,TH0
为高8位,TL0
为低8位,所以总共能计数 0 ~ 65535。当时钟系统发出一个脉冲周期后,计数系统每收到一个脉冲周期,计数单位就会 + 1,当超出最大值后溢出,发出中断请求
时钟系统
时钟系统有两个来源,SYSclk
和T0 Pin引脚
SYSclk
:系统时钟,即晶振周期,本开发板上的晶振为12MHz。该时钟系统有两条线路,分别为12T分频和6T分频。如果为12T分频,则输出为1MHz,周期为1微秒,即每隔1微秒输出到计数系统一次,进行一次计数
控制部分
- 当
C/T=0
,多路开关连接到系统时钟的分频输出,T0对时钟周期计数,T0工作在定时方式;C/T=1
,多路开关连接到外部脉冲输入P3.4/T0
,即工作在计数方式 - 当
GATE=0(TMOD.3)
时,如TR0=1,则定时器计数。GATE = 1,允许由外部输入INT0控制定时器0,这样可以实现脉宽测量
定时器和中断系统工作方式
如下
定时器相关寄存器
寄存器是连接软硬件的媒介
在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式
寄存器相当于一个复杂机器的“操作按钮”
因为TCON
涉及到TMOD
,我们先讲解TMOD
TMOD定时器/计数器工作模式寄存器
-
TMOD高4位为控制定时器1,低四位为控制定时器0
-
定时和计数功能由特殊功能寄存器TMOD的控制位
C/T
进行选择; -
M1和M0组合决定定时器/计数器的工作模式,共4种模式,其中模式0、1、2两个定时器相同,模式3不同
后续操作使用定时器T0,所以着重讲解定时器T0,定时器T1配置基本相同
位 | 符号 | 功能 |
---|---|---|
TMOD.3(第3位) | GATE | TMOD.3控制定时器0,置1 时只有在INT0脚为高及TR0控制位置1时才可打开定时器/计数器0 |
TMOD.2 | C/T | TMOD.2控制定时器0用作定时器或计数器;清0用作定时器 (从内部系统时钟输入),置1用作计数器(从T0/P3.4脚输入) |
GATE的逻辑可参看下图
- 当GATE = 0时,经过非门取反为1,则或门一定为1,此时只要TR0 = 1即可开启定时器0
- 当GATE = 1时,经过非门取反为0,需要INT0 = 1,或门才为1,还需要TR0 = 1才可开启定时器0
M1
和M0
用作选择工作模式
TCON
TCON为定时器/计数器T0、T1的控制寄存器,同时也锁存T0、T1溢出中断源和外部请求中断源等,TCON格式如下:
TF1,TR1
和TF0,TR0
作用相同,只是前者是控制定时器T1,后者是控制定时器T0。后续操作使用定时器T0,所以讲解TF0和TR0
TF0
:定时器/计数器T0溢出中断标志
。T0被允许计数后,从初值开始计数+1,当最高位产生溢出后,由硬件置“1”TF0
,向CPU请求中断,一直保持CPU响应该中断时,才由硬件清零“0”TF0
(也可由程序查询清“0”)TR0
:定时器T0的运行控制位
。该位由软件置位和清零。当GATE(TMOD.3) = 0,TR0 = 1就允许T0开始计数,TR0 = 0禁止计数
;GATE(TMOD.3) = 1,TR0 = 1且INT0输入高电频,才允许T0计数IE0
:外部中断0请求源(INT0P3.2)标志
。IE0 = 1外部中断0向CPU请求中断,当CPU响应外部中断时,由硬件清“0”IE0(边沿触发方式)IT0
:外部中断0触发方式控制位
。IT0 = 0时,外部中断0为低电平触发方式,当INT0(P3.2)输入低电频时,置位IE0。采用低电频触发方式时,外部中断源(输入到INT0)必须保持低电频有效,直到该中断被CPU响应,同时在该中断服务程序执行完之前,外部中断源必须被清除(P3.2要变高),否则将产生另一次中断。当IT0 = 1时,则外部中断0(INT0)端口由“1” -> “0” 下降沿跳变,激活中断请求标志位IE0,向主机请求中断处理
编码
初始化定时器
初始化定时器主要分为三部分:定时器选择,中断系统,计数单元
定时器选择
- 首先我们要选择定时器,而非计数器。通过
TMOD
的C/T = 0
。 - 接下来,选择定时器工作模式——16位定时器/计数器,即TH0和TL0全用。通过
TMOD
的M0 = 0, M1 = 1
因为TMOD是不可位寻址,只能8位一起赋值,不能向P2I/O口
那样,可以使用P2_1 = 1,单独给第一位赋1
因为我们使用定时器T0,所以保持定时器T1部分配置不变
TMOD &= 0xF0; //高4位保持不变,低4位清零
TMOD |= 0x01; //高4位保持不变,低4位为0001
计数单元
系统频率为12MHz,我们选择12T模式,则到达计数单元的时钟为1MHz,即1us 计数加1
16位定时器范围为0 ~ 65535,我们想实现1ms的周期,可以设置初值为64535,距离溢出为1000us,即1ms
TH0 = 64535 / 256; //高8位
TL0 = 64535 % 256; //低8位
但其实定时器溢出是达到65536才会发出中断请求,上述设置会有1us的误差,追求准确的话,TL0再加1即可
TH0 = 64535 / 256; //高8位
TL0 = 64535 % 256 + 1; //低8位
中断系统
- 首先要打开定时器的中断开关。通过
TCON
的TF0和TR0
TF0 = 0; //溢出标志位,初始化为0,硬件置1
TR0 = 1; //允许中断
- 其次打开中断系统开关
//中断开关
ET0 = 1; //定时器0的中断开关
EA = 1; //中断总开关
PT0 = 0; //中断优先级为低
到此定时器的初始化就完成了,完整代码如下:
#include <REGX52.h>
/**
* @brief 初始化定时器0
* @parm 无
* @retval 无
*/
void Timer0Init()
{
//TMOD选择定制器0,不可位寻址,只能一起赋值
//高4位是定时器1,低4位是定时器0
//从高到底分别为GATE,C/T,M1,M0,详细看说明手册
//M1, M0 为01,选择16位定时器
TMOD &= 0xF0; //高4位保持不变,低4位清零
TMOD |= 0x01; //高4位保持不变,低4位为0001
//定时器初值,每当超过65535,会发生一次中断,1us + 1
//设置初值为64535,距离超时有1000us,即1ms
TH0 = 64535 / 256; //高8位
TL0 = 64535 % 256 + 1; //低8位
//定时器中断
TF0 = 0; //溢出标志位,初始化为0,硬件置1
TR0 = 1; //允许中断
//中断开关
ET0 = 1; //定时器0的中断开关
EA = 1; //中断总开关
PT0 = 0; //中断优先级为低
}
也可以使用STC-ISP
直接生成定时器初始化方法
注意上述配置都要选择正确,不然无法达到预期效果
其中代码中的AUXR &= 0x7F
是较高版本的51单片机的新配置,本型号不支持,需要将该行代码删除
定时器时钟
只有1T和12T模式,要选择6T,可在左侧设置
中断处理函数
定时器0的中断号为1, 中断处理函数如下:
void Timer0_Rountine(void) interrupt 1
{
}
我们要实现每隔1s进行的任务,可使用额外的变量辅助
void Timer0_Rountine(void) interrupt 1
{
static unsigned int timeCount = 0;
timeCount++;
if(timeCount >= 1000)
{
timeCount = 0;
}
}
注意
:每次发送中断,还需要对计数单元进行初始化,否则计数单元溢出就不是1us了
void Timer0_Rountine(void) interrupt 1
{
TH0 = 64535 / 256; //高8位
TL0 = 64535 % 256 + 1; //低8位
static unsigned int timeCount = 0;
timeCount++;
if(timeCount >= 1000)
{
timeCount = 0;
}
}
独立按键改变LED流水灯方向
使用模块化编程
延迟器模块——用于等待按键松开
Delay.h
#ifndef __DELAY_H__
#define __DELAT_H__
void Delayms(unsigned char xms);//等待指定毫秒
#endif
Delay,c
//等待指定时间,单位是毫秒
void Delayms(unsigned char xms) //@12.000MHz
{
while(xms--)
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
独立按键模块——检测哪个按键按下
SoleKey.h
#ifndef __SOLEKEY_H__
#define __SOLEKEY_H__
unsigned char soleKey();
#endif
SoleKey.c
#include <REGX52.h>
#include "Delay.h"
/**
* @brief 获取独立按键的键码(哪个被按下)
* @parm 无
* @retval 独立按键的键码
*/
unsigned char soleKey()
{
unsigned char keyNum = 0;
if(P3_1 == 0){Delayms(20);while(P3_1 == 0);Delayms(20);keyNum = 1;}
if(P3_0 == 0){Delayms(20);while(P3_0 == 0);Delayms(20);keyNum = 2;}
if(P3_2 == 0){Delayms(20);while(P3_2 == 0);Delayms(20);keyNum = 3;}
if(P3_3 == 0){Delayms(20);while(P3_3 == 0);Delayms(20);keyNum = 4;}
return keyNum;
}
定时器模块——初始化定时器,提供定时器中断处理函数模板
Timer.h
#ifndef __TIMER_H__
#define __TIMER_H__
void Timer0Init();
#endif
Timer.c
#include <REGX52.h>
/**
* @brief 初始化定时器0
* @parm 无
* @retval 无
*/
void Timer0Init()
{
//定时器模式
//TMOD选择定制器0,不可位寻址,只能一起赋值
//高4位是定时器1,低4位是定时器0
//从高到底分别为GATE,C/T,M1,M0
//GATE = 0,只要TR0为1就可以打开定时器
//C/T = 0,选择定时器
//M1, M0 为01,代表选择模式1——16位定时器
//选择定时器T0
//所以低四位为0001
TMOD &= 0xF0; //高4位保持不变,低4位清零
TMOD |= 0x01; //高4位保持不变,低4位为0001
//计数单元中断
TF0 = 0; //溢出标志位,初始化为0,硬件置1
TR0 = 1; //允许中断
//中断开关
ET0 = 1; //定时器0的中断开关
EA = 1; //中断总开关
PT0 = 0; //中断优先级为低
//定时器初值,每当超过65535,会发生一次中断,1us + 1
//设置初值为64535,距离超时有1000us,即1ms
TH0 = 64535 / 256; //高8位
TL0 = 64535 % 256 + 1; //低8位
}
定时器0中断函数模板
//void Timer0_Rountine(void) interrupt 1
//{
// TH0 = 64535 / 256; //高8位
// TL0 = 64535 % 256 + 1; //低8位
// static unsigned int timeCount = 0;
// timeCount++;
// if(timeCount >= 1000)
// {
// timeCount = 0;
// }
//}
主程序
主函数循环检测按键按下,定时器0中断处理函数根据按键按下情况改变LED流水灯方向
main.c
#include <REGX52.h>
#include <INTRINS.h>
#include "Timer.h"
#include "SoleKey.h"
unsigned int timeCount = 0, mode = 0, keyNum = 0;
/**
* @brief 根据独立按键改变LED流水灯的方向
* @parm 无
* @retval 无
*/
void Timer0_Routine(void) interrupt 1
{
//注意每次中断都要初始化定时值
TH0 = 64535 / 255; //高8位
TL0 = 64535 % 255; //低8位
timeCount++;
if(timeCount >= 1000)
{
timeCount = 0;
if(mode == 0)//模式一,LED向右
P2 = _crol_(P2, 1);
else//模式二,LED向左
P2 = _cror_(P2, 1);
}
}
void main()
{
Timer0Init();
P2 = 0xFE;//初始化LED灯为只有D1亮
while(1)
{
keyNum = soleKey();
if(keyNum == 1)
{
mode++;
if(mode >= 2)
mode = 0;
}
}
}
补充
:改变LED流水灯方向使用_cror_
和_crol_
这两个方法,其作用为循环右移和左移,不用考虑溢出导致的全0的情况,其函数声明在头文件<INTRINS.h>
以上就是本篇博客的所有内容,感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。