摘要:美國微芯公司研制的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寄存器的內容保存到臨時備份寄存器中
?。?]MOVWFW_TEMP ;復制W到它的臨時備份寄存器W_TEMP中
?。?]SWAPFSTATUS,W ;將STATUS寄存器高低半字節交換后放入W
?。?]CLRFSTATUS ;不管當前處在哪個體,都設置體0作當前體
?。?]MOVWFSTATUS_TEMP ;保存STATUS到體0上的臨時寄存器STATUS_TEMP
?。?]MOVF PCLATH, W ;把寄存器PCLATH內容復制到W中
?。?]MOVWFPCLATH_TEMP ;經W將PCLATH內容轉到臨時寄存器PCLATH_TEMP
?。?]CLRFPCLATH ;不管當前處在哪頁,都把PCLATH設置成指向頁0(中斷服務程序的核心部分)
?。?]MOVFPCLATH_TEMP, W ;經過W轉移
?。?]MOVWFPCLATH ;恢復PCLATH內容
?。?0]SWAPFSTATUS_TEMP,W ;將STATUS_TEMP寄存器高低半字節交換后放入W
[11]MOVWFSTATUS ;把W內容移動到STATUS寄存器,(同時也把當前體恢復到原先的體上)
?。?2]SWAPFW_TEMP,F ;將W_TEMP內容高低半字節交換后放回
?。?3]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條)才可以省略。
?。?)在主程序和中斷服務程序中不都存在跨頁跳轉。例如:用戶程序沒有使用第0頁2KB空間之外的程序存儲器,或者用戶程序雖然超出了2KB的范圍,但是,在主程序和中斷服務程序中沒有同時用到GOTO或CALL指令,都能滿足該條。
?。?) 在主程序和中斷服務程序中沒有同時使用以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”或清“0”。
?。?)當開放某一中斷源時,該中斷源就是通過中斷標志位向CPU申請中斷的。無論什么原因,只要將中斷標志位置位,就會產生中斷。如果用軟件強行將中斷標志位置位,也會產生中斷。
?。?)如果在中斷被屏蔽(或禁止)的情況下,中斷標志位被置位,只要不被清除就會一直潛伏下來,那么,一旦解除屏蔽,就會立即產生中斷。
?。?)如果在中斷被禁止的情況下,中斷標志位已經被置位,但是,假如在允許其中斷之前將它清除,那么,即使解除禁止,它也不會產生中斷。
?。?)當CPU相應的任何一個中斷時,全局中斷屏蔽位GIE將會自動清0;當中斷返回時它又會自動恢復為1。如果在中斷處理期間用軟件將已經復位的GIE重新置位,這時再出現中斷請求,就可以形成中斷嵌套。也就是說,如果在響應某一中斷期間又響應了其他中斷請求,就形成了中斷嵌套。發生中斷嵌套時,前一中斷處理過程被暫停而進入后一中斷處理,當后一中斷過程被處理完畢之后,才會繼續處理前一中斷。照此方式,還可以形成多級嵌套,甚至自身嵌套。不過嵌套的級數絕對不能超過硬件堆棧的深度。
(6) 對于中斷響應和處理時間有嚴格要求的應用,保護現場的指令安排也應考慮延時問題。
?。?)如果同時發生多個中斷請求,得到優先處理的中斷完全取決于在中斷服務程序中檢查中斷源的順序。原因是各個中斷源之間不存在優先級別之分。
如果清除中斷標志位的指令安排在中斷服務程序的尾部,就有可能丟失響應在處理中斷期間該中斷源第2次中斷請求的機會。