1. 程式人生 > >S5PV210中斷的介紹與配置

S5PV210中斷的介紹與配置

一、介紹

1、三星S5PV210中斷體系介紹

異常向量表(向量中斷控制器)
異常向量表是CPU中某些特定地址的特定定義,當中斷髮生的時候,中斷要通知CPU處理中斷,在CPU設計時,定義了CPU中一些特定地址作為特定異常的入口地址,異常向量表的實現,是基於SoC內部的向量中斷控制器。
  關於向量中斷控制器的描述,這裡摘錄了網友“亦大樂諜“的部落格:

S5PV210是三星推出的一款基於Cortex-A8的Soc,其內部整合的中斷控制器由4個ARM PrimeCell PL192向量中斷控制器級連(daisy-chain)而成,每個PL192 VIC(Vectored Interrupt Controller)支援32箇中斷源,所以最多支援128個。S5PV210使用了其中的93個。所謂“向量”是指當中斷髮生時,軟體可以直接從VIC得到提前設好的中斷服務程式ISR(Interrupt Service Routine)。 CPU在對VIC進行初始化的過程中,將各個中斷源的ISR寫入到VIC中儲存起來,在中斷髮生以後,VIC會自動挑選出當前優先順序最高的中斷源,並將其ISR推送到VICADDRESS暫存器裡,CPU可以直接拿到ISR(一般就是一個函式的起始地址),將PC跳轉到這個地址取執行就可以了,速度大大加快 在這裡插入圖片描述

在這裡插入圖片描述

以上講的是CPU硬體設計時對異常向量表的支援,接下來就需要軟體支援。硬體已近決定了的傳送什麼異常CPU自動跳轉PC到那個地址執行,軟體需要做的就是把處理這個異常的程式碼的收地孩子填入這個異常向量的地址。

二、異常處理的兩個階段

(1)第一個階段之所以能夠進行,主要依賴於CPU設計時提供的異常向量表機制。第一個階段的主要任務是從異常發生到響應異常並且儲存/恢復現場,跳轉到真正的異常處理程式處。 (2)第二個階段的目的是識別多箇中斷源中究竟是哪一個發生了中斷,然後呼叫相應的中斷處理程式來處理這個中斷。

三、程式設計思路

整個中斷的工作分為2部分: 第一部分是我們為中斷響應的預備工作 1、初始化中斷控制器 2、繫結寫好的isr到中斷控制器 3、相應的中斷的所有條件的使能 第二部分是當硬體產生中斷後如何自動執行isr 1、第一步,經過異常向量表跳轉入IRQ/FIQ入口 2、第二步,做中斷現場保護(在start.s),然後跳入isr_handler 3、第三步,在isr_handler中先去搞清楚是哪個VIC中斷了,然後直接去VIC的ADDR暫存器中去isr來執行即可 4、第四步,isr執行完,中斷現場恢復,直接返回繼續做常規任務。

四、使用外部中斷時需要配置的幾個重要的暫存器

1、VIC0INTENABLE和VIC0INTENCLEAR 在這裡插入圖片描述 (1) VICnINTENABLE ------interrupt enable VINTENCLEAR ------interrupt enable clear (2)INTENABLE暫存器負責相應的中斷的使能;INTENCLEAR暫存器負責相應的中斷的禁止。 (3)當我們想使能(意思就是啟用這個中斷,意思就是當硬體產生中斷時CPU能接收的到)某個中斷時,只要在這個中斷編號對應的VICnINTENABLE的相應bit位寫1即可(注意這個位寫1其他位寫0對其他位沒有影響);如果我們想禁止某個中斷源時,只要向VICnINTENCLEAR中相應的位寫1即可。注意:這裡的設計一共有2種:有些CPU是中斷使能和禁止是一個暫存器位,寫1就使能寫0就禁止(或者反過來寫1就禁止寫0就使能),這樣的中斷使能設計就要非常小心,要使用我們之前說過的讀改寫三部曲來操作; 另一種就是使能和禁止分開為2個暫存器,要使能就寫使能暫存器,要禁止就寫禁止暫存器。這樣的好處是我們使能/禁止操作時不需要讀改寫,直接寫即可。 2、VICnINTSELECT(Interrupt Select Register,中斷模式選擇暫存器,0選擇IRQ ,1FIQ) 在這裡插入圖片描述

(1)設定各個中斷的模式為irq還是fiq。一般都設定成irq (2)IRQ和FIQ究竟的區別: irq是普通中斷,fiq是快速中斷。快速中斷提供一種更快響應處理的中斷通道,用於對實時性要求很高的中斷源。fiq在CPU設計時預先提供了一些機制保證fiq可以被快速處理,從而保證實時性。fiq的限制就是隻能有一箇中斷源被設定為fiq,其他都是irq。 (3)CPU如何保證fiq比irq快?有2個原因:第一,fiq模式有專用的r8~r12,因此在fiq的isr中可以直接使用r8-r12而不用儲存,這就能節省時間;第二,異常向量表中fiq是最後一個異常向量入口。因此fiq的isr不需要跳轉,可以直接寫在原地,這樣就比其他異常少跳轉一次,省了些時間。 3、VICnIRQSTATUS和VICnFIQSTATUS 在這裡插入圖片描述 中斷狀態暫存器,是隻讀的。當發生了中斷時,硬體會自動將該暫存器的對應位置為1。 4、VICnVECTPRIORITY0~VICnVECTPRIORITY31

(1)中斷優先順序設定暫存器,設定多箇中斷同時發生時先處理誰後處理誰的問題。一般來說高優先順序的中斷可以打斷低優先順序的中斷,從而巢狀處理中斷。當然了有些硬體/軟體可以設定不支援中斷巢狀。 5、VICnVECTADDR0~VICnVECTADDR31、VICnADDR

在這裡插入圖片描述 (1)VICnVECTADDR0~31這32個暫存器分別用來存放真正的各個中斷對應的isr的函式地址。相當於每一箇中斷源都有一個VECTADDR暫存器,程式設計師在設定中斷的時候,把這個中斷的isr地址直接放入這個中斷對應的VECTADDR暫存器即可。 (2)VICnADDR這個暫存器是隻需要讀的,它裡面的內容是由硬體自動設定的。當發生了相應中斷時,硬體會自動識別中斷編號,並且會自動找到這個中斷的VECTADDR暫存器,然 後將其讀出複製到VICnADDR中,供我們使用。這樣的設計避免了軟體查詢中斷源和isr,節省了時間,提高了210的中斷響應速度。

五、程式碼分析

(一)程式碼分析—中斷模式下的現場保護和恢復

IRQ_handle:

// 設定IRQ模式下的棧 0xD003_7F80

 ldr sp, =IRQ_STACK   

// 儲存LR // 因為ARM有流水線,所以PC的值會比真正執行的程式碼+8,

 sub lr, lr, #4

// 儲存r0-r12和lr到irq模式下的棧上面

 stmfd sp!, {r0-r12, lr}

// 在此呼叫真正的isr來處理中斷

 bl irq_handler

// 處理完成開始恢復現場,其實就是做中斷返回,關鍵是將r0-r12,pc,cpsr一起恢復

 ldmfd sp!, {r0-r12, pc}^

(二)程式碼分析—從main函式中中斷的相關函式入手拆解

 int main(void)
{
 int n = 0;
 uart_init();
 
 // 如果程式中要使用中斷,就要呼叫中斷初始化來初步初始化中斷控制器
 system_init_exception();
 eint_init();
 // 繫結isr到中斷控制器硬體
 intc_setvectaddr(KEY_DOWN, key_isr_eint2);
 intc_setvectaddr(KEY_BACK, key_isr_eint16171819);
 intc_enable(KEY_DOWN);
 intc_enable(KEY_BACK);
 
 while (1)
 {
  printf("%d ", n++);
  delay(10000);
 }
 
 return 0;
}
  • ①程式碼:key_init_interrupt();

void key_init_interrupt(void) { //1、外部中斷對應的GPIO模式設定 rGPH0CON |= 0xFF<<8; //GPH0_2和GPH0_3設定為外部中斷模式 rGPH2CON |= 0XFFFF<<0; //GPH2_0,1,2,3共4個引腳設為外部中斷模式 //2、中斷觸發模式設定,這裡設為下降沿觸發 EXT_INT_0_CON &= ~(0XFF<<8); //bit8~bit15全部清零 EXT_INT_0_CON |= ((2<<8)|(2<<12));//EXT_INT2和EXT_INT3設定位下降沿觸發。 //3、 中斷允許,清掛 rEXT_INT_0_MASK &= ~(3<<2); rEXT_INT_2_MASK &= ~(3<<0x0f<<0); //4、清掛起,清除是寫1 rEXT_INT_0_PEND |= (3<<2); rEXT_INT_2_PEND |= (0x0F<<0); }

  • ②程式碼:System_init_exception(); // 主要功能:繫結第一階段異常向量表;禁止所有中斷;選擇所有中斷型別為IRQ; // 清除VICnADDR為0

void system_init_exception(void) { // 第一階段處理,

繫結異常向量表 r_exception_reset = (unsigned int)reset_exception; r_exception_undef = (unsigned int)undef_exception; r_exception_sotf_int = (unsigned int)sotf_int_exception; r_exception_prefetch = (unsigned int)prefetch_exception; r_exception_data = (unsigned int)data_exception; r_exception_irq = (unsigned int)IRQ_handle; r_exception_fiq = (unsigned int)IRQ_handle; // 初始化中斷控制器的基本暫存器 intc_init(); }

  • 程式碼:void intc_init(void) // 禁止所有中斷 // 為什麼在中斷初始化之初要禁止所有中斷? // 因為中斷一旦開啟,因為外部或者硬體自己的原因產生中斷後一定就會尋找isr // 而我們可能認為自己用不到這個中斷就沒有提供isr,這時它自動拿到的就是亂碼 // 則程式很可能跑飛,所以不用的中斷一定要關掉。 // 一般的做法是先全部關掉,然後再逐一開啟自己感興趣的中斷。一旦開啟就必須 // 給這個中斷提供相應的isr並繫結好。

void intc_init(void) { VIC0INTENCLEAR = 0xffffffff; VIC1INTENCLEAR = 0xffffffff; VIC2INTENCLEAR = 0xffffffff; VIC3INTENCLEAR = 0xffffffff; // 選擇中斷型別為IRQ VIC0INTSELECT = 0x0; VIC1INTSELECT = 0x0; VIC2INTSELECT = 0x0; VIC3INTSELECT = 0x0; // 清VICxADDR intc_clearvectaddr(); } -程式碼:void intc_clearvectaddr(void) // 清除需要處理的中斷的中斷處理函式的地址 void intc_clearvectaddr(void) { // VICxADDR:當前正在處理的中斷的中斷處理函式的地址 VIC0ADDR = 0; VIC1ADDR = 0; VIC2ADDR = 0; VIC3ADDR = 0; }

  • ③ 程式碼:intc_setve

ctaddr(KEY_INT2, isr_eint2); // 繫結isr到中斷控制器硬體

 intc_setvectaddr(KEY_INT2, isr_eint2);
 intc_setvectaddr(KEY_INT3, isr_eint3);
 intc_setvectaddr(KEY_INT16_19, isr_eint16171819);

// 繫結我們寫的isr到VICnVECTADDR暫存器
// 繫結過之後我們就把isr地址交給硬體了,剩下的我們不用管了,硬體自己會處理
// 等發生相應中斷的時候,我們直接到相應的VICnADDR中去取isr地址即可。
// 引數:intnum是int.h定義的物理中斷號,handler是函式指標,就是我們寫的isr
// VIC0VECTADDR定義為VIC0VECTADDR0暫存器的地址,就相當於是VIC0VECTADDR0~31這個
// 陣列(這個陣列就是一個函式指標陣列)的首地址,然後具體計算每一箇中斷的時候
// 只需要首地址+偏移量即可。

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; }

  • ③程式碼:intc_ena

ble(KEY_DOWN); // 使能中斷 // 通過傳參的intnum來使能某個具體的中斷源,中斷號在int.h中定義,是物理中斷號

#define NUM_HSMMC3 (96+2) #define NUM_CEC (96+3) #define NUM_TSI (96+4) #define NUM_MDNIE0 (96+5) #define NUM_MDNIE1 (96+6) #define NUM_MDNIE2 (96+7) #define NUM_MDNIE3 (96+8) #define NUM_ADC1 (96+9) #define NUM_PENDN1 (96+10) #define NUM_ALL (200)

void intc_enable(unsigned long intnum) { unsigned long temp; // 確定intnum在哪個暫存器的哪一位 // <32就是0~31,必然在VIC0 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; } }