一,Cortex-A7 中斷系統
Cortex-A7 內核有 8 個異常中斷
跟 STM32 一樣, Cortex-A7 也有中斷向量表,中斷向量表也是在代碼的最前面。 CortexA7 內核有 8 個異常中斷,這 8 個異常中斷的中斷向量表如下圖
復位中斷(Rest), CPU 復位以後就會進入復位中斷, 我們可以在復位中斷服務函數裏面做一些初始化工作,比如初始化 SP 指針、DDR 等等。
未定義指令中斷(Undefined Instruction),如果指令不能識別的話就會產生此中斷。
軟中斷(Software Interrupt,SWI),由 SWI 指令引起的中斷,Linux 的系統調用會用 SWI 指令來引起軟中斷,通過軟中斷來陷入到內核空間。
指令預取中止中斷(Prefetch Abort),預取指令的出錯的時候會產生此中斷。
數據訪問中止中斷(Data Abort),訪問數據出錯的時候會產生此中斷。
IRQ 中斷(IRQ Interrupt),外部中斷,前面已經說了,芯片內部的外設中斷都會引起此中斷的發生。
FIQ 中斷(FIQ Interrupt),快速中斷,如果需要快速處理中斷的話就可以使用此中。
我們常用的就是復位中斷和 IRQ 中斷,Cortex-A 內核 CPU 的所有外部中斷都屬於IQR 中斷。
Cortex-A 內核 CPU 的所有外部中斷都屬於這個 IQR 中斷,當任意一個外部中斷髮生的時候都會觸發 IRQ 中斷。在 IRQ 中斷服務函數裏面就可以讀取指定的寄存器來判斷髮生的具體是什麼中斷,進而根據具體的中斷做出相應的處理。這些外部中斷和 IQR 中斷的關係如圖 所示:
左側的 Software0_IRQn~PMU_IRQ2_IRQ 這些都是 I.MX6U 的中斷,他們都屬於 IRQ 中斷。當圖 17.1.2.1 左側這些中斷中任意一個發生的時候 IRQ 中斷都會被觸發,所以我們需要在 IRQ 中斷服務函數中判斷究竟是左側的哪個中斷髮生了,然後再做出具體的處
理。
二,中斷控制器GIC
GIC 是 ARM 公司給 Cortex-A/R 內核提供的一箇中斷控制器,類似 Cortex-M 內核中的NVIC。目前 GIC 有 4 個版本:V1~V4, V1 是最老的版本,已經被廢棄了。 V2~V4 目前正在大量的使用。 GIC V2 是給 ARMv7-A 架構使用的,比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等,V3 和 V4 是給 ARMv8-A/R 架構使用的,也就是 64 位芯片使用的。 I.MX6U 是 Cortex-A 內核的,因此我們主要講解 GIC V2。 GIC V2 最多支持 8 個核。 ARM 會根據 GIC 版本的不同研發出不同的 IP 核,那些半導體廠商直接購買對應的 IP 核即可,比如 ARM 針對 GIC V2 就開發出了 GIC400 這個中斷控制器 IP 核。當 GIC 接收到外部中斷信號以後就會報給 ARM 內核,但是ARM 內核只提供了四個信號給 GIC 來彙報中斷情況: VFIQ、 VIRQ、 FIQ 和 IRQ,他們之間的關係如圖 17.1.3.1 所示
VFIQ、VIRQ、FIQ、IRQ這些是什麼呢
VFIQ:虛擬快速 FIQ。
VIRQ:虛擬快速 IRQ。
FIQ:快速中斷 IRQ。
IRQ:外部中斷 IRQ。
我們這裏只談 IRQ即是外部中斷
下面瞭解一下GIC工作原理
左側部分就是中斷源,中間部分就是 GIC 控制器,最右側就是中斷控制器向處理器內核發送中斷信息。中間 GIC 部分將衆多的中斷源分爲分爲三類:
①、SPI(Shared Peripheral Interrupt),共享中斷,顧名思義,所有 Core 共享的中斷,這個是最
常見的,那些外部中斷都屬於 SPI 中斷(注意!不是 SPI 總線那個中斷) 。比如按鍵中斷、串口中斷等等,這些中斷所有的 Core 都可以處理,不限定特定 Core。
②、PPI(Private Peripheral Interrupt),私有中斷,我們說了 GIC 是支持多核的,每個核肯定有自己獨有的中斷。 這些獨有的中斷肯定是要指定的核心處理, 因此這些中斷就叫做私有中斷。
③、SGI(Software-generated Interrupt),軟件中斷,由軟件觸發引起的中斷,通過向寄存器GICD_SGIR 寫入數據來觸發,系統會使用 SGI 中斷來完成多核之間的通信。
中斷 ID
中斷源有很多,爲了區分這些不同的中斷源肯定要給他們分配一個唯一 ID,這些 ID 就是中斷 ID。每一個 CPU 最多支持 1020 箇中斷 ID,中斷 ID 號爲 ID0~ID1019。這 1020 個 ID 包含了 PPI、SPI 和 SGI.
ID0~ID15:這 16 個 ID 分配給 SGI。
ID16~ID31:這 16 個 ID 分配給 PPI。
ID32~ID1019:這 988 個 ID 分配給 SPI,像 GPIO 中斷、串口中斷等這些外部中斷。
I.MX6U 的總共使用了 128 箇中斷 ID,加上前面屬於 PPI 和 SGI 的 32 個 ID,I.MX6U 的中斷源共有 128+32=160個。。
128 箇中斷 ID 對應的中斷在《I.MX6ULL 參考手冊》的“3.2 Cortex A7 interrupts”小節。
MCIMX6Y2C.h裏的枚舉類型 IRQn_Type,枚舉出了 I.MX6U 的所有中斷。
2.3 GID邏輯分塊
GIC 架構分爲了兩個邏輯塊: Distributor 和 CPU Interface, 也就是分發器端和 CPU 接口端。
例程“9_int”(原子LINUX),core_ca7.h 定義了 GIC 結構體。
結構體第 5 行是 GIC 的分發器端相關寄存器,
其相對於 GIC 基地址偏移爲 0X1000,獲取到GIC 基地址以後只需要加上 0X1000 即可訪問 GIC 分發器端(Distributor)寄存器。
其相對於 GIC 基地址的偏移爲 0X2000,獲取到 GIC 基地址以後只需要加上 0X2000 即可訪問 GIC 的 CPU 接口段寄存器。
2.3.1 Distributor( 分發器端)
此邏輯塊負責處理各個中斷事件的分發問題,也就是中斷事件應該發送到哪個 CPU Interface 上去。分發器收集所有的中斷源,可以控制每個中斷的優先級, 它總是將優先級最高的中斷事件發送到 CPU 接口端。 分發器端要做的主要工作如下:
①、全局中斷使能控制。
②、控制每一箇中斷的使能或者關閉。
③、設置每個中斷的優先級。
④、設置每個中斷的目標處理器列表。
⑤、設置每個外部中斷的觸發模式:電平觸發或邊沿觸發。
⑥、設置每個中斷屬於組 0 還是組 1。
2.3.2 CPU Interface(CPU 接口端)
CPU 接口端聽名字就知道是和 CPU Core 相連接的,因此每個 CPU Core 都可以在 GIC 中找到一個與之對應的 CPU Interface。CPU 接口端就是分發器和 CPU Core 之間的橋樑,CPU 接口端主要工作如下:
①、使能或者關閉發送到 CPU Core 的中斷請求信號。
②、應答中斷。
③、通知中斷處理完成。
④、設置優先級掩碼,通過掩碼來設置哪些中斷不需要上報給 CPU Core。
⑤、定義搶佔策略。
⑥、當多箇中斷到來的時候,選擇優先級最高的中斷通知給 CPU Core。
上面提到了要用到GIC的基地址
GIC 控制器的寄存器基地址在哪裏呢?這個就需要用到 Cortex-A 的 CP15 協處
理器了。
關於 CP15 協處理器和其相關寄存器的詳細內容請參考下面兩份文檔:
《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》第 1469 頁“B3.17
Oranization of the CP15 registers in a VMSA implementation” 。
《Cortex-A7 Technical ReferenceManua.pdf》第 55 頁“Capter 4 System Control” 。
CP15 協處理器一般用於存儲系統管理,但是在中斷中也會使用到,CP15 協處理器一共有
16 個 32 位寄存器。CP15 協處理器的訪問通過指令完成
MRC: 將 CP15 協處理器中的寄存器數據讀到 ARM 寄存器中。
MCR: 將 ARM 寄存器的數據寫入到 CP15 協處理器寄存器中。
MRC 就是讀 CP15 寄存器, MCR 就是寫 CP15 寄存器, MCR 指令格式如下:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
cond:指令執行的條件碼,如果忽略的話就表示無條件執行。
opc1:協處理器要執行的操作碼。
Rt: ARM 源寄存器,要寫入到 CP15 寄存器的數據就保存在此寄存器中。
CRn: CP15 協處理器的目標寄存器。
CRm: 協處理器中附加的目標寄存器或者源操作數寄存器,如果不需要附加信息就將
CRm 設置爲 C0,否則結果不可預測。
opc2: 可選的協處理器特定操作碼,當不需要的時候要設置爲 0。
MRC 的指令格式和 MCR 一樣,只不過在 MRC 指令中 Rt 就是目標寄存器,也就是從
CP15 指定寄存器讀出來的數據會保存在 Rt 中。而 CRn 就是源寄存器,也就是要讀取的寫處
理器寄存器。
假如我們要將 CP15 中 C0 寄存器的值讀取到 R0 寄存器中,那麼就可以使用如下命令:
MRC p15, 0, r0, c0, c0, 0
中斷使能
IRQ 和 FIQ 總中斷使能
寄存器 CPSR 的 I=1 禁止 IRQ,當 I=0 使能 IRQ;F=1 禁止 FIQ,F=0 使能 FIQ。
我們還有更簡單的指令來完成 IRQ 或者 FIQ 的使能和禁止.
ID0~ID1019 中斷使能和禁止
GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用來完成外部中斷的使能和禁止,對於 Cortex-A7 內核來說中斷 ID 只使用了 512 個。一個 bit 控制一箇中斷 ID 的使能,那麼就需要 512/32=16 個 GICD_ISENABLER 寄存器來完成中斷的使能。同理,也需要 16 個GICD_ICENABLER 寄存器來完成中斷的禁止。
**其中 GICD_ISENABLER0 的 bit[15:0]對應ID15-0 的 SGI 中斷,
GICD_ISENABLER0 的 bit[31:16]對應 ID31-16 的 PPI 中斷。
剩下的GICD_ISENABLER1–GICD_ISENABLER15 就是控制 SPI 中斷的。
中斷優先級設置
優先級數
Cortex-A7 最多可以支持 256 個優先級,數字越小,優先級越高!I.MX6U 選擇了 32 個優先級。在使用中斷的時候需要初始化 GICC_PMR 寄存器,此寄存器用來決定使用幾級優先級。
搶佔優先級和子優先級各佔多少位是由寄存器 GICC_BPR 來決定的, GICC_BPR 寄存器結
GICC_PMR 寄存器只有低 8 位有效,這 8 位最多可以設置 256 個優先級。
I.MX6U 支持 32 個優先級,所以 GICC_PMR 要設置爲 0b11111000。
4.2 搶佔優先級和子優先級位數設置
搶佔優先級和子優先級各佔多少位是由寄存器 GICC_BPR 來決定的。
寄存器GICC_BPR只有低3位有效, 其值不同, 搶佔優先級和子優先級佔用的位數也不同。
爲了簡單起見,一般將所有的中斷優先級位都配置爲搶佔優先級.
下圖是原子Linux core_ca7.h文件的一些常用函數
下面來使用gpio實現外部中斷的一個例子:
中斷初始化:
/* 中斷嵌套計數器 */
static unsigned int irqNesting;
/* 中斷服務函數表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
/*
* @description : 中斷初始化函數
* @param : 無
* @return : 無
*/
void int_init(void)
{
GIC_Init(); /* 初始化GIC */
system_irqtable_init(); /* 初始化中斷表 */
__set_VBAR((uint32_t)0x87800000); /* 中斷向量表偏移,偏移到起始地址 */
}
/*
* @description : 初始化中斷服務函數表
* @param : 無
* @return : 無
*/
void system_irqtable_init(void)
{
unsigned int i = 0;
irqNesting = 0;
/* 先將所有的中斷服務函數設置爲默認值 */
for(i = 0; i < NUMBER_OF_INT_VECTORS; i++)
{
system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);
}
}
/*
* @description : 給指定的中斷號註冊中斷服務函數
* @param - irq : 要註冊的中斷號
* @param - handler : 要註冊的中斷處理函數
* @param - usrParam : 中斷服務處理函數參數
* @return : 無
*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
irqTable[irq].irqHandler = handler;
irqTable[irq].userParam = userParam;
}
/*
* @description : C語言中斷服務函數,irq彙編中斷服務函數會
調用此函數,此函數通過在中斷服務列表中查
找指定中斷號所對應的中斷處理函數並執行。
* @param - giccIar : 中斷號
* @return : 無
*/
void system_irqhandler(unsigned int giccIar)
{
uint32_t intNum = giccIar & 0x3FFUL;
/* 檢查中斷號是否符合要求 */
if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
{
return;
}
irqNesting++; /* 中斷嵌套計數器加一 */
/* 根據傳遞進來的中斷號,在irqTable中調用確定的中斷服務函數*/
irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
irqNesting--; /* 中斷執行完成,中斷嵌套寄存器減一 */
}
/*
* @description : 默認中斷服務函數
* @param - giccIar : 中斷號
* @param - usrParam : 中斷服務處理函數參數
* @return : 無
*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{
while(1)
{
}
}
GPIO初始化:
/*
* @description : GPIO初始化。
* @param - base : 要初始化的GPIO組。
* @param - pin : 要初始化GPIO在組內的編號。
* @param - config : GPIO配置結構體。
* @return : 無
*/
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
base->IMR &= ~(1U << pin);
if(config->direction == kGPIO_DigitalInput) /* GPIO作爲輸入 */
{
base->GDIR &= ~( 1 << pin);
}
else /* 輸出 */
{
base->GDIR |= 1 << pin;
gpio_pinwrite(base,pin, config->outputLogic); /* 設置默認輸出電平 */
}
gpio_intconfig(base, pin, config->interruptMode); /* 中斷功能配置 */
}
/*
* @description : 讀取指定GPIO的電平值 。
* @param - base : 要讀取的GPIO組。
* @param - pin : 要讀取的GPIO腳號。
* @return : 無
*/
int gpio_pinread(GPIO_Type *base, int pin)
{
return (((base->DR) >> pin) & 0x1);
}
/*
* @description : 指定GPIO輸出高或者低電平 。
* @param - base : 要輸出的的GPIO組。
* @param - pin : 要輸出的GPIO腳號。
* @param - value : 要輸出的電平,1 輸出高電平, 0 輸出低低電平
* @return : 無
*/
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{
if (value == 0U)
{
base->DR &= ~(1U << pin); /* 輸出低電平 */
}
else
{
base->DR |= (1U << pin); /* 輸出高電平 */
}
}
/*
* @description : 設置GPIO的中斷配置功能
* @param - base : 要配置的IO所在的GPIO組。
* @param - pin : 要配置的GPIO腳號。
* @param - pinInterruptMode: 中斷模式,參考枚舉類型gpio_interrupt_mode_t
* @return : 無
*/
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode)
{
volatile uint32_t *icr;
uint32_t icrShift;
icrShift = pin;
base->EDGE_SEL &= ~(1U << pin);
if(pin < 16) /* 低16位 */
{
icr = &(base->ICR1);
}
else /* 高16位 */
{
icr = &(base->ICR2);
icrShift -= 16;
}
switch(pin_int_mode)
{
case(kGPIO_IntLowLevel):
*icr &= ~(3U << (2 * icrShift));
break;
case(kGPIO_IntHighLevel):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
break;
case(kGPIO_IntRisingEdge):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
break;
case(kGPIO_IntFallingEdge):
*icr |= (3U << (2 * icrShift));
break;
case(kGPIO_IntRisingOrFallingEdge):
base->EDGE_SEL |= (1U << pin);
break;
default:
break;
}
}
/*
* @description : 使能GPIO的中斷功能
* @param - base : 要使能的IO所在的GPIO組。
* @param - pin : 要使能的GPIO在組內的編號。
* @return : 無
*/
void gpio_enableint(GPIO_Type* base, unsigned int pin)
{
base->IMR |= (1 << pin);
}
/*
* @description : 禁止GPIO的中斷功能
* @param - base : 要禁止的IO所在的GPIO組。
* @param - pin : 要禁止的GPIO在組內的編號。
* @return : 無
*/
void gpio_disableint(GPIO_Type* base, unsigned int pin)
{
base->IMR &= ~(1 << pin);
}
/*
* @description : 清除中斷標誌位(寫1清除)
* @param - base : 要清除的IO所在的GPIO組。
* @param - pin : 要清除的GPIO掩碼。
* @return : 無
*/
void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
{
base->ISR |= (1 << pin);
}
外部中斷配置:
/*
* @description : 初始化外部中斷
* @param : 無
* @return : 無
*/
void exit_init(void)
{
gpio_pin_config_t key_config;
/* 1、設置IO複用 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 複用爲GPIO1_IO18 */
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
/* 2、初始化GPIO爲中斷模式 */
key_config.direction = kGPIO_DigitalInput;
key_config.interruptMode = kGPIO_IntFallingEdge;
key_config.outputLogic = 1;
gpio_init(GPIO1, 18, &key_config);
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中對應的中斷 */
system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 註冊中斷服務函數 */
gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中斷功能 */
}
/*
* @description : GPIO1_IO18最終的中斷處理函數
* @param : 無
* @return : 無
*/
void gpio1_io18_irqhandler(void)
{
static unsigned char state = 0;
/*
*採用延時消抖,中斷服務函數中禁止使用延時函數!因爲中斷服務需要
*快進快出!!這裏爲了演示所以採用了延時函數進行消抖,後面我們會講解
*定時器中斷消抖法!!!
*/
delay(10);
if(gpio_pinread(GPIO1, 18) == 0) /* 按鍵按下了 */
{
state = !state;
beep_switch(state);
}
gpio_clearintflags(GPIO1, 18); /* 清除中斷標誌位 */
}
感謝閱讀,如有錯誤歡迎指正!