嵌入式Linux應用開發完全手冊(三)中斷
阿新 • • 發佈:2018-11-12
9 中斷體系結構
9.1 ARM中斷體系
ARM CPU工作模式和狀態
- 工作模式,7種,1種使用者模式,其他6選中特權模式
- usr 使用者模式,ARM處理器正常的工作模式
- fiq 快速中斷模式,高速資料傳輸或者通道處理
- irq 中斷模式,通用中斷處理
- svc 管理模式,作業系統使用的保護模式
- abt 資料訪問終止模式,資料或指令預取終止時進入該模式,用於虛擬儲存及儲存保護
- sys 系統模式,執行具有特權的作業系統任務
- und 未定義指令終止模式,未定義的指令得到執行的時候進入該模式,可用於支援硬體協處理器的軟體模擬
大多數程式運行於使用者模式,進入特權模式是為了處理中斷、異常,或者訪問被保護的系統資源。
- 工作狀態,2種
- ARM狀態,處理器執行32位的ARM指令
- Thumb狀態,處理器執行16位的Thumb指令
CPU一上電就處於ARM狀態,無需關心CPU狀態。
- 暫存器
- 同一時刻可見的暫存器有17個
- r0 - r12
- r13 sp
- r14 lr
- r15 PC
- 不同模式下並不會複用所有的暫存器
- fiq r8 - r14 是獨立的
- svc,abt,irq,und r13, r14 是獨立的
- fiq,svc,abt,irq,und SPSR是獨立的
- CPSR各位含義,詳見CPSR
- N 結果是否為負數
- Z 運算結果是否為0
- C 進位/錯位/移位溢位
- V 溢位
- I,F IRQ中斷禁止,FIQ中斷禁止
- T Thumb狀態
- mode CPU當前工作模式
- 同一時刻可見的暫存器有17個
異常進入和返回流程
- 異常進入
- 異常工作模式下的lr儲存進入前的下一個指令地址
- CPSR複製到SPSR
- 設定CPSR到當前異常的工作模式
- 設定PC到這個異常的異常向量表入口地址
- 異常返回
- 異常模式下的LR,減去適當的值,設定到PC
- SPSR的值恢復到CPSR
中斷處理過程
- 中斷控制器彙集各類外設發出的中斷訊號,通知CPU
- CPU儲存當前程式執行環境,呼叫中斷服務程式ISR來處理這些中斷
- ISR中通過讀取中斷控制器、外設的相關暫存器識別中斷源,進行相應處理
- 清除中斷,讀寫中斷控制器和外設暫存器
- 恢復被打斷的程式
2440中斷處理流程
- 中斷源
- 含有自子中斷的中斷源,上圖可以看到,子中斷先過一遍mask,再設定SRCPND
- 不含有子中斷的中斷源,直接設定SRCPND
- 中斷遮蔽
- 子中斷先經過INTSUBMASK判斷
- 不含有子中斷和經過判斷的子中斷,再過一遍INTMASK
- 優先順序
- 如果是FIQ,INTMOD是1,不用經過優先順序選擇,直接執行。FIQ只能分配一個。
- 如果是IRQ,需要經過優先順序選擇,最高優先順序的中斷,被設定到INTPND
- 中斷執行
- ISR(中斷處理程式)讀INTPND或者INTOFFSET確定中斷源
9.2 2440中斷控制暫存器
SUBSRCPND
- 2440有15個子中斷源,能看到分為5組,分別是這5箇中斷的子中斷
- INT_WDT_AC97 看門狗
- INT_CAM 攝像頭
- INT_ADC 觸控板
- INT_UART0, UART1, UART2 串列埠
- 發生中斷的時候相應的位被自動設定成1
- 想要清除的話,需要再寫入對應位置的1,請注意不是清零,是寫1
INTSUBPND
某位被置1的時候,對應中斷被遮蔽
SRCPND
處於pending狀態的中斷源。
來源有兩類,參考上文,帶有子中斷的,和不帶有子中斷的。
清除某一位,需要對某一位寫1而不是清0.
INTMSK
遮蔽SRCPND中的中斷源。
某位寫1即遮蔽該中斷,只能遮蔽IRQ,不能遮蔽FIQ。
INTMOD
某位寫1,即設定該中斷位FIQ,見上文,同時只能有一箇中斷設定位FIQ。
一般設定最緊急的中斷位FIQ。
PRIORITY
- 優先順序暫存器控制優先順序仲裁器的行為
- 2440的優先順序仲裁,分為2個階段
- 1級仲裁,即仲裁器0到仲裁器5,小組賽,選擇出優勝者
- 2級仲裁,即仲裁器6,決賽,各 小組選出的中斷再經過決賽選擇總冠軍
- 每組的優先順序順序可以配置,通過ARB_SLE
- 每組的優先順序模式可以配置,是固定不變的,還是輪轉的
- 固定不變的,按照ARB_SLE的配置順序
- 輪轉的,已經被服務的中斷,其優先順序降到倒數第二,僅高於REQ5
- 即使輪轉,最高和最低,REQ0和REQ5都不會變化
INTPND
經過優先順序篩選之後,從SRCPND中選出的中斷,在INTPND中設定相應的位為1.
同一時間,只能有一位是1.
要清除這個中斷,需要將相應的位置1,而不是清零,通過INTPND = INTPND 操作即可。
INTOFFSET
跟INTPND是聯動的,INTPND對應位置1表示有中斷等待處理,INTOFFSET這時設定成一個數字,跟INTPND表示的是同一個意思,編號為多少的中斷正在等待處理。
清除SRCPND,INTPND暫存器的時候,INTOFFSET暫存器被自動清除。
9.3 例項
jz2440開發板上有4個按鍵,分別對應外部中斷EINT0,EINT2,EINT11,EINT19,通過這4個按鍵操作LED燈。
- 程式結構
- 啟動檔案 head.s
- C程式
- 中斷初始化 init.c
- 中斷例程 interrrupt.c
- 主程式 main.c
- 中斷向量
- 發生異常的時候,ARM核按照一個向量表決定如何跳轉到響應的例程
- 當前的CPU處理的方式是在向量表的對應入口存放跳轉指令
- 較新的CPU,或者第三方定製的CPU,也有可能存放的是例程地址,而不是指令
- 如果是指令,可以接受的指令如下
- b [address]
- ldr pc,[pc,#offset]
- ldr pc,[pc,#-ff0]
- mov pc,#immediate
- 向量位置
- reset svc 0x00
- undef und 0x04
- SWI svc 0x08
- I-abt abt 0x0C
- D-abt abt 0x10
- 未定義 未定義 0x14
- IRQ IRQ 0x18
- FIQ FIQ 0x1C
head.s
[head.s]
.extern main
.text
.global _start
_start: @ 這個例子裡邊,因為使用了IRQ,所以開始的部分按照IRQ的要求設定中斷向量,從地址0開始
b Reset @ 0x00, reset異常,上電首先執行
HandleUndef:
b HandleUndef @ 0x04, 未定義異常,一下發生的異常,如果沒有處理例程,就直接在對應位置死迴圈
HandleSWI:
b HandleSWI @ 0x08, SWI軟中斷異常
HandlePreFetchAbort:
b HandlePreFetchAbort @ 0x0C, 預取指異常
HandleDataAbort:
b HandleDataAbort @ 0x10, 資料訪問異常
HandleReserved:
b HandleReserved @ 0x14,未定義
b HandleIRQ @ 0x18, 中斷處理
HandleFIQ:
b HandleFIQ @ 0x1c,快速中斷
Reset:
ldr r0,=0x53000000 @ 關閉看門狗
mov r1,#0
str r1,[r0]
ldr sp,=4096 @ 設定棧指標,因為下面都是C函式,所以需要設定棧指標
msr cpsr_c,#0xd2 @ 進入中斷模式,CPSR 11010010
@ 為什麼不是cpsr,而是cpsr_c,因為cpsr_c表示cpsr的低8位
@ IF都設定上了,IRQ和FIQ遮蔽,模式10010,IRQ模式
ldr sp,3072 @ 設定IRQ模式的棧指標,見上文,暫存器的拷貝
msr cpsr_c,#0xdf @ 進入系統模式,模式11111
ldr dp,=4096 @ 設定系統模式的棧指標,CPU復位之後,處於系統模式
bl init_led
bl init_irq
msr cpsr_c,#5f @ 開啟I,IRQ去掉遮蔽
ldr lr,=halt_loop
ldr pc,=main
halt_loop:
b halt_loop
HandleIRQ:
sub lr,lr,#4 @ 返回地址
stmdb sp!,{r0-r12,lr} @ 儲存使用到的暫存器,!的含義是sp隨著資料傳送的改變而改變
@ 現在sp,已經是IRQ模式的sp
@ IRQ模式的sp,比系統模式少1000位元組,啥意思,是怕覆蓋了系統模式下的棧資料?
ldr lr,=int_return
ldr pc,=EINT_Handle @ ISR例程,在interrupt.c中實現
int_return:
ldmia sp!,{r0-r12,lr}^ @ 中斷返回,^的意思是將SPSR恢復到CPSR
init.c
init.c程式中設定中斷暫存器的狀態。
用到的定義,放在標頭檔案int_key_led.h中。
[int_key_led.h]
#ifndef _INT_KEY_LED_H
#define _INT_KEY_LED_H
/*
* LED1,LED2,LED4對應GPF4、GPF5、GPF6
*/
#define GPF_OUT(x) (1 << ((x) * 2))
#define GPF_MSK(x) (3 << ((x) * 2))
#define GPG_MSK(x) GPF_MSK(x)
/*
* S2,S3,S4對應GPF0、GPF2、GPG3
*/
#define GPF_EINT(x) (2 << ((x) * 2))
#define GPG_EINT(x) GPF_EINT(x)
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define INTOFFSET (*(volatile unsigned long *)0x4a000014)
#define EINTMASK (*(volatile unsigned long *)0x560000a4)
#define PRIORITY (*(volatile unsigned long *)0x4a00000c)
#define INTMSK (*(volatile unsigned long *)0x4a000008)
#define EINTPEND (*(volatile unsigned long *)0x560000a8)
#define SRCPND (*(volatile unsigned long *)0x4a000000)
#define INTPND (*(volatile unsigned long *)0x4a000010)
/* LED燈對應的二進位制數字,可以表示0到7 */
#define LED_NUM(x) (~(((x) & ~((~0) << 3)) << 4))
#define LED_MSK(x) (1 << ((x) + 4))
#endif
/*
* init.c: 進行一些初始化
*/
#include "int_key_led.h"
void init_led(void)
{
// LED1,LED2,LED4對應的3根引腳設為輸出
GPFCON &= ~(GPF_MSK(4) | GPF_MSK(5) | GPF_MSK(6));
GPFCON |= GPF_OUT(4) | GPF_OUT(5) | GPF_OUT(6);
}
/*
* 初始化GPIO引腳為外部中斷
* GPIO引腳用作外部中斷時,預設為低電平觸發、IRQ方式(不用設定INTMOD)
*/
void init_irq( )
{
// S2,S3對應的2根引腳設為中斷引腳 EINT0,ENT2
GPFCON &= ~(GPF_MSK(0) | GPF_MSK(2));
GPFCON |= GPF_EINT(0) | GPF_EINT(2);
// S4對應的引腳設為中斷引腳EINT11
GPGCON &= ~GPF_MSK(3);
GPGCON |= GPF_EINT(3);
// 對於EINT11,需要在EINTMASK暫存器中使能它
EINTMASK &= ~(1 << 11);
/*
* 設定優先順序:
* ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ3,即EINT0 > EINT2
* 仲裁器1、6無需設定
* 最終:
* EINT0 > EINT2 > EINT11即K2 > K3 > K4
*/
PRIORITY = (PRIORITY & ((~1) | (3 << 7))) | (0 << 7) ;
// EINT0、EINT2、EINT8_23使能
INTMSK &= (~(1 << 0)) & (~(1 << 2)) & (~(1 << 5));
}
interrupt.c
函式EINT_Handle的實現。
#include "int_key_led.h"
void EINT_Handle()
{
unsigned long oft = INTOFFSET;
unsigned long val;
GPFDAT &= ~(LED_MSK(0) | LED_MSK(1) | LED_MSK(2));
GPFDAT |= LED_NUM(oft);
//清中斷
if( oft == 5 )
EINTPEND = (1<<11); // EINT8_23合用IRQ5
SRCPND = 1 << oft;
INTPND = 1 << oft;
}
main.c
死迴圈。
int main()
{
while(1);
return 0;
}
Makefile
src := $(shell ls *.c *.s)
obj := $(patsubst %.s, %.o, $(src))
obj := $(patsubst %.c, %.o, $(obj))
int_key_led.bin : $(obj)
arm-linux-ld -Ttext 0x00000000 $^ -o int_key_led_elf
arm-linux-objcopy -O binary -S int_key_led_elf int_key_led.bin
%.o : %.c
arm-linux-gcc -c -o [email protected] $<
%.o : %.s
arm-linux-gcc -c -o [email protected] $<
clean :
rm *.o *_elf *.bin