一、用汇编语言设计/INT1中断服务程序
二、用C51语言设计/INT1中断服务程序(与4.3.6节视频相同)
三、中断优先级控制
发生中断时程序执行的流程示意
图1是发生中断时程序执行的流程示意。假设在执行主循环LOOP内的MOV B,#3指令过程中,发生了n号中断。n是中断号,/INT0是0号中断,T0是1号中断,/INT1是2号中断,T1是3号中断,Serial是4号中断。

图1 发生中断时程序执行的流程示意
首先,系统会自动在MOV B,#3指令的下方插入一条虚拟指令LCALL 8n+3。这条指令不是程序员编写的,实际中并不存在,不占用程序存储器空间。
执行完MOV B,#3指令,接着执行LCALL 8n+3这条子程序调用指令。执行子程序调用指令时,系统自动将PC(断点的地址,即ADD A,B这条指令的首地址)送入堆栈保护起来。然后,将8n+3(中断入口地址)赋值给PC,程序跳转至8n+3。
因为各中断入口地址之间才差了8个字节,比较小,通常容不下中断服务程序,所以常将中断服务要操作的具体指令放置在另外编写的子程序中。编程时在中断入口地址处放置一条长跳转指令LJMP INT_SERVICE,程序再由8n+3跳转至中断服务子程序入口处,即标号INT_SERVICE的地址。
然后,执行中断服务子程序INT_SERVICE。在中断服务子程序内,首先要手动编程保护现场,接着执行具体的中断任务,然后手动编程恢复现场,最后执行中断服务子程序返回指令RETI。
在执行RETI指令时,系统会自动将前面发生中断,执行LCALL 8n+3指令时放入堆栈保存的断点地址弹出至PC,PC的值就等于ADD A,B这条指令的首地址,程序继续执行该指令(ADD A,B)。
整个中断处理过程至此结束。
关于中断处理的几个问题
(1)什么是断点地址?执行MOV B,#3指令的过程中来了中断,断点地址不是该指令的地址吗?为什么是下一条指令(ADD A, B)的首地址?
发生中断时,必须把当前正在执行的指令(MOV B,#3)执行完毕,才能够响应中断,不能说该指令(MOV B,#3)执行到一半就走了。断点就是待会中断服务子程序执行完毕回来要继续执行的指令(ADD A, B)的首地址。
(2)为什么要保护断点?该操作由谁完成?
保护断点,就是要让中断服务子程序执行完毕,从哪里来,回哪里去。
该操作由系统自动完成,不用我们编程,断点地址保存在堆栈里面(片内RAM区)。执行LCALL addr16和ACALL addr11指令,系统会自动保护断点。
(3)为什么要保护现场?该操作由谁完成?
如果有一些关键的特殊功能寄存器、存储单元我们在主函数里面使用了,在中断服务子程序里面又使用了,那么要将它们在堆栈里面保护起来。否则执行完中断服务子程序,回去执行主程序时,会发生错误。
用汇编语言编程时,该操作由程序员编程实现;用C51编程时,编译器会自动帮我们保护现场(包括后面的恢复现场)。
例如,主循环中我们希望程序执行A加B的操作,结果为5。如果将图1中保护现场(PUSH ACC和PUSH B)和恢复现场(POP B和POP ACC)的4条语句删除掉,若如图1所示发生中断,又没有保护现场,执行中断后回去执行A加B的操作,结果是25(为什么?),会发生错误。
(4)保护现场和恢复现场的指令,累加器A能否写成ACC?
不能。写成A是寄存器寻址,写成ACC是直接寻址,堆栈操作必须采用直接寻址。详细解释请参考本平台3.2节“疑难指令”第一点。
(5)中断返回指令RETI能否用RET替代?
不能。详细解释请参考本平台3.2节“疑难指令”第八点。