裸機——中斷2
1. 中斷是什麼?
中斷是一種異常,特點是CPU被打斷後執行中斷處理程式,然後再恢復執行原來的程式。
中斷是軟體依託硬體提供的機制實現的,理解異常,必須先理解硬體的機制。
2. 硬體提供的機制——異常向量表
當發生異常時,硬體會自動讓pc跳到異常向量表對應位置執行,
異常向量表如下:
可以看出異常向量表就是陣列,陣列元素是函式指標,按照訪問記憶體的方式訪問。
板子剛啟動時,處於SVC模式,查閱 iROM_Application_note 可以知道異常向量表的基地址。
我們可以根據上面知識寫出下面程式碼:
#defineEXCEPTION_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)) #definerSOFTWARE__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(); }
以上就完成中斷。