優先級 | 異常 |
1 | 復位 |
2 | 數據異常終止 |
3 | FIQ |
4 | IRQ |
5 | 預取異常終止 |
6 | 未定義指令 |
7 | SWI |
... ...
簡單描述下這個中斷處理過程:1.計算返回地址 2.保存環境 3.切換到系統模式 4.實際中斷處理 5.切換回IRQ模式 6.恢復環境 7.返回
這里的動作就像括號一樣層層包圍著實際中斷處理函數,就是為了讓實際中斷處理函數在系統模式下進行,而非irq模式下。正是在系統模式下,中斷才能被新的IRQ打斷,才會造成類似嵌套的效果。
宏是一段獨立的程序代碼,它是通過偽指令定義的,在程序中使用宏指令即可調用宏。當程序被匯編時,匯編程序將對每個調用進行展開,用宏定義取代源程序中的宏指令。
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
;********************************* /
| 歡迎光臨 (http://www.raoushi.com/bbs/) | Powered by Discuz! X3.1 |