1. 程式人生 > >裸機——中斷2

裸機——中斷2

1. 中斷是什麼?

  中斷是一種異常,特點是CPU被打斷後執行中斷處理程式,然後再恢復執行原來的程式。

  中斷是軟體依託硬體提供的機制實現的,理解異常,必須先理解硬體的機制。

2. 硬體提供的機制——異常向量表 

  當發生異常時,硬體會自動讓pc跳到異常向量表對應位置執行,

  異常向量表如下:

 

  可以看出異常向量表就是陣列,陣列元素是函式指標,按照訪問記憶體的方式訪問。

  板子剛啟動時,處於SVC模式,查閱 iROM_Application_note 可以知道異常向量表的基地址。

我們可以根據上面知識寫出下面程式碼:

#define
EXCEPTION_BASE 0xD0037400 #define rFIQ_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x1C)) #define rIRQ_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x18)) #define rDATA_ABORT_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x10)) #define rPREFECTH_ABORT_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x0C)) #define
rSOFTWARE__EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x08)) #define rUNDEFINED_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE + 0x04)) #define rRESET_EXCEPTION (*(volatile unsigned int *)(EXCEPTION_BASE)) #include "stdio.h" void fiq_exception(void) { printf(
"fiq_exception\n"); } void irq_exception(void) { printf("irq_exception\n"); } void data_abort_exception(void) { printf("data_abort_exception\n"); } void prefetch_exception(void) { printf("prefetch_exception\n"); } void software_exception(void) { printf("software_exception\n"); } void undefined_exception(void) { printf("undefined_exception\n"); } void reset_exception(void) { printf("reset_exception\n"); } void system_exception_init() { rFIQ_EXCEPTION = (unsigned int)fiq_exception; rIRQ_EXCEPTION = (unsigned int)irq_exception; rDATA_ABORT_EXCEPTION = (unsigned int)data_abort_exception; rPREFECTH_ABORT_EXCEPTION = (unsigned int)prefetch_exception; rSOFTWARE__EXCEPTION = (unsigned int)software_exception; rUNDEFINED_EXCEPTION = (unsigned int)undefined_exception; rRESET_EXCEPTION = (unsigned int)reset_exception; }

這樣將中斷處理程式繫結到異常向量表,如果發生了異常就會跳轉執行對應列印語句。

但上面的程式碼不能實現中斷,因為中斷是停下手上的工作,去完成中斷服務,然後返回繼續原先的工作,這要求保護現場,處理中斷,恢復現場。

所以異常向量表繫結的程式應該這樣寫:

irq_exception:
    ldr sp, =IRQ_STACK  @由於異常發生時,CPU模式切換,IRQ的棧需要設定
    sub lr, lr, #4    @ 由於ARM的流水線技術,所以 返回地址需要計算
    stmfd sp!, {r0-r12, lr} @ 將返回地址和暫存器值保持到棧中
    bl irq_handler      @ 處理中斷
    ldmfd sp!, {r0-r12, pc}^ @ 先將棧中保持的值恢復到暫存器中,然後pc跳轉到返回地址,最後 cpsr 恢復

上面還有一個細節,程式碼中沒有體現,即異常發生時 硬體自動將 cpsr 的值儲存到 spsr 中,所以再保護現場中我們沒有手動完成這步。

 

上面就完成了中斷處理的框架,下面討論 irq_handler

3.硬體對異常的支援

   irq_handler的目標是找到中斷處理程式,並執行。

  這就需要了解SoC對中斷提供的其他支援了。

  以S5PV210為例,210有120種左右的中斷,每個中斷都有一個自己的暫存器用來繫結自己的處理程式入口地址。

  並且210將這120種中斷分為4組,每組有一個狀態暫存器,當發生中斷時,狀態 暫存器的對應為置一,

                每組還有一個地址暫存器,當發生中斷時,硬體自動將中斷的入口地址寫入那個地址暫存器。

 

下面給出與中斷相關暫存器的總結:

中斷最基本暫存器大致分為三類:
(1)中斷檢視
    IRQStatus
    FIQStatus
    RawInterrupt

(2)中斷設定
     禁止和使能
   VICINTENABLE
   VICINTENCLEAR
     模式選擇
   VICINTSELECT

(3)ISR vector address
    地址繫結
   VICVECTADDR[0-31]
    地址獲取
   VICADDRESS

 

  這樣我們就知道了程式設計思路:

  

  中斷髮生前

  (1)初始化中斷:

    先禁止所有中斷(防止跑飛),選擇中斷模式,中斷地址暫存器清零。

    將要使用的中斷的程式入口地址繫結到對應的暫存器中。

    開啟要使用的中斷。

  中斷髮生後

  (2)尋找中斷服務程式,並執行

    通過檢視4組中斷狀態暫存器,如果非0,即為該組發生了中斷,中斷服務程式即在該組的地址暫存器中。

    從地址暫存器中取出程式入口地址並執行。

  (3)清中斷

    當中斷處理完後,將4組中斷狀態暫存器置0。

 

那麼(1)初始化中斷就應該這樣:

void intc_clearvectaddr(void)
{
    VIC0ADDR = 0;
    VIC1ADDR = 0;
    VIC2ADDR = 0;
    VIC3ADDR = 0;
}

void int_init()
{
    // 禁止所有中斷
    VIC0INTENCLEAR = 0xffffffff;
    VIC1INTENCLEAR = 0xffffffff;
    VIC2INTENCLEAR = 0xffffffff;
    VIC3INTENCLEAR = 0xffffffff;

    // 選擇中斷型別為IRQ
    VIC0INTSELECT = 0x0;
    VIC1INTSELECT = 0x0;
    VIC2INTSELECT = 0x0;
    VIC3INTSELECT = 0x0;
    
    // 清零地址暫存器
    intc_clearvectaddr();
}

void system_exception_init()
{
    // 設定異常向量表
    rFIQ_EXCEPTION                =        (unsigned int)fiq_exception;
    rIRQ_EXCEPTION                =        (unsigned int)irq_exception;
    rDATA_ABORT_EXCEPTION        =       (unsigned int)data_abort_exception;
    rPREFECTH_ABORT_EXCEPTION    =       (unsigned int)prefetch_exception;
    rSOFTWARE__EXCEPTION        =       (unsigned int)software_exception;
    rUNDEFINED_EXCEPTION        =       (unsigned int)undefined_exception;
    rRESET_EXCEPTION            =       (unsigned int)reset_exception;

    // 初始化中斷
    int_init();
}

main.c中繫結要用的中斷服務程式和開啟中斷

void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
    //VIC0
    if(intnum<32)
    {
        *( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;
    }
    //VIC1
    else if(intnum<64)
    {
        *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
    }
    //VIC2
    else if(intnum<96)
    {
        *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
    }
    //VIC3
    else
    {
        *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
    }
    return;
}


// 使能中斷
void intc_enable(unsigned long intnum)
{
    unsigned long temp;

    if(intnum<32)
    {
        temp = VIC0INTENABLE;
        temp |= (1<<intnum);        // 如果是第一種設計則必須位操作,第二種設計可以
                                    // 直接寫。
        VIC0INTENABLE = temp;
    }
    else if(intnum<64)
    {
        temp = VIC1INTENABLE;
        temp |= (1<<(intnum-32));
        VIC1INTENABLE = temp;
    }
    else if(intnum<96)
    {
        temp = VIC2INTENABLE;
        temp |= (1<<(intnum-64));
        VIC2INTENABLE = temp;
    }
    else if(intnum<NUM_ALL)
    {
        temp = VIC3INTENABLE;
        temp |= (1<<(intnum-96));
        VIC3INTENABLE = temp;
    }
    // NUM_ALL : enable all interrupt
    else
    {
        VIC0INTENABLE = 0xFFFFFFFF;
        VIC1INTENABLE = 0xFFFFFFFF;
        VIC2INTENABLE = 0xFFFFFFFF;
        VIC3INTENABLE = 0xFFFFFFFF;
    }

}

 

int main()
{

    uart_init();
    
    system_exception_init();
    // 繫結isr到中斷控制器硬體
    intc_setvectaddr(KEY_EINT2, isr_eint2);
    intc_setvectaddr(KEY_EINT3, isr_eint3);
    
    // 使能中斷
    intc_enable(KEY_EINT2);
    intc_enable(KEY_EINT3);
    
    
    printf("hello world\n");

    return 0;
}

 

第二步這樣做

unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{
    if(ucontroller == 0)
        return    VIC0IRQSTATUS;
    else if(ucontroller == 1)
        return     VIC1IRQSTATUS;
    else if(ucontroller == 2)
        return     VIC2IRQSTATUS;
    else if(ucontroller == 3)
        return     VIC3IRQSTATUS;
    else
    {}
    return 0;
}

void irq_handler(void)
{
    printf("irq_handler.\n");

    unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
    int i=0;
    void (*isr)(void) = NULL;

    for(i=0; i<4; i++)
    {
        if(intc_getvicirqstatus(i) != 0)
        {
            isr = (void (*)(void)) vicaddr[i];
            break;
        }
    }
    (*isr)();        
}

第三步

void isr_eint2(void)
{// 第一,中斷處理程式碼
    printf("isr_eint2_LEFT.\n");
    // 第二,清除中斷掛起
    intc_clearvectaddr();
}

這樣就可以處理SoC內部外設產生的中斷,但是對於外部裝置的中斷還不能處理。

 

4.外部中斷

  中斷分為SoC內部中斷,如定時器,外部中斷,如按鍵。

  對於外部中斷是通過GPIO傳輸的,所以需要將GPIO設定為外部中斷模式。

  外部中斷重要的暫存器如下:

EXT_INT_CON:
     設定觸發模式為電平觸發還是邊沿觸發。
EXT_INT_MASK:
    禁止或使能
EXT_INT_PEND:
    外部中斷掛起,但中斷髮生後,PEND的對應位會置1,就會不停的向SoC發中斷請求。

  那麼對於按鍵的中斷初始化就如下:

// 以中斷方式來處理按鍵的初始化
void key_init_interrupt(void)
{
    // 1. 外部中斷對應的GPIO模式設定
    rGPH0CON |= 0xFF<<8;        // GPH0_2 GPH0_3設定為外部中斷模式
    rGPH2CON |= 0xFFFF<<0;        // GPH2_0123共4個引腳設定為外部中斷模式
    
    // 2. 中斷觸發模式設定
    rEXT_INT_0_CON &= ~(0xFF<<8);    // bit8~bit15全部清零
    rEXT_INT_0_CON |= ((2<<8)|(2<<12));        // EXT_INT2和EXT_INT3設定為下降沿觸發
    rEXT_INT_2_CON &= ~(0xFFFF<<0);
    rEXT_INT_2_CON |= ((2<<0)|(2<<4)|(2<<8)|(2<<12));    
    
    // 3. 中斷允許
    rEXT_INT_0_MASK &= ~(3<<2);            // 外部中斷允許
    rEXT_INT_2_MASK &= ~(0x0f<<0);
    
    // 4. 清掛起,清除是寫1,不是寫0
    rEXT_INT_0_PEND |= (3<<2);
    rEXT_INT_2_PEND |= (0x0F<<0);
}

  對於外部中斷,中斷處理完後還需要清中斷掛起

void isr_eint2(void)
{
    printf("isr_eint2_LEFT.\n");
    // 第二,清除中斷掛起
    rEXT_INT_0_PEND |= (1<<2);
    intc_clearvectaddr();
}

  以上就完成中斷。