目錄
一、前言
中斷是打斷當前程序執行,快速響應突發事件的一種機制。中斷的觸發源有很多種,比如外部引腳電平變化以及外設的各種事件中斷等等,當中斷髮生時,S3C2440 CPU的PC指針跳往固定的中斷向量地址處,執行中斷處理函數,之後再返回到原先的程序斷點處繼續執行程序。
二、實驗目標
採用按鍵中斷的方式,通過JZ2440開發板上的三個獨立按鍵分別控制開發板上的三個LED的亮滅。
三、硬件連線
獨立按鍵連線如下:
獨立按鍵只用了三個,分別連接到了EINT0,EINT2,EINT11,即S3C2440的外部中斷0,2和11。對應的真實物理引腳分別是GPF0,GPF2和GPG3。S3C2440的外部中斷引腳不是隨便選的,是定死了哪個引腳能用哪個外部中斷的。這點上就比不上STM32這類單片機了,STM32是任意普通引腳都可以做外部中斷的。而且STM32支持引腳功能的重映射,就是一個引腳上的各種功能可以映射到其他引腳上用,S3C2440貌似沒看到有這個功能。又想了想,畢竟S3C2440是定位於跑操作系統的,和單片機的定位着重點不同,術業有專攻。
LED連線如下:
三個LED分別連接在GPF4,GPF5和GPF6上。
四、S3C2440中斷體系
1、工作模式
ARM體系的CPU有以下7中工作模式:
(1)用戶模式(usr):ARM處理器正常的程序執行狀態。
(2)快速中斷模式(fiq):用於高速數據傳輸或通道處理。
(3)中斷模式(irq):用於通用的中斷處理。
(4)管理模式(svc):操作系統使用的保護模式。
(5)數據訪問終止模式(abt):當數據或指令預取終止時進入該模式,可用於虛擬存儲即存儲保護。
(6)系統模式(sys):運行具有特權的操作系統任務。
(7)未定義指令終止模式(und):當未定義的指令執行時進入該模式,可用於支持硬件協處理器的軟件仿真。
以上的模式可通過軟件來進行切換,發生各類中斷的時候CPU也會自動進入相應的模式。除用戶模式外,其他6種工作模式都屬於特權模式。大多數程序運行於用戶模式,進入特權模式是爲了處理中斷、異常、或者訪問被保護的系統資源。
在S3C2440中,這些不同的模式都有一組寄存器(r0到r15,CPSR和SPSR)與各自模式相對應,這些寄存器有些是共用的,有些是某個模式下獨有的(如上圖帶三角陰影的寄存器),雖然寄存器名字都一樣,但只是該模式下使用。這些寄存器除r15外都是通用寄存器,其中r13到r15這3個寄存器有特殊的意義,r13稱爲棧指針寄存器(SP),就是C語言用到的那個堆棧指針;r14稱爲程序連接寄存器(subroutime Link Register)或連接寄存器,當執行彙編指令BL進行子程序跳轉的時候,r14中會得到r15(程序計數器PC)的備份,當子程序返回的時候,r14(lr寄存器)的值會賦給r15(PC指針),這就使得程序能夠回到斷點位置繼續向下執行了。
每個模式都有獨立的r13和r14寄存器,即每個模式都有獨立的堆棧指針(SP)和連接寄存器(lr)。在不同模式間跳轉的時候,需要將上一個模式的所有公用寄存器進行壓棧操作,以便在之後可以再出棧恢復這些寄存器的值,達到模式恢復的效果。其中快中斷模式fiq從r8到r15都是獨立的,在進入該模式的時候需要保存的寄存器數目更少(r0到r7),所以進入該中斷會更快。
還有第17個寄存器CPSR,即“當前程序狀態寄存器”(Current Program Status Register)。裏面保存了各種狀態位。如下:
這裏挑幾個重點說明:
(1)I位和F位:中斷禁止位和快中斷禁止位,當這些位置位時,CPU不響應這些中斷。
(2)T位:置位時CPU處於Thumb狀態,運行Thumb指令集;否則爲ARM狀態,運行ARM指令集,不同指令集這裏不詳細分析,在JZ2440上做實驗基本都是用ARM指令集。
(3)M0到M4:工作模式位,表示CPU處於什麼工作模式,修改這些位,可以使CPU進入對應的工作模式。
還有一個寄存器叫SPSR寄存器(Saved Program Status Register),功能是保存前一個工作模式的CPSR值,這樣當要返回一個工作模式時,可以將SPSR的值恢復到CPSR中。
2、中斷響應流程
有兩類中斷請求源:
(1)“Request sources(without sub register)”中的中斷源被觸發之後,SRCPND(中斷請求狀態)寄存器中相應位被置1,如果INTMSK(中斷屏蔽控制)寄存器未將相應中斷屏蔽的話,判斷該中斷是否是快速中斷,如果且快速中斷模式是使能的,CPU響應快速中斷;如果是普通的中斷,PRIORITY(IRQ優先級控制)寄存器的相應位被置1表示產生了該中斷,之後INTPND(中斷請求狀態)寄存器挑出PRIORITY寄存器中的優先級最高的中斷進行響應,注意,PRIORITY中可能標記了很多的中斷需要處理,但是INTPND一次只挑出一個優先級最高的進行處理,在一箇中斷沒處理完的時候,不響應其他的中斷,
(2)“Request sources(with sub register)”這個中斷源和(1)的區別可以理解爲(1)的中斷源是那些大模塊比如DMA、定時器,串口等等的某個模塊的總的中斷請求,而串口模塊又存在了RX或者TX中斷,這些中斷稱爲中斷次級源。該類中斷源發生時SUBSRCPND的相應位置位,如果SUBMASK的對應位沒有置位來屏蔽這個中斷,那麼會引發SRCPND的對應位置位,之後的處理流程和(1)中一樣。
注意點:
(1)S3C2440的快速中斷(fiq)可以打斷普通中斷(irq),反之不行,普通中斷不可以打斷普通中斷,快速中斷也不可以打斷快速中斷,快速中斷(fiq)只能設置一個,即不存在中斷嵌套。快速中斷(fiq)不引起INTPND和INTOFFSET寄存器的變化,所以這兩個寄存器只在IRQ中斷中有效。
(2)SUBSRCPND、SRCPND、PRIORITY、INTPND中相應位被置位後,CPU響應了對應的中斷過後並不會自動清除這些位,所以需要我們在程序中人爲清除對應的位,清除的方法是往對應位寫1清除,簡單的寫法是“INTPND = INTPND”不清除的話該次中斷結束之後會立刻進入相同的中斷。
(3)不論SUBMASK或者MASK寄存器的相應位有沒有置位屏蔽,只要產生了對應的中斷,中斷請求狀態寄存器中都會置位表示有這個中斷產生了,上不上報是後續流程的事。
(4)fiq中斷由於只能設置一個,一旦產生用戶就必定知道是哪個中斷源引發的。irq中斷有很多個,可以通過讀取INTPND寄存器或者INTOFFSET寄存器來確定中斷源。
3、中斷優先級
S3C2440的中斷優先級設置是要遵循固定的模式的,並不像STM32那樣可以隨意設置。這裏的中斷優先級設置只是對IRQ中斷而言的,FIQ中斷不受這個規則限制。
中斷優先級通過7個仲裁器完成(ARBITER0到ARBITER6),包括6個一級仲裁器(ARBITER0到ARBITER5)和1個二級仲裁器(ARBITER6),說白了就是二級仲裁器從6個一級仲裁器中挑一個優先級最高的中斷報上去給CPU處理,其中REQ0和QEQ5的優先級順序是固定的,一個最高,一個最低,REQ1到REQ4可以通過配置更改它們的內部優先級順序,下面貼上一張PRIORITY寄存器的定義表來說明:
通過設置ARB_SELn可以設置REQ1到REQ4的內部順序,通過設置ARB_MODEn可以設置是否需要自動更改排序,就是處理完該組的一次中斷請求後,把對應ARB_SELn的值加1取餘4,自動輪換一下優先級順序。爲什麼要搞個這個功能呢?我猜應該是解決某些中斷長期佔用導致另外一些中斷難以響應的問題,試想一下如果開始優先級是REQ1到REQ4,用戶把這四個中斷同等重要來看待,然後REQ1有個中斷一直都有請求,REQ2這時也來了箇中斷請求,但是由於REQ1優先級更高霸佔了中斷請求的處理資源,然後REQ2就一直得不到響應了。
接下來還是要提一點,REQ0到REQ5的優先級順序只有4種排列方式,個人感覺還是不太靈活,能像STM32那樣自由配置就好了。
4、中斷處理代碼
在進入或者退出中斷時需要保存和恢復程序的運行環境。示例代碼如下:
(1)IRQ中斷,進入和退出的代碼如下:
sub lr, lr, #4 @計算返回地址
stmdb sp!, {r0-r12,lr} @保存使用到的寄存器
... ... @處理中斷
ldmia sp!, {r0-r12,pc}^ @中斷返回
@^表示將spsr的值賦給cpsr,恢復之前的工作模式
(2)FIQ中斷,進入和退出的代碼如下:
sub lr, lr, #4 @計算返回地址
stmdb sp!, {r0-r7,lr} @保存使用到的寄存器
... ... @處理快速中斷
ldmia sp!, {r0-r7,pc}^ @中斷返回
@^表示將spsr的值賦給cpsr,恢復之前的工作模式
五、代碼編寫
接下來是具體的實驗代碼,分爲以下幾個文件:
head.S:啓動文件。
main.c:初始化及各種C函數。
Makefile:編譯代碼。
各個文件的具體內容如下:
head.S
@*************************************************************************
@ File:head.S
@*************************************************************************
.text
.global _start
_start:
@******************************************************************************
@ 中斷向量,本程序中,除Reset和HandleIRQ外,其它異常都沒有使用
@******************************************************************************
b Reset
@ 0x04: 未定義指令中止模式的向量地址
HandleUndef:
b HandleUndef
@ 0x08: 管理模式的向量地址,通過SWI指令進入此模式
HandleSWI:
b HandleSWI
@ 0x0c: 指令預取終止導致的異常的向量地址
HandlePrefetchAbort:
b HandlePrefetchAbort
@ 0x10: 數據訪問終止導致的異常的向量地址
HandleDataAbort:
b HandleDataAbort
@ 0x14: 保留
HandleNotUsed:
b HandleNotUsed
@ 0x18: 中斷模式的向量地址
b HandleIRQ
@ 0x1c: 快中斷模式的向量地址
HandleFIQ:
b HandleFIQ
Reset:
ldr sp, =4096 @設置堆棧,因爲要調用C語言函數
bl disable_watch_dog @關WATCH DOG
msr cpsr_c, #0xd2 @ 進入中斷模式
ldr sp, =3072 @ 設置中斷模式棧指針
msr cpsr_c, #0xd3 @ 進入管理模式
ldr sp, =4096 @ 設置管理模式棧指針,
@ 其實復位之後,CPU就處於管理模式,
@ 前面的“ldr sp, =4096”完成同樣的功能,此句可省略
bl init_led @ 初始化LED的GPIO管腳
bl init_irq @ 調用中斷初始化函數
msr cpsr_c, #0x53 @ 設置I-bit=0,開IRQ中斷
ldr lr, =halt_loop @ 設置返回地址
ldr pc, =main @ 調用main函數
halt_loop:
b halt_loop
HandleIRQ:
sub lr, lr, #4 @ 計算返回地址
stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
@ 注意,此時的sp是中斷模式的sp
@ 初始值是上面設置的3072
ldr lr, =int_return @ 設置調用ISR即EINT_Handle函數後的返回地址
ldr pc, =EINT_Handle @ 調用中斷服務函數
int_return:
ldmia sp!, { r0-r12,pc }^ @ 中斷返回, ^表示將spsr的值複製到cpsr
main.c
#define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned int
/* WOTCH DOG register */
#define REG_WTCON (*(volatile unsigned long *)0x53000000)
/* GPIO Configure*/
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPFUP (*(volatile unsigned long *)0x56000058)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
#define GPGUP (*(volatile unsigned long *)0x56000068)
#define REG_EXTINT0 (*(volatile unsigned long *)0x56000088)
#define REG_EXTINT1 (*(volatile unsigned long *)0x5600008C)
/* interrupt Configure */
#define REG_EINTMASK (*(volatile unsigned long *)0x560000A4)
#define REG_INTMSK (*(volatile unsigned long *)0X4A000008)
#define REG_INTOFFSET (*(volatile unsigned long *)0x4A000014)
#define REG_EINTPEND (*(volatile unsigned long *)0x560000A8)
#define REG_SRCPND (*(volatile unsigned long *)0X4A000000)
#define REG_INTPND (*(volatile unsigned long *)0X4A000010)
// #define REG_INTSUBMSK (*(volatile unsigned long *)0x560000A8)
// #define REG_SUBSRCPND (*(volatile unsigned long *)0X4A000018)
void disable_watch_dog();
void init_led();
void init_irq();
void EINT_Handle();
/*上電後,WATCH DOG默認是開着的,要把它關掉 */
void disable_watch_dog()
{
REG_WTCON = 0;
}
void init_led()
{
GPFCON &= ~((DWORD)(3 << (2 * 4)) | (3 << (2 * 5)) | (3 << (2 * 6)));
GPFCON |= ((DWORD)(1 << (2 * 4)) | (1 << (2 * 5)) | (1 << (2 * 6))); //GPF4、GPF5、GPF6輸出模式
GPFDAT |= (1 << 4) | (1 << 5) | (1 << 6); //輸出高電平,LED全滅
}
void init_irq()
{
//配置爲外部中斷模式
GPFCON &= ~((DWORD)(3 << (2 * 0)) | (3 << (2 * 2)));
GPFCON |= ((DWORD)(2 << (2 * 0)) | (2 << (2 * 2)));
GPGCON &= ~((DWORD)(3 << (2 * 3)));
GPGCON |= ((DWORD)(2 << (2 * 3)));
//上拉
GPFUP |= (1 << 0) | (1 << 2);
GPGUP |= (1 << 3);
//設置下降沿觸發
REG_EXTINT0 &= ~((DWORD)(7 << 0) | (7 << 8));
REG_EXTINT0 |= ((DWORD)(2 << 0) | (2 << 8));
REG_EXTINT1 &= ~((DWORD)(7 << 12));
REG_EXTINT1 |= ((DWORD)(2 << 12));
//使能EINT11中斷
REG_EINTMASK &= ~((DWORD)1<<11);
//使能EINT0、EINT2、EINT11中斷
REG_INTMSK &= ~((DWORD)(1 << 0) | (1 << 2) | (1 << 5));
}
void EINT_Handle()
{
BYTE bIntOffset = REG_INTOFFSET;
switch(bIntOffset)
{
case 0:
GPFDAT ^= ((DWORD)1 << 4); //電平翻轉
break;
case 2:
GPFDAT ^= ((DWORD)1 << 5); //電平翻轉
break;
case 5:
if(REG_EINTPEND & (1 << 11))
{
GPFDAT ^= ((DWORD)1 << 6); //電平翻轉
}
break;
default:
break;
}
if(bIntOffset == 5)
{
//清子中斷
REG_EINTPEND = (DWORD)1 << 11;
}
REG_SRCPND = (DWORD)1 << REG_INTOFFSET;
REG_INTPND = (DWORD)1 << REG_INTOFFSET;
}
int main()
{
while(1);
return 0;
}
Makefile
objs := head.o main.o
interrupt.bin: $(objs)
arm-linux-ld -Ttext 0x0000000 -g -o interrupt_elf $^
arm-linux-objcopy -O binary -S interrupt_elf $@
arm-linux-objdump -D -m arm interrupt_elf > interrupt.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f interrupt.bin interrupt_elf interrupt.dis *.o
linux下執行make命令後將bin文件燒寫到NandFlash中,隨後選擇開發板Nand啓動,然後可以看到三個獨立按鍵可以分別控制三個LED燈的亮滅。
六、實驗總結
本次實驗加深了對ARM彙編的理解,熟悉了ARM的7種工作模式以及各種工作模式的特點,知道怎麼在各種模式下進行切換,掌握了中斷處理機制以及現場保存和恢復的編程實現,可謂是收穫頗豐。
七、參考資料
《嵌入式Linux應用開發完全手冊》
《可能是最通俗易懂的方式講解ARM中斷原理以及中斷嵌套》https://blog.csdn.net/thisway_diy/article/details/78056764
《第014課 Jz2400_ARM異常與中斷體系詳解》https://blog.csdn.net/thisway_diy/article/details/79397343