IRQ.S分析
以下是“IRQ.S分析”的部分摘錄
一直想對中斷嵌套有更深層次的了解。原因就在于一個項目中在中斷頻率較高的情況下,又按鍵中斷觸發,有時候會導致死機。
先來看兩個問題:
1.為什么除了進入復位異常模式外,在別的異常處理模式中都允許FIQ中斷?
答:所以FIQ可以用來處理實時性較高或是系統中至關重要的事。
2.數據訪問中止異常的優先級大于FIQ異常,為什么在數據訪問異常處理模式中,還允許 FIQ中斷呢?這樣不就成了:在高優先級異常處理中允許低優先級的中斷發生嗎?即使這樣,因為FIQ中斷的優先級 < 數據異常中斷優先級,也不會進入FIQ中斷處理程序啊,這樣不就更沒有用處了??
答:在高優先級異常處理中允許低優先級的中斷發生,是可以的。(1)如果CPU不支持中斷嵌套,會丟棄低優先級的中斷。(2)如果CPU支持中斷嵌套,低優先級的中斷會被掛起,待退出高優先級后再處理低級中斷。
這兩個問題說明了各種異常模式也是有優先級的,并且低優先級異常能被高優先級異常中斷。當CPU不支持嵌套時,會丟棄低優先級中斷,支持嵌套時會掛起,等高優先級處理后再處理低優先級。
優先級 | 異常 | 1 | 復位 | 2 | 數據異常終止 | 3 | FIQ | 4 | IRQ | 5 | 預取異常終止 | 6 | 未定義指令 | 7 | SWI |
RAM7中斷優先級表
既然不支持嵌套的cpu不能被低優先級的中斷打斷,那么為什么arm7tdmi的中斷處理過程會被其他中斷打斷呢?
... ... 簡單描述下這個中斷處理過程:1.計算返回地址 2.保存環境 3.切換到系統模式 4.實際中斷處理 5.切換回IRQ模式 6.恢復環境 7.返回 這里的動作就像括號一樣層層包圍著實際中斷處理函數,就是為了讓實際中斷處理函數在系統模式下進行,而非irq模式下。正是在系統模式下,中斷才能被新的IRQ打斷,才會造成類似嵌套的效果。
在一般的中斷處理程序中,需要按照以下步驟進行響應:
1.計算返回地址
2.保存會有改變的寄存器組
3.調用實際中斷響應函數
4.恢復寄存器組
5.返回被中斷處
為了使arm7這類不支持中斷嵌套的cpu能使用中斷嵌套,必須在進入實際中斷響應函數前將cpu模式切換為sys32系統模式,在處理結束后繼續恢復為IRQ模式。因為sys32模式能被IRQ異常模式打斷,所以在中斷處理的過程中仍然能響應新的中斷。
注:原模板中在“Startup.s”中有如下的語句
;Build the SYS stack
;設置系統模式堆棧
MSR CPSR_c, #0xdf
LDR SP, =StackUsr
MOV PC, R0
為了中斷嵌套,需將“MSR CPSR_c, #0xdf”語句改為“MSR CPSR_c, #0x5f”,即在系統模式下允許IRQ中斷。(注部分是自己添加的內容)
所以arm7這類不支持嵌套的cpu的中斷嵌套的響應流程如下:
1.計算返回地址(注意:此時處理器是在IRQ模式下)
2.保存會有改變的寄存器組
3.切換為sys32系統模式
4.調用實際中斷響應函數
5.切換回IRQ模式
6.恢復寄存器組
7.返回被中斷處
實現代碼如下:
IRQ中斷,先保存前一中斷在sys32模式下的處理程序的寄存器組,然后切換到sys32模式,再進入此中斷的響應程序。當處理完此中斷之后,再恢復前一中斷在sys32模式下的寄存器組,直到中斷處理完之后,恢復成程序的寄存器組。
因此,可以將中斷看作是隨機的函數調用,需要在進入時保存寄存器組,在退出時恢復寄存器組。因為中斷不同于普通的函數調用,同事發生了模式的變化,所以在保存和恢復寄存器組的時候,應該加上CPSR寄存器。又由于arm7在IRQ模式下不能進行中斷嵌套,所以需要切換到sys32模式進行實際的中斷響應。
當發生中斷嵌套時,程序是怎么工作的呢?
-程序0正在正常運行,突然被中斷1打斷后,進入到中斷處理程序。
-保存程序0當中的寄存器組,然后切換到sys32模式,進行中斷1的實際響應。
-此時又允許被中斷,中斷1處理過程中,又被中斷2打斷。
-中斷2響應時候,保存中斷1中斷的寄存器組,進入sys32模式進行中斷2的響應
-當中斷2響應結束后,恢復到中斷1在sys32模式下的響應程序的寄存器狀態
-當中斷1響應結束后,恢復到程序0的寄存器狀態。
要讀懂源碼,還得看ARM匯編偽指令宏的應用。
【轉】ARM匯編偽指令宏的用法詳解(MACRO MEND)
宏是一段獨立的程序代碼,它是通過偽指令定義的,在程序中使用宏指令即可調用宏。當程序被匯編時,匯編程序將對每個調用進行展開,用宏定義取代源程序中的宏指令。 MACRO、MEND 語法格式: MACRO [$ label] macroname{ $ parameter1, $ parameter,…… } 指令序列 MEND MACRO偽操作標識宏定義的開始,MEND標識宏定義的結束。用MACRO及MEND定義一段代碼,稱為宏定義體,這樣在程序中就可以通過宏指令多次調用該代碼段。其中, $ label在宏指令被展開時,label會被替換成相應的符號,通常是一個標號。在一個符號前使用$表示程序被匯編時將使用相應的值來替代$后的符號。macroname為所定義的宏的名稱。$parameter為宏指令的參數。當宏指令被展開時將被替換成相應的值,類似于函數中的形式參數,可以在宏定義時為參數指定相應的默認值。 宏指令的使用方式和功能與子程序有些相似,子程序可以提供模塊化的程序設計、節省存儲空間并提高運行速度。但在使用子程序結構時需要保護現場,從而增加了系統的開銷,因此,在代碼較短且需要傳遞的參數較多時,可以使用宏匯編技術。 首先使用MACRO和MEND等偽操作定義宏。包含在 MACRO 和 MEND 之間的代碼段稱為宏定義體,在MACRO偽操作之后的一行聲明宏的原型(包含宏名、所需的參數),然后就可以在匯編程序中通過宏名來調用它。在源程序被匯編時,匯編器將宏調用展開,用宏定義體代替源程序中的宏定義的名稱,并用實際參數值代替宏定義時的形式參數。宏定義中的$label是一個可選參數。當宏定義體中用到多個標號時,可以使用類似$label.$internallabel的標號命名規則使程序易讀。 MACRO 、 MEND 偽操作可以嵌套使用。 使用示例: MACRO $HandlerLabel HANDLER $HandleLabel ;宏的名稱為HANDLER,有1個參數$HandleLabel $HandlerLabel sub sp,sp,#4 ;decrement sp(to store jump address) stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original address) ldr r0,=$HandleLabel;load the address of HandleXXX to r0 ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR) MEND ;在程序中調用該宏 HandlerFIQ HANDLER HandleFIQ ;通過宏的名稱HANDLER調用宏,其中宏的標號為HandlerFIQ,參數為HandleFIQ HandlerIRQ HANDLER HandleIRQ HandlerUndef HANDLER HandleUndef HandlerSWI HANDLER HandleSWI HandlerDabort HANDLER HandleDabort HandlerPabort HANDLER HandlePabort 也許我們會問想格式中的[$ label]到底有什么作用? 當宏定義體內部跳轉時,這個參數會起到至關重要的作用。要想在宏內部跳轉,就必須在宏定義體內部有程序標號如(LOOP),如果不使用參數($ label),當在一個程序段內調用兩次宏的時候,編譯器就會出現錯誤,因為當匯編時產生了兩個相同名字的程序標號。 例子: 宏的定義體: MACRO $PM DELAY $CanShu $PM LDR R7,=$CanShu ; ;LDR R7,[R7] ;此時參數是一個立即數 如果是變量的話 是會用到這一句 $PM.LOOP SUBS R7,R7,#0X01 BNE $PM.LOOP MEND 在程序段中的使用:(使用兩次) ... ... AA DELAY 0X000005F0 ... ... BB DELAY 0X00000FF0 ... 此時調用多次,編譯器就不會出現問題,例子中的AA和BB僅僅是一個標號,用戶可以自行書寫,因為在宏指令唄展開時,這個符號在匯編時將使用相應的值替代,0x00000FF0是一個參數 在此處是一個立即數,用戶可自行使用為變量等。 現在進行源碼分析
源碼文件:IRQ.inc
;/****************** Copyright (c)**************************
;** 廣州周立功單片機發展有限公司
;** 研 究 所
;** 產品一部
;**
;**
;**
;**--------------文件信息--------------------------------
;**文 件 名: IRQ.inc
;**創 建 人: 陳明計
;**最后修改日期: 2004年8月27日
;**描 述: 定義IRQ匯編接口代碼宏
;**
;**--------------歷史版本信息------------------------------
;** 創建人: 陳明計
;** 版 本: v1.0
;** 日 期: 2004年8月27日
;** 描 述: 原始版本
;**
;**--------------當前版本修訂------------------------------
;** 修改人:
;** 日 期:
;** 描 述:
;**
;**----------------------------------------------------------
;************************************** /
NoInt EQU 0x80
USR32Mode EQU 0x10
SVC32Mode EQU 0x13
SYS32Mode EQU 0x1f
IRQ32Mode EQU 0x12
FIQ32Mode EQU 0x11
;引入的外部標號在這聲明
IMPORT OSIntCtxSw ;任務切換函數
IMPORT OSIntExit ;中斷退出函數
IMPORT OSTCBCur
IMPORT OSTCBHighRdy
IMPORT OSIntNesting ;中斷嵌套計數器
IMPORT StackUsr
IMPORT OsEnterSum
CODE32
AREA IRQ,CODE,READONLY
MACRO
$IRQ_Label HANDLER $IRQ_Exception_Function
EXPORT $IRQ_Label ; 輸出的標號
IMPORT $IRQ_Exception_Function ; 引用的外部標號
$IRQ_Label
SUB LR, LR, #4 ; 計算返回地址
;分析,在執行此指令時,處理器是由于產生了IRQ中斷被迫從其它模式切換到IRQ模式的,此時的PC值是被中斷了的其它模式下的預取指令的地址(由3級流水線導致),是當前執行指令的地址+8(RAM狀態下,Thumb下為+4),當已進入中斷時,LR里面裝的是PC,所以下一條要執行的指令地址就是LR-4(RAM狀態下)。
STMFD SP!, {R0-R3, R12, LR} ; 保存任務環境
;這里為什么只把R0-R3,R12,LR保存呢,其它不用嗎,是這樣的,我們可以從你裝的ADS1.2目錄下的PDF文件夾里面的ADS_DeveloperGuide_D.PDF文件的2.2就可以發現R4-R11裝的是局部變量,在進行函數跳轉時,編譯器它會自動保護它們的。 MRS R3, SPSR ; 保存狀態
STMFD SP, {R3, SP, LR}^ ; 保存用戶狀態的R3,SP,LR,注意不能回寫
; 如果回寫的是用戶的SP,所以后面要調整SP
;分析,1.先看看SPSR,在7中模式中,用戶模式和系統模式沒有SPSR,其它模式各有自己的SPSR,它的作用是保存異常發生前的CPSR的值。2.再看看“^”,當在STM中使用時,表示加載的寄存器列表“{R3, SP, LR}”是用戶模式的寄存器,而不是當前模式的寄存器,3.關于回寫,即如果語句是STMFD SP!, {R3, SP, LR}^,就是多一個感嘆號,對于這個“!”,本人理解是跟C語言的指針后跟“++”或“—”類似,至于是“++”還是“—”取決于STM后面的模式標識。
《ARM嵌入式系統基礎教程——周立功等編著》中有如下一段:
LDF和STM——多寄存器加載/存儲指令(P83)
指令格式:
STM{cond}<模式> Rn{!},reglist{^}
當Rn在寄存器列表中且使用后追“!”時,對于STM指令,若Rn為寄存器列表中的最低數字寄存器,則會將Rn的初值保存;其它情況下Rn的加載值和存儲值不可預知。(P84)
但看源文件后面的注釋“; 如果回寫的是用戶的SP,所以后面要調整SP”,說明回寫的話保存的是用戶的SP初值,不解???既然跟了“^”,說明兩個SP并非同一個寄存器,“!”跟“^”到底誰是老大,誰說了算啊??
再看“IRQ.S分析”一文中的解釋:
因為寄存器列表中包涵有SP,且第一個操作數寄存器也是SP,雖然意義上不是同一個SP,但是此時如果使用:STMFD SP!, {R3, SP, LR}^ 回寫SP是不行的,編譯就不會通過,違反了ARM匯編的規則設置。所以要寫成:STMFD SP, {R3, SP, LR}^ 然后后面(注意:好像還不能立刻減回來,要隔個一兩句再減,不然可能有警告!)再把SP減回來:SUB SP, SP, #4*3而 STMFD SP!, {R3,LR}^ 用ADS編譯可能會有一個警告,但是功能是對的。
我用H-JTAG + S3C2410 仿真看過
對于出棧的情況也是一樣。
LDR R2, =OSIntNesting ; OSIntNesting++
LDRB R1, [R2]
ADD R1, R1, #1
STRB R1, [R2]
;分析:以上幾行代碼功能是實現中斷嵌套計數器OSIntNesting +1操作
SUB SP, SP, #4*3
;分析:這里就是源文件的注釋“; 如果回寫的是用戶的SP,所以后面要調整SP”的實現,即調整堆棧指針SP的位置。
MSR CPSR_c, #(NoInt | SYS32Mode) ; 切換到系統模式
;分析:只有切換到系統模式,讓后面的服務程序在系統模式下運行,才能實現嵌套,那再一次開中斷又再哪兒進行呢??請看$IRQ_Exception_FunctionC語言要實現中斷嵌套的代碼規則。
CMP R1, #1
LDREQ SP, =StackUsr
;分析:以上兩句是判斷是否是第一次進入中斷,如果是第一次進入中斷則設定系統模式的堆棧指針。
BL $IRQ_Exception_Function ; 調用c語言的中斷處理程序
;這一句就不再多說了。 ;重點說說IRQ_Exception_Function該怎么寫,以下是一個模板,見《μC/OSII下的ARM7中斷過程分析及優化方法》一文。 3 中斷的優化 改寫μC/OSII 內核中 HANDLER 宏可以實現ARM的中斷嵌套,這樣做雖然提高了系統的實時性,但損害了系統運行的穩定性和可移植性。通過對中斷過程的分析,下面給出一種編寫中斷服務程序的模板,充分利用ISR執行在特權模式——系統模式這一特點來實現中斷嵌套的條件。中斷服務程序模板如下: void ISR(void) { OS_ENTER_CRITICAL();//在中斷服務程序中關中斷 /*清中斷標志*/ //防止沒有清中斷標志使得中斷多次進入 /*禁止低優先級中斷*/ //禁止低優先級中斷 S_EXIT_CRITICAL(); //在中斷服務程序中開中斷 VICVectAddr=0; //將中斷服務程序的入口地址置0 /*用戶的C語言代碼*/ //進行用戶在中斷中要做的工 } 由于Handler宏中已將LR、SPSR、返回地址和發生中斷前的堆棧指針等寄存器入棧保存,所以接下來要做的就只剩下開關中斷的工作。由于 在進入C中斷處理程序之前進入的是關中斷系統模式,所以必須在C語言中重新打開中斷,而C語言是不能進行寄存器操作的,因此必須調用軟中斷 OS_EXIT_CRITICAL()重新打開中斷。在開中斷之前,要判斷將全局變量OsEnterSum減1后是否為0,所以必須在調用開中斷之前調用 軟中斷OS_ENTER_CRITICAL()將OsEnterSum變成1。在臨界區中可以進行一些處理,如清中斷標志、關低優先級中斷等。進行C語言 中斷服務程序之后要將VICVectAddr置位為0,這是ARM7處理器核的要求必須進行這樣的編寫,否則會導致一些錯誤(如不能第2次進入中斷等)。 實例: void IRQ_FIFOP(void)
{
uint32 temp, temp32;
temp=VICIntEnable; // 保存中斷信息
VICIntEnClr=(1<<16); // 禁止當前中斷
EnableIRQ(); // 打開IRQ中斷
VICVectAddr=0x00; // 清除中斷邏輯,以便VIC可以響應更高優先級IRQ中斷 while( (EXTINT&0x04)!=0 ) //EINT2 1<<16
{
MACISR(); EXTINT = 0x0F; // 清除EINT0中斷標志
}
VICIntEnable=temp; //恢復中斷使能 }
MSR CPSR_c, #(NoInt | SYS32Mode) ; 切換到系統模式
;因為在執行C語言的中斷響應函數時,還可以響應中斷,處理的模式會發生變化,所以在函數執行完時強制返回到系統模式,做好中斷退出的準備。
;以下的代碼還需結合uCOS來分析。
LDR R2, =OsEnterSum ; OsEnterSum,使OSIntExit退出時中斷關閉
MOV R1, #1
STR R1, [R2]
;以上3句語句是給OsEnterSum賦值為1。為什么賦值為1呢?試想想,中斷是怎么嵌套進去的?再一次中斷是在執行$IRQ_Exception_Function函數中有可能(如果發生)被嵌套進去,利用C語言中的括號原理:中斷1—>{中斷2—>(中斷3—>())},這里的大括號就相當于第一次$IRQ_Exception_Function函數執行,當程序跑出$IRQ_Exception_Function時,大括號結束了。所以OsEnterSum只能是1了。
BL OSIntExit
;這句關鍵說說OSIntExit與OsEnterSum有何干?OSIntExit里面會調用OS_EXIT_CRITICAL()函數,而OS_EXIT_CRITICAL()是在Os_cpu.h中通過軟件中斷來實現的(__swi(0x03) void OS_EXIT_CRITICAL(void)),再看看軟件中斷代碼:
void SWI_Exception(int SWI_Num, int *Regs)
{
……
case 0x03: /* 開中斷函數OS_EXIT_CRITICAL(),參考os_cpu.h文件 */
if (--OsEnterSum == 0)
{
__asm
{
MRS R0, SPSR
BIC R0, R0, #NoInt
MSR SPSR_c, R0
}
}
break;
……
}
在代碼中如果--OsEnterSum == 0就給把中斷關了。那豈不是以后就永遠沒發響應中斷了?關鍵是周工的東東使用了uCOS的 #define OS_CRITICAL_METHOD 2 /* 選擇開、關中斷的方式 */,所以以后能不能再響應中斷去看看uCOS。
LDR R2, =OsEnterSum ; 因為中斷服務程序要退出,所以OsEnterSum=0
MOV R1, #0
STR R1, [R2]
;以上3句為OsEnterSum賦值0,為什么要賦值0呢?因為最后一個中斷要退出了,當然沒得數可計了啊!
MSR CPSR_c, #(NoInt | IRQ32Mode) ; 切換回irq模式
;切換到IQR中斷模式,恢復用戶模式的參數。 LDMFD SP, {R3, SP, LR}^ ; 恢復用戶狀態的R3,SP,LR,注意不能回寫
; 如果回寫的是用戶的SP,所以后面要調整SP
LDR R0, =OSTCBHighRdy
;讀出就緒表中任務最高優先級,判斷是否需要任務切換
LDR R0, [R0]
LDR R1, =OSTCBCur
LDR R1, [R1]
CMP R0, R1 ;判斷被掛起的任務是不是具有最高優先級
ADD SP, SP, #4*3 ; 如果不是則進行任務切換
MSR SPSR_cxsf, R3
LDMEQFD SP!, {R0-R3, R12, PC}^ ; 不進行任務切換
LDR PC, =OSIntCtxSw ; 進行任務切換
MEND
END
;/*********************************************************************************************************
;** End Of File
;********************************************************************************************************/ 不好意思有點亂哦!!!
源碼文件:IRQ.s
;/******************** Copyright (c)*************************
;** Guangzou ZLG-MCU Development Co.,LTD.
;** graduate school
;**
;**
;**--------------File Info-------------------------------------------------------------------------------
;** File Name: IRQ.s
;** Last modified Date: 2004-06-14
;** Last Version: 1.1
;** Descriptions: The irq handle that what allow the interrupt nesting.
;**
;**-----------------------------------------------------------------------------------
;** Created By: Chenmingji
;** Created date: 2004-09-17
;** Version: 1.0
;** Descriptions: First version
;**
;**-----------------------------------------------------------------------------------
;** Modified by:
;** Modified date:
;** Version:
;** Descriptions:
;**
;************************************************************ /
INCLUDE ..\..\arm\irq.inc ; Inport the head file 引入頭文件
CODE32
AREA IRQ,CODE,READONLY
;/* 以下添加中斷句柄,用戶根據實際情況改變 */
;/* Add interrupt handler here,user could change it as needed */
;/*中斷*/
;/*Interrupt*/
IRQ_Handler HANDLER IRQ_Exception
;/*定時器0中斷*/
;/*Time0 Interrupt*/
Timer0_Handler HANDLER Timer0_Exception
END
;/*********************************
;** End Of File
;********************************* /
|