1. 程式人生 > >嵌入式Linux應用開發完全手冊(三)中斷

嵌入式Linux應用開發完全手冊(三)中斷

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當前工作模式

異常進入和返回流程

  • 異常進入
    1. 異常工作模式下的lr儲存進入前的下一個指令地址
    2. CPSR複製到SPSR
    3. 設定CPSR到當前異常的工作模式
    4. 設定PC到這個異常的異常向量表入口地址
  • 異常返回
    1. 異常模式下的LR,減去適當的值,設定到PC
    2. SPSR的值恢復到CPSR

中斷處理過程

  1. 中斷控制器彙集各類外設發出的中斷訊號,通知CPU
  2. CPU儲存當前程式執行環境,呼叫中斷服務程式ISR來處理這些中斷
  3. ISR中通過讀取中斷控制器、外設的相關暫存器識別中斷源,進行相應處理
  4. 清除中斷,讀寫中斷控制器和外設暫存器
  5. 恢復被打斷的程式
    Markdown

2440中斷處理流程

Markdown

  • 中斷源
    • 含有自子中斷的中斷源,上圖可以看到,子中斷先過一遍mask,再設定SRCPND
    • 不含有子中斷的中斷源,直接設定SRCPND
    • 中斷遮蔽
    • 子中斷先經過INTSUBMASK判斷
    • 不含有子中斷和經過判斷的子中斷,再過一遍INTMASK
    • 優先順序
    • 如果是FIQ,INTMOD是1,不用經過優先順序選擇,直接執行。FIQ只能分配一個。
    • 如果是IRQ,需要經過優先順序選擇,最高優先順序的中斷,被設定到INTPND
    • 中斷執行
    • ISR(中斷處理程式)讀INTPND或者INTOFFSET確定中斷源

9.2 2440中斷控制暫存器

SUBSRCPND

Markdown

  • 2440有15個子中斷源,能看到分為5組,分別是這5箇中斷的子中斷
    • INT_WDT_AC97 看門狗
    • INT_CAM 攝像頭
    • INT_ADC 觸控板
    • INT_UART0, UART1, UART2 串列埠
  • 發生中斷的時候相應的位被自動設定成1
  • 想要清除的話,需要再寫入對應位置的1,請注意不是清零,是寫1

INTSUBPND

Markdown
某位被置1的時候,對應中斷被遮蔽

SRCPND

Markdown
Markdown
處於pending狀態的中斷源。
來源有兩類,參考上文,帶有子中斷的,和不帶有子中斷的。
清除某一位,需要對某一位寫1而不是清0.

INTMSK

Markdown
Markdown
遮蔽SRCPND中的中斷源。
某位寫1即遮蔽該中斷,只能遮蔽IRQ,不能遮蔽FIQ。

INTMOD

Markdown
Markdown
某位寫1,即設定該中斷位FIQ,見上文,同時只能有一箇中斷設定位FIQ。
一般設定最緊急的中斷位FIQ。

PRIORITY

Markdown
Markdown
- 優先順序暫存器控制優先順序仲裁器的行為
Markdown

  • 2440的優先順序仲裁,分為2個階段
    • 1級仲裁,即仲裁器0到仲裁器5,小組賽,選擇出優勝者
    • 2級仲裁,即仲裁器6,決賽,各 小組選出的中斷再經過決賽選擇總冠軍
  • 每組的優先順序順序可以配置,通過ARB_SLE
  • 每組的優先順序模式可以配置,是固定不變的,還是輪轉的
    • 固定不變的,按照ARB_SLE的配置順序
    • 輪轉的,已經被服務的中斷,其優先順序降到倒數第二,僅高於REQ5
    • 即使輪轉,最高和最低,REQ0和REQ5都不會變化

INTPND

Markdown
Markdown
經過優先順序篩選之後,從SRCPND中選出的中斷,在INTPND中設定相應的位為1.
同一時間,只能有一位是1.
要清除這個中斷,需要將相應的位置1,而不是清零,通過INTPND = INTPND 操作即可。

INTOFFSET

Markdown
跟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