ARM(IMX6U)ARM Cortex-A7中断系统(GPIO按键中断驱动蜂鸣器)
參考:Linux之ARM Cortex-A7 中斷系統(tǒng)詳解
作者:一只青木呀
發(fā)布時間: 2020-09-16 16:07:22
網(wǎng)址:https://blog.csdn.net/weixin_45309916/article/details/108290225
目錄
- 1、中斷是什么
- 2、回顧STM32中斷系統(tǒng)
- 2.1、中斷向量表(對應的中斷服務函數(shù))
- 2.2、NVIC(ARM對應的GIC)
- 2.3、中斷使能
- 2.4、中斷服務函數(shù)
- 3、Cortex-A7 中斷系統(tǒng)詳解
- 3.1、中斷向量表
- 中斷向量表介紹
- 創(chuàng)建中斷向量表
- 3.2、GIC 中斷控制器簡介
- 3.2.1、GIC 控制器總覽
- 3.2.2、中斷 ID(中斷源共有128+32=160個)
- 3.3.3、GIC 邏輯分塊(分發(fā)器端和CPU接口端,CP15協(xié)處理器寄存器可以訪問到)
- 3.3、CP15 協(xié)處理器寄存器
- 3.3.1、c0 寄存器
- 3.3.2、c1 寄存器(使能MMU和Cache)
- 3.3.3、c12 寄存器(中斷向量偏移)
- 3.3.4、c15 寄存器
- 3.3.5、 總結(jié)
- 3.4、中斷使能
- 3.4.1、IRQ 和 FIQ 總中斷使能
- 3.4.2、 ID0~ID1019 中斷使能和禁止
- 3.5、中斷優(yōu)先級設(shè)置
- 3.5.1、優(yōu)先級數(shù)配置
- 3.5.2、搶占優(yōu)先級和子優(yōu)先級位數(shù)設(shè)置
- 3.6.3、優(yōu)先級設(shè)置
- 3.6.4、總結(jié)
- 4.GPIO按鍵中斷實驗編寫
- 課堂筆記
- 移植SDK包中斷相關(guān)文件
- 修改匯編start.S(編寫中斷向量表 -> Rest中斷 -> SVC模式跳轉(zhuǎn)main函數(shù) -> IRQ中斷)
- 通用中斷驅(qū)動編寫
- bsp_int.h(匯編調(diào)用這里的system_irqhandler中斷處理函數(shù))
- bsp_int.c(初始化GIC及中斷向量偏移、中斷處理函數(shù)組->定義、初始化<注冊>、實現(xiàn))
- 修改GPIO 驅(qū)動文件(下降沿觸發(fā)方式、使能、清除位)
- GIC也要配置(使能中斷ID、優(yōu)先級、注冊中斷處理函數(shù)、使能GPIO中斷功能)
- bsp_gpio.h
- bsp_gpio.c
- 外部按鍵中斷驅(qū)動文件編寫(類似STM32某個GPIO對應的具體中斷服務函數(shù)編寫 )
- bsp_exit.h
- bsp_exit.c(初始化中斷觸發(fā)方式、配置GIC使能注冊中斷處理服務函數(shù))
- main.c主函數(shù)
- 5.實驗程序的編譯與下載驗證
- 編譯
- 下載驗證
1、中斷是什么
中斷系統(tǒng)是一個處理器重要的組成部分,中斷系統(tǒng)極大的提高了 CPU 的執(zhí)行效率
2、回顧STM32中斷系統(tǒng)
STM32 的中斷系統(tǒng)主要有以下幾個關(guān)鍵點:
- ①、 中斷向量表。
- ②、 NVIC(內(nèi)嵌向量中斷控制器)。
- ③、 中斷使能。
- ④、 中斷服務函數(shù)。
課堂筆記:
2.1、中斷向量表(對應的中斷服務函數(shù))
中斷向量表是一個表,這個表里面存放的是中斷向量。中斷服務程序的入口地址或存放中斷服務程序的首地址(就是中斷服務函數(shù)名)成為中斷向量,因此中斷向量表是一系列中斷服務程序入口地址組成的表。這些中斷服務程序(函數(shù))在中斷向量表中的位置是由半導體廠商定好的,當某個中斷被觸發(fā)以后就會自動跳轉(zhuǎn)到中斷向量表中對應的中斷服務程序(函數(shù))入口地址處。中斷向量表在整個程序的最前面,比如 STM32F103 的中斷向量表如下所示:
__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD NMI_Handler ; NMI HandlerDCD HardFault_Handler ; Hard Fault HandlerDCD MemManage_Handler ; MPU Fault HandlerDCD BusFault_Handler ; Bus Fault HandlerDCD UsageFault_Handler ; Usage Fault HandlerDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVC_Handler ; SVCall HandlerDCD DebugMon_Handler ; Debug Monitor HandlerDCD 0 ; ReservedDCD PendSV_Handler ; PendSV HandlerDCD SysTick_Handler ; SysTick HandlerExternal InterruptsDCD WWDG_IRQHandler ; Window WatchdogDCD PVD_IRQHandler ; PVD through EXTI Line detectDCD TAMPER_IRQHandler ; TamperDCD RTC_IRQHandler ; RTCDCD FLASH_IRQHandler ; Flash/* 省略掉其它代碼 */DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & l5上表就是 STM32F103 的中斷向量表,中斷向量表都是鏈接到代碼的最前面,比如一般 ARM 處理器都是從地址 0X00000000 開始執(zhí)行指令的,那么中斷向量表就是從 0X00000000 開始存放的。上表中第 1 行的“__initial_sp”就是第一條中斷向量,存放的是棧頂指針,接下來是第 2 行復位中斷復位函數(shù) Reset_Handler 的入口地址,依次類推,直到第 27 行的最后一個中斷服務函數(shù) DMA2_Channel4_5_IRQHandler 的入口地址,這樣 STM32F103 的中斷向量表就建好了。
我們說 ARM 處理器都是從地址 0X00000000 開始運行的,但是我們學習 STM32 的時候代碼是下載到 0X8000000 開始的存儲區(qū)域中。因此中斷向量表是存放到 0X8000000 地址處的,而不是 0X00000000,這樣不是就出錯了嗎?為了解決這個問題, Cortex-M 架構(gòu)引入了一個新的概念——中斷向量表偏移,通過中斷向量表偏移就可以將中斷向量表存放到任意地址處,中斷向量表偏移配置在函數(shù) SystemInit 中完成,通過向 SCB_VTOR 寄存器寫入新的中斷向量表首地址即可,代碼如下所示:
void SystemInit (void) {RCC->CR |= (uint32_t)0x00000001;/* 省略其它代碼 */#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;#elseSCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;#endif }第 8 行和第 10 行就是設(shè)置中斷向量表偏移,第 8 行是將中斷向量表設(shè)置到 RAM 中,第10 行是將中斷向量表設(shè)置到 ROM 中,基本都是將中斷向量表設(shè)置到 ROM 中,也就是地址0X8000000 處。第 10 行用到了 FALSH_BASE 和 VECT_TAB_OFFSET,這兩個都是宏,定義如下所示:
#define FLASH_BASE ((uint32_t)0x08000000) #define VECT_TAB_OFFSET 0x0因此第 10 行的代碼就是: SCB->VTOR=0X080000000,中斷向量表偏移設(shè)置完成。
STM32中斷向量表和中斷向量偏移和I.MX6U中斷向量表和中斷向量偏移的關(guān)系?
I.MX6U 所使用的 Cortex-A7 內(nèi)核也有中斷向量表和中斷向量表偏移,而且其含義和 STM32 是一模一樣的!只是用到的寄存器不同而已,概念完全相同。
2.2、NVIC(ARM對應的GIC)
中斷系統(tǒng)得有個管理機構(gòu),對于 STM32 這種 Cortex-M 內(nèi)核的單片機來說這個管理機構(gòu)叫做 NVIC,全稱叫做 Nested Vectored Interrupt Controller。
作用就是中斷管理機構(gòu),使能和關(guān)閉指定的中斷、設(shè)置中斷優(yōu)先級。
關(guān)于 NVIC 本教程不作詳細的講解,既然 Cortex-M 內(nèi)核有個中斷系統(tǒng)的管理機構(gòu)—NVIC,那么 I.MX6U 所使用的 Cortex-A7 內(nèi)核是不是也有個中斷系統(tǒng)管理機構(gòu)?答案是肯定的,不過 Cortex-A 內(nèi)核的中斷管理機構(gòu)不叫做NVIC,而是叫做 GIC,全稱是 general interrupt controller。
2.3、中斷使能
要使用某個外設(shè)的中斷,肯定要先使能這個外設(shè)的中斷,以 STM32F103 的 PE2 這個 IO 為例,假如我們要使用 PE2 的輸入中斷肯定要使用如下代碼來使能對應的中斷:
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //搶占優(yōu)先級 2, NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子優(yōu)先級 2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中斷通道 NVIC_Init(&NVIC_InitStructure);上述代碼就是使能 PE2 對應的 EXTI2 中斷,同理,如果要使用 I.MX6U 的某個中斷的話也需要使能其對應的中斷。
2.4、中斷服務函數(shù)
我們使用中斷的目的就是為了使用中斷服務函數(shù),當中斷發(fā)生以后中斷服務函數(shù)就會被調(diào)用,我們要處理的工作就可以放到中斷服務函數(shù)中去完成。同樣以 STM32F103 的 PE2 為例,其中斷服務函數(shù)如下所示:
/* 外部中斷 2 服務程序 */ void EXTI2_IRQHandler(void) { /* 中斷處理代碼 */ }當PE2 引腳的中斷觸發(fā)以后就會調(diào)用其對應的中斷處理函數(shù) EXTI2_IRQHandler,我們可以在函數(shù) EXTI2_IRQHandler 中添加中斷處理代碼。同理, I.MX6U 也有中斷服務函數(shù),當某個外設(shè)中斷發(fā)生以后就會調(diào)用其對應的中斷服務函數(shù)。
3、Cortex-A7 中斷系統(tǒng)詳解
3.1、中斷向量表
中斷向量表介紹
跟 STM32 一樣, Cortex-A7 也有中斷向量表,中斷向量表也是在代碼的最前面。 CortexA7 內(nèi)核有 8 個異常中斷,這 8 個異常中斷的中斷向量表如下表所示:
| 0X00 | 復位中斷(Rest) | 特權(quán)模式(SVC) |
| 0X04 | 未定義指令中斷(Undefined Instruction) | 未定義指令中止模式(Undef) |
| 0X08 | 軟中斷(Software Interrupt,SWI) | 特權(quán)模式(SVC) |
| 0X0C | 指令預取中止中斷(Prefetch Abort) | 中止模式 |
| 0X10 | 數(shù)據(jù)訪問中止中斷(Data Abort) | 中止模式 |
| 0X14 | 未使用(Not Used) | 未使用 |
| 0X18 | IRQ 中斷(IRQ Interrupt) | 外部中斷模式(IRQ) |
| 0X1C | FIQ 中斷(FIQ Interrupt) | 快速中斷模式(FIQ) |
中斷向量表里面都是中斷服務函數(shù)的入口地址,因此一款芯片有什么中斷都是可以從中斷向量表看出來的。從上表中可以看出, Cortex-A7 一共有 8 個中斷,而且還有一個中斷向量未使用,實際只有 7 個中斷。和“示例代碼的 STM32F103 中斷向量表比起來少了很多!難道一個能跑 Linux 的芯片只有這 7 個中斷?明顯不可能的!那類似 STM32 中的EXTI9_5_IRQHandler、 TIM2_IRQHandler 這樣的中斷向量在哪里? I2C、 SPI、定時器等等的中斷怎么處理呢?
這個就是 Cortex-A 和 Cotex-M 在中斷向量表這一塊的區(qū)別,對于 Cortex-M 內(nèi)核來說,中斷向量表列舉出了一款芯片所有的中斷向量,包括芯片外設(shè)的所有中斷。對于 CotexA 內(nèi)核來說并沒有這么做,在上表中有個 IRQ 中斷, Cortex-A 內(nèi)核 CPU 的所有外部中斷都屬于這個 IQR 中斷,當任意一個外部中斷發(fā)生的時候都會觸發(fā) IRQ 中斷。在 IRQ 中斷服務函數(shù)里面就可以讀取指定的寄存器來判斷發(fā)生的具體是什么中斷,進而根據(jù)具體的中斷做出相應的處理。這些外部中斷和 IQR 中斷的關(guān)系如圖 所示:
在上圖中,左側(cè)的 Software0_IRQn~PMU_IRQ2_IRQ 這些都是 I.MX6U 的中斷,他們都屬于 IRQ 中斷。當圖左側(cè)這些中斷中任意一個發(fā)生的時候 IRQ 中斷都會被觸發(fā),所以我們需要在 IRQ 中斷服務函數(shù)中判斷究竟是左側(cè)的哪個中斷發(fā)生了,然后再做出具體的處理。
在中斷系統(tǒng)表中一共有 7 個中斷,簡單介紹一下這 7 個中斷:
| ①、復位中斷(Rest) | CPU 復位以后(一上電)就會進入復位中斷,我們可以在復位中斷服務函數(shù)里面做一些初始化工作,比如初始化 SP 指針、 DDR 等等。 |
| ②、未定義指令中斷(Undefined Instruction) | 如果指令不能識別的話就會產(chǎn)生此中斷。 |
| ③、軟中斷(Software Interrupt,SWI) | 由 SWI 指令引起的中斷, Linux 的系統(tǒng)調(diào)用會用 SWI指令來引起軟中斷,通過軟中斷來陷入到內(nèi)核空間。 |
| ④、指令預取中止中斷(Prefetch Abort) | 預取指令的出錯的時候會產(chǎn)生此中斷。 |
| ⑤、數(shù)據(jù)訪問中止中斷(Data Abort) | 訪問數(shù)據(jù)出錯的時候會產(chǎn)生此中斷。 |
| ⑥、 IRQ 中斷(IRQ Interrupt) | 外部中斷,前面已經(jīng)說了,芯片內(nèi)部的外設(shè)中斷都會引起此中斷的發(fā)生。 |
| ⑦、 FIQ 中斷(FIQ Interrupt) | 快速中斷,如果需要快速處理中斷的話就可以使用此中。 |
在上面的 7 個中斷中,我們常用的就是復位中斷和 IRQ 中斷,所以我們需要編寫這兩個中斷的中斷服務函數(shù)。
創(chuàng)建中斷向量表
首先我們要根據(jù)表的內(nèi)容來創(chuàng)建中斷向量表(需要用戶自己來創(chuàng)建,而STM32提前幫我們創(chuàng)建好了),中斷向量表處于程序最開始的地方,比如我們前面例程的 start.S 文件最前面,中斷向量表如下:
.global _start /* 全局標號 */_start: /* 中 斷 向 量 表 */ldr pc, =Reset_Handler /* 復位中斷服務函數(shù) */ldr pc, =Undefined_Handler /* 未定義指令中斷服務函數(shù) */ldr pc, =SVC_Handler /* SVC(Supervisor)中斷服務函數(shù) */ldr pc, =PrefAbort_Handler /* 預取終止中斷服務函數(shù) */ldr pc, =DataAbort_Handler /* 數(shù)據(jù)終止中斷服務函數(shù) */ldr pc, =NotUsed_Handler /* 未使用中斷服務函數(shù) */ldr pc, =IRQ_Handler /* IRQ 中斷服務函數(shù) */ldr pc, =FIQ_Handler /* FIQ(快速中斷)未定義中斷服務函數(shù) *//* 復位中斷服務函數(shù)實現(xiàn) */ Reset_Handler: /* 復位中斷具體處理過程 *//* 未定義指令中斷服務函數(shù)實現(xiàn) 都寫成死循環(huán)*/Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC 中斷 */ SVC_Handler:ldr r0, =SVC_Handlerbx r0/* 預取終止中斷 */ PrefAbort_Handler:ldr r0, =PrefAbort_Handlerbx r0/* 數(shù)據(jù)終止中斷 */ DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0/* 未使用的中斷 */ NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0/* IRQ 中斷!重點!!!!! */ IRQ_Handler: /* 復位中斷具體處理過程 */FIQ_Handler:ldr r0, =FIQ_Handlerbx r0第 4 到 11 行是中斷向量表,當指定的中斷發(fā)生以后就會調(diào)用對應的中斷復位函數(shù),比如復位中斷發(fā)生以后就會執(zhí)行第 4 行代碼,也就是調(diào)用函數(shù) Reset_Handler,函數(shù) Reset_Handler就是復位中斷的中斷復位函數(shù),其它的中斷同理。
第 14 到 50 行就是對應的中斷服務函數(shù),中斷服務函數(shù)都是用匯編編寫的,我們實際需要編寫的只有復位中斷服務函數(shù) Reset_Handler 和 IRQ 中斷服務函數(shù) IRQ_Handler,其它的中斷本教程沒有用到,所以都是死循環(huán)。在編寫復位中斷復位函數(shù)和 IRQ 中斷服務函數(shù)之前我們還需要了解一些其它的知識,否則的話就沒法編寫。
課堂筆記:
后面會講:
3.2、GIC 中斷控制器簡介
3.2.1、GIC 控制器總覽
I.MX6U(Cortex-A)的中斷控制器叫做 GIC
GIC 是 ARM 公司給 Cortex-A/R 內(nèi)核提供的一個中斷控制器(關(guān)于GIC 的詳細內(nèi)容請參考開發(fā)板光盤中的文檔《ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf》,不同的架構(gòu)對應的控制器版本不同 ),類似 Cortex-M 內(nèi)核中的NVIC。
目前 GIC 有 4 個版本:V1~V4, V1 是最老的版本,已經(jīng)被廢棄了。 V2~V4 目前正在大量的使用。 GIC V2 是給 ARMv7-A 架構(gòu)使用的,比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等,V3 和 V4 是給 ARMv8-A/R 架構(gòu)使用的,也就是 64 位芯片使用的。
I.MX6U 是 Cortex-A 內(nèi)核的,因此我們主要講解 GIC V2。 GIC V2 最多支持 8 個核。 ARM 會根據(jù) GIC 版本的不同研發(fā)出不同的 IP 核,那些半導體廠商直接購買對應的 IP 核即可,比如 ARM 針對 GIC V2 就開發(fā)出了 GIC400 這個中斷控制器 IP 核。
當 GIC 接收到外部中斷信號以后就會報給 ARM 內(nèi)核,但是ARM 內(nèi)核只提供了四個信號給 GIC 來匯報中斷情況: VFIQ、 VIRQ、 FIQ 和 IRQ,他們之間的關(guān)系如圖所示:
在上圖中, GIC 接收眾多的外部中斷,然后對其進行處理,最終就只通過四個信號報給 ARM 內(nèi)核,這四個信號的含義如下:
- VFIQ:虛擬快速 FIQ。
- VIRQ:虛擬快速 IRQ。
- FIQ:快速中斷 IRQ。
- IRQ:外部中斷 IRQ
我們只使用 IRQ,所以相當于 GIC 最終向 ARM 內(nèi)核就上報一個 IRQ信號。那么 GIC 是如何完成這個工作的呢? GICV2 的邏輯圖如圖下所示:
圖中左側(cè)部分就是中斷源,中間部分就是 GIC 控制器,最右側(cè)就是中斷控制器向處理器內(nèi)核發(fā)送中斷信息。我們重點要看的肯定是中間的 GIC 部分, GIC 將眾多的中斷源分為分為三類:
-
①、SPI(Shared Peripheral Interrupt),共享中斷(重點),顧名思義,所有核Core 共享的中斷,這個是最常見的,那些外部中斷都屬于 SPI 中斷(注意!不是 SPI 總線那個中斷) 。比如按鍵中斷、串口中斷等等,這些中斷所有的 Core 都可以處理,不限定特定 Core。
-
②、PPI(Private Peripheral Interrupt),私有中斷,我們說了 GIC 是支持多核的,每個核肯定有自己獨有的中斷。這些獨有的中斷肯定是要指定的核心處理,因此這些中斷就叫做私有中斷。
-
③、SGI(Software-generated Interrupt),軟件中斷,由軟件觸發(fā)引起的中斷,通過向寄存器GICD_SGIR 寫入數(shù)據(jù)來觸發(fā),系統(tǒng)會使用 SGI 中斷來完成多核之間的通信。
3.2.2、中斷 ID(中斷源共有128+32=160個)
中斷源有很多,為了區(qū)分這些不同的中斷源肯定要給他們分配一個唯一 ID,這些 ID 就是中斷 ID。每一個 CPU 最多支持 1020 個中斷 ID,中斷 ID 號為 ID0~ID1019。這 1020 個 ID 包含了 PPI、 SPI 和 SGI,那么這三類中斷是如何分配這 1020 個中斷 ID 的呢?這 1020 個 ID 分配如下:
- ID0~ID15:這 16 個 ID 分配給 SGI。
- ID16~ID31:這 16 個 ID 分配給 PPI。
- ID32~ID1019:這 988 個 ID 分配給 SPI(重點)。
I.MX6U 的總共使用了 128 個中斷 ID,加上前面屬于 PPI 和 SGI 的 32 個 ID, I.MX6U 的中斷源共有 128+32=160個,這 128 個中斷 ID 對應的中斷在《I.MX6ULL 參考手冊》的“3.2 Cortex A7 interrupts”小節(jié),中斷源如表所示:
| 0 | 32 | boot | 用于在啟動異常的時候通知內(nèi)核。 |
| 1 | 33 | ca7_platform | DAP 中斷,調(diào)試端口訪問請求中斷。 |
| 2 | 34 | sdma | SDMA 中斷。 |
| 3 | 35 | tsc | TSC(觸摸)中斷。 |
| 4 | snvs_lp_wrapper 和snvs_hp_wrapper | SNVS 中斷。 | |
| … | … | …… | …… |
| 124 | 156 | 無 | 保留 |
| 125 | 157 | 無 | 保留 |
| 126 | 158 | 無 | 保留 |
| 127 | 159 | pmu | PMU中斷 |
這里只列舉了一小部分并沒有給出 I.MX6U 完整的中斷源
NXP 官方 SDK中的文件 MCIMX6Y2C.h,在此文件中定義了一個枚舉類型 IRQn_Type,此枚舉類型就枚舉出了 I.MX6U 的所有中斷,代碼如下所示:
typedef enum IRQn {/* Auxiliary constants */NotAvail_IRQn = -128, /**< Not available device specific interrupt *//* Core interrupts */Software0_IRQn = 0, /**< Cortex-A7 Software Generated Interrupt 0 */Software1_IRQn = 1, /**< Cortex-A7 Software Generated Interrupt 1 */Software2_IRQn = 2, /**< Cortex-A7 Software Generated Interrupt 2 */Software3_IRQn = 3, /**< Cortex-A7 Software Generated Interrupt 3 */Software4_IRQn = 4, /**< Cortex-A7 Software Generated Interrupt 4 */Software5_IRQn = 5, /**< Cortex-A7 Software Generated Interrupt 5 */Software6_IRQn = 6, /**< Cortex-A7 Software Generated Interrupt 6 */Software7_IRQn = 7, /**< Cortex-A7 Software Generated Interrupt 7 */Software8_IRQn = 8, /**< Cortex-A7 Software Generated Interrupt 8 */Software9_IRQn = 9, /**< Cortex-A7 Software Generated Interrupt 9 */Software10_IRQn = 10, /**< Cortex-A7 Software Generated Interrupt 10 */Software11_IRQn = 11, /**< Cortex-A7 Software Generated Interrupt 11 */Software12_IRQn = 12, /**< Cortex-A7 Software Generated Interrupt 12 */Software13_IRQn = 13, /**< Cortex-A7 Software Generated Interrupt 13 */Software14_IRQn = 14, /**< Cortex-A7 Software Generated Interrupt 14 */Software15_IRQn = 15, /**< Cortex-A7 Software Generated Interrupt 15 */VirtualMaintenance_IRQn = 25, /**< Cortex-A7 Virtual Maintenance Interrupt */HypervisorTimer_IRQn = 26, /**< Cortex-A7 Hypervisor Timer Interrupt */VirtualTimer_IRQn = 27, /**< Cortex-A7 Virtual Timer Interrupt */LegacyFastInt_IRQn = 28, /**< Cortex-A7 Legacy nFIQ signal Interrupt */SecurePhyTimer_IRQn = 29, /**< Cortex-A7 Secure Physical Timer Interrupt */NonSecurePhyTimer_IRQn = 30, /**< Cortex-A7 Non-secure Physical Timer Interrupt */LegacyIRQ_IRQn = 31, /**< Cortex-A7 Legacy nIRQ Interrupt *//* Device specific interrupts */IOMUXC_IRQn = 32, /**< General Purpose Register 1 from IOMUXC. Used to notify cores on exception condition while boot. */DAP_IRQn = 33, /**< Debug Access Port interrupt request. */SDMA_IRQn = 34, /**< SDMA interrupt request from all channels. */TSC_IRQn = 35, /**< TSC interrupt. */SNVS_IRQn = 36, /**< Logic OR of SNVS_LP and SNVS_HP interrupts. */LCDIF_IRQn = 37, /**< LCDIF sync interrupt. */RNGB_IRQn = 38, /**< RNGB interrupt. */CSI_IRQn = 39, /**< CMOS Sensor Interface interrupt request. */PXP_IRQ0_IRQn = 40, /**< PXP interrupt pxp_irq_0. */SCTR_IRQ0_IRQn = 41, /**< SCTR compare interrupt ipi_int[0]. */SCTR_IRQ1_IRQn = 42, /**< SCTR compare interrupt ipi_int[1]. */WDOG3_IRQn = 43, /**< WDOG3 timer reset interrupt request. */Reserved44_IRQn = 44, /**< Reserved */APBH_IRQn = 45, /**< DMA Logical OR of APBH DMA channels 0-3 completion and error interrupts. */WEIM_IRQn = 46, /**< WEIM interrupt request. */RAWNAND_BCH_IRQn = 47, /**< BCH operation complete interrupt. */RAWNAND_GPMI_IRQn = 48, /**< GPMI operation timeout error interrupt. */UART6_IRQn = 49, /**< UART6 interrupt request. */PXP_IRQ1_IRQn = 50, /**< PXP interrupt pxp_irq_1. */SNVS_Consolidated_IRQn = 51, /**< SNVS consolidated interrupt. */SNVS_Security_IRQn = 52, /**< SNVS security interrupt. */CSU_IRQn = 53, /**< CSU interrupt request 1. Indicates to the processor that one or more alarm inputs were asserted. */USDHC1_IRQn = 54, /**< USDHC1 (Enhanced SDHC) interrupt request. */USDHC2_IRQn = 55, /**< USDHC2 (Enhanced SDHC) interrupt request. */SAI3_RX_IRQn = 56, /**< SAI3 interrupt ipi_int_sai_rx. */SAI3_TX_IRQn = 57, /**< SAI3 interrupt ipi_int_sai_tx. */UART1_IRQn = 58, /**< UART1 interrupt request. */UART2_IRQn = 59, /**< UART2 interrupt request. */UART3_IRQn = 60, /**< UART3 interrupt request. */UART4_IRQn = 61, /**< UART4 interrupt request. */UART5_IRQn = 62, /**< UART5 interrupt request. */eCSPI1_IRQn = 63, /**< eCSPI1 interrupt request. */eCSPI2_IRQn = 64, /**< eCSPI2 interrupt request. */eCSPI3_IRQn = 65, /**< eCSPI3 interrupt request. */eCSPI4_IRQn = 66, /**< eCSPI4 interrupt request. */I2C4_IRQn = 67, /**< I2C4 interrupt request. */I2C1_IRQn = 68, /**< I2C1 interrupt request. */I2C2_IRQn = 69, /**< I2C2 interrupt request. */I2C3_IRQn = 70, /**< I2C3 interrupt request. */UART7_IRQn = 71, /**< UART-7 ORed interrupt. */UART8_IRQn = 72, /**< UART-8 ORed interrupt. */Reserved73_IRQn = 73, /**< Reserved */USB_OTG2_IRQn = 74, /**< USBO2 USB OTG2 */USB_OTG1_IRQn = 75, /**< USBO2 USB OTG1 */USB_PHY1_IRQn = 76, /**< UTMI0 interrupt request. */USB_PHY2_IRQn = 77, /**< UTMI1 interrupt request. */DCP_IRQ_IRQn = 78, /**< DCP interrupt request dcp_irq. */DCP_VMI_IRQ_IRQn = 79, /**< DCP interrupt request dcp_vmi_irq. */DCP_SEC_IRQ_IRQn = 80, /**< DCP interrupt request secure_irq. */TEMPMON_IRQn = 81, /**< Temperature Monitor Temperature Sensor (temperature greater than threshold) interrupt request. */ASRC_IRQn = 82, /**< ASRC interrupt request. */ESAI_IRQn = 83, /**< ESAI interrupt request. */SPDIF_IRQn = 84, /**< SPDIF interrupt. */Reserved85_IRQn = 85, /**< Reserved */PMU_IRQ1_IRQn = 86, /**< Brown-out event on either the 1.1, 2.5 or 3.0 regulators. */GPT1_IRQn = 87, /**< Logical OR of GPT1 rollover interrupt line, input capture 1 and 2 lines, output compare 1, 2, and 3 interrupt lines. */EPIT1_IRQn = 88, /**< EPIT1 output compare interrupt. */EPIT2_IRQn = 89, /**< EPIT2 output compare interrupt. */GPIO1_INT7_IRQn = 90, /**< INT7 interrupt request. */GPIO1_INT6_IRQn = 91, /**< INT6 interrupt request. */GPIO1_INT5_IRQn = 92, /**< INT5 interrupt request. */GPIO1_INT4_IRQn = 93, /**< INT4 interrupt request. */GPIO1_INT3_IRQn = 94, /**< INT3 interrupt request. */GPIO1_INT2_IRQn = 95, /**< INT2 interrupt request. */GPIO1_INT1_IRQn = 96, /**< INT1 interrupt request. */GPIO1_INT0_IRQn = 97, /**< INT0 interrupt request. */GPIO1_Combined_0_15_IRQn = 98, /**< Combined interrupt indication for GPIO1 signals 0 - 15. */GPIO1_Combined_16_31_IRQn = 99, /**< Combined interrupt indication for GPIO1 signals 16 - 31. */GPIO2_Combined_0_15_IRQn = 100, /**< Combined interrupt indication for GPIO2 signals 0 - 15. */GPIO2_Combined_16_31_IRQn = 101, /**< Combined interrupt indication for GPIO2 signals 16 - 31. */GPIO3_Combined_0_15_IRQn = 102, /**< Combined interrupt indication for GPIO3 signals 0 - 15. */GPIO3_Combined_16_31_IRQn = 103, /**< Combined interrupt indication for GPIO3 signals 16 - 31. */GPIO4_Combined_0_15_IRQn = 104, /**< Combined interrupt indication for GPIO4 signals 0 - 15. */GPIO4_Combined_16_31_IRQn = 105, /**< Combined interrupt indication for GPIO4 signals 16 - 31. */GPIO5_Combined_0_15_IRQn = 106, /**< Combined interrupt indication for GPIO5 signals 0 - 15. */GPIO5_Combined_16_31_IRQn = 107, /**< Combined interrupt indication for GPIO5 signals 16 - 31. */Reserved108_IRQn = 108, /**< Reserved */Reserved109_IRQn = 109, /**< Reserved */Reserved110_IRQn = 110, /**< Reserved */Reserved111_IRQn = 111, /**< Reserved */WDOG1_IRQn = 112, /**< WDOG1 timer reset interrupt request. */WDOG2_IRQn = 113, /**< WDOG2 timer reset interrupt request. */KPP_IRQn = 114, /**< Key Pad interrupt request. */PWM1_IRQn = 115, /**< hasRegInstance(`PWM1`)?`Cumulative interrupt line for PWM1. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */PWM2_IRQn = 116, /**< hasRegInstance(`PWM2`)?`Cumulative interrupt line for PWM2. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */PWM3_IRQn = 117, /**< hasRegInstance(`PWM3`)?`Cumulative interrupt line for PWM3. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */PWM4_IRQn = 118, /**< hasRegInstance(`PWM4`)?`Cumulative interrupt line for PWM4. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */CCM_IRQ1_IRQn = 119, /**< CCM interrupt request ipi_int_1. */CCM_IRQ2_IRQn = 120, /**< CCM interrupt request ipi_int_2. */GPC_IRQn = 121, /**< GPC interrupt request 1. */Reserved122_IRQn = 122, /**< Reserved */SRC_IRQn = 123, /**< SRC interrupt request src_ipi_int_1. */Reserved124_IRQn = 124, /**< Reserved */Reserved125_IRQn = 125, /**< Reserved */CPU_PerformanceUnit_IRQn = 126, /**< Performance Unit interrupt ~ipi_pmu_irq_b. */CPU_CTI_Trigger_IRQn = 127, /**< CTI trigger outputs interrupt ~ipi_cti_irq_b. */SRC_Combined_IRQn = 128, /**< Combined CPU wdog interrupts (4x) out of SRC. */SAI1_IRQn = 129, /**< SAI1 interrupt request. */SAI2_IRQn = 130, /**< SAI2 interrupt request. */Reserved131_IRQn = 131, /**< Reserved */ADC1_IRQn = 132, /**< ADC1 interrupt request. */ADC_5HC_IRQn = 133, /**< ADC_5HC interrupt request. */Reserved134_IRQn = 134, /**< Reserved */Reserved135_IRQn = 135, /**< Reserved */SJC_IRQn = 136, /**< SJC interrupt from General Purpose register. */CAAM_Job_Ring0_IRQn = 137, /**< CAAM job ring 0 interrupt ipi_caam_irq0. */CAAM_Job_Ring1_IRQn = 138, /**< CAAM job ring 1 interrupt ipi_caam_irq1. */QSPI_IRQn = 139, /**< QSPI1 interrupt request ipi_int_ored. */TZASC_IRQn = 140, /**< TZASC (PL380) interrupt request. */GPT2_IRQn = 141, /**< Logical OR of GPT2 rollover interrupt line, input capture 1 and 2 lines, output compare 1, 2 and 3 interrupt lines. */CAN1_IRQn = 142, /**< Combined interrupt of ini_int_busoff,ini_int_error,ipi_int_mbor,ipi_int_txwarning and ipi_int_waken */CAN2_IRQn = 143, /**< Combined interrupt of ini_int_busoff,ini_int_error,ipi_int_mbor,ipi_int_txwarning and ipi_int_waken */Reserved144_IRQn = 144, /**< Reserved */Reserved145_IRQn = 145, /**< Reserved */PWM5_IRQn = 146, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */PWM6_IRQn = 147, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */PWM7_IRQn = 148, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */PWM8_IRQn = 149, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */ENET1_IRQn = 150, /**< ENET1 interrupt */ENET1_1588_IRQn = 151, /**< ENET1 1588 Timer interrupt [synchronous] request. */ENET2_IRQn = 152, /**< ENET2 interrupt */ENET2_1588_IRQn = 153, /**< MAC 0 1588 Timer interrupt [synchronous] request. */Reserved154_IRQn = 154, /**< Reserved */Reserved155_IRQn = 155, /**< Reserved */Reserved156_IRQn = 156, /**< Reserved */Reserved157_IRQn = 157, /**< Reserved */Reserved158_IRQn = 158, /**< Reserved */PMU_IRQ2_IRQn = 159 /**< Brown-out event on either core, gpu or soc regulators. */ } IRQn_Type;課堂筆記:
3.3.3、GIC 邏輯分塊(分發(fā)器端和CPU接口端,CP15協(xié)處理器寄存器可以訪問到)
GIC 架構(gòu)分為了兩個邏輯塊: Distributor 和 CPU Interface(CP15協(xié)處理器寄存器可以訪問到),也就是分發(fā)器端和 CPU 接口端。這兩個邏輯塊的含義如下:
Distributor(分發(fā)器端): 從GICV2 的邏輯圖中可以看出,此邏輯塊負責處理各個中斷事件的分發(fā)問題,也就是中斷事件應該發(fā)送到哪個 CPU Interface 上去。分發(fā)器收集所有的中斷源,可以控制每個中斷的優(yōu)先級,它總是將優(yōu)先級最高的中斷事件發(fā)送到 CPU 接口端。分發(fā)器端要做的主要工作如下:
- ①、全局中斷使能控制。
- ②、控制每一個中斷的使能或者關(guān)閉。
- ③、設(shè)置每個中斷的優(yōu)先級。
- ④、設(shè)置每個中斷的目標處理器列表。
- ⑤、設(shè)置每個外部中斷的觸發(fā)模式:電平觸發(fā)或邊沿觸發(fā)。
- ⑥、設(shè)置每個中斷屬于組 0 還是組 1。
CPU Interface(CPU 接口端): CPU 接口端聽名字就知道是和 CPU Core 相連接的,因此在圖中每個 CPU Core 都可以在 GIC 中找到一個與之對應的 CPU Interface。 CPU 接口端就是分發(fā)器和 CPU Core 之間的橋梁, CPU 接口端主要工作如下:
- ①、使能或者關(guān)閉發(fā)送到 CPU Core 的中斷請求信號。
- ②、應答中斷。
- ③、通知中斷處理完成。
- ④、設(shè)置優(yōu)先級掩碼,通過掩碼來設(shè)置哪些中斷不需要上報給 CPU Core。
- ⑤、定義搶占策略。
- ⑥、當多個中斷到來的時候,選擇優(yōu)先級最高的中斷通知給 CPU Core。
構(gòu)體里面的寄存器分為了分發(fā)器端和 CPU 接口端,寄存器定義如下所示:
typedef struct {uint32_t RESERVED0[1024];__IOM uint32_t D_CTLR; /*!< Offset: 0x1000 (R/W) Distributor Control Register */__IM uint32_t D_TYPER; /*!< Offset: 0x1004 (R/ ) Interrupt Controller Type Register */__IM uint32_t D_IIDR; /*!< Offset: 0x1008 (R/ ) Distributor Implementer Identification Register */uint32_t RESERVED1[29];__IOM uint32_t D_IGROUPR[16]; /*!< Offset: 0x1080 - 0x0BC (R/W) Interrupt Group Registers */uint32_t RESERVED2[16];__IOM uint32_t D_ISENABLER[16]; /*!< Offset: 0x1100 - 0x13C (R/W) Interrupt Set-Enable Registers */uint32_t RESERVED3[16];__IOM uint32_t D_ICENABLER[16]; /*!< Offset: 0x1180 - 0x1BC (R/W) Interrupt Clear-Enable Registers */uint32_t RESERVED4[16];__IOM uint32_t D_ISPENDR[16]; /*!< Offset: 0x1200 - 0x23C (R/W) Interrupt Set-Pending Registers */uint32_t RESERVED5[16];__IOM uint32_t D_ICPENDR[16]; /*!< Offset: 0x1280 - 0x2BC (R/W) Interrupt Clear-Pending Registers */uint32_t RESERVED6[16];__IOM uint32_t D_ISACTIVER[16]; /*!< Offset: 0x1300 - 0x33C (R/W) Interrupt Set-Active Registers */uint32_t RESERVED7[16];__IOM uint32_t D_ICACTIVER[16]; /*!< Offset: 0x1380 - 0x3BC (R/W) Interrupt Clear-Active Registers */uint32_t RESERVED8[16];__IOM uint8_t D_IPRIORITYR[512]; /*!< Offset: 0x1400 - 0x5FC (R/W) Interrupt Priority Registers */uint32_t RESERVED9[128];__IOM uint8_t D_ITARGETSR[512]; /*!< Offset: 0x1800 - 0x9FC (R/W) Interrupt Targets Registers */uint32_t RESERVED10[128];__IOM uint32_t D_ICFGR[32]; /*!< Offset: 0x1C00 - 0xC7C (R/W) Interrupt configuration registers */uint32_t RESERVED11[32];__IM uint32_t D_PPISR; /*!< Offset: 0x1D00 (R/ ) Private Peripheral Interrupt Status Register */__IM uint32_t D_SPISR[15]; /*!< Offset: 0x1D04 - 0xD3C (R/ ) Shared Peripheral Interrupt Status Registers */uint32_t RESERVED12[112];__OM uint32_t D_SGIR; /*!< Offset: 0x1F00 ( /W) Software Generated Interrupt Register */uint32_t RESERVED13[3];__IOM uint8_t D_CPENDSGIR[16]; /*!< Offset: 0x1F10 - 0xF1C (R/W) SGI Clear-Pending Registers */__IOM uint8_t D_SPENDSGIR[16]; /*!< Offset: 0x1F20 - 0xF2C (R/W) SGI Set-Pending Registers */uint32_t RESERVED14[40];__IM uint32_t D_PIDR4; /*!< Offset: 0x1FD0 (R/ ) Peripheral ID4 Register */__IM uint32_t D_PIDR5; /*!< Offset: 0x1FD4 (R/ ) Peripheral ID5 Register */__IM uint32_t D_PIDR6; /*!< Offset: 0x1FD8 (R/ ) Peripheral ID6 Register */__IM uint32_t D_PIDR7; /*!< Offset: 0x1FDC (R/ ) Peripheral ID7 Register */__IM uint32_t D_PIDR0; /*!< Offset: 0x1FE0 (R/ ) Peripheral ID0 Register */__IM uint32_t D_PIDR1; /*!< Offset: 0x1FE4 (R/ ) Peripheral ID1 Register */__IM uint32_t D_PIDR2; /*!< Offset: 0x1FE8 (R/ ) Peripheral ID2 Register */__IM uint32_t D_PIDR3; /*!< Offset: 0x1FEC (R/ ) Peripheral ID3 Register */__IM uint32_t D_CIDR0; /*!< Offset: 0x1FF0 (R/ ) Component ID0 Register */__IM uint32_t D_CIDR1; /*!< Offset: 0x1FF4 (R/ ) Component ID1 Register */__IM uint32_t D_CIDR2; /*!< Offset: 0x1FF8 (R/ ) Component ID2 Register */__IM uint32_t D_CIDR3; /*!< Offset: 0x1FFC (R/ ) Component ID3 Register */__IOM uint32_t C_CTLR; /*!< Offset: 0x2000 (R/W) CPU Interface Control Register */__IOM uint32_t C_PMR; /*!< Offset: 0x2004 (R/W) Interrupt Priority Mask Register */__IOM uint32_t C_BPR; /*!< Offset: 0x2008 (R/W) Binary Point Register */__IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register */__OM uint32_t C_EOIR; /*!< Offset: 0x2010 ( /W) End Of Interrupt Register */__IM uint32_t C_RPR; /*!< Offset: 0x2014 (R/ ) Running Priority Register */__IM uint32_t C_HPPIR; /*!< Offset: 0x2018 (R/ ) Highest Priority Pending Interrupt Register */__IOM uint32_t C_ABPR; /*!< Offset: 0x201C (R/W) Aliased Binary Point Register */__IM uint32_t C_AIAR; /*!< Offset: 0x2020 (R/ ) Aliased Interrupt Acknowledge Register */__OM uint32_t C_AEOIR; /*!< Offset: 0x2024 ( /W) Aliased End Of Interrupt Register */__IM uint32_t C_AHPPIR; /*!< Offset: 0x2028 (R/ ) Aliased Highest Priority Pending Interrupt Register */uint32_t RESERVED15[41];__IOM uint32_t C_APR0; /*!< Offset: 0x20D0 (R/W) Active Priority Register */uint32_t RESERVED16[3];__IOM uint32_t C_NSAPR0; /*!< Offset: 0x20E0 (R/W) Non-secure Active Priority Register */uint32_t RESERVED17[6];__IM uint32_t C_IIDR; /*!< Offset: 0x20FC (R/ ) CPU Interface Identification Register */uint32_t RESERVED18[960];__OM uint32_t C_DIR; /*!< Offset: 0x3000 ( /W) Deactivate Interrupt Register */ } GIC_Type;結(jié)構(gòu)體 GIC_Type 就是 GIC 控制器,列舉出了 GIC 控制器的所有寄存器,可以通過結(jié)構(gòu)體 GIC_Type 來訪問 GIC 的所有寄存器。
第 5 行是 GIC 的分發(fā)器端相關(guān)寄存器,其相對于 GIC 基地址偏移為 0X1000,因此我們獲取到 GIC 基地址以后只需要加上 0X1000 即可訪問 GIC 分發(fā)器端寄存器。
第 51 行是 GIC 的 CPU 接口端相關(guān)寄存器,其相對于 GIC 基地址的偏移為 0X2000,同樣的,獲取到 GIC 基地址以后只需要加上 0X2000 即可訪問 GIC 的 CPU 接口段寄存器。那么問題來了? GIC 控制器的寄存器基地址在哪里呢?這個就需要用到 Cortex-A 的 CP15 協(xié)處理器了
3.3、CP15 協(xié)處理器寄存器
關(guān) 于 CP15 協(xié)處理 器和其 相關(guān)寄存 器的詳細 內(nèi)容 請參考下 面兩份文 檔: 《 ARMArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》 第 1469 頁“B3.17 Oranizationof the CP15 registers in a VMSA implementation”。《Cortex-A7 Technical ReferenceManua.pdf》 第55 頁“Capter 4 System Control”。
CP15 協(xié)處理器一般用于存儲系統(tǒng)管理,但是在中斷中也會使用到, CP15 協(xié)處理器一共有16 個 32 位寄存器。 CP15 協(xié)處理器的訪問通過如下幾個指令完成:
| MRC | 將 CP15 協(xié)處理器中的寄存器數(shù)據(jù)讀到 ARM 寄存器中。 |
| MCR | 將 ARM 寄存器的數(shù)據(jù)寫入到 CP15 協(xié)處理器寄存器中。 |
指令格式:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>| cond | 指令執(zhí)行的條件碼,如果忽略的話就表示無條件執(zhí)行。 |
| opc1 | 協(xié)處理器要執(zhí)行的操作碼。 |
| Rt | ARM 源寄存器,要寫入到 CP15 寄存器的數(shù)據(jù)就保存在此寄存器中。 |
| CRn | CP15 協(xié)處理器的目標寄存器。 |
| CRm | 協(xié)處理器中附加的目標寄存器或者源操作數(shù)寄存器,如果不需要附加信息就將CRm |
| opc2 | 可選的協(xié)處理器特定操作碼,當不需要的時候要設(shè)置為 0。 |
MRC 的指令格式和 MCR 一樣,只不過在 MRC 指令中 Rt 就是目標寄存器,也就是從CP15 指定寄存器讀出來的數(shù)據(jù)會保存在 Rt 中。而 CRn 就是源寄存器,也就是要讀取的寫處理器寄存器。
假如我們要將 CP15 中 C0 寄存器的值讀取到 R0 寄存器中,那么就可以使用如下命令:
MRC p15, 0, r0, c0, c0, 0CP15 協(xié)處理器有 16 個 32 位寄存器, c0~c15
3.3.1、c0 寄存器
CP15 協(xié)處理器有 16 個 32 位寄存器, c0~c15,在使用 MRC 或者 MCR 指令訪問這 16 個寄存器的時候,指令中的 CRn、 opc1、 CRm 和 opc2 通過不同的搭配,其得到的寄存器含義是不同的。比如 c0 在不同的搭配情況下含義如下圖所示:
在圖中當 MRC/MCR 指令中的 CRn=c0, opc1=0, CRm=c0, opc2=0 的時候就表示此時的 c0 就是 MIDR 寄存器,也就是主 ID 寄存器,這個也是 c0 的基本作用。對于 Cortex-A7內(nèi)核來說, c0 作為 MDIR 寄存器的時候其含義如圖所示:
在圖中各位所代表的含義如下:
| bit31:24 | 廠商編號, 0X41, ARM。 |
| bit23:20 | 內(nèi)核架構(gòu)的主版本號, ARM 內(nèi)核版本一般使用 rnpn 來表示,比如 r0p1,其中 r0后面的 0 就是內(nèi)核架構(gòu)主版本號。 |
| bit19:16 | 架構(gòu)代碼, 0XF, ARMv7 架構(gòu)。 |
| bit15:4 | 內(nèi)核版本號, 0XC07, Cortex-A7 MPCore 內(nèi)核。 |
| bit3:0 | 內(nèi)核架構(gòu)的次版本號, rnpn 中的 pn,比如 r0p1 中 p1 后面的 1 就是次版本號。 |
3.3.2、c1 寄存器(使能MMU和Cache)
c1 寄存器同樣通過不同的配置,其代表的含義也不同,如圖所示:
在圖中當 MRC/MCR 指令中的 CRn=c1, opc1=0, CRm=c0, opc2=0 的時候就表示此時的 c1 就是 SCTLR 寄存器,也就是系統(tǒng)控制寄存器,這個是 c1 的基本作用。 SCTLR 寄存器主要是完成控制功能的,比如使能或者禁止 MMU、 I/D Cache 等, c1 作為 SCTLR 寄存器的時候其含義如下圖所示:
SCTLR 的位比較多,我們就只看本次會用到的幾個位:
| bit13 | V , 中斷向量表基地址選擇位,為 0 的話中斷向量表基地址為 0X00000000,軟件可以使用 VBAR 來重映射此基地址,也就是中斷向量表重定位。為 1 的話中斷向量表基地址為0XFFFF0000,此基地址不能被重映射。 |
| bit12 | I, I Cache 使能位,為 0 的話關(guān)閉 I Cache,為 1 的話使能 I Cache。 |
| bit11 | Z,分支預測使能位,如果開啟 MMU 的話,此位也會使能。 |
| bit10 | SW, SWP 和 SWPB 使能位,當為 0 的話關(guān)閉 SWP 和 SWPB 指令,當為 1 的時候就使能 SWP 和 SWPB 指令。 |
| bit9:3 | 未使用,保留。 |
| bit2 | C, D(D代表date) Cache 和緩存一致性使能位,為 0 的時候禁止 D Cache 和緩存一致性,為 1 時使能。 |
| bit1 | A,內(nèi)存對齊檢查使能位,為 0 的時候關(guān)閉內(nèi)存對齊檢查,為 1 的時候使能內(nèi)存對齊檢查。 |
| bit0 | M, MMU 使能位,為 0 的時候禁止 MMU,為 1 的時候使能 MMU。 |
如果要讀寫 SCTLR 的話,就可以使用如下命令:
MRC p15, 0, <Rt>, c1, c0, 0 ;讀取 SCTLR 寄存器,數(shù)據(jù)保存到 Rt 中。 MCR p15, 0, <Rt>, c1, c0, 0 ;將 Rt 中的數(shù)據(jù)寫到 SCTLR(c1)寄存器中。3.3.3、c12 寄存器(中斷向量偏移)
c12 寄存器通過不同的配置,其代表的含義也不同,如圖所示:
在上圖中當 MRC/MCR 指令中的 CRn=c12, opc1=0, CRm=c0, opc2=0 的時候就表示此時 c12 為 VBAR 寄存器,也就是向量表基地址寄存器。設(shè)置中斷向量表偏移的時候就需要將新的中斷向量表基地址寫入 VBAR 中,比如在前面的例程中,代碼鏈接的起始地址為0X87800000,而中斷向量表肯定要放到最前面,也就是 0X87800000 這個地址處。所以就需要設(shè)置 VBAR 為 0X87800000,設(shè)置命令如下:
3.3.4、c15 寄存器
c15 寄存器也可以通過不同的配置得到不同的含義
在圖中,我們需要 c15 作為 CBAR 寄存器,因為 GIC 的基地址就保存在 CBAR中,我們可以通過如下命令獲取到 GIC 基地址:
獲取到 GIC 基地址以后就可以設(shè)置 GIC 相關(guān)寄存器了,比如我們可以讀取當前中斷 ID,當前中斷 ID 保存在 GICC_IAR 中,寄存器 GICC_IAR 屬于 CPU 接口端寄存器,寄存器地址相對于 CPU 接口端起始地址的偏移為 0XC,因此獲取當前中斷 ID 的代碼如下:
MRC p15, 4, r1, c15, c0, 0 ;獲取 GIC 基地址 ADD r1, r1, #0X2000 ;GIC 基地址加 0X2000 得到 CPU 接口端寄存器起始地址 LDR r0, [r1, #0XC] ;讀取 CPU 接口端起始地址+0XC 處的寄存器值,也就是寄存器GIC_IAR 的值3.3.5、 總結(jié)
| c0 | 寄存器可以獲取到處理器內(nèi)核信息; |
| c1 | 寄存器可以使能或禁止 MMU、 I/D Cache 等; |
| c12 | 寄存器可以設(shè)置中斷向量偏移; |
| c15 | 寄存器可以獲取 GIC 基地址。 |
3.4、中斷使能
中斷使能包括兩部分,一個是 IRQ 或者 FIQ 總中斷使能,另一個就是 ID0~ID1019 這 1020個中斷源的使能。
3.4.1、IRQ 和 FIQ 總中斷使能
IRQ 和 FIQ 分別是外部中斷和快速中斷的總開關(guān),就類似家里買的進戶總電閘,然后ID0~ID1019 這 1020 個中斷源就類似家里面的各個電器開關(guān)。要想開電視,那肯定要保證進戶總電閘是打開的,因此要想使用 I.MX6U 上的外設(shè)中斷就必須先打開 IRQ 中斷(本教程不使用FIQ)。在“6.3.2 程序狀態(tài)寄存器”小節(jié)已經(jīng)講過了,寄存器 CPSR 的 I=1 禁止 IRQ,當 I=0 使能 IRQ; F=1 禁止 FIQ, F=0 使能 FIQ。我們還有更簡單的指令來完成 IRQ 或者 FIQ 的使能和禁止,圖表所示:
| cpsid i | 禁止 IRQ 中斷。 |
| cpsie i | 使能 IRQ 中斷。 |
| cpsid f | 禁止 FIQ 中斷。 |
| cpsie f | 使能 FIQ 中斷 |
3.4.2、 ID0~ID1019 中斷使能和禁止
GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用來完成外部中斷的使能和禁止,對于 Cortex-A7 內(nèi)核來說中斷 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 中斷的。
3.5、中斷優(yōu)先級設(shè)置
3.5.1、優(yōu)先級數(shù)配置
學過 STM32 都知道 Cortex-M 的中斷優(yōu)先級分為搶占優(yōu)先級和子優(yōu)先級,兩者是可以配置的。同樣的 Cortex-A7 的中斷優(yōu)先級也可以分為搶占優(yōu)先級和子優(yōu)先級,兩者同樣是可以配置的。 Cortex-A7 最多可以支持 256 個優(yōu)先級,數(shù)字越小,優(yōu)先級越高!半導體廠商自行決定選擇多少個優(yōu)先級。 I.MX6U 選擇了 32 個優(yōu)先級。在使用中斷的時候需要初始化 GICC_PMR 寄存器,此寄存器用來決定使用幾級優(yōu)先級,寄存器結(jié)構(gòu)如圖所示:
GICC_PMR 寄存器只有低 8 位有效,這 8 位最多可以設(shè)置 256 個優(yōu)先級,其他優(yōu)先級數(shù)設(shè)置如下表所示:
| 11111111 | 256 個優(yōu)先級。 |
| 11111110 | 128 個優(yōu)先級。 |
| 11111100 | 64 個優(yōu)先級。 |
| 11111000 | 32 個優(yōu)先級 |
| 11110000 | 16 個優(yōu)先級。 |
I.MX6U 支持 32 個優(yōu)先級,所以 GICC_PMR 要設(shè)置為 0b11111000。
3.5.2、搶占優(yōu)先級和子優(yōu)先級位數(shù)設(shè)置
搶占優(yōu)先級和子優(yōu)先級各占多少位是由寄存器 GICC_BPR 來決定的, GICC_BPR 寄存器結(jié)構(gòu)如下圖所示:
寄存器 GICC_BPR 只有低 3 位有效,其值不同,搶占優(yōu)先級和子優(yōu)先級占用的位數(shù)也不同,配置如下表所示:
| 0 | [7:1] | [0] | 7 級搶占優(yōu)先級, 1 級子優(yōu)先級。 |
| 1 | [7:2] | [1:0] | 6 級搶占優(yōu)先級, 2 級子優(yōu)先級。 |
| 2 | [7:3] | [2:0] | 5 級搶占優(yōu)先級, 3 級子優(yōu)先級。 |
| 3 | [7:4] | [3:0] | 4 級搶占優(yōu)先級, 4 級子優(yōu)先級。 |
| 4 | [7:5] | [4:0] | 3 級搶占優(yōu)先級, 5 級子優(yōu)先級。 |
| 5 | [7:6] | [5:0] | 2 級搶占優(yōu)先級, 6 級子優(yōu)先級。 |
| 6 | [7:7] | [6:0] | 1 級搶占優(yōu)先級, 7 級子優(yōu)先級。 |
| 7 | 無 | [7:0] | 0 級搶占優(yōu)先級, 8 級子優(yōu)先級 |
為了簡單起見,一般將所有的中斷優(yōu)先級位都配置為搶占優(yōu)先級,比如 I.MX6U 的優(yōu)先級位數(shù)為 5(32 個優(yōu)先級),所以可以設(shè)置 Binary point 為 2,表示 5 個優(yōu)先級位全部為搶占優(yōu)先級。
3.6.3、優(yōu)先級設(shè)置
前面已經(jīng)設(shè)置好了 I.MX6U 一共有 32 個搶占優(yōu)先級,數(shù)字越小優(yōu)先級越高。具體要使用某個中斷的時候就可以設(shè)置其優(yōu)先級為 0~31。某個中斷 ID 的中斷優(yōu)先級設(shè)置由寄存器D_IPRIORITYR 來完成,前面說了 Cortex-A7 使用了 512 個中斷 ID,每個中斷 ID 配有一個優(yōu)先級寄存器,所以一共有 512 個 D_IPRIORITYR 寄存器。如果優(yōu)先級個數(shù)為 32 的話,使用寄存器 D_IPRIORITYR 的 bit7:4 來設(shè)置優(yōu)先級,也就是說實際的優(yōu)先級要左移 3 位。比如要設(shè)置ID40 中斷的優(yōu)先級為 5,示例代碼如下:
GICD_IPRIORITYR[40] = 5 << 3;3.6.4、總結(jié)
優(yōu)先級設(shè)置主要有三部分:
- ①、設(shè)置寄存器 GICC_PMR,配置優(yōu)先級個數(shù),比如 I.MX6U 支持 32 級優(yōu)先級。
- ②、設(shè)置搶占優(yōu)先級和子優(yōu)先級位數(shù),一般為了簡單起見,會將所有的位數(shù)都設(shè)置為搶占優(yōu)先級。
- ③、設(shè)置指定中斷 ID 的優(yōu)先級,也就是設(shè)置外設(shè)優(yōu)先級。
4.GPIO按鍵中斷實驗編寫
課堂筆記
這里我們用中斷的方式來讀取按鍵狀態(tài),改變之前用輪詢的方式,釋放CPU的資源。
移植SDK包中斷相關(guān)文件
將SDK 包中的文件core_ca7.h 拷貝到本章試驗工程中的“imx6ul”文件夾中,參考試驗“9_int”中core_ca7.h 進行修改。主要留下和GIC 相關(guān)的內(nèi)容,我們重點是需要core_ca7.h 中的10 個API 中斷函數(shù),這10 個函數(shù)如表17.3.1.1 所示:
移植好core_ca7.h 以后,修改文件imx6ul.h,在里面加上如下一行代碼:
#include "core_ca7.h"實現(xiàn)方式是利用C語言內(nèi)嵌匯編,就是使用C語言來實現(xiàn)匯編指令,從而實現(xiàn)一些中斷功能的API,這樣我們就可以調(diào)用了。
修改匯編start.S(編寫中斷向量表 -> Rest中斷 -> SVC模式跳轉(zhuǎn)main函數(shù) -> IRQ中斷)
8個中斷所在手冊的位置。
第6 到14 行是中斷向量表,17.1.2 小節(jié)已經(jīng)講解過了。
第17 到81 行是復位中斷服務函數(shù)Reset_Handler,第19 行先調(diào)用指令“cpsid i”關(guān)閉IRQ,
第24 到30 行是關(guān)閉I/D Cache、MMU、對齊檢測和分支預測。第33 行到42 行是匯編版本的中斷向量表重映射。第50 到68 行是設(shè)置不同模式下的sp 指針,分別設(shè)置IRQ 模式、SYS 模式和SVC 模式的棧指針,每種模式的棧大小都是2MB。第70 行調(diào)用指令“cpsie i”重新打開IRQ 中斷,第72 到79 行是操作CPSR 寄存器來打開IRQ 中斷。當初始化工作都完成以后就可以進入到main 函數(shù)了,第81 行就是跳轉(zhuǎn)到main 函數(shù)。
第110 到144 行是中斷服務函數(shù)IRQ_Handler,這個是本章的重點,因為所有的外部中斷最終都會觸發(fā)IRQ 中斷,所以IRQ 中斷服務函數(shù)主要的工作就是區(qū)分當前發(fā)生的什么中斷(中斷ID)?然后針對不同的外部中斷做出不同的處理。
第111 到115 行是保存現(xiàn)場,第117 到122行是獲取當前中斷號,中斷號被保存到了r0 寄存器中。第131 和132 行才是中斷處理的重點,這兩行相當于調(diào)用了函數(shù)system_irqhandler,函數(shù)system_irqhandler 是一個C 語言函數(shù),此函數(shù)有一個參數(shù),這個參數(shù)中斷號,所以我們需要傳遞一個參數(shù)。匯編中調(diào)用C 函數(shù)如何實現(xiàn)參數(shù)傳遞呢?根據(jù)ATPCS(ARM-Thumb Procedure Call Standard)定義的函數(shù)參數(shù)傳遞規(guī)則,在匯編調(diào)用C 函數(shù)的時候建議形參不要超過4 個,形參可以由r0~r3 這四個寄存器來傳遞,如果形參大于4 個,那么大于4 個的部分要使用堆棧進行傳遞。所以給r0 寄存器寫入中斷號就可以了函數(shù)system_irqhandler 的參數(shù)傳遞,在136 行已經(jīng)向r0 寄存器寫入了中斷號了。中斷的真正處理過程其實是在函數(shù)system_irqhandler 中完成,稍后需要編寫函數(shù)system_irqhandler。
第137 行向GICC_EOIR 寄存器寫入剛剛處理完成的中斷號,當一個中斷處理完成以后必須向GICC_EOIR 寄存器寫入其中斷號表示中斷處理完成。
第139 到143 行就是恢復現(xiàn)場。
第144 行中斷處理完成以后就要重新返回到曾經(jīng)被中斷打斷的地方運行,這里為什么要將lr-4 然后賦給pc 呢?而不是直接將lr 賦值給pc?
ARM 的指令是三級流水線:取指、譯指、執(zhí)行,這三級流水線循環(huán)執(zhí)行,比如當前正在執(zhí)行第一條指令的同時也對第二條指令進行譯碼,第三條指令也同時被取出存放在 R15(PC)中。
pc 指向的是正在取值的地址,這就是很多書上說的pc=當前執(zhí)行指令地址+8。比如下面代碼示例:
0X2000 MOV R1, R0 ;執(zhí)行 0X2004 MOV R2, R3 ;譯指 0X2008 MOV R4, R5 ;取值PC上面示例代碼中,左側(cè)一列是地址,中間是指令,最右邊是流水線。當前正在執(zhí)行0X2000地址處的指令“MOV R1, R0”,但是PC 里面已經(jīng)保存了0X2008 地址處的指令“MOV R4, R5”。
假設(shè)此時發(fā)生了中斷,中斷發(fā)生的時候保存在lr 中的是pc 的值,也就是地址0X2008。當中斷處理完成以后肯定需要回到被中斷點接著執(zhí)行,如果直接跳轉(zhuǎn)到lr 里面保存的地址處(0X2008)開始運行,那么就有一個指令沒有執(zhí)行,那就是地址0X2004 處的指令“MOV R2, R3”,顯然這是一個很嚴重的錯誤!所以就需要將lr-4 賦值給pc,也就是pc=0X2004,從指令“MOV R2,R3”開始執(zhí)行。
通用中斷驅(qū)動編寫
在start.S 文件中我們在中斷服務函數(shù)IRQ_Handler 中調(diào)用了C 函數(shù)system_irqhandler 來處理具體的中斷。此函數(shù)有一個參數(shù),參數(shù)是中斷號,但是函數(shù)system_irqhandler 的具體內(nèi)容還沒有實現(xiàn),所以需要實現(xiàn)函數(shù)system_irqhandler 的具體內(nèi)容。不同的中斷源對應不同的中斷處理函數(shù),I.MX6U 有160 個中斷源,所以需要160 個中斷處理函數(shù)。
我們可以將這些中斷處理函數(shù)放到一個數(shù)組里面,中斷處理函數(shù)在數(shù)組中的標號就是其對應的中斷號。當中斷發(fā)生以后函數(shù)system_irqhandler 根據(jù)中斷號從中斷處理函數(shù)數(shù)組中找到對應的中斷處理函數(shù)并執(zhí)行即可。
在bsp 目錄下新建名為“int”的文件夾,在bsp/int 文件夾里面創(chuàng)建bsp_int.c 和bsp_int.h 這兩個文件。在bsp_int.h 文件里面輸入如下內(nèi)容:
bsp_int.h(匯編調(diào)用這里的system_irqhandler中斷處理函數(shù))
#ifndef _BSP_INT_H #define _BSP_INT_H #include "imx6ul.h" /*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名 : bsp_int.c 作者 : 左忠凱 版本 : V1.0 描述 : 中斷驅(qū)動頭文件。 其他 : 無 論壇 : www.wtmembed.com 日志 : 初版V1.0 2019/1/4 左忠凱創(chuàng)建 ***************************************************************//* 定義中斷處理函數(shù) */ //中斷ID 傳遞給中斷處理函數(shù)的參數(shù)// typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);/* 中斷處理函數(shù)結(jié)構(gòu)體 為了給中斷處理函數(shù)傳參*/ typedef struct _sys_irq_handle {system_irq_handler_t irqHandler; /* 中斷處理函數(shù) */void *userParam; /* 中斷處理函數(shù)的參數(shù) */ } sys_irq_handle_t;/* 函數(shù)聲明 */ void int_init(void);//中斷初始化 void system_irqtable_init(void);//中斷處理函數(shù)組初始化 void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam); void system_irqhandler(unsigned int giccIar); //具體的中斷處理函數(shù)實現(xiàn) void default_irqhandler(unsigned int giccIar, void *userParam); #endif第20~24 行是中斷處理結(jié)構(gòu)體,結(jié)構(gòu)體sys_irq_handle_t 包含一個中斷處理函數(shù)和中斷處理函數(shù)的用戶參數(shù)。一個中斷源就需要一個sys_irq_handle_t 變量,I.MX6U 有160 個中斷源,因此需要160 個sys_irq_handle_t 組成中斷處理數(shù)組。
bsp_int.c(初始化GIC及中斷向量偏移、中斷處理函數(shù)組->定義、初始化<注冊>、實現(xiàn))
#include "bsp_int.h" /*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名 : bsp_int.c 作者 : 左忠凱 版本 : V1.0 描述 : 中斷驅(qū)動文件。 其他 : 無 論壇 : www.wtmembed.com 日志 : 初版V1.0 2019/1/4 左忠凱創(chuàng)建 ***************************************************************//* 中斷嵌套計數(shù)器 記錄當前嵌套了多少個中斷*/ static unsigned int irqNesting;/* 中斷服務函數(shù)表 保留多個結(jié)構(gòu)體*/ //這個宏里面定義了160個中斷ID號 static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];/*中斷初始化函數(shù)* @description : 中斷初始化函數(shù)* @param : 無* @return : 無*/ void int_init(void) {GIC_Init(); /* 初始化GIC */system_irqtable_init(); /* 初始化中斷服務函數(shù)組 */__set_VBAR((uint32_t)0x87800000); /* 中斷向量偏移,偏移到起始地址 要在中斷發(fā)生之前完成 也可以用匯編寫 參考上面匯編文件 這里調(diào)用SDK很方便,不用看那么多寄存器、地址 */ }/* 初始化中斷服務函數(shù)表 對160個中斷服務函數(shù)初始化一個默認值* @description : 初始化中斷服務函數(shù)表 * @param : 無* @return : 無*/ void system_irqtable_init(void) {unsigned int i = 0;irqNesting = 0;/* 先將所有的中斷服務函數(shù)設(shè)置為默認值 */for(i = 0; i < NUMBER_OF_INT_VECTORS; i++){ //下面實現(xiàn)了這個默認函數(shù)system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);} }/*注冊中斷處理函數(shù)* @description : 給指定的中斷號注冊中斷服務函數(shù) * @param - irq : 要注冊的中斷號* @param - handler : 要注冊的中斷處理函數(shù)* @param - usrParam : 中斷服務處理函數(shù)參數(shù)* @return : 無*/ void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) {irqTable[irq].irqHandler = handler;//傳過來default_irqhandler函數(shù)名irqTable[irq].userParam = userParam;//傳過來空指針 }/* 具體的中斷處理函數(shù)實現(xiàn)* @description : C語言中斷服務函數(shù),irq匯編中斷服務函數(shù)會調(diào)用此函數(shù),此函數(shù)通過在中斷服務列表中查找指定中斷號所對應的中斷處理函數(shù)并執(zhí)行。* @param - giccIar : 中斷號* @return : 無*/ //中斷ID 前面匯編文件R0寄存器傳遞來的參數(shù) void system_irqhandler(unsigned int giccIar) {//bit0-bit9全為1 獲取低十位uint32_t intNum = giccIar & 0x3ff;/* 檢查中斷號是否符合要求 */ //大于160 宏定義if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)){ //低十位全部為1 1023 (作者也忘記為啥就要判斷是否1023)return;//返回 不要處理了}irqNesting++; /* 中斷嵌套計數(shù)器加一 *//* 根據(jù)傳遞進來的中斷ID號,在irqTable中調(diào)用確定的中斷服務函數(shù)*/irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);irqNesting--; /* 中斷執(zhí)行完成,中斷嵌套寄存器減一 */}/* 默認中斷服務函數(shù) 什么都不做* @description : 默認中斷服務函數(shù)* @param - giccIar : 中斷號* @param - usrParam : 中斷服務處理函數(shù)參數(shù)* @return : 無*/ void default_irqhandler(unsigned int giccIar, void *userParam) {while(1) {} }第14 行定義了一個變量irqNesting,此變量作為中斷嵌套計數(shù)器。
第17 行定了中斷服務函數(shù)數(shù)組irqTable,這是一個sys_irq_handle_t 類型的結(jié)構(gòu)體數(shù)組,數(shù)組大小為I.MX6U 的中斷源個數(shù),即160 個。
第24~28 行是中斷初始化函數(shù)int_init,在此函數(shù)中首先初始化了GIC,然后初始化了中斷服務函數(shù)表,最終設(shè)置了中斷向量表偏移。
第36~46 行是中斷服務函數(shù)表初始化函數(shù)system_irqtable_init,初始化irqTable,給其賦初值。
第55~59 行是注冊中斷處理函數(shù)system_register_irqhandler,此函數(shù)用來給指定的中斷號注冊中斷處理函數(shù)。如果要使用某個外設(shè)中斷,那就必須調(diào)用此函數(shù)來給這個中斷注冊一個中斷處理函數(shù)。
第68~86 行就是前面在start.S 中調(diào)用的system_irqhandler 函數(shù),此函數(shù)根據(jù)中斷號在中斷處理函數(shù)表irqTable 中取出對應的中斷處理函數(shù)并執(zhí)行。此函數(shù)有一個參數(shù),參數(shù)是中斷號,就是前面匯編文件R0寄存器傳過來的。
第94~99 行是默認中斷處理函數(shù)default_irqhandler,這是一個空函數(shù),主要用來給初始化中斷函數(shù)處理表。
修改GPIO 驅(qū)動文件(下降沿觸發(fā)方式、使能、清除位)
在前幾章節(jié)試驗中我們只是使用到了GPIO 最基本的輸入輸出功能,本章我們需要使用GPIO 的中斷功能。所以需要修改文件GPIO 的驅(qū)動文件bsp_gpio.c 和bsp_gpio.h,加上中斷相關(guān)函數(shù)。
GIC也要配置(使能中斷ID、優(yōu)先級、注冊中斷處理函數(shù)、使能GPIO中斷功能)
bsp_gpio.h
相比前面試驗的bsp_gpio.h 文件,“示例代碼17.3.3.2”中添加了一個新枚舉類型:gpio_interrupt_mode_t,枚舉出了GPIO 所有的中斷觸發(fā)類型。還修改了結(jié)構(gòu)體gpio_pin_config_t,在里面加入了interruptMode 成員變量。最后就是添加了一些跟中斷有關(guān)的函數(shù)聲明,bsp_gpio.h文件的內(nèi)容總體還是比較簡單的。
#ifndef _BSP_GPIO_H #define _BSP_GPIO_H #define _BSP_KEY_H #include "imx6ul.h" /*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名 : bsp_gpio.h 作者 : 左忠凱 版本 : V1.0 描述 : GPIO操作文件頭文件。 其他 : 無 論壇 : www.wtmembed.com 日志 : 初版V1.0 2019/1/4 左忠凱創(chuàng)建V2.0 2019/1/4 左忠凱修改添加GPIO中斷相關(guān)定義***************************************************************//* * 枚舉類型和GPIO結(jié)構(gòu)體定義 */ typedef enum _gpio_pin_direction {kGPIO_DigitalInput = 0U, /* 輸入 */kGPIO_DigitalOutput = 1U, /* 輸出 */ } gpio_pin_direction_t;/** GPIO中斷觸發(fā)類型枚舉 由GPIO_ICR寄存器配置*/ typedef enum _gpio_interrupt_mode {kGPIO_NoIntmode = 0U, /* 無中斷功能 */kGPIO_IntLowLevel = 1U, /* 低電平觸發(fā) */kGPIO_IntHighLevel = 2U, /* 高電平觸發(fā) */kGPIO_IntRisingEdge = 3U, /* 上升沿觸發(fā) */kGPIO_IntFallingEdge = 4U, /* 下降沿觸發(fā) */kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都觸發(fā) */ } gpio_interrupt_mode_t; /** GPIO配置結(jié)構(gòu)體*/ typedef struct _gpio_pin_config {gpio_pin_direction_t direction; /* GPIO方向:輸入還是輸出 */uint8_t outputLogic; /* 如果是輸出的話,默認輸出電平 */gpio_interrupt_mode_t interruptMode; /* 中斷方式 上面定義的枚舉類型*/ } gpio_pin_config_t;/* 函數(shù)聲明 */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config); int gpio_pinread(GPIO_Type *base, int pin); void gpio_pinwrite(GPIO_Type *base, int pin, int value); void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pinInterruptMode); void gpio_enableint(GPIO_Type* base, unsigned int pin); void gpio_disableint(GPIO_Type* base, unsigned int pin); void gpio_clearintflags(GPIO_Type* base, unsigned int pin);#endifbsp_gpio.c
在bsp_gpio.c 文件中首先修改了gpio_init 函數(shù),在此函數(shù)里面添加了中斷配置代碼。另外也新增加了4 個函數(shù),如下:
- gpio_intconfig:配置GPIO 的中斷初始化(觸發(fā)方式)功能。
- gpio_enableint:GPIO 中斷使能函數(shù)。
- gpio_disableint:GPIO 中斷禁止(不使能)函數(shù)。
- gpio_clearintflags:GPIO 中斷標志位清除函數(shù)。
bsp_gpio.c 文件重點就是增加了一些跟GPIO 中斷有關(guān)的函數(shù),都比較簡單。
#include "bsp_gpio.h" /*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名 : bsp_gpio.h 作者 : 左忠凱 版本 : V1.0 描述 : GPIO操作文件。 其他 : 無 論壇 : www.wtmembed.com 日志 : 初版V1.0 2019/1/4 左忠凱創(chuàng)建V2.0 2019/1/4 左忠凱修改:修改gpio_init()函數(shù),支持中斷配置.添加gpio_intconfig()函數(shù),初始化中斷添加gpio_enableint()函數(shù),使能中斷添加gpio_clearintflags()函數(shù),清除中斷標志位***************************************************************//** @description : GPIO初始化。* @param - base : 要初始化的GPIO組。* @param - pin : 要初始化GPIO在組內(nèi)的編號。* @param - config : GPIO配置結(jié)構(gòu)體。* @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); /* 設(shè)置默認輸出電平 */}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); /* 輸出高電平 */} }/*GPIO中斷初始化函數(shù)* @description : 設(shè)置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);//EDGE_SEL寄存器要清零,否則ICR寄存器無效(手冊上有講)if(pin < 16) /* 低16位 */ //不同的引腳使用不同的ICR寄存器{icr = &(base->ICR1);}else /* 高16位 */{icr = &(base->ICR2);icrShift -= 16; //ICR使用0~15來操作}switch(pin_int_mode){case(kGPIO_IntLowLevel)://低電平觸發(fā)*icr &= ~(3U << (2 * icrShift));//同時操作兩個位,我們操作寄存器大部分都是一位一位操作的break;case(kGPIO_IntHighLevel)://高電平觸發(fā)*icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));break; //清零case(kGPIO_IntRisingEdge)://上升沿觸發(fā)*icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));break; //清零case(kGPIO_IntFallingEdge)://下降沿觸發(fā)*icr |= (3U << (2 * icrShift));break;case(kGPIO_IntRisingOrFallingEdge)://跳變沿觸發(fā)base->EDGE_SEL |= (1U << pin);break;default:break;} }/** @description : GPIO的中斷使能* @param - base : 要使能的IO所在的GPIO組。* @param - pin : 要使能的GPIO在組內(nèi)的編號。* @return : 無*/ void gpio_enableint(GPIO_Type* base, unsigned int pin) { base->IMR |= (1 << pin); }/** @description : 禁止 GPIO的中斷功能* @param - base : 要禁止的IO所在的GPIO組。* @param - pin : 要禁止的GPIO在組內(nèi)的編號。* @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); }外部按鍵中斷驅(qū)動文件編寫(類似STM32某個GPIO對應的具體中斷服務函數(shù)編寫 )
bsp_exit.h
本例程的目的是以中斷的方式編寫KEY 按鍵驅(qū)動,當按下KEY 以后觸發(fā)GPIO 中斷,然后在中斷服務函數(shù)里面控制蜂鳴器的開關(guān)。所以接下來就是要編寫按鍵KEY 對應的UART1_CTS 這個IO 的中斷驅(qū)動,在bsp 文件夾里面新建名為“exit”的文件夾,然后在bsp/exit里面新建bsp_exit.c 和bsp_exit.h 兩個文件。在bsp_exit.h 文件中輸入如下代碼:
#ifndef _BSP_EXIT_H #define _BSP_EXIT_H /*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名 : bsp_exit.c 作者 : 左忠凱 版本 : V1.0 描述 : 外部中斷驅(qū)動頭文件。 其他 : 配置按鍵對應的GPIP為中斷模式 論壇 : www.wtmembed.com 日志 : 初版V1.0 2019/1/4 左忠凱創(chuàng)建 ***************************************************************/ #include "imx6ul.h"/* 函數(shù)聲明 */ void exit_init(void); /* 中斷初始化 */ void gpio1_io18_irqhandler(unsigned int giccIar, void *userParam); /* 中斷處理函數(shù) */#endifbsp_exit.c(初始化中斷觸發(fā)方式、配置GIC使能注冊中斷處理服務函數(shù))
/*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名 : bsp_exit.c 作者 : 左忠凱 版本 : V1.0 描述 : 外部中斷驅(qū)動。 其他 : 配置按鍵對應的GPIP為中斷模式 論壇 : www.wtmembed.com 日志 : 初版V1.0 2019/1/4 左忠凱創(chuàng)建 ***************************************************************/ #include "bsp_exit.h" #include "bsp_gpio.h" #include "bsp_int.h" #include "bsp_delay.h" #include "bsp_beep.h"/** @description : 初始化外部中斷* @param : 無* @return : 無*/ void exit_init(void) {gpio_pin_config_t key_config;/* 1、設(shè)置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;//下降沿gpio_init(GPIO1, 18, &key_config);//將GPIO1 IO18初始為下降沿GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);/* 使能GIC中對應的中斷ID */system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注冊中斷服務函數(shù) */gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中斷功能 */ }/** @description : GPIO1_IO18最終的中斷處理函數(shù)* @param : 無* @return : 無*/ //中斷ID void gpio1_io18_irqhandler(unsigned int giccIar, void *userParam) { static unsigned char state = 0;/**中斷服務函數(shù)中禁止使用延時函數(shù)!因為中斷服務需要*快進快出!!這里為了演示所以采用了延時函數(shù)進行消抖,后面我們會講解*定時器中斷消抖法!!!*/delay(10);if(gpio_pinread(GPIO1, 18) == 0) /* 按鍵按下了 */{state = !state;beep_switch(state); //控制蜂鳴器鳴叫}gpio_clearintflags(GPIO1, 18); /* 清除中斷標志位 */ }bsp_exit.c 文件只有兩個函數(shù)exit_init 和gpio1_io18_irqhandler,exit_init 是中斷初始化函數(shù)。
第14~ 24 行都是初始化KEY 所使用的UART1_CTS 這個IO,設(shè)置其復用為GPIO1_IO18,然后配置GPIO1_IO18 為下降沿觸發(fā)中斷。
重點是第26~ 28 行,在26 行調(diào)用函數(shù)GIC_EnableIRQ來使能GPIO_IO18 所對應的中斷總開關(guān),I.MX6U 中GPIO1_IO16~IO31 這16 個IO 共用ID99。
27 行調(diào)用函數(shù)system_register_irqhandler 注冊ID99 所對應的中斷處理函數(shù),GPIO1_IO16~IO31這16 個IO 共用一個中斷處理函數(shù),至于具體是哪個IO 引起的中斷,那就需要在中斷處理函數(shù)中判斷了。
28 行通過函數(shù)gpio_enableint 使能GPIO1_IO18 這個IO 對應的中斷。
函數(shù)gpio1_io18_irqhandler 就是27 行注冊的中斷處理函數(shù),也就是我們學習STM32 的時候某個GPIO 對應的中斷服務函數(shù)。在此函數(shù)里面編寫中斷處理代碼,
第50 行就是蜂鳴器開關(guān)控制代碼,也就是我們本試驗的目的。當中斷處理完成以后肯定要清除中斷標志位,
第53行調(diào)用函數(shù)gpio_clearintflags 來清除GPIO1_IO18 的中斷標志位。
main.c主函數(shù)
#include "bsp_clk.h" #include "bsp_delay.h" #include "bsp_led.h" #include "bsp_beep.h" #include "bsp_key.h" #include "bsp_int.h" #include "bsp_exit.h"/** @description : main函數(shù)* @param : 無* @return : 無*/ int main(void) {unsigned char state = OFF;int_init(); /* 初始化中斷(一定要最先調(diào)用!) */imx6u_clkinit(); /* 初始化系統(tǒng)時鐘 */clk_enable(); /* 使能所有的時鐘 */led_init(); /* 初始化led */beep_init(); /* 初始化beep */key_init(); /* 初始化key */exit_init(); /* 初始化按鍵中斷 */while(1) { state = !state;led_switch(LED0, state);//燈來回閃爍delay(500);}return 0; }5.實驗程序的編譯與下載驗證
編譯
在第十六章實驗的Makefile 基礎(chǔ)上修改變量TARGET 為int,在變量INCDIRS 和SRCDIRS中追加“bsp/exit”和bsp/int,修改完成以后如下所示:
CROSS_COMPILE ?= arm-linux-gnueabihf- TARGET ?= intCC := $(CROSS_COMPILE)gcc LD := $(CROSS_COMPILE)ld OBJCOPY := $(CROSS_COMPILE)objcopy OBJDUMP := $(CROSS_COMPILE)objdumpINCDIRS := imx6ul \bsp/clk \bsp/led \bsp/delay \bsp/beep \bsp/gpio \bsp/key \bsp/exit \bsp/intSRCDIRS := project \bsp/clk \bsp/led \bsp/delay \bsp/beep \bsp/gpio \bsp/key \bsp/exit \bsp/intINCLUDE := $(patsubst %, -I %, $(INCDIRS))SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S)) CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))SFILENDIR := $(notdir $(SFILES)) CFILENDIR := $(notdir $(CFILES))SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o)) COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o)) OBJS := $(SOBJS) $(COBJS)VPATH := $(SRCDIRS).PHONY: clean$(TARGET).bin : $(OBJS)$(LD) -Timx6ul.lds -o $(TARGET).elf $^$(OBJCOPY) -O binary -S $(TARGET).elf $@$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis$(SOBJS) : obj/%.o : %.S$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<$(COBJS) : obj/%.o : %.c$(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<clean:rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)第2 行修改變量TARGET 為“int”,也就是目標名稱為“int”。
第13、14 行在變量INCDIRS 中添加GPIO 中斷和通用中斷驅(qū)動頭文件(.h)路徑。
第23、24 行在變量SRCDIRS 中添加GPIO 中斷和通用中斷驅(qū)動文件(.c)路徑。
鏈接腳本保持不變
下載驗證
使用Make 命令編譯代碼,編譯成功以后使用軟件imxdownload 將編譯完成的int.bin 文件下載到SD 卡中,命令如下:
chmod 777 imxdownload //給予imxdownload 可執(zhí)行權(quán)限,一次即可 ./imxdownload int.bin /dev/sdd //燒寫到SD 卡中,不能燒寫到/dev/sda 或sda1 設(shè)備里面!燒寫成功以后將SD 卡插到開發(fā)板的SD 卡槽中,然后復位開發(fā)板。本試驗效果其實和試驗“8_key”一樣,按下KEY 就會打開蜂鳴器(之前的輪詢模式就改成了中斷模式),再次按下就會關(guān)閉蜂鳴器。LED0 會不斷閃爍,周期大約500ms。
總結(jié)
以上是生活随笔為你收集整理的ARM(IMX6U)ARM Cortex-A7中断系统(GPIO按键中断驱动蜂鸣器)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 曲线的参数方程简介
- 下一篇: 官方系统镜像烧写(windows下使用O