中断的嵌套与优先级处理:设想一下,我们正在“斗地主”,电话铃响了,同时又有人按了门铃,你该先做那样呢?如果你正是在等一个很重要的电话,你一般不会去理会门铃的,而反之,你正在等一个重要的客人,则可能就先去开门了。如果不是这两者(即不等电话,也不是等人上门),你可能会按你通常的习惯去处理。总之这里存在一个优先级的问题,单片机中也是如此,也有优先级的问题。优先级的问题不仅仅发生在两个中断同时产生的情况,也发生在一个中断已产生,又有一个中断产生的情况,比如你正接电话,有人按门铃的情况,或你正开门与人交谈,又有电话响了情况。考虑一下?CPU也一样,所以MCS-51对此设置了中断优先级别控制寄存器IP,我们只要按设计要求设定每一个中断次序即可。 中断的响应过程:我们正在“斗地主”,电话铃响了,在接电话之前我们必须先记住现在的出牌情况,然后去接电话(因为接完了,我们还要继续斗呢):电话铃响我们要到放电话的地方去,门铃响我们要到门那边去,也就是不同的中断,我们要在不同的地点处理,而这个地点通常事先是固定的。计算机中也是采用的这种方法,五个中断源,每个中断产生后都到一个固定的地方去找处理这个中断的程序(这就是在学习指令时编写程序时都要这样写 当然在去之前首先要保存产生中断时程序正执行的地址”就是打牌时去接电话后该谁出牌“,以便处理完中断后回到原来的地方继续往下执行程序。而要中断返回只要一句指令”RETI“,具体地说,中断响应可以分为以下几个方面: 对于单片机的CPU 来说,其每一个机器周期都顺序地检查其自身的5个中断源,如有中断发生则激活相应的中断请求,但要让CPU响应中断还要做如下工作: 1。现行中断是否为最高级别的中断或CPU正在处理同级别的中断; 2。现行的机器周期不是所执行指令的最后一个机器周期; 3。如果CPU正在执行RETI(中断返回指令)、或执行访问中断控制寄存器IE、IP的指令,则CPU至少需要再执行一条指令,才能响应新的中断,这是规定。 CPU响应中断后将自动做如下工作: 3、执行中断处理程序(如中断处理程序较长,大于七个语句即大于7条指令则要在相应的中断入口处用跳转指令如LJMP,执行完中断程序后,还要有”中断返回“就是从中断处返回到主程序,继续执行,指令为RETI语句。 究竟单片机是怎么样找到中断程序所在位置,又怎么返回的呢?我们稍后再谈。 与中断有关的特殊功能寄存器、中断入口、包括5个中断请求源,4个用于中断控制的寄存器IE、IP、TCON和SCON来控制中断的开、关和各种中断源的优先级确定。
(1)外部中断请求源:即外中断INT0和INT1,经由外部引脚引入的,在单片机上有两个引脚,名称为INT0、INT1,分别对应P3.2、P3.3这两个引脚。在内部的特殊功能寄存器TCON中的低四位是与外中断有关的,低4位
的4个中断标志的使用为MCS-51系列规定,方法如下: IT0:为INT0外部引脚触发方式控制位,可由软件进行置位和清0,IT0=0时INT0为低电平触发方式,IT0=1,INT0为边沿触发方式,指INT0引脚上的电平发生由高到低的负跳变时CPU将产生中断。这两种方式的差异将在以后再谈。 IE0:外部INT0中断请求标志位。当有外部的中断请求时即INT0引脚电平发生突变时,这位就会置1(这由硬件来完成),在CPU响应中断后,由硬件将IE0清0。 IT1、IE1的用途和IT0、IE0相同。 (2)内部中断请求源 TF0:定时器计数器T0的溢出中断标记,当T0计数高位产生溢出时,由硬件置位TF0。当CPU响应中断后,再由硬件将TF0清0。 TF1:与TF0类似。 TI、RI:串行口发送、接收中断,特殊功能寄存器为SCON,在串口中再讲解。 2、中断允许寄存器IE 在MCS-51中断系统中,中断的允许或禁止是由片内可进行位寻址的8位中断允许特殊功能寄存器IE来控制的。见下表
其中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可如下表:
即8CH,如:用一句汇编语句MOV IE,#8CH;即可将设定完成。 当然,我们也可以用位操作指令 SETB EA SETB EX1 来实现它。 3、五个中断源的默认优先级与中断服务入口地址(编程时不对特殊功能寄存器IP作任何设定时) 外中断0:0003H; 定时器0:000BH 外中断1:0013H 定时器1:001BH 串口 :0023H 它们的默认优先级由高外中断0到低(串口)的方式排列。 写到这里,大家应当明白,为什么前面有一些程序一始我们这样写: ORG 0000H LJMP MAIN ORG 0030H MAIN: 。 。 。 这样写的目的,就是为了让出中断源所占用的入口地址。当然,在程序中没用中断时,直接从0000H开始写程序,在原理上并没有错,但在实际工作中最好不这样做。 优先级:单片机初默认优先级外还可以在你需要时人工设置高、低优先级,即可以由程序员设定那些中断是高优先级、哪些中断是低优先级,由于只有两级,必有一些中断处于同一级别,处于同一级别的,就由单片机默认的优先级确定。 我们可以用指令对优先级进行设置。看表2 中断优先级中由中断优先级寄存器IP来设置的,IP中某位设为1,相应的中断就是高优先级,否则就是低优先级。
PS---串行口中断优先级控制 例:设有如下要求,将T0、外中断1设为高优先级,其它为低优先级,求IP的值。 IP的首3位没用,可任意取值,设为000,后面根据要求写就可以了
因此,最终,IP的值就是06H,(06H是如何得到的呢,这就有编码方式的问题,在二进制中,每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响应中断时,首先把当前指令的下一条指令(就是中断返回后将要执行的指令)的地址送入堆栈,然后根据中断标记,将相应的中断入口地址送入PC,PC是程序指针,CPU取指令就根据PC中的值,PC中是什么值,就会到什么地方去取指令,所以程序就会转到中断入口处继续执行。这些工作都是由硬件自己来完成的,不必我们去考虑。这里还有个问题,大家是否注意到,每个中断向量地址只间隔了8个单元,如0003-000B,在如此少的空间中如何完成中断程序呢?很简单,你在中断处安排一个LJMP指令,不就可以把中断程序跳转到任何地方了吗?这在上面以讲到过。 下面给出完整的主程序供参考: ORG 0000H LJMP MAIN;跳转到主程序MAIN ORG 0003H LJMP INT0 ;转外中断0处理子程序 ORG 000BH RETI ;没有用定时器0中断,在此放一条RETI返回指令,万一 “不小心“产生了中断,则马上返回主程序。 ORG 0013H AJMP INT1;转外中断1处理子程序 。 。 。 ORG 0030H MAIN: 。 。 INT0: ;外中断0处理子程序段 PUSH ACC;保存累加器A中的数据 。。。。 。。。 POP ACC;还原ACC中的数据 POP PSW RETI;外中断0处理子程序段,别的中断子程序同样添加即可。 。 。 END;END为程序结束语句,不可缺少。 从上例中可以发现中断程序完成后,有一条RETI返回指令,执行这条指令后,CPU将会把堆栈中保存着的地址取出,送回程序计数器PC,那么程序就会从主程序的中断处继续往下执行了。注意:CPU所做的保护工作是很有限的,只保护了一个地址,而其它的所有东西都不保护,所以如果你在主程序中用到了如A、PSW等,在中断程序中又要用它们的值,还要保证回到主程序后这里面的数据还是没执行中断以前的数据,就得自己保护起来,如上例INT0:子程序段。 利用定时器实现灯的闪烁 在学单片机时我们第一个例子就是灯的闪烁,那是用延时程序做的,现在回想起来,这样做不很恰当,为什么呢?延时的时候CPU就不能再干其它的事了,这在实时性要求高的设备中是绝对不允许的,这时就必须用定时器来达到延时的目的,我们可以用定时器来实现LED的闪烁功能,让网友体会一下定时中断的作用。 例1:查询方式
可以下载本站提供的全中文的编译软件MEDWIN,进入后单击文件菜单----新建----如ABC.asm(注意一定将文件的类型ASM写上(汇编的),后保存即可 ,键入程序,编译后生成.hex文件(烧写文件烧写到实验板看到了什么?灯在闪烁了,这可是用定时器做的,不再是主程序的循环了。简单地分析一下程序,为什么用JBC呢?TF0是定时/计数器0的溢出标记位,当定时器产生溢出后,该位由0变1,所以查询该位就可知定时时间是否已到。该位为1后,要用软件将标记位清0,以便下一次定时是间到时该位由0变1,所以用了JBC指令,该指位在判1转移的同时,还将该位清0。以上程序是可以实现灯的闪烁了,可是主程序除了让灯闪烁外,还是不能做其他的事啊!不,不对,我们可以在LOOP:……和AJMP LOOP指令之间插入一些指令来做其他的事情,只要保证执行这些指令的时间少于定时时间就行了。那我们在用软件延时程序的时候不是也可以用一些指令来替代DJNZ吗?是的,但是那就要求你精确计算所用指令的时间,然后再减去相应的DJNZ循环次数,很不方便,而现在只要求所用指令的时间少于定时时间就行,显然要求低了。当然,这样的方法还是不好,所以我们常用以下的方法来实现。 程序2:用中断实现
上面的例子中,定时时间一到,TF0由0变1,就会引发中断,CPU将自动转至000B处寻找程序并执行,由于留给定时器中断的空间只有8个字节,显然不足以写下所有有中断处理程序,所以在000B处安排一条跳转指令,转到实际处理中断的程序处,这样,中断程序可以写在任意地方,也可以写任意长度了。进入定时中断后,首先要保存当前的一些状态,程序中只演示了保存存ACC和PSW,实际工作中应该根据需要将可能会改变的单元的值都推入堆栈进行保护(本程序中实际不需保存护任何值,这里只作个演示)。 上面的两个程序运行后,我们发现灯的闪烁非常快,根本分辨不出来,只是视觉上感到灯有些晃动而已,为什么呢?我们可以计算一下,定时器中预置的数是5536,所以每计60000个脉冲就是定时时间到,这60000个脉冲的时间是多少呢?我们的晶振是12M,所以就是60000微秒,即60毫秒,因此速度是非常快的。如果我想实现一个1S的定时,该怎么办呢?在该晶振濒率下,最长的定时也就是65.536个毫秒.咋办?很简单,只要对中断的次数进行计数即可,1秒18次就够了,请看下面给出一个例子。
上例中先用定时/计数器0做一个50毫秒的定时器,定时是间到了以后并不是立即取反P10,而是将计数器中的值加1,如果计数器计到了20,就取反P1.0,并清掉计数器中的值重新计数,否则直接返回,这样,就变成了20次定时中断才取反一次P10,因此定时时间就延长了成了20*50即1000毫秒了-----1秒了吗。 这个思路在工程中是非常有用的,有的时候我们需要若干个定时器,可51中总共才有2个,怎么办呢?其实,只要这几个定时的时间有一定的公约数,我们就可以用软件计数加以实现,如我要实现P10口所接灯按1S每次,而P11口所接灯按2S每次闪烁,怎么实现呢?对了我们用两个计数器,一个在它计到20时,取反P10,并清零,就如上面所示,另一个计到40取反P11,然后清0,不就行了吗?这部份的程序如下
将上例写入实验板看看结果如何,定时器用处很多,望网友认真实践。 |