51微控制器 interrupt和 using使用詳解
注:下文的“BANK”和“工作暫存器組”,這兩個名詞是一個概念。
首先推薦一篇文章,點選開啟連結
這篇文章大部分是翻譯軟體直接翻譯的,有些翻譯的不通順的地方、有歧義的地方,直接讀讀英文原版就清晰了。
如果連結掛了,自行搜尋:關於如何利用Keil C實現51微控制器中斷功能(interrupt、using關鍵字的用法
官方文件裡關於using的說明可參閱2個地方,(1)keil軟體選單欄->Help->uVision Heip,開啟幫助檔案,然後依次展開Ax51 Assembler User Guide -> Control Statement -> Reference -> USING,(2)幫助檔案依次展開CX51 Compiler User‘s Guide-> Language Extensions -> Function Declarations -> Register Banks
下面是我對interrupt和 using使用理解,
首先看interrupt,這個比較簡單,直接看一個外部中斷0服務函式的例子
void ext_int0_src() interrupt 0 using 2//
{
/*外部中斷0的服務函式*/
}
interrupt 0,這裡的數字0是外部中斷的中斷向量號,每一箇中斷向量都有對應的一個入口地址。如果對中斷向量表的概念比較熟悉的話,這裡很好理解,不再贅述。
下面主要看using的使用方法,這個比較複雜,我們從頭說起。
因為51的RAM空間極其有限,keil在編譯51的C程式時,C語言函式中的區域性變數、形參、返回值、返回地址會優先使用工作暫存器R0~R7(因為工作暫存器讀寫速度最快),如果這8個位元組不夠用,keil會為其餘的區域性變數分配RAM空間,這個空間在編譯完成後就固定下來了,假如某個函式中的區域性變數a編譯後位於RAM的0x5d位置,但是keil也有可能為另一個函式的中的區域性變數b分配空間時,把b也分配到RAM的0x5d位置,相當於a和b分時複用同一個RAM空間,這種編譯手段也就導致了函式的不可重入特性。51的可重入函式由模擬棧來實現,可參閱本部落格的另一篇文章。對於STM32等RAM較為充足的微控制器來說,keil分配區域性變數就不採用這種分時複用RAM的方法了,而是通過棧,具體地說就是,在呼叫函式前,先把實參新增到工作暫存器(工作暫存器並不是全部用來傳參,還有一些用來傳遞函式的返回值、函式的返回地址等),若工作暫存器滿足不了傳參需求,那麼其餘實參將被壓棧,這樣,在進入函式之後,通過讀取工作暫存器和出棧,即可令形參獲得實參的值;另外函式中的區域性變數一般也先從工作暫存器開始分配空間,若空間不足,會從棧中申請空間。這種編譯方法是最常見的,而上一段keil針對51的編譯手段比較另類。
51的工作暫存器R0~R7共有4組(分別是BANK 0、1、2、3),在任何時刻,都只有1個工作組生效!這4個組(BANK)在RAM中的位置分別是[00H, 07H]、[08H, 0FH]、[10H, 17H]、[18H, 1FH],換句話說,RAM中的00H地址、08H地址、10H地址、18H地址,這四個地址的名字都叫R0,那麼在彙編程式中我們經常看到類似MOV R0,#07這樣的語句,這個#07到底被放到了RAM的哪個地址中去了呢?00H?08H?10H?還是18H?到底是這4個地址中的哪一個,取決於51的PSW暫存器的RS1和RS0兩個位,若PSW.RS=2,就意味著第2組工作暫存器生效,R0的地址就是10H。
上面提到了51不可重入函式的區域性變數分時共享同一RAM空間的情況,需要注意的是,未使用的工作暫存器組所在的RAM地址(從00H~1FH共32位元組)不會被複用,換句話說,如果我們在程式中沒有手動切換工作暫存器組,那麼有些暫存器組就在程式的自始至終從不被使用,白白浪費了很多RAM。舉個例子,普通函式只使用了BANK 0,且中斷函式通過宣告using 1只使用了bank 1,那麼bank 2和bank3不會被程式的任何地方給用到(唯一的例外:模擬棧指標,下面有講到);再舉個例子,假設整個51程式中都沒有使用中斷,也即只使用了BANK 0,那麼BANK 1、2、3所在的空間在整個程式執行過程中都是閒著的(唯一的例外:模擬棧指標)。
51在上電後,PSW的RS兩個位預設為0,也即51預設使用工作暫存器組BANK 0,在預設狀態下,對於普通的C語言函式,其傳參、申請區域性變數、匯出函式的返回值等功能,keil將其翻譯成彙編以後,肯定要使用R0~R7;對於51的中斷服務函式,它沒有形參,也不用返回值,但是一般肯定有區域性變數,這時就需要用到R0~R7了;試想,在執行普通函式時,R0~R7已經被使用了,在執行普通函式時,一旦發生中斷,而中斷函式也需要使用R0~R7,那怎麼辦?我們最先想到的是,在執行中斷服務函式前先把R0~R7入棧(像累加器A、狀態PSW等也要入棧這個不用說大家也知道),在中斷服務完成後把R0~R7出棧,然後就能恢復現場,回到普通函式中去了,但是這8個Rn不能直接入棧,PUSH R0這樣的語句是不允許的,要想R0入棧只能用兩句:MOV A R0; PUSH A;這樣的後果是,每次工作暫存器入棧都需要2*8=16條彙編語句才能完成,再加上A、B、PSW等暫存器入棧等,相當於每次中斷都要消耗大量的時間來出棧入棧,影響程式速度。如何解決這一問題呢?51提供了這樣一種機制,切換工作暫存器組,過程如下:
普通函式的執行過程中正在使用BANK0的R0~R7,執行過程中突然發生了中斷,而中斷函式也想使用R0~R7,在執行中斷服務函式前,我們切換工作暫存器組,切換的具體方法就是直接修改PSW的RS兩個位元位,而不必把BANK 0入棧,本文開頭的例子中using 2,就是說,在進入外部中斷0的服務函式前,先入棧CPU暫存器,再把工作暫存器組由0切換成2,在退出中斷服務後,先由BANK2切換回BANK0,並彈出CPU暫存器,由於BANK0和BANK2處在不同的RAM空間,互相不干擾,切換回BANK0之後就把那個普通函式的現場給恢復了。
還有兩個問題點需要說明:
問題1、對於同一優先順序的中斷,可以using同一個暫存器組,因為同一優先順序的中斷不會互相打斷,也就是說,例如:我把同一優先順序的中斷函式都using 2,這些函式也不會衝突地使用R0~R7,他們只會分時複用BANK 2。但是對於不同優先順序的中斷函式,必須手動設定為讓他們使用不同的BANK,因為高優先順序的中斷會打斷低優先順序的中斷,如果使用相同的BANK,一旦發生中斷巢狀,低優先順序服務函式正在使用的R0~R7將會被覆蓋。這些東西都是說的在用C程式設計時的情況,如果用的是彙編程式設計,那我們可以自由的任意壓棧出棧CPU暫存器,任意的壓棧出棧BANKn,只要程式設計師自己心裡清楚地知道哪些Rn正在被使用,那他就能寫出安全的程式。用C程式設計的話,程式設計師比較省心,只要使用了using指令,就能輕易地保護R0~R7。當然,在彙編中使用USING也是可以的,也不用壓棧出棧BANK,也很省心。
問題2、如果我們使用了keil為51構造的模擬棧,以大編譯模式為例,keil會給模擬棧指標?C_XBP分配一個RAM空間(2個位元組),這個空間在哪呢?通過檢視map檔案(字尾名為.m51)我們發現,若我們的中斷函式只使用了BANK 1(普通函式預設已把BANK 0給用了,除非程式設計師為所有的普通函式都用using 1或2或3來修飾,如果不是閒的蛋疼沒人會這麼做),這時模擬棧指標?C_XBP將會被分配到BANK 2的R0和R1所在的位置上,也即RAM的10H、11H地址。如果普通函式使用了BANK0,中斷函式只使用了using 2,那麼?C_XBP將會被分配到BANK 1的R0和R1所在的位置上;如果普通函式使用了BANK0,中斷函式使用了BANK1和BANK2,那麼?C_XBP將會被分配到BANK 3的R0和R1所在的位置上。
綜上所述,在進入中斷服務程式後,要麼通過修改PSW切換BANK 號碼來保護R0~R7,要麼把R0~R7入棧來保護R0~R7。使用using n編寫的C語言中斷服務函式,keil就給翻譯成了PSW切換PSW切換的方式。