目录
1.定时器与中断系统简介
1.1中断系统
1.2定时器
1.2.1定时器简介
1.2.2定时器大致原理及其配置
1.2.3定时器所需的所有配置总介
2.定时器0实现LED闪烁
3.使用软件生成定时器初始化程序
1.定时器与中断系统简介
1.1中断系统
首先,我们需要来了解一下什么是中断系统:首先,中断系统指的是我们程序运行过程中由于中断指令而需要停下当前的动作去做中断所要求的事情,完成中断要求的事情之后才可以回来继续完成之前事情的一个规则。
就好比我们正在吃饭,但是突然厨房的水烧开了,我们就不得不去先去处理烧开的水,处理完之后再回来继续吃饭。我们去处理厨房烧开的水这个动作就叫做我们的”中断“。
中断分为三大类:外部中断,定时器中断和串口中断
我们使用的STC89C5X系列单片机提供了 8个中断请求源,其中,外部中断有四个,分别是外部中断0(INT0)、外部中断 1(INT1)、外部中断 2(INT2)、外部中断 3(INT3);定时器中断有三个,分别是定时器 0中断(Timer0)、定时器 1中断(Timer1)、定时器 2中断(Timer2);串口中断一个,就叫串口中断(UART)
中断和中断之间还是有”三六九等的“,就像我们需要做的事情是有一个优先级的,比方说我们正在吃饭,但是这个时候,厨房的水烧开了,我们去把这个水处理一下,但是我们不小心摔倒了,这个时候我们优先处理的事情应该是从”处理烧开的水“变成”先爬起来“,这个就叫做中断的嵌套,先处理优先级更高的那个。我们应该要去处理中断优先级更高的那件事,就算我们正在处理应该中断的过程中,我们还是要去先处理更高优先级的中断请求,这也是中断嵌套的基本规则。
这里有一张表格,写的就是中断的优先级:
我们可以看到,中断优先级的基本排序是:外部中断0(INT0) > 定时器 0中断(Timer0)>外部中断 1(INT1) > 定时器 1中断(Timer1) > 串口中断(UART)>定时器 2中断(Timer2)>外部中断 2(INT2)>外部中断 3(INT3)
1.2定时器
1.2.1定时器简介
- 最初始的51单片机有两组定时器/计数器,因为既可以定时,又可以计数,故称之为定时器/计数器。T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源
- 定时器/计数器和单片机的 CPU是相互独立的。定时器/计数器工作的过程是自动完成的,不需要 CPU的参与。
- 51单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加 1。
有了定时器/计数器之后,可以增加单片机的效率,一些简单的重复加 1的工作可以交给定时器/计数器处理。CPU转而处理一些复杂的事情。同时可以实现精确定时作用。
所以我们一般会使用定时器来代替之前的Delay,Delay的使用是对CPU执行的,在这段时间里,CPU都无法执行任何任务,而是在Delay函数中执行循环。所以我们现在可以采用定时器去把我们的Delay替换,转而把我们的CPU留出更多的时间去处理更加高效的事情。
1.2.2定时器大致原理及其配置
大部分情况下,我们使用的最多的还是定时器0,这里是定时器的大致原理图
我上图所示的C/T中T上有一个横线表示低电平有效,C表示高电平有效,C又是Clock的缩写,也就是说C/T = 1的话,我们使用的是计数器,如果C/T = 0,则说明我们使用的是计时器系统,这里的SYSclk叫做系统时钟,即晶振周期。我们一般使用的是12T mode运行。
而则是一个控制开关,有这个小系统输出的值为高电平时,开关才会闭合,才会继续往后面执行操作。
这里有必要介绍一下的是是非门电路,输入1则输出0,输入0则输出1;是或门,输入两个数有一个为1则输出1,都是0时才输出0;是与门,输入两个都是1时输出的才是1,否则输出的是0。
这里我们需要反向推理一下,假设我们想要最终输出1,那么与门的两个输入口需要都是高电平1,所有这里TR0必须是1,然后就是这个或门的输出要是1,则说明这个门的输入只要有一个1就可以完成任务,或门连接的是 和,即门控端GATE取反值和外部中断0的值,我们要实现输出有一个为1即可,假如我们需要只靠外部中断0控制整个电路的通闭,那么我们就要把GATE设为1,即输出的为0,这样就只靠外部中断0实现控制电路,但是我们这里不需要依靠外部中断支配,所以我们这里直接把门控端GATE设为0就好了,这样无论外部中断0输出的是什么都不会影响我们的电路闭合。
配置好了前面的部分,我们来配置一下后面的,这里要分成两部分,第一部分是 表示的是定时器/计数器0的计数数值,计数数值的高8bit位存在寄存器TH0中,后8bit位存在TL0中,最大可以表示到65535,每个单元一般时1us,直到超出了这个最大值,计数就会溢出,来到溢出标志位,这个就是一个判断定时器溢出的标志位,通过判断TF0来判断计数是否溢出,如果溢出,就会进入到中断中去。这就是上面的图的流程。
我们配置的是定时器0,即T0的话,我们的流程就是这样的:
经过刚刚的一系列计数,溢出,到达中断允许位,我们就来到了中断,这部分统称为IE,我们使用T0就要配置ET0为1,EA为1,才可以使这部分闭合,这部分也叫做“使能”,也就是这部分不闭合,我们就无法实现中断。然后就是到达IP选择PT0 = 1或者PT0 = 0,这就是选择高优先级和低优先级的,其实选择哪个都没有太大的影响,主要是完成前面的配置,保证电路通畅即可。
1.2.3定时器所需的所有配置总介
这里写了两个寄存器列表,我们实现中断,所以从中断寄存器出发,我们必须要配置的是IE使能和IP/IPH的高低优先级选择。
然后后面的进行分类,定时器T0和T1实现TCON的配置即可,我们实现T0定时器,就只要关关心TCON这个控制寄存器,然后我们需要实现的是T0,所以我们看到定时器0相关的寄存器列表中正好有TCON控制寄存器,然后我们还要实现TMOD模式寄存器的配置和TL0与TH0计数存放计数数值的初始化即可。
总而言之,我们实现T0,需要配置IE使能(ET0和EA都配置为1),IP/IPH(即PT0)高低优先级选择,TCON控制寄存器,TMOD模式寄存器,TH0和TL0计数数值初始化。
需要主要介绍的是TCON和TMOD这两个寄存器:
这里TCON是“可位寻址”而TMOD是“不可位寻址”,意思就是TCON的每一位的配置可以单独配置,比如里面的IE0,IR0,而不用影响到IE1和IR1,但是TMOD就不一样,只能整体赋值,我们不能把里面的单个数拉出来赋值,只可以给一个十六进制的数把八位同时配置完成。
TCON: 我们需要配置TCON,就只要让TR0 = 1让计数开始,IE0= 1使能让电路接通,IT0 = 1让
的控制只由TR0掌控。
至于TF0标志位的置位和清零,可以说可有可无,初始置为0就好了,后面也不用再管。
TMOD:
这里把IT0配置为1了,所以其实在TMOD中的门控端GATE是多少都不重要了
由于是不可位寻址,我们只能整体赋值,按照前面分析,我们操作定时器0的话需要把C/T = 0,GATE最好也设为0。
M1和M0是模式的选择,主要的区别如下:
M1 = 0, M0 = 0: 13位定时/计数模式
- 定时器/计数器0 使用 TH0 和 TL0 寄存器的所有位进行计数。
M1 = 0, M0 = 1: 16位定时/计数模式
- 定时器/计数器0 使用 TH0 和 TL0 寄存器的所有位进行计数。
M1 = 1, M0 = 0: 8位自动重装定时/计数模式
- 定时器/计数器0 先使用 TL0 寄存器计数,当 TL0 计数溢出时,自动从 TH0 中重新装载计数值,然后继续计数。
M1 = 1, M0 = 1: 2个8位定时/计数模式
- 定时器/计数器0 分为两个独立的8位定时/计数器:T0 高位和 T0 低位。TH0 用作高8位计数器,TL0 用作低8位计数器。
我们一般默认选择两个8位的计数器,也就是M1 = 0, M0 = 1的16位定时/计数模式
所以综上所述,我们配置TMOD寄存器的最后四位,而且是配置为0001,前四位与定时器1有关,我们一般不作处理。
但是这里又只能整体赋值,所以TMOD的配置T0可以先把后四位清零,前四位不动,再把前四位不动,后四位变成0001,我们可以使用按位与(&)和按位或(|)两个操作符。
先让TMOD &= 0xf0;就让前四位不变,后四位变成0000,再使用TMOD |= 0xf1;就让前四位不变,后四位变成0001,然后我们的TMOD配置就大功告成。
2.定时器0实现LED闪烁
学习了配置定时器,我们就要学习怎么使用定时器,这里使用定时器配置间隔时长为1s的LED闪烁。
这里我们就要写一个定时器初始化函数,把寄存器配置好,再使用中断实现功能。
首先,我们需要配置模式寄存器TMOD,让TMOD选用定时器0并使用16位计时,所以我们就可以写出
TMOD &= 0xf0;
TMOD |= 0xf1;
然后是TCON寄存器
这里主要是注意我们需要的是1s也就是1000*1000us,但是由于计数器限制,我们只能得到1ms的计时,就像Delay函数那样可以使用以1ms为基本单位的计时,累加起来就可以组成自己想要的时间
这里就配置一个1000us,那么距离计数值溢出(65535)就相差1000,所以我们初始值为64535,TH0高四位为64535/256,TL0第四位为64535%256.
TL0 = 64535%256; //设置定时初值
TH0 = 64535/256;//设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
PT0 = 0; //选择低优先级
ET0 = 1; //使能配置
EA = 1; //使能配置
这样就写完了我们的定时器0初始化程序:
void Timer0_Init()
{
TMOD &= 0xf0;
TMOD |= 0xf1;
TL0 = 64535%256; //设置定时初值
TH0 = 64535/256;//设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
PT0 = 0; //选择低优先级
ET0 = 1; //使能配置
EA = 1; //使能配置
}
配置完初始化程序之后,我们就开始配置中断程序:
中断程序的命名参照这张图:
具体的流程就是这样,我们在中断函数中实现一下我们的代码:
void Timer0_Rountine() interrupt 1
{
static int count = 0;//只有函数第一次被调用的时候才会初始化
count++;
TL0 = 64535%256;
TH0 = 64535/256;//重新设置时间,否则默认设置为0
if(count >= 1000)//到达1s
{
count = 0;//归0
P2_0 = ~P2_0;//开关灯
}
}
这样我们的代码就完全写完了:
#include <REGX52.H>
void Timer0_Init()
{
TMOD &= 0xf0;
TMOD |= 0xf1;
TL0 = 64535%256;
TH0 = 64535/256;
TF0 = 0;
TR0 = 1;
PT0 = 0;
ET0 = 1;
EA = 1;
}
void main()
{
Timer0_Init();
while(1)
{
//主程序
}
}
void Timer0_Rountine() interrupt 1
{
static int count = 0;//只有函数第一次被调用的时候才会初始化
count++;
TL0 = 64535%256;
TH0 = 64535/256;//重新设置时间,否则默认设置为0
if(count >= 1000)//到达1s
{
count = 0;//归0
P2_0 = ~P2_0;//开关灯
}
}
这里我们并没有需要使用CPU执行主程序,所以while循环中的程序就不写了,还有一个要注意的点,就是头文件#include <REGX52.H>中才有我们需要配置的寄存器的地址,所以要包含这个头文件我们才可以使用这个定时器并配置程序。
到这里我们的定时器实现LED闪烁就算结束了。
3.使用软件生成定时器初始化程序
我们的一个软件中可以生成定时器初始化程序:
我们设置好之后就可以得到一个定时器的较为准确的一个代码:
void Timer0Init(void) //1毫秒@11.0592MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xCD; //设置定时初值
TH0 = 0xD4; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
我们的单片机上没有AUXR这个配置的选项,不需要配置它,然后就是这里还有一些代码我们需啊哟自己配置,比如IE使能和优先级选择。我们全部修改完成之后就是这样:
void Timer0Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xCD; //设置定时初值
TH0 = 0xD4; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
PT0 = 0; //优先级配置
ET0 = 1; //使能配置
EA = 1; //使能配置
}
然后我们就可以快乐地使用定时器了!