中断概念
中断是指 CPU 在处理某一事件A时,发生了另一事件B,请求 CPU 迅速去处理(中断发生);CPU 暂时停止当前的工作(中断响应),转去处理事件B(中断服务);待 CPU 将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。
中断优先级
单片机在执行程序的时候,可能在同一时刻有多个中断,那么就要通过中断优先级来判断哪一个中断先执行。
下面是单片机默认中断源的优先级:
中断源 | 默认中段级别 | 序号(C语言用) | 入口地址(汇编语言用) |
---|---|---|---|
INT0-外部中断0 | 最高 | 0 | 0003H |
T0-定时器/计数器0中断 | 第2 | 1 | 000BH |
INT1-外部中断1 | 第3 | 2 | 0013H |
T1-定时器/计数器1中断 | 第4 | 3 | 001BH |
TI/RI-串行口中断 | 第5 | 4 | 0023H |
T2-定时器/计数器2中断 | 最低 | 5 | 002BH |
中断优先级寄存器 IP
在51单片机中,高优先级中断能够打断低优先级中断从而形成中断嵌套,多个中断同时产生时,按照默认中断优先级响应中断,当然也可以自己设定中断优先级。
下面是优先级寄存器IP:
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | — | — | — | PS | PT1 | PX1 | PT0 | PX0 |
位地址 | — | — | — | BCH | BBH | BAH | B9H | B8H |
PS = 1,串行口中断定义为高优先级中断。
PS = 0,串行口中断定义为低优先级中断。
PT1 = 1,定时器/计数器1中断定义为高优先级中断。
PT1 = 0,定时器/计数器1中断定义为低优先级中断。
PX1 = 1,外部中断1定义为高优先级中断。
PX1 = 0,外部中断1定义为低优先级中断。
PT0 = 1,定时器/计数器0中断定义为高优先级中断。
PT0 = 0,定时器/计数器0中断定义为低优先级中断。
PX0 = 1,外部中断0定义为高优先级中断。
PX0 = 0,外部中断0定义为低优先级中断。
中断允许寄存器 IE
中断允许寄存器用来设定各个中断源的打开和关闭。
想使用那个中断,应先修改此寄存器,让改中断处于打开状态。
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | EA | — | ET2 | ES | ET1 | EX1 | ET0 | RX0 |
位地址 | AFH | — | ADH | ACH | ABH | AAH | A9H | A8H |
EA = 1,打开全局中断控制,在此条件下,由各个中断控制位确定相应中断的打开或关闭。
EA = 0,关闭全部中断。
在使用中断前,应先设置好IE寄存器。
ET2 = 1,打开 T2 中断。
ET2 = 0,关闭 T2 中断。
ES = 1,打开串行口中断。
ES = 0,关闭串行口中断。
ET1 = 1,打开 T1 中断。
ET1 = 1,关闭 T1 中断。
EX1 = 1,打开外部中断1中断。
EX1 = 0,关闭外部中断1中断。
ETO = 1,打开 T0 中断。
ETO = 0,关闭 T0 中断。
EX0 = 1,打开外部中断0中断。
EX0 = 0,关闭外部中断0中断。
单片机定时器中断
51单片机内部共有两个16位可编程的定时器/计数器,即定时器 T0 和定时器 T1。52单片机内部多一个 T2 定时器/计数器。它们既有定时功能又有计数功能,通过设置与它们相关的特殊功能寄存器可以选择启用定时功能或计数功能。
需要注意的是,这个定时器系统是单片机内部一个独立的硬件部分,它与CPU和晶振通过内部某些控制线连接并相互作用,CPU一旦设置开启定时功能后,定时器便在晶振的作用下自动开始计时,当定时器的计数器计满后,会产生中断,即通知CPU该如何处理。
定时器/计数器由高8位和低8位两个寄存器组成(共16位),TH0和TL0,
定时器工作方式寄存器 TMOD
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | GATE | C/T | M1 | M0 | GATEC | C/T | M1 | M0 |
GATE = 0,定时器/计数器启动与停止仅受TCON寄存器中 TRX(X=0,1)来控制。
GATE = 1,定时器/计数器启动与停止由TCON寄存器中 TRX(X=0,1)和外部中断引脚(INTO或INT1)上的电平状态来共同控制。
C/T = 1,为计数器模式。
C/T = 0,为定时器模式。
每个定时器/计数器都有4种工作方式,它们由M1、M0设定:
M1 | M0 | 工作方式 |
---|---|---|
0 | 0 | 方式0,为13位定时器/计数器 |
0 | 1 | 方式1,为16位定时器/计数器 |
1 | 0 | 方式2,8位初值自动重装的8位定时器/计数器 |
1 | 1 | 方式3,仅适用于T0,分成两个8位计数器,T1停止计数 |
通过设置此寄存器,从而控制定时器/计数器的工作方式。
定时器控制寄存器 TCOD
位序号 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|---|
位符号 | TF1 | TR1 | TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
位地址 | 8FH | 8EH | 8DH | 8CH | 8BH | 8AH | 89H | 88H |
当定时器1溢出时,硬件会使TF1置1,并且申请中断。当进入中断服务程序之后,硬件自动置0。
需要注意的是,如果使用定时器的中断,那么该位完全不用人为去操作,但是如果使用软件查询方式的话,当查查询到该位置1后,就需要用软件清0。
软件清0关闭定时器1。
当GATE = 1,且 INT1 位高电平时,TR1置1启动定时器1;
当GATE = 0,TR1置1启动定时器1。
当IT1 = 0时,为电平触发方式,若INT1脚为低电平,则置1,否则IE1清0。
IT1 = 0,为电平触发方式,引脚 INT1 上低电平有效。
IT1 = 1,为跳变沿触发方式,引脚 INT1 上的电平从高到低的负跳变有效。
定时器的工作方式
每个定时器都有四种工作模式,通过TMOD寄存器中的M1,M0控制,下面主要从定时器0的工作方式1(16位定时器)讲起。
方式1的计数器是16位的,那么TL0寄存器就作为低8位,TH0寄存器作为高8位,组成了这个16位计数器。
由图可知,当GATE=0,TR0=1时,TL0会在机器周期的作用下开始加1计数,TL0计满时TH0便开始计数,直至计满溢出,TF0置1,接着就向CPU申请中断,CPU会做出中断处理。这种情况下,只要TR0为1,那么计数就不会停止。这就是整个工作方式,其他8位、13位的工作方式也是大同小异的。
初始值计算
定时器启动时,会在原先数值上开始加1计数,如果开始没有给初始值,那么就会从0开始加1。
现在加入晶振为12MHZ,12个时钟周期为一个机器周期,那么一个机器周期就是1us,计满TH0和TL0就需要216-1个数,再有一个脉冲计数器溢出。那么溢出一次共需65536us,大约为65.5ms。所以可以给TH0和TL0初始值来控制溢出的时间,便可以精准控制时间了。
下面举一个计算定时1ms的例子:
1ms那就需要记1000个数,TH0和TL0中应装入的总数为65536-1000=64536,首先把64536对256取模:64536/256=252装入TH0中,然后把64536对256求余:64536%256=24装入TL0中。
总的来说,当定时器方式1时,设机器周期为Tcy,定时器产生一尺中断时间为t,那么需要计算的个数N=t/Tcy,那么装入TH0和TL0中的个数分别为:THX=(65536-N)/256, TLX=(65536-N)%256
机器周期Tcy等于系统的晶振周期,像上面例子12MHZ的晶振,机器周期为1us。再例如晶振频率为11.0592MHZ,那么机器周期为12x(1/11059200)≈1.09us。还是计算1ms,那么N=1000/1.09≈917。
中断服务程序
C51的中断函数格式为:
void 函数名() interrupt 中断号 using 工作组
{
中断服务程序内容
}
中断函数不能返回任何值,后面的函数名可以随便取;中断函数不带任何参数,中断号是指单片机中几个中断的序号,上面提到过。这个序号是编译识别不同中断的唯一符号,务必要写对。后面的“using 工作组”是指这个中断函数使用单片机内存中4组工作寄存器中的那一组,程序再编译时会自动分配工作组,所以我们可以省略不写。
程序具体实现
在我们学完以上内容之后,就可以正式开始使用定时器中断来写一些程序了。
下面将写一个让LED以1秒亮灭的代码。
初始化定时器
在写单片机的定时器时,要在使用定时器之前进行定时器的初始化,通常定时器初始化步骤 如下:
1. 对TMOD赋值,以确定T0和T1的工作方式。
2. 计算初始值,并分别给对应的寄存器赋值(TH0/1,TL0/1)。
3. 中断方式时,对IE赋值,开放中断。
4. 让TR0或TR1置位,启动定时器/计数器定时或计数。
主程序
下面为定时器0方式1的初始实例:
#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int
sbit LED=P1^0;
uchar num;
void main()
{
TMOD=0x01; //设置定时器0为工作方式1(M1、M0为0、1)
TH0=(65536-1000)/256; //装入初始值定时1ms
TL0=(65536-1000)%256;
EA=1; //开总中断
ET0=1; //开定时器0中断
TR0=1; //启动定时器0
while(1); //程序停在此处
}
void T0_time() interrupt 1
{
TH0=(65536-1000)/256; //装入初始值
TL0=(65536-1000)%256;
num++; //每中断一次num加1
if(num==1000) //判断如果num等于1000,相当于过了1s
{
num=0; //将num清0
LED=~LED; //让LED状态取反
}
}
分析
程序开始执行,进入主程序后,首先进行定时器中断的初始化,代码中的11~16行都为初始化操作。初始化结束之后程序就停在了while(1); 处,那么就有人会问,程序既然停在了此处那为什么LED会闪烁呢?其实定时器是不受主程序影响的,一旦开启定时器,定时器便会开始计数,当计数溢出时,自动进入中断服务程序执行相应代码。
在中断服务程序中会发现再次装入了初始值,那就是因为每次溢出后TH0和TL0会从0开始计数,再次装入初始值之后,保证下次中断同样是1ms。中断程序中,每次中断都会对num进行判断,当num等于1000时,也就是过了1s,LED就会亮灭。
注意:中断服务中不要写过多的处理语句,因为处理语句过多,中断服务程序会消耗较多的时间,当下次中断到来的时候,就会丢失这次中断,造成程序的混乱。一般的原则是:能在主程序中完成的功能不在中断函数中写,若非要在中断程序中实现功能,那么一定要高效、简洁。
所以上面的实例就可以修改成:
//主程序中改为:
while(1)
{
if(num==1000)
{
num=0;
LED=~LED;
}
}
//中断函数中改为:
void T0_time() interrupt 1
{
TH0=(65536-1000)/256; //装入初始值
TL0=(65536-1000)%256;
num++; //每中断一次num加1
}
总结
这篇博客主要讲了中断的概念,定时器怎么使用。
主要介绍的是定时器0方式1的具体实现方法,此外还有其他定时器的中断这里就不一一阐述了,具体用法与此例子大同小异。
中断是一个很基础的东西,会在许多地方都能用到,最主要的就是掌握最主要的那几个控制寄存器,然后学会根据想要中断的时间计算初始值。
本篇算是比较入门的讲解,本人水平有限,文章中可能会有一些错误,如果您发现那里有错误,也请下方评论指出。