摘要:美国微芯公司研制的PIC系列单片机,其硬件结构和指令系统采用了与众不同的设计手法。在架构上和概念上对传统单片机进行了一些突破性的变革,但也给这类单片机的应用带来了一些特殊问题。本文针对PIC16F87X系列单片机中断的特点,及其在应用过程中应该注意的几个问题进行必要的说明。内容包括中断源、中断逻辑、中断相关的寄存器、中断的延时、中断的现场保护以及注意事项等。 四、 中断的处理
前在世界一些著名的单片机产品系列中,PIC16F87X系列单片机是芯片内部包含有外围设备模块数量最多的单片机品种之一。PIC16F874和PIC16F877单片机的芯片内部集成了15个外围设备模块;PIC16F873和PIC16F876单片机的芯片内部集成了12个外围设备模块。在最近推出的该系列的新型号中, PIC16F870单片机的芯片内部集成了10个外围设备模块;PIC16F871单片机的芯片内部集成了13个外围设备模块;PIC16F872单片机的芯片内部也集成了10个外围设备模块(比PIC16F870多了1个USART模块,少了1个SSP模块)。
这些外围设备模块在启用时以及在工作过程中,都或多或少地需要CPU参与控制、协调或交换数据等各种服务工作。由于CPU的运行速度非常高,而各个外围设备模块的工作速度却非常低,况且这些外围设备模块也不是频繁地要求CPU对其服务。因此,通常采取一种让众多外围设备模块共享1个CPU,并且能够及时得到CPU服务的调度方法——中断。
一、 PIC16F87X的中断源
PIC系列单片机是当今世界上很有影响力的精简指令集(RISC)微控制器,具有丰富的中断功能。其中功能强大的中、高挡型号的中断源有18种之多。在PIC单片机家族中,排位属于中上水平的PIC16F87X子系列单片机具备的中断源多达14种。其中,单片机的型号不同,中断源的种类、个数也不同,如表1所列。其不足之处是:中断矢量只有1个,并且各个中断源之间也没有优先级别之分,不具备非屏蔽中断。
表1 PIC16F87X单片机的中断源及其数量
中断源种类 | 中断源志位 | 中断源蔽位 | 873/ 876 | 874/ 877 | 870 | 871 | 872 |
外部触发中断INT | INTF | INTE | √ | √ | √ | √ | √ |
TMR0溢出中断 | T0IF | T0IE | √ | √ | √ | √ | √ |
RB端口电平变化中断 | RBIF | RBIE | √ | √ | √ | √ | √ |
TMR1溢出中断 | TMR1IF | TMR1IE | √ | √ | √ | √ | √ |
TMR2中断 | TMR2IF | TMR2IE | √ | √ | √ | √ | √ |
CCP1中断 | CCP1IF | CCP1IE | √ | √ | √ | √ | √ |
CCP2中断 | CCP2IF | CCP2IE | √ | √ | |||
SCI同步发送中断 | TXIF | TXIE | √ | √ | √ | √ | |
SCI同步接收中断 | RCIF | RCIE | √ | √ | √ | √ | |
SSP中断 | SSPIF | SSPIE | √ | √ | √ | ||
SSP I2C总线碰撞中断 | BCLIF | BCLIE | √ | √ | √ | ||
并行端口中断 | PSPIF | PSPIE | √ | √ | |||
A/D转换中断 | ADIF | ADIE | √ | √ | √ | √ | √ |
E2PROM中断 | EEIF | EEIE | √ | √ | √ | √ | √ |
13种 | 14种 | 10种 | 11种 | 10种 |
从表1中可以看出,各中断源基本上都是与各个外围设备模块相对应的。其中,多数外围设备模块对应着1个中断源(比如定时器/计数器TMR0模块),有的外围设备模块对应着2个中断源(比如通用同步/接收/发送器SCI模块),也有的外围设备模块没有中断源与之对应(比如输入/输出端口RA和RC模块),还有的中断源没有外围设备模块与之对应(比如外部触发中断源INT)。
二、 PIC16F87X的中断硬件逻辑
在PIC16F87X的子系列中,具体型号不同,中断逻辑电路也存在着差异,中断源的种类和个数也不同:最多的具备14种中断源;最少的具备10种中断源(详见表1)。其中并行端口模块和并行端口中断源,只有40脚封装的型号(PIC16F871、PIC16F874和PIC16F877)才会具备;而对于28脚封装的型号(PIC16F870、PIC16F872、PIC16F873和PIC16F876)则不具备。
PIC16F87X系列单片机中断系统的逻辑电路如图1所示。每一种中断源对应着1个中断标志位(记为XXXF,F是Flag的第1个英文字母)和1个中断屏蔽位或者叫中断使能位(记为XXXE,E是Enable的第1个英文字母)。中断源产生的中断标志信号是否得以向前传递,将受控于对应的中断屏蔽位。每一个中断标志位都对应着1个触发器。当中断源申请CPU中断时,与之对应的触发器就由硬件自动置位,而该触发器的清零是由用户安排程序来实现的;每一个中断屏蔽位也对应着1个触发器。该触发器的置位和清零均是由用户程序完成的。
图1描绘的逻辑电路是1个由简单的门电路构成的组合逻辑电路。将全部14个中断源按2个梯队并列排开,第1梯队中只安排了3个中断源,其余的中断源全部安排到第2梯队中。这样做是为了与早期的PIC系列单片机型号相兼容(前些年研制出的单片机型号片内配置的外围设备模块数量较少,相应的中断源的数量自然也就少,比如PIC16C61只有第1梯队中的3个中断源)。近期研制的一些PIC单片机新型号是在原有的单片机芯片基础之上进行一些功能扩展而得来的。
所有的中断源都受全局中断屏蔽位(也可以称为总屏蔽位)GIE的控制。第1梯队的中断源不仅受全局中断屏蔽位的控制,还要受各自中断屏蔽位的控制;第2梯队的中断源不仅受到全局中断屏蔽位和各自中断屏蔽位的控制,还要额外受到1个外设中断屏蔽位PEIE的控制。
三、 中断相关的寄存器
与中断功能有关的特殊功能寄存器共有5个:中断控制寄存器INTCON、第1外围设备中断标志寄存器PIR1、第1外围设备中断屏蔽寄存器(又称中断使能寄存器)PIE1、第2外围设备中断标志寄存器PIR2和第2外围设备中断屏蔽寄存器PIE2。如表2所列,5个寄存器中共有40位,其中使用了30位。分别与图1中的中断逻辑电路的输入逻辑信号成严格对应关系,也与逻辑表达式成严格对应关系。这5个寄存器都具有在RAM数据存储器中统一编码的地址。也就是说,PIC单片机可以把这5个特殊寄存器当作普通寄存器单元来访问(即读出或写入操作)。这样有利于减少指令集的指令类型和指令数量,也便于学习、记忆和编程。
单片机复位后,由硬件自动对全局中断屏蔽位进行设置GIE=0,将屏蔽所有的中断源。中断返回指令“RETFIE”执行后,也由硬件自动对总屏蔽位进行设置GIE=1,重新开放所有的中断源。不论各种中断屏蔽位和全局中断屏蔽位GIE处于何种状态(是开放还是禁止),当某一中断源的中断条件满足时,都会发出中断请求,相应的中断标志位都会被置位(=1)。但是,是否能够得到CPU的响应,则要根据该中断源所涉及到的中断屏蔽位的状态而定。CPU响应中断后,由硬件自动对全局中断屏蔽位进行清零(GIE=0),屏蔽所有的中断源,以免发生重复中断响应,然后,由硬件自动把当前的程序计数器PC值(即程序断点地址)压入堆栈(实际为硬件堆栈),并且把PC寄存器置以中断向量地址(0004H),从而转向并开始执行中断服务程序。进入中断服务程序后,程序中必须安排指令,检查发出请求的中断源(如果同时开放多个中断源的话)。这可以通过检查各个中断源的标志位来实现。一旦确定出发出申请的中断源,就用软件把该中断源的标志位人为地清零,否则,执行中断返回指令“RETFIE”。重开中断后,由于中断标志位仍为“1”而引起CPU重复响应同一个中断请求。中断服务程序的末尾必须放置1条中断返回指令“RETFIE”。执行该条指令后,不仅可以重开中断,而且还可以由硬件自动将保留在堆栈顶部的断点地址弹出,并放回到程序计数器PC中,使CPU返回和继续执行被中断的主程序。
1 中断的延时响应和延时处理
1次中断过程,从中断源发出请求到得到CPU的响应必然存在一定的延迟时间。
在图2中,第1行是系统时钟脉冲信号,每4个时钟周期对应1个指令周期。第2行就是指令周期信号。该信号只有在RC振荡模式下,从OSC2脚上可以向片外送出。第3行是单片机外部引脚INT送入的中断脉冲信号。外部中断信号INT是用边沿触发的。假设预先设定的是INT中断信号上升沿有效的话,则该信号的上升沿将会在1个时钟周期后引发中断标志位INTF被置位。第4行代表INTF信号。每个指令周期内的第2个时钟脉冲上升沿时,该信号被抽检1次。一旦检测到INTF信号被设置为“1”,则CPU会在接下来的1个指令周期内,将全局中断屏蔽位GIE清零。第5行是全局中断屏蔽位GIE。在GIE信号被清零的下一个指令周期内,程序计数器PC被置入中断向量0004H,见图2中第6行。同时在该指令周期内完成到中断服务程序的跳转,并且实现提取该子程序的首条指令,即指令(0004H),见图2中第7行。在其后的1个指令周期内,正式开始执行中断服务程序的第1条指令,见图2中第8行。自INT引脚输入有效信号,到中断服务程序的第1条指令得到执行,大约需要3~4个指令周期的延时。更精确的延迟时间取决于中断事件的发生时机。
以上描述的只是1次中断从申请到得到CPU的响应的延迟时间。下面分析从CPU响应1次中断到该中断得到有效处理的延迟时间。由于具有中断功能的PIC系列单片机(低档产品PIC16C5X和PIC12C5X系列不具备中断功能),采用的是“多源中断”的设计方案(即1个中断向量对应着多个中断源),只有惟一的1个中断向量,或者说只有1个中断服务程序入口地址。这就意味着,此类单片机的中断服务程序只能编写1个。这类单片机的硬件结构得到了简化,那么,相应的软件设计上就得多开销一些。在1个中断服务程序中,若想对多个中断源作出处理,就必须在进入中断服务程序后,首先执行调查具体中断源的一条或多条指令,其后才能对查到的中断源作出有针对性的服务。如此以来,就形成了1次中断从CPU响应到进入针对性处理的延迟时间。该时间有长有短,它会随着被开放的中断源的个数的增加而增加。最好情况是只有1个中断源被开放,这时不需要检测中断源就可以立即进入针对性处理;最坏情况是所有中断源全部开放,此时用在检测中断源上的时间会最长。
另外,PIC单片机中采用的是硬件堆栈结构。其好处是既不占用程序存储器
空间,也不占用数据存储器空间,同时也不需用户去操作堆栈指针;但此时也带来1个不可回避的弱点,即不具备像其他单片机指令系统中的压栈(PUSH)和出栈(POP)指令那样,实现中断现场的保护会麻烦一些,并且占用的处理时间也相应多一点。
2 中断的现场保护问题
中断现场的保护是中断技术中一个很重要的环节。在进入中断服务程序期间,只有返回地址,即程序计数器PC的值被自动压入堆栈。若需要保留其他寄存器的内容,就得由程序员另想办法。由于PIC单片机的指令系统中没有像其他单片机那样的PUSH(入栈)和POP(出栈)之类的指令,所以要用1段用户程序来实现类似的功能。因为是用1段程序来实现现场保护,而程序的执行有可能会影响到W寄存器和STATUS寄存器,所以,首先应该把这2个寄存器保护起来,然后再去保存其他用户认为有必要保护的寄存器。并且在PIC单片机中,中断现场数据不是保留到芯片的堆栈存储区中,而是保留在用户自己选择的一些文件寄存器(即RAM数据存储器单元)中,当然一般应该选择通用寄存器来保护现场。下面给出的是1段原厂家最新提供的实现保护中断现场的范例程序片段。
;将W、STATUS和PCLATH寄存器的内容保存到临时备份寄存器中
[1]MOVWFW_TEMP ;复制W到它的临时备份寄存器W_TEMP中
[2]SWAPFSTATUS,W ;将STATUS寄存器高低半字节交换后放入W
[3]CLRFSTATUS ;不管当前处在哪个体,都设置体0作当前体
[4]MOVWFSTATUS_TEMP ;保存STATUS到体0上的临时寄存器STATUS_TEMP
[5]MOVF PCLATH, W ;把寄存器PCLATH内容复制到W中
[6]MOVWFPCLATH_TEMP ;经W将PCLATH内容转到临时寄存器PCLATH_TEMP
[7]CLRFPCLATH ;不管当前处在哪页,都把PCLATH设置成指向页0(中断服务程序的核心部分)
[8]MOVFPCLATH_TEMP, W ;经过W转移
[9]MOVWFPCLATH ;恢复PCLATH内容
[10]SWAPFSTATUS_TEMP,W ;将STATUS_TEMP寄存器高低半字节交换后放入W
[11]MOVWFSTATUS ;把W内容移动到STATUS寄存器,(同时也把当前体恢复到原先的体上)
[12]SWAPFW_TEMP,F ;将W_TEMP内容高低半字节交换后放回
[13]SWAPFW_TEMP,W ;再次将W_TEMP内容高低半字节交换后放入W
这段程序适用于PIC16CXX系列中各款型号的单片机。在这段例程之前,假设预先对于待保留的各个寄存器都分别定义了相应的临时备份寄存器。用后缀“_TEMP”表示临时备份寄存器,例如“W”的临时备份寄存器记为“W_TEMP”。对于这些临时备份寄存器究竟需要定义多少个,定义在通用寄存器区域中的哪个位置,都是值得考究的问题。并且单片机的型号不同,其内部的通用寄存器区域的分布也不同,因此这就使得临时备份寄存器定义的数量和位置也不能相同。
例如,对于PIC16F873/874来说,要求寄存器W_TEMP必须在文件寄存器(即RAM数据存储器)的体0和体1上各定义1个,并且这2个W_TEMP寄存器单元必须具有相同的体内地址码(比如,在体0上把W_TEMP定义在20H单元,则在体1上就把另一个W_TEMP定义在A0H单元);而其他寄存器的临时备份寄存器(如STATUS_TEMP和PCLATH_TEMP)都仅仅需要在体0上定义1个即可。
又例如,对于PIC16F87X子系列中的其他5款型号来说,情况有所不同。其文件寄存器各个体的顶端部分有16个地址空间,都会寻址到相同的16个物理单元上。这16个单元不需要体选寻址,或者说,寻址这16个单元与体选码无关,即与当前所处的体无关。因此,将各个临时备份寄存器都安排在这个位置(W_TEMP也只需要定义1个即可)最为合适。这样做可以使得现场保护和现场恢复变得非常容易。中断是一种随机发生的事件。进入中断服务程序后,第1个要保存的应该是工作寄存器W。原因是PIC单片机没有在“不同寄存器”之间进行直接传递的指令,这样的功能得用W作中转(需要2条指令)才能实现,所以应该先把W寄存器腾空(对应程序中第1条指令)。急于腾空W寄存器,又不能破坏当前状态寄存器STATUS中的体选码,还不能影响当前状态寄存器STATUS内的标志位,可又无法确定主程序所处的RAM数据存储器当前体是哪一个,就只好在主程序所有可能选择到的每一个RAM数据存储器体上的相同位置,都定义1个W_TEMP临时备份寄存器。
一旦把工作寄存器W腾空后,紧接着就应将状态寄存器STATUS的内容转移到W中。完成这一操作的指令也不能影响到STATUS寄存器内部原有的标志位,原因是STATUS寄存器的内容在此之前还没有安全地保护起来。经过仔细分析得知,PIC16系列单片机的指令系统中有3条“MOV”传送指令。但是,只有1条“MOVF f,W”是以RAM单元为源寄存器,以W为目标寄存器的;而这条指令的操作过程又偏偏会影响“Z”标志位。因此,该指令就不能使用了,只好用1条既有高、低半字节交换功能又有传递功能的“SWAPFSTATUS,W”来勉强顶替(对应程序中第2条指令)。不过在此只利用它的传递功能,其交换功能带来的多余操作还得记下来,等到工作完成之后还得把它倒换回来。
STATUS寄存器的内容已经保存到W中时,就可以大胆地将其清0了,以便把定义着STATUS_TEMP和PCLATH_TEMP的体0设置为当前体(对应程序中第3条指令)。经过以上几步特别需要谨慎的操作过后,就可以轻而易举地将寄存器STATUS和PCLATH的内容保存到各自的临时备份寄存器中了(对应程序中第4~6条指令)。
在单片机初始加电时,自动将PCLATH清0,以避免其内容出现随机值,也就是为了避免在以后的程序运行过程中CPU发生不可预料的跳转,而造成程序的“跑飞”。由此可见,寄存器PCLATH对于程序的安全运行是至关重要的,不可轻视。程序一旦进入服务程序后,PCLATH的当前值为何就无从考证,实际上就失去了对于PCLATH内容的知情权。只好像单片机初始上电那样将其清0,重新把它强行“拉入”知情范围(对应程序中第7条指令)。
PCLATH的内容在2种情况下会影响到程序的走向:第1种情况是当执行GOTO和CALL这2条跳转指令时,11位地址码来源于指令码中,决定程序存储器页面的(PC值的)最高2位,来源于PCLATH<4:3>,即这种情况下只有PCLATH的2位影响程序走向。单单就这一种情况而言,只要用户程序不超过第0页(或称页0)的2KB范围,对于程序员来说,PC值的最高2位可以忽略,因而PCLATH寄存器PCLATH<4:3>的2位也可以忽略。第2种情况是,以PCL为目标的算术运算、逻辑运算或传送操作指令(PIC16系列单片机的指令系统中具备14条这样的指令),在操作过程中,自动用PCLATH寄存器的低5位装载PC的高5位PC<12:8>,影响程序走向的PCLATH内容就多达5位。即使对于用户程序不超过(第0页范围内的)2KB的情况,也至少会有3位影响到程序的走向。对于程序员来说,PCLATH的内容就不可忽略,必须保护。
总而言之,对于寄存器PCLATH的保护和处理(对应程序中阴影标出的部分指令,即第5~9条)并不是什么情况下都是必需的,但是在编写中断服务程序时,统一安排这些指令也没有任何坏处。只要主程序和中断服务程序中都不需要修改PCLATH寄存器的内容,就可以不保护它。具体地说,只有当同时满足以下2个条件时,阴影标出的部分指令(即第5~9条)才可以省略。
(1)在主程序和中断服务程序中不都存在跨页跳转。例如:用户程序没有使用第0页2KB空间之外的程序存储器,或者用户程序虽然超出了2KB的范围,但是,在主程序和中断服务程序中没有同时用到GOTO或CALL指令,都能满足该条。
(2) 在主程序和中断服务程序中没有同时使用以PCL为目标的操作指令(比如查表)。
保护现场的操作次序与恢复现场的操作次序应该相反。程序中的第8~11条就是按照相反的顺序恢复寄存器PCLATH和 STATUS内容的。但是,不要忘记保护现场时采用“SWAPF STATUS,W”指令产生的多余的交换操作,在此只好再采用同样的方法将其交换回来(对应程序中第10条指令)。最后2条指令,将W_TEMP内容的高、低半字节交换了2遍,才被恢复到工作寄存器W中。如果只用1条传送指令“MOVF W_TEMP,W”又会产生1个新的问题:“MOVF W_TEMP,W”指令会影响“Z”标志位,会破坏此前已经被恢复的寄存器STATUS的内容,这是我们所不希望的,也是不能容忍的。因此,在程序中利用了2条不影响标志位的SWAP指令(即第12,13两条指令)。虽然麻烦一点,但可以使这个问题得到圆满的解决。
最后必须进一步强调的是,并不是所有情况下编写的中断服务程序中都需要现场保护,或者都需要像以上范例程序那样进行现场保护。有些情况下仅仅保护W、STATUS和PCLATH这3个寄存器还不够。不过在此程序片段的基础上,再增加或者减少需要保护的寄存器的个数都是轻而易举的事。不要忘记,在保护任何文件寄存器之前都必须先把工作寄存器W保护起来才行得通。
3 需要注意的几个问题
(1)中断标志位的状态与该中断源是否产生中断无关。换句话说,不管是否允许其中断,只要满足中断的条件,中断标志位就会被置位。另外,也可以利用软件将中断标志位置“1”或清“0”。
(2)当开放某一中断源时,该中断源就是通过中断标志位向CPU申请中断的。无论什么原因,只要将中断标志位置位,就会产生中断。如果用软件强行将中断标志位置位,也会产生中断。
(3)如果在中断被屏蔽(或禁止)的情况下,中断标志位被置位,只要不被清除就会一直潜伏下来,那么,一旦解除屏蔽,就会立即产生中断。
(4)如果在中断被禁止的情况下,中断标志位已经被置位,但是,假如在允许其中断之前将它清除,那么,即使解除禁止,它也不会产生中断。
(5)当CPU相应的任何一个中断时,全局中断屏蔽位GIE将会自动清0;当中断返回时它又会自动恢复为1。如果在中断处理期间用软件将已经复位的GIE重新置位,这时再出现中断请求,就可以形成中断嵌套。也就是说,如果在响应某一中断期间又响应了其他中断请求,就形成了中断嵌套。发生中断嵌套时,前一中断处理过程被暂停而进入后一中断处理,当后一中断过程被处理完毕之后,才会继续处理前一中断。照此方式,还可以形成多级嵌套,甚至自身嵌套。不过嵌套的级数绝对不能超过硬件堆栈的深度。
(6) 对于中断响应和处理时间有严格要求的应用,保护现场的指令安排也应考虑延时问题。
(7)如果同时发生多个中断请求,得到优先处理的中断完全取决于在中断服务程序中检查中断源的顺序。原因是各个中断源之间不存在优先级别之分。
如果清除中断标志位的指令安排在中断服务程序的尾部,就有可能丢失响应在处理中断期间该中断源第2次中断请求的机会。