1.               单片机自学(六):单片机的中断系统


  2. 有关中断的概念

     

       什么是中断,在上一课中TCON的控制字低4位,我们没有讲,我们现在打个比方:你休息在家和几个朋友在打牌就“斗地主”吧,这时-----有人按了门铃了,电话铃响了,你烧的水开了….等等诸如此类的事件发生了,你咋办??哎!麻烦呐,开门、接电话、----,而打牌就暂时“中断”一下啦,而引起这次“中断”的事件我们把可以称之为中断源,单片机中更有一些可以引起中断的事件了如:上一课讲到的定时器/计数器、单片机外接的设备向单片机请求如打印、显示等都能引起“中断”的发生,在MCS-51中一共有5个:两个外部中断、两个计数/定时器中断、一个串行口中断。

        中断的嵌套与优先级处理:设想一下,我们正在“斗地主”,电话铃响了,同时又有人按了门铃,你该先做那样呢?如果你正是在等一个很重要的电话,你一般不会去理会门铃的,而反之,你正在等一个重要的客人,则可能就先去开门了。如果不是这两者(即不等电话,也不是等人上门),你可能会按你通常的习惯去处理。总之这里存在一个优先级的问题,单片机中也是如此,也有优先级的问题。优先级的问题不仅仅发生在两个中断同时产生的情况,也发生在一个中断已产生,又有一个中断产生的情况,比如你正接电话,有人按门铃的情况,或你正开门与人交谈,又有电话响了情况。考虑一下?CPU也一样,所以MCS-51对此设置了中断优先级别控制寄存器IP,我们只要按设计要求设定每一个中断次序即可。

       中断的响应过程:我们正在“斗地主”,电话铃响了,在接电话之前我们必须先记住现在的出牌情况,然后去接电话(因为接完了,我们还要继续斗呢):电话铃响我们要到放电话的地方去,门铃响我们要到门那边去,也就是不同的中断,我们要在不同的地点处理,而这个地点通常事先是固定的。计算机中也是采用的这种方法,五个中断源,每个中断产生后都到一个固定的地方去找处理这个中断的程序(这就是在学习指令时编写程序时都要这样写
    “ORG 0000H,
      AJMP MAIN;
      ORG 0030H;00-30H之间就是中断的固定入口地址
    MAIN:。。。
         。。。

        当然在去之前首先要保存产生中断时程序正执行的地址”就是打牌时去接电话后该谁出牌“,以便处理完中断后回到原来的地方继续往下执行程序。而要中断返回只要一句指令”RETI“,具体地说,中断响应可以分为以下几个方面:

     对于单片机的CPU 来说,其每一个机器周期都顺序地检查其自身的5个中断源,如有中断发生则激活相应的中断请求,但要让CPU响应中断还要做如下工作:

    1。现行中断是否为最高级别的中断或CPU正在处理同级别的中断;

    2。现行的机器周期不是所执行指令的最后一个机器周期;

    3。如果CPU正在执行RETI(中断返回指令)、或执行访问中断控制寄存器IE、IP的指令,则CPU至少需要再执行一条指令,才能响应新的中断,这是规定。

    CPU响应中断后将自动做如下工作:
     
    1、保护断点,即保存下一要执行的指令的地址,就是把这个地址送入堆栈。
     
    2、寻找中断入口,根据5个不同的中断源所产生的中断,查找5个不同的入口地址。在这5个入口地址处存放有中断处理程序(这是程序编写时放在那儿的,如果没把中断程序放在那儿,就错了,中断程序就不能被执行到,即00H-30H之间)。

    3、执行中断处理程序(如中断处理程序较长,大于七个语句即大于7条指令则要在相应的中断入口处用跳转指令如LJMP,执行完中断程序后,还要有”中断返回“就是从中断处返回到主程序,继续执行,指令为RETI语句。

    究竟单片机是怎么样找到中断程序所在位置,又怎么返回的呢?我们稍后再谈。

  3. MCS-51中断系统的结构:

     

     与中断有关的特殊功能寄存器、中断入口、包括5个中断请求源,4个用于中断控制的寄存器IEIPTCONSCON来控制中断的开、关和各种中断源的优先级确定。

     

  1. 中断请求源:

    8031提供5个中断请求源,两个由/INT0、/INT1(P3.2和P3.3引脚)输入的外部中断请求,两个由片内的定时器/计数器溢出中断请求TF0、TF1,第五个为片内的串行口中断请求TI、RI所组成。

1)外部中断请求源:即外中断INT0和INT1,经由外部引脚引入的,在单片机上有两个引脚,名称为INT0INT1,分别对应P3.2P3.3这两个引脚。在内部的特殊功能寄存器TCON中的低四位是与外中断有关的,低4位 的4个中断标志的使用为MCS-51系列规定,方法如下:

IT0INT0外部引脚触发方式控制位,可由软件进行置位和清0,IT0=0INT0为低电平触发方式,IT0=1INT0为边沿触发方式,指INT0引脚上的电平发生由高到低的负跳变时CPU将产生中断。这两种方式的差异将在以后再谈。

IE0:外部INT0中断请求标志位。当有外部的中断请求时即INT0引脚电平发生突变时,这位就会置1(这由硬件来完成),在CPU响应中断后,由硬件将IE00

IT1IE1的用途和IT0IE0相同。

2)内部中断请求源

TF0:定时器计数器T0的溢出中断标记,当T0计数高位产生溢出时,由硬件置位TF0。当CPU响应中断后,再由硬件将TF00

TF1:与TF0类似。

TIRI:串行口发送、接收中断,特殊功能寄存器为SCON,在串口中再讲解。

2、中断允许寄存器IE

MCS51中断系统中,中断的允许或禁止是由片内可进行位寻址的8位中断允许特殊功能寄存器IE来控制的。见下表

EA

---

----

ES

ET1

EX1

ET0

EX0

其中EA是总开关,如果它等于0,则所有中断都不允许。

ES-串行口中断允许;ES=1,允许串行口中断,ES=0,禁止中断;

ET1-定时器1中断允许;ET1=1,允许中断

EX1-外中断1中断允许;EX1=1,允许中断

ET0-定时器0中断允许;ET0=1,允许中断

EX0-外中断0中断允许;EX0=1,允许中断

如:我们要设置允许外中断1,定时器1中断允许,其它不允许,则IE可如下表:

EA

---

----

ES

ET1

EX1

ET0

EX0

1

0

0

0

1

1

0

0

8CH,如:用一句汇编语句MOV IE,#8CH;即可将设定完成。

当然,我们也可以用位操作指令

SETB EA
SETB ET1

SETB EX1

来实现它。

3、五个中断源的默认优先级与中断服务入口地址(编程时不对特殊功能寄存器IP作任何设定时)

外中断00003H;

定时器0000BH

外中断10013H

定时器1001BH

串口 0023H

它们的默认优先级由高外中断0到低(串口)的方式排列。

写到这里,大家应当明白,为什么前面有一些程序一始我们这样写:

ORG 0000H

LJMP  MAIN

ORG 0030H

MAIN

这样写的目的,就是为了让出中断源所占用的入口地址。当然,在程序中没用中断时,直接从0000H开始写程序,在原理上并没有错,但在实际工作中最好不这样做。

优先级:单片机初默认优先级外还可以在你需要时人工设置高、低优先级,即可以由程序员设定那些中断是高优先级、哪些中断是低优先级,由于只有两级,必有一些中断处于同一级别,处于同一级别的,就由单片机默认的优先级确定。

我们可以用指令对优先级进行设置。看表2

中断优先级中由中断优先级寄存器IP来设置的,IP中某位设为1,相应的中断就是高优先级,否则就是低优先级。

 

——

——

——

PS

PT1

PX1

PT0

PX0

 PS---串行口中断优先级控制
  PT1---定时器1中断优先级控制
  PX1---外中断1优先级控制
  PT0---定时0中断优先级控制
  PX0---外中断0优先级控制

例:设有如下要求,将T0、外中断1设为高优先级,其它为低优先级,求IP的值。

IP的首3位没用,可任意取值,设为000,后面根据要求写就可以了

--

---

----

PS

PT1

PX1

PT0

PX0

0

0

0

0

0

1

1

0

因此,最终,IP的值就是06H06H是如何得到的呢,这就有编码方式的问题,在二进制中,每4个二进制数为一组,8421方式,如:有二进制1010,第一位是1则它的值就是8,第二位是0,则她的值就是0,第三位是1,则就是2,第四位是0,则是0;网友可以发现和8421是对应的,则二进制数1010对应的16进制数为8+2=10,而在16进制数中A就表示10,所以1010转换为16进制为OAH;网友可读数制与编码)

例:在上例中,如果5个中断请求同时发生,求中断响应的次序。

响应次序为:定时器0->外中断1->外中断0->实时器1->串行中断。

2、中断响应过程

CPU响应中断时,首先把当前指令的下一条指令(就是中断返回后将要执行的指令)的地址送入堆栈,然后根据中断标记,将相应的中断入口地址送入PCPC是程序指针,CPU取指令就根据PC中的值,PC中是什么值,就会到什么地方去取指令,所以程序就会转到中断入口处继续执行。这些工作都是由硬件自己来完成的,不必我们去考虑。这里还有个问题,大家是否注意到,每个中断向量地址只间隔了8个单元,如0003000B,在如此少的空间中如何完成中断程序呢?很简单,你在中断处安排一个LJMP指令,不就可以把中断程序跳转到任何地方了吗?这在上面以讲到过。

下面给出完整的主程序供参考:

ORG 0000H

LJMP  MAIN;跳转到主程序MAIN

ORG 0003H

LJMP INT0 转外中断0处理子程序

ORG 000BH

RETI 没有用定时器0中断,在此放一条RETI返回指令,万一 “不小心“产生了中断,则马上返回主程序。

ORG 0013H

AJMP INT1;转外中断1处理子程序

ORG 0030H

MAIN:
MOV SP,#70H;设置堆栈栈顶,因CPU上电复位后SP 初始地址为07H,落在了工作寄存器区,易造成程序不稳定,所以要养成习惯重置SP的地址。
。。。。

INT0:   ;外中断0处理子程序段

PUSH ACC;保存累加器A中的数据
PUSH PSW;保存程序状态字PSW中的各标志

。。。。

。。。

POP ACC;还原ACC中的数据

POP PSW

RETI;外中断0处理子程序段,别的中断子程序同样添加即可。

END;END为程序结束语句,不可缺少。

    从上例中可以发现中断程序完成后,有一条RETI返回指令,执行这条指令后,CPU将会把堆栈中保存着的地址取出,送回程序计数器PC,那么程序就会从主程序的中断处继续往下执行了。注意:CPU所做的保护工作是很有限的,只保护了一个地址,而其它的所有东西都不保护,所以如果你在主程序中用到了如APSW等,在中断程序中又要用它们的值,还要保证回到主程序后这里面的数据还是没执行中断以前的数据,就得自己保护起来,如上例INT0:子程序段。

利用定时器实现灯的闪烁

在学单片机时我们第一个例子就是灯的闪烁,那是用延时程序做的,现在回想起来,这样做不很恰当,为什么呢?延时的时候CPU就不能再干其它的事了,这在实时性要求高的设备中是绝对不允许的,这时就必须用定时器来达到延时的目的,我们可以用定时器来实现LED的闪烁功能,让网友体会一下定时中断的作用。

1:查询方式

ORG 0000H

AJMP MAIN

ORG 30H

MAIN:
MOV SP,#70H;设置堆栈栈顶,因CPU上电复位后SP 初始地址为07H,落在了工作寄存器区,易造成程序不稳定,所以要养成习惯重置SP的地址。

MOV P1,#0FFH ;关所有 LED

MOV TMOD,#00000001B ;定时/计数器0工作于方式1

MOV TH0,#15H

MOV TL0,#0A0H ;即数5536,初值的取得可参考“定时器一课”

SETB TR0 ;定时/计数器0开始运行

LOOP:    ;标号

JBC TF0,NEXT ;如果TF0等于1,则清TF0并转NEXT处

AJMP LOOP ;否则跳转到LOOP处运行

NEXT:

CPL P1.0;取反

MOV TH0,#15H

MOV TL0,#9FH;重置定时/计数器的初值

AJMP LOOP;返回、循环

 

END

 可以下载本站提供的全中文的编译软件MEDWIN,进入后单击文件菜单----新建----如ABC.asm(注意一定将文件的类型ASM写上(汇编的),后保存即可键入程序,编译后生成.hex文件(烧写文件烧写到实验板看到了什么?灯在闪烁了,这可是用定时器做的,不再是主程序的循环了。简单地分析一下程序,为什么用JBC呢?TF0是定时/计数器0的溢出标记位,当定时器产生溢出后,该位由01,所以查询该位就可知定时时间是否已到。该位为1后,要用软件将标记位清0,以便下一次定时是间到时该位由01,所以用了JBC指令,该指位在判1转移的同时,还将该位清0

    以上程序是可以实现灯的闪烁了,可是主程序除了让灯闪烁外,还是不能做其他的事啊!不,不对,我们可以在LOOP……AJMP LOOP指令之间插入一些指令来做其他的事情,只要保证执行这些指令的时间少于定时时间就行了。那我们在用软件延时程序的时候不是也可以用一些指令来替代DJNZ吗?是的,但是那就要求你精确计算所用指令的时间,然后再减去相应的DJNZ循环次数,很不方便,而现在只要求所用指令的时间少于定时时间就行,显然要求低了。当然,这样的方法还是不好,所以我们常用以下的方法来实现。

程序2:用中断实现

ORG 0000H

AJMP MAIN

ORG 000BH ;定时器0的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG 30H

MAIN:
MOV SP,#70H;设置堆栈栈顶,因CPU上电复位后SP 初始地址为07H,落在了工作寄存器区,易造成程序不稳定,所以要养成习惯重置SP的地址。

MOV P1,#0FFH ;关所 灯

MOV TMOD,#00000001B ;定时/计数器0工作于方式1

MOV TH0,#15H

MOV TL0,#0A0H ;即数5536

SETB EA ;开总中断允许

SETB ET0 ;开定时/计数器0允许

SETB TR0 ;定时/计数器0开始运行

LOOP:

AJMP LOOP ;循环等待定时中断的产生,真正工作时,这里可写任意程序

TIME0: ;定时器0的中断处理程序

PUSH ACC

PUSH PSW ;将PSW和ACC推入堆栈保护

CPL P1.0

MOV TH0,#15H

MOV TL0,#0A0H ;重置定时常数

POP PSW

POP ACC

RETI

END

 

上面的例子中,定时时间一到,TF001,就会引发中断,CPU将自动转至000B处寻找程序并执行,由于留给定时器中断的空间只有8个字节,显然不足以写下所有有中断处理程序,所以在000B处安排一条跳转指令,转到实际处理中断的程序处,这样,中断程序可以写在任意地方,也可以写任意长度了。进入定时中断后,首先要保存当前的一些状态,程序中只演示了保存存ACCPSW,实际工作中应该根据需要将可能会改变的单元的值都推入堆栈进行保护(本程序中实际不需保存护任何值,这里只作个演示)。

上面的两个程序运行后,我们发现灯的闪烁非常快,根本分辨不出来,只是视觉上感到灯有些晃动而已,为什么呢?我们可以计算一下,定时器中预置的数是5536,所以每计60000个脉冲就是定时时间到,这60000个脉冲的时间是多少呢?我们的晶振是12M,所以就是60000微秒,即60毫秒,因此速度是非常快的。如果我想实现一个1S的定时,该怎么办呢?在该晶振濒率下,最长的定时也就是65.536个毫秒.咋办?很简单,只要对中断的次数进行计数即可,1秒18次就够了,请看下面给出一个例子。

ORG 0000H

AJMP MAIN

ORG 000BH ;定时器0的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG  0030H

MAIN:
MOV SP,#70H;设置堆栈栈顶,因CPU上电复位后SP 初始地址为07H,落在了工作寄存器区,易造成程序不稳定,所以要养成习惯重置SP的地址。

MOV P1,#0FFH ;关所 灯

MOV 30H,#00H ;假设用30H单元作中断计次的存储,先计数器预清0

MOV TMOD,#00000001B ;定时/计数器0工作于方式1

MOV TH0,#3CH

MOV TL0,#0B0H ;即数15536

SETB EA ;开总中断允许

SETB ET0 ;开定时/计数器0允许

SETB TR0 ;定时/计数器0开始运行

LOOP: AJMP LOOP ;循环等待定时中断的产生,真正工作时,这里可写任意程序

TIME0: ;定时器0的中断处理程序

PUSH ACC

PUSH PSW ;将PSW和ACC推入堆栈保护

INC 30H  ;每中断一次计数器加1

MOV A,30H

CJNE A,#20,T_RET ;30H单元中的值到了20了吗?

CPL P1.0 ;到了,取反P10

MOV 30H,#0 ;清计数器

T_RET:  计数未到20

MOV TH0,#15H

MOV TL0,#9FH ;重置定时常数50Ms

POP PSW

POP ACC

RETI

END

    上例中先用定时/计数器0做一个50毫秒的定时器,定时是间到了以后并不是立即取反P10,而是将计数器中的值加1,如果计数器计到了20,就取反P1.0,并清掉计数器中的值重新计数,否则直接返回,这样,就变成了20次定时中断才取反一次P10,因此定时时间就延长了成了20*501000毫秒了-----1秒了吗。

这个思路在工程中是非常有用的,有的时候我们需要若干个定时器,可51中总共才有2个,怎么办呢?其实,只要这几个定时的时间有一定的公约数,我们就可以用软件计数加以实现,如我要实现P10口所接灯按1S每次,而P11口所接灯按2S每次闪烁,怎么实现呢?对了我们用两个计数器,一个在它计到20时,取反P10,并清零,就如上面所示,另一个计到40取反P11,然后清0,不就行了吗?这部份的程序如下

ORG 0000H

AJMP MAIN

ORG 000BH ;定时器0的中断向量地址

AJMP TIME0 ;跳转到真正的定时器程序处

ORG 0030H

MAIN:
MOV SP,#70H;设置堆栈栈顶,因CPU上电复位后SP 初始地址为07H,落在了工作寄存器区,易造成程序不稳定,所以要养成习惯重置SP的地址。

MOV P1,#0FFH ;关 LED

MOV 30H,#00H ;软件计数器预清0
MOV 31H,#00H;

MOV TMOD,#00000001B ;定时/计数器0工作于方式1

MOV TH0,#3CH

MOV TL0,#0B0H ;即数15536/ 50MS

SETB EA ;开总中断允许

SETB ET0 ;开定时/计数器0允许

SETB TR0 ;定时/计数器0开始运行

LOOP: AJMP LOOP ;真正工作时,这里可写任意程序

TIME0: ;定时器0的中断处理程序

PUSH ACC

PUSH PSW ;将PSW和ACC推入堆栈保护

INC 30H

INC 31H ;两个计数器都加1

MOV A,30H

CJNE A,#20,T_NEXT ;30H单元中的值到了20了吗?

 CPL P1.0 ;到了,取反P10

MOV 30H,#0 ;清软件计数器

T_NEXT:

MOV A,31H

CJNE A,#40,T_RET ;31h单元中的值到40了吗?

 

CPL P1.1

MOV 31H,#0 ;到了,取反P11,清计数器,返回

T_RET:

MOV TH0,#15H

MOV TL0,#9FH ;重置定时常数

POP PSW

POP ACC

RETI

END



   将上例写入实验板看看结果如何,定时器用处很多,望网友认真实践。




        返回单片机与电子制作首页                                           上一课      下一课