GPIO中斷實驗
Cortex-A7內核有8個異常中斷如圖:
中斷向量表裏面都是中斷服務函數的入口地址,因此一款芯片有什麼中斷都是可以從中斷向量表看出來的。 還有一個未使用,其實只有7箇中斷
對於 Cortex-M 內核來説,中斷向量表列舉出了一款芯片所有的中斷向量,包括芯片外設的所有中斷。對於 CotexA 內核來説並沒有這麼做,在表 17.1.2.1 中有個 IRQ 中斷, Cortex-A 內核 CPU 的所有外部中斷都屬於這個 IRQ 中斷,當任意一個外部中斷髮生的時候都會觸發 IRQ 中斷。在 IRQ 中斷服務函數裏面就可以讀取指定的寄存器來判斷髮生的具體是什麼中斷,進而根據具體的中斷做出相應的處理。 關係如下:
當左側這些中斷任意一個發生了的時候,IRQ中斷都會被觸發,只需要在IRQ中斷服務函數中片段究竟是哪個中斷就能具體事情具體分析處理 了。
中斷類型
除了未使用的異常中斷其他七種介紹如下:
①、復位中斷(Rest), CPU 復位以後就會進入復位中斷,我們可以在復位中斷服務函數裏面做一些初始化工作,比如初始化 SP 指針、 DDR 等等。
②、未定義指令中斷(Undefined Instruction),如果指令不能識別的話就會產生此中斷。
③、軟中斷(Software Interrupt,SWI),由 SWI 指令引起的中斷, Linux 的系統調用會用 SWI指令來引起軟中斷,通過軟中斷來陷入到內核空間。
④、指令預取中止中斷(Prefetch Abort),預取指令的出錯的時候會產生此中斷。
⑤、數據訪問中止中斷(Data Abort),訪問數據出錯的時候會產生此中斷。
⑥、 IRQ 中斷(IRQ Interrupt),外部中斷,前面已經説了,芯片內部的外設中斷都會引起此中斷的發生。
⑦、 FIQ 中斷(FIQ Interrupt),快速中斷,如果需要快速處理中斷的話就可以使用此中斷。
最常用的為 ①、復位中斷(Rest)和③、軟中斷(Software Interrupt,SWI
中斷示例
首先第一步是創建中斷向量表,處於處於程序最開始的地方
1.編寫按鍵中斷例程
使用KEY0的GPIO口UART1_CTS這個IO。編寫UART1_CTS的中斷代碼
2.修改strat.S
- 添加中斷向量表,編寫復位中斷服務函數和IRQ中斷服務函數
_start:
ldr pc, = Reset_Handler //復位中斷服務函數
ldr pc,= Undefined_Handler
ldr pc, = SVC_Handler
ldr pc, = PreAbort_Handler
ldr pc, = DataAbort_Handler
ldr pc, = NotUsed_Handler
ldr pc, = IRQ_Handler
ldr pc, = FIQ_Handler
//復位中斷函數
Reset_Handler:
//重點編寫
//未定義指令中斷函數
Undefined_Handler:
ldr r0, = Undefined_Handler
bx r0
//SVC_終端服務函數
SVC_Handler:
ldr r0, = SVC_Handler
bx r0
//
PreAbort_Handler:
ldr r0, = PreAbort_Handler
bx r0
//數據終止中斷服務函數
DataAbort_Handler:
ldr r0, = DataAbort_Handler
bx r0
NotUsed_Handler:
ldr r0, = NotUsed_Handler
bx r0
IRQ_Handler:
//重點編寫
FIQ_Handler :
ldr r0, = FIQ_Handler
bx r0
除了Reset_Handler和IRQ_Handler以外都是死循環,先學習一下這兩個中斷函數的編寫
在編寫復位中斷復位函數和 IRQ 中斷服務函數之前我們還需要了解一些其它的知識,否則的話就沒法編寫。
GIC
Cortex-A/R 內核提供的一箇中斷控制器 ,類似 Cortex-M 內核中的NVIC。 有四個版本V1 - V4 (V1已經被捨棄)、
GIC V2 是給 ARMv7-A 架構使用的,比如 Cortex-A7(四核處理器)、 Cortex-A9、 Cortex-A15 等, V3 和 V4 是給 ARMv8-A/R 架構使用的,也就是 64 位芯片使用的。
GIC 的主要作用和功能包括:
1.中斷管理與分發
收集中斷請求: GIC 接收來自系統內各種外設(如定時器、串口、GPIO、PCIe控制器等)的中斷請求信號。
中斷識別: 每個中斷源都被分配一個唯一的中斷ID (Interrupt ID)。GIC 負責識別是哪個中斷源發出了請求。
中斷分發: 在多核系統中,GIC 能夠智能地將中斷路由到合適的處理器核心(CPU Core)進行處理。它可以根據配置,將中斷分發給特定的核心,或者分發給當前空閒的核心,從而實現中斷負載均衡。
2中斷優先級管理 (Interrupt Prioritization):
GIC 允許為每個中斷源配置優先級。當有多箇中斷同時發生時,GIC 會根據預設的優先級規則,選擇優先級最高的中斷先發送給處理器。
支持搶佔 (Pre-emption),即高優先級中斷可以打斷正在執行的低優先級中斷服務程序。
3中斷使能/禁用與掛起/清除 (Enabling/Disabling and Pending/Clearing):
提供寄存器接口,允許軟件(通常是操作系統或驅動程序)對特定的中斷進行使能或禁用,以控制其是否可以被處理器響應。
記錄中斷的掛起狀態 (Pending Status),即使中斷當前被禁用或被更高優先級中斷阻塞,其掛起位仍會被設置,表示有中斷請求發生。
提供清除掛起狀態的機制,在中斷服務程序處理完成後,需要清除對應的掛起位。
4中斷類型支持 (Interrupt Types):
- SGI (Software-Generated Interrupts - 軟件生成中斷): 允許一個處理器核心通過寫入GIC的寄存器來向其他處理器核心發送中斷,常用於多核間的通信和同步。
- PPI (Private Peripheral Interrupts - 私有外設中斷): 每個處理器核心都有自己的私有外設中斷,隻影響當前核心。例如,某個核心的私有定時器中斷。
- SPI (Shared Peripheral Interrupts - 共享外設中斷): 系統中所有處理器核心都可以接收和處理的共享外設中斷。大多數外設中斷都屬於此類。
- LPI (Locality-specific Peripheral Interrupts - 區域特定外設中斷): GICv3及更高版本引入的,通常與消息信號中斷 (MSI) 相關聯,用於支持更復雜的系統中斷拓撲。
5虛擬化支持 (Virtualization Support - GICv2/v3/v4/v5):
- 這是 GIC 相對於 NVIC 的一個顯著優勢。現代 ARM 處理器常用於虛擬化環境,例如運行多個虛擬機(VM)。GIC 提供了硬件層面的支持,使得 Hypervisor(虛擬機管理器)可以更高效地管理虛擬機的中斷。
- GIC 可以將物理中斷 (Physical Interrupts) 映射到虛擬中斷 (Virtual Interrupts),並將其注入到相應的虛擬機中,而無需 Hypervisor 頻繁介入,從而降低了虛擬化開銷,提升了性能。
- 最新的 GIC 版本(如 GICv4、GICv5)提供了更強大的虛擬化功能,例如直接向虛擬機注入虛擬SGI和LPI。
6安全管理 (Security Management):
- GIC 支持 ARM 的 TrustZone 技術,可以將中斷分為安全中斷 (Secure Interrupts) 和非安全中斷 (Non-secure Interrupts)
GIC 的內部結構(邏輯塊)
通常分為兩個主要部分:
1 分發器 (Distributor - GICD):
- 負責接收所有的物理中斷源(包括 SGI, PPI, SPI 等)。
- 管理中斷的使能/禁用、優先級設置、路由目標(將中斷髮送到哪個或哪些CPU核心)。
- 維護中斷的掛起狀態。
- 通常每個GIC只有一個分發器。
2 CPU 接口 (CPU Interface - GICC):
- 每個處理器核心都有一個對應的CPU接口。
- CPU接口負責接收分發器發送給該核心的中斷。
- 提供接口讓CPU讀取當前最高優先級的中斷ID,並通知GIC該中斷已被處理(End of Interrupt - EOI)。
- 管理CPU自身的優先級屏蔽(阻止低於某個優先級的中斷)。
ARM 會根據 GIC 版本的不同研發出不同的 IP 核 ,ARM 針對 GIC V2 就開發出了 GIC400 這個中斷控制器 IP 核
當 GIC 接收到外部中斷信號以後就會報給 ARM 內核,但是ARM 內核只提供了四個信號給 GIC 來彙報中斷情況: VFIQ、 VIRQ、 FIQ 和 IRQ
分發器端:此邏輯塊負責處理各個中斷事件的分發問題,也就是中斷事件應該發送到哪個 CPU Interface 上去。分發器收集所有的中斷源,可以控制每個中斷的優先級,它總是將優先級最高的中斷事件發送到 CPU 接口端。分發器端要做的主要工作如下:
①、全局中斷使能控制。②、控制每一箇中斷的使能或者關閉。③、設置每個中斷的優先級。④、設置每個中斷的目標處理器列表。⑤、設置每個外部中斷的觸發模式:電平觸發或邊沿觸發。⑥、設置每個中斷屬於組 0 還是組 1。
CPU Interface(CPU 接口端) :CPU接口端和CPU內核直接相連,在每個CPUCore都可以在GIC中找到一個與之對應的CPU Interface 。 CPU接口端就是分發器和CPUCore 之間的橋樑,主要工作有:①、使能或者關閉發送到 CPU Core 的中斷請求信號 ②、應答中斷。③、通知中斷處理完成。④、設置優先級掩碼,通過掩碼來設置哪些中斷不需要上報給 CPU Core。⑤、定義搶佔策略。⑥、當多箇中斷到來的時候,選擇優先級最高的中斷通知給 CPU Core。
GIC 接收眾多的外部中斷,然後對其進行處理,最終就只通過四個信號報給 ARM 內核
VFIQ:虛擬快速 FIQ 不討論
VIRQ:虛擬外部 IRQ。 不討論
FIQ:快速中斷 IRQ 不討論
IRQ:外部中斷 IRQ。
中斷源分類
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,那麼這三類中斷是如何分配這 1020 箇中斷 ID 的呢?這 1020 個 ID 分配如下:
ID0~ID15:這 16 個 ID 分配給 SGI。***
ID16~ID31:這 16 個 ID 分配給 PPI。
ID32~ID1019:這 988 個 ID 分配給 SPI,像 GPIO 中斷、串口中斷等這些外部中斷 (由半導體廠商根據情況定義)
在移植的MCIMX6Y2.h中
/* ----------------------------------------------------------------------------
-- Interrupt vector numbers
---------------------------------------------------------------------------- */
/*!
* @addtogroup Interrupt_vector_numbers Interrupt vector numbers
* @{
*/
/** Interrupt Number Definitions */
//中斷源160個
#define NUMBER_OF_INT_VECTORS 160 /**< Number of interrupts in the Vector table */
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 */
//.
//.
//.
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;
該枚舉類型IRQn_Type就枚舉了IXM6U的所有中斷
GIC 結構體
core_ca7.h 定義了 GIC 結構體 此結構體裏面的寄存器分為了分發器端和 CPU 接口端,可以通過結構體 GIC_Type 來訪問 GIC 的所有寄存器 ,如分發器端的寄存器,其相對GIC基地址偏移為01000因此我們獲取到 GIC 基地址以後只需要加上 0X1000 即可訪問 GIC 分發器端寄存器。 CPU 接口端相關寄存器 ,其相對於 GIC 基地址的偏移為 0X2000,
GIC 分發器端寄存器 0x1000——0x1fff
CPU 接口端相關寄存器 0x2000 —— 0x3fff
GIC 控制器的寄存器基地址在哪裏呢?這個就需要用到 Cortex-A 的 CP15 協處理器了
CP15 協處理器
CP15 協處理器一般用於存儲系統管理,但是在中斷中也會使用到, CP15 協處理器一共有16 個 32 位寄存器。
CP15 協處理器的訪問通過如下2個指令完成:
MRC: 將 CP15 協處理器中的寄存器數據讀到 ARM 寄存器中。
MCR: 將 ARM 寄存器的數據寫入到 CP15 協處理器寄存器中。
MRC 就是讀 CP15 寄存器, MCR 就是寫 CP15 寄存器, MCR 指令格式如下:
MCR{cond} p15, , , , ,
cond:指令執行的條件碼,如果忽略的話就表示無條件執行。
opc1:協處理器要執行的操作碼。
Rt: ARM 源寄存器,要寫入到 CP15 寄存器的數據就保存在此寄存器中。
CRn: CP15 協處理器的目標寄存器。
CRm: 協處理器中附加的目標寄存器或者源操作數寄存器,如果不需要附加信息就將CRm 設置為 C0,否則結果不可預測。
opc2: 可選的協處理器特定操作碼,當不需要的時候要設置為 0。MRC 的指令格式和 MCR 一樣,只不過在 MRC 指令中 Rt 就是目標寄存器,也就是從CP15 指定寄存器讀出來的數據會保存在 Rt 中。
而 CRn 就是源寄存器,也就是要讀取的寫處理器寄存器。
//將 CP15 中 C0 寄存器(ID寄存器)的值讀取到 R0 寄存器中,那麼就可以使用如下命令:
MRC p15, 0, r0, c0, c0, 0
//讀取C1寄存器到r0中,由上圖可得
MRC p15, 0, r0, c1, c0, 0
//讀取C2寄存器到r0中,由上圖可得
MRC p15, 0, r0, c2, c0, 0
CP15 協處理器有 16 個 32 位寄存器, c0~c15,本章來看一下 c0、 c1、 c12 和 c15 這四個寄存器,因為我們本章實驗要用到這四個寄存器
Cp15寄存器還可以關閉I,D ache 和 MMU
由上圖可知C1寄存器就是SCTLR寄存器
SCTLR 寄存器主要是完成控制功能的,比如使能或者禁止 MMU、 I/D Cache 等, c1 作為 SCTLR 寄存器的時候其含義如圖
SCTLR 的位比較多,我們就只看本章會用到的幾個位:
bit13: V , 中斷向量表基地址選擇位,為 0 的話中斷向量表基地址為 0X00000000,軟件可以使用 VBAR 來重映射此基地址,也就是中斷向量表重定位。為 1 的話中斷向量表基地址為0XFFFF0000,此基地址不能被重映射。
bit12: I, I Cache 使能位,為 0 的話關閉 I Cache,為 1 的話使能 I Cache。bit11: Z,分支預測使能位,如果開啓 MMU 的話,此位也會使能。
bit10: SW, SWP 和 SWPB 使能位,當為 0 的話關閉 SWP 和 SWPB 指令,當為 1 的時候就使能 SWP 和 SWPB 指令。
bit9:3:未使用,保留。
bit2: C, D Cache 和緩存一致性使能位,為 0 的時候禁止 D Cache 和緩存一致性,為 1 時使能。
bit1: A,內存對齊檢查使能位,為 0 的時候關閉內存對齊檢查,為 1 的時候使能內存對齊檢查。
bit0: M, MMU 使能位,為 0 的時候禁止 MMU,為 1 的時候使能 MMU。
/*
*關閉 I,D Cache 和 MMU
* 修改SCTLR寄存器採用 讀 - 改 寫 的方式
*
*/
MRC p15, 0, r0, c1, c0, 0 //讀取SCTLR寄存器到r0寄存器中去
//改:使用bic 指令是“位清除”的意思 結果 = 原始值 AND (NOT 要清零的位的掩碼)
// bic ,目標寄存器, 第一個操作數寄存器 ,第二操作數
//第二操作數可以是 # 開頭的常量。這個常量需要滿足 ARM 架構對立即數的編碼要求(通常是 8 位立即數通過旋轉得到)。
bic r0, r0, #(1 << 12) //關閉I Cache
bic r0, r0, #(1 << 11) //關閉分支預測
bic r0, r0, #(1 << 2) //關閉D Cache 和緩存
bic r0, r0, #(1 << 1) //關閉內存對齊
bic r0, r0, #(1 << 0) //關閉MMU
MCR p15, 0, r0, c1, c0, 0 //r0寄存器寫入SCTLR寄存器到中去
清理BSS段:
//清除BSS段
ldr r0, _bss_start
ldr r1, _bss_end
mov r2, #0
bss_loop:
stmia r0!,{r2}
cmp r0,r1 //比較r0和r1的值
ble bss_loop //如果r0地址小於等於r1,繼續清除bss段
設置中斷向量偏移
將新的中斷向量表首地址寫入CP15協處理器的VBAR寄存器
ldr r0, =0x87800000
dsb //確保之前的數據操作都完成了,然後才允許之後的所有指令繼續執行。
isb //指令會刷新處理器管道 (pipeline),使得 ISB 之後的所有指令都必須從緩存或內存中重新獲取。
MCR p15, 0, r0, c12, c0, 0 //設置VBAR寄存器 = 0x 87800000
dsb
isb
設置處理器進入九種工作模式的指針地址
設置處理器九種工作模式下對應的SP指針,要使用中斷那麼必須設置IRQ模式下的SP指針
cpsid i /* 關閉全局中斷 */
//設置IRQ模式SP指針
mrs r0,cpsr //讀取cpsr到r0
bic r0,r0,#0x1f //按位或清除bit4-0
orr r0,r0,#0x12 //進入IRQ模式
msr cpsr,r0 //將r0寫入cpsr
ldr sp, = 0x80600000 //設置IRQ模式下的sp*
//設置SYS模式的SP指針
mrs r0,cpsr //讀取cpsr到r0
bic r0,r0,#0x1f //按位或清除bit4-0
orr r0,r0,#0x1f //進入SYS模式
msr cpsr,r0 //將r0寫入cpsr
ldr sp, = 0x80400000 //設置SYS模式下的sp*
//設置SVC模式SP指針
mrs r0,cpsr //讀取cpsr到r0
bic r0,r0,#0x1f //按位或清除bit4-0
orr r0,r0,#0x13 //進入SVC模式
msr cpsr,r0 //將r0寫入cpsr
ldr sp, = 0x80200000
cpsie i /* 打開全局中斷 */
跳轉到main函數
b main
Reset_Handler:
- 編寫復位中斷函數,內容如下
①、關閉I.D Cache和MMU
③處理BSS段
②設置處理器九種工作模式下對應的SP指針,要使用中斷那麼必須設置IRQ模式下的SP指針
設置處理器進入SVC模式下
最後一步編寫IRQ_Handler中斷服務函數
.global _start /* 全局標號 */
/*
* 描述: _start函數,首先是中斷向量表的創建
* 參考文檔:ARM Cortex-A(armV7)編程手冊V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM處理器模型和寄存器)
* ARM Cortex-A(armV7)編程手冊V4.0.pdf P165 11.1.1 Exception priorities(異常)
*/
_start:
ldr pc, =Reset_Handler /* 復位中斷 */
ldr pc, =Undefined_Handler /* 未定義中斷 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中斷 */
ldr pc, =PrefAbort_Handler /* 預取終止中斷 */
ldr pc, =DataAbort_Handler /* 數據終止中斷 */
ldr pc, =NotUsed_Handler /* 未使用中斷 */
ldr pc, =IRQ_Handler /* IRQ中斷 */
ldr pc, =FIQ_Handler /* FIQ(快速中斷)未定義中斷 */
/* 復位中斷 */
Reset_Handler:
cpsid i /* 關閉全局中斷 */
/* 關閉I,DCache和MMU
* 採取讀-改-寫的方式。
*/
mrc p15, 0, r0, c1, c0, 0 /* 讀取CP15的C1寄存器到R0中 */
bic r0, r0, #(0x1 << 12) /* 清除C1寄存器的bit12位(I位),關閉I Cache */
bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),關閉D Cache */
bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),關閉對齊 */
bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),關閉分支預測 */
bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),關閉MMU */
mcr p15, 0, r0, c1, c0, 0 /* 將r0寄存器中的值寫入到CP15的C1寄存器中 */
#if 0
/* 彙編版本設置中斷向量表偏移 */
ldr r0, =0X87800000
dsb
isb
mcr p15, 0, r0, c12, c0, 0
dsb
isb
#endif
/* 設置各個模式下的棧指針,
* 注意:IMX6UL的堆棧是向下增長的!
* 堆棧指針地址一定要是4字節地址對齊的!!!
* DDR範圍:0X80000000~0X9FFFFFFF
*/
/* 進入IRQ模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 將r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */
msr cpsr, r0 /* 將r0 的數據寫入到cpsr_c中 */
ldr sp, =0x80600000 /* 設置IRQ模式下的棧首地址為0X80600000,大小為2MB */
/* 進入SYS模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 將r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */
msr cpsr, r0 /* 將r0 的數據寫入到cpsr_c中 */
ldr sp, =0x80400000 /* 設置SYS模式下的棧首地址為0X80400000,大小為2MB */
/* 進入SVC模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 將r0寄存器中的低5位清零,也就是cpsr的M0~M4 */
orr r0, r0, #0x13 /* r0或上0x13,表示使用SVC模式 */
msr cpsr, r0 /* 將r0 的數據寫入到cpsr_c中 */
ldr sp, =0X80200000 /* 設置SVC模式下的棧首地址為0X80200000,大小為2MB */
cpsie i /* 打開全局中斷 */
#if 0
/* 使能IRQ中斷 */
mrs r0, cpsr /* 讀取cpsr寄存器值到r0中 */
bic r0, r0, #0x80 /* 將r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允許IRQ中斷 */
msr cpsr, r0 /* 將r0重新寫入到cpsr中 */
#endif
b main /* 跳轉到main函數 */
/* 未定義中斷 */
Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0
/* SVC中斷 */
SVC_Handler:
ldr r0, =SVC_Handler
bx r0
/* 預取終止中斷 */
PrefAbort_Handler:
ldr r0, =PrefAbort_Handler
bx r0
/* 數據終止中斷 */
DataAbort_Handler:
ldr r0, =DataAbort_Handler
bx r0
/* 未使用的中斷 */
NotUsed_Handler:
ldr r0, =NotUsed_Handler
bx r0
/* FIQ中斷 */
FIQ_Handler:
ldr r0, =FIQ_Handler
bx r0
/* IRQ中斷!重點!!!!! */
IRQ_Handler:
/* 保存現場 */
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 ,其他寄存器由相關中斷機制自動保存,其餘才需要我們手動保存*/
mrs r0, spsr /* 讀取spsr寄存器 */
push {r0} /* 保存spsr寄存器 */
/* 尋找向量表的地址 */
mrc p15, 4, r1, c15, c0, 0 /*CDAR寄存器保存了GIC控制器的基地址
*從CP15的C0寄存器內的值到R1寄存器中
* 參考文檔ARM Cortex-A(armV7)編程手冊V4.0.pdf P49
* Cortex-A7 Technical ReferenceManua.pdf P68 P138
*/
/* 讀取中斷ID並保存 */
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
* GICC_IAR寄存器保存這當前發生中斷的中斷號,我們要根據
* 這個中斷號來絕對調用哪個中斷服務函數
*/
push {r0, r1} /* 保存r0,r1 */
cps #0x13 /* 進入SVC模式,允許其他中斷再次進去 */
push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加載C語言中斷處理函數到r2寄存器中*/
blx r2 /* 運行C語言中斷處理函數,帶有一個參數為GIC_IAR寄存器的值,保存在R0寄存器中 */
pop {lr} /* 執行完C語言中斷服務函數,lr出棧 */
cps #0x12 /* 進入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中斷執行完成,寫EOIR */
pop {r0}
msr spsr_cxsf, r0 /* 恢復spsr */
pop {r0-r3, r12} /* r0-r3,r12出棧 */
pop {lr} /* lr出棧 */
subs pc, lr, #4 /* 將lr-4賦給pc */
中斷服務函數編寫的基本步驟
①保存現場
②尋找中斷執行的地址
③跳轉到中斷服務函數中執行
④清除中斷標誌位(在中斷服務函數中實現)
⑤恢復現場,回到中斷點接着執行,-4的原因是因為保存的pc是自動加4之後的值
為什麼是 subs pc, lr, #4?
移植頭文件
將core_ca7.h移植到imx6ul文件夾裏中。該文件中是Cortex-A7內核通用文件。主要移植包含了GIC結構體和對一些中斷相關寄存器的讀寫操作。
如:
FORCEDINLINE __STATIC_INLINE uint32_t __get_VBAR(void)
{
return __MRC(15, 0, 12, 0, 0);
}
FORCEDINLINE __STATIC_INLINE void __set_VBAR(uint32_t vbar)
{
__MCR(15, 0, vbar, 12, 0, 0);
}
__get_VBAR(void)
__set_VBAR(uint32_t vbar)
兩個函數可以直接獲取和設置中斷向量表的基地址。
重新編寫start.S,用#if 0 #endif註釋掉左邊的代碼 ,可以在int.c中重新實現向量表的偏移
可以在初始化時設置中斷向量偏移地址。
GIC結構體包括了分發器端和CPU端,包括了GIC_Init初始化函數
在ixm6ul.h中引用該文件
#include "core_ca7.h"
2.清理BSS段太前導致中斷向量表無法在0x87800000存放
中斷向量表一定需要在0x87800000存放,將清理BSS段代碼後移。
即87800000必須是
87800000 <_start>:
87800000: e59ff100 ldr pc, [pc, #256] ; 87800108 <IRQ_Handler+0x54>
87800004: e59ff100 ldr pc, [pc, #256] ; 8780010c <IRQ_Handler+0x58>
87800008: e59ff100 ldr pc, [pc, #256] ; 87800110 <IRQ_Handler+0x5c>
8780000c: e59ff100 ldr pc, [pc, #256] ; 87800114 <IRQ_Handler+0x60>
87800010: e59ff100 ldr pc, [pc, #256] ; 87800118 <IRQ_Handler+0x64>
87800014: e59ff100 ldr pc, [pc, #256] ; 8780011c <IRQ_Handler+0x68>
87800018: e59ff100 ldr pc, [pc, #256] ; 87800120 <IRQ_Handler+0x6c>
8780001c: e59ff100 ldr pc, [pc, #256] ; 87800124 <IRQ_Handler+0x70>
中斷初始化
在導入core_ca7.h後對GIC進行初始化並聲明
在int.c中實現中斷的操作
int.c:
void int_Init(void){
GIC_Init();
//中斷向量偏移設置,這裏設置了那麼就不需要在start.S中設置
system_irqtable_init(); //該函數初始化中斷處理函數。
__set_VBAR((uint32_t)0x87800000);
}
在int.h中定義
定義函數指針,後續指向一個具體的中斷處理函數,中斷處理函數指針進行定義
typedef void (*system_irq_handler_t)(unsigned int gicciar, void * param);
//定義一個函數指針,參數是中斷ID號,傳遞給中斷的參數
使用數組指向每一箇中斷處理函數
中斷處理函數結構體
typedef struct _sys_irq_handle
{
system_irq_handler_t irqHandler ;
void *userParam;
}sys_irq_handle_t;
int.c:
定義中斷處理函數表,即160(32ppi+128sgi)箇中斷處理函數
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
static unsigned int irqNesting; //記錄中斷嵌套個數
//初始化上面的中斷處理函數表
void system_irqtable_init(void){
unsigned int i = 0;
irqNesting = 0;
for(i = 0;i< NUMBER_OF_INT_VECTORS;i++){
irqTable[i].irqHandler =default_irqhandler ; //默認中斷處理函數
irqTable[i].userParam = NULL;
}
}
void default_irqhandler(unsigned int gicciar,void *userParam){
while(1){} //默認中斷函數為死循環
}
//註冊中斷處理函數
void system_register_irqhandler(IRQn_Type irq,system_irq_handler_t handler, void *userParam){
irqTable[irq].irqHandler = handler;
irqTable[irq].userParam = userParam;
}
//具體的中斷處理函數,IRQ_Handler會調用此函數
void system_irqhandler(unsigned int gicciar){
uint32_t intNum = gicciar & 0x3ff ;// 9位中斷ID
if(intNum>=NUMBER_OF_INT_VECTORS ){
return;
}
irqNesting++;
//根據中斷ID號,讀取中斷處理函數,然後執行。
irqTable[intNum].irqHandler(intNum,irqTable[intNum].userParam);
irqNesting--;
}
中斷處理函數配置編寫
設置GPIO中斷的觸發方式,如何觸發中斷——低電平觸發、高電平觸發、上升沿觸發、下降沿觸發。
使能GPIO對應的中斷——GPIO_IMR寄存器
是否使用雙邊沿觸發——GPIOx_EDGE_SEL
處理完中斷函數後,檢查中斷標誌位,並及時清除——GOIOx_ISR(寫1清零)
修改GPIO的配置,添加中斷功能
gpio.h
#ifndef __BSP_GPIO_H
#define __BSP_GPIO_H
#include "imx6ul.h"
//定義中斷觸發類型
typedef enum _gpio_interrupt_mode
{
kGPIO_NoIntmode = 0U, /* 無中斷功能 */
kGPIO_IntLowLevel = 1U, /* 低電平觸發 */
kGPIO_IntHighLevel = 2U, /* 高電平觸發 */
kGPIO_IntRisingEdge = 3U, /* 上升沿觸發 */
kGPIO_IntFallingEdge = 4U, /* 下降沿觸發 */
kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都觸發 */
} gpio_interrupt_mode_t;
//枚舉類型和Gpio結構體
//輸入還是輸出
typedef enum _gpio_pin_direction{
kGPIO_DigitalInput = 0,
kGPIO_DigitalOutput = 1U,
}gpio_pin_direction;
//Gpio結構體,規定了輸出方向和輸出電平
typedef struct _gpio_pin_config
{
gpio_pin_direction direction;/* data */
uint8_t outputLogic;
gpio_interrupt_mode_t interruptMode; /* 中斷方式 ,新增一個屬性用於控制! */
}gpio_pin_config_t;
void gpio_init(GPIO_Type *base,int pin , gpio_pin_config_t *config);
void gpio_pinWrite(GPIO_Type *base,int pin, int value);
int gpio_pinRead(GPIO_Type *base,int pin);
//中斷使能就是寄存器GPIO_IMR對應位寫1
void gpio_enable(GPIO_Type *base,int pin);
//中斷失能就是寄存器GPIO_IMR對應位寫0
void gpio_Disable(GPIO_Type *base,int pin);
//清除中斷標誌位寄存器GPIO_ISR對應位寫1
void gpio_ClearintFlags(GPIO_Type *base,int pin);
//中斷配置函數,主要配置ICR寄存器
void gpio_intconfig(GPIO_Type *base,int pin,gpio_interrupt_mode_t pin_int_mode);
#endif
gpio.c
#include "bsp_gpio.h"
void gpio_init(GPIO_Type *base,int pin , gpio_pin_config_t *config){
base->IMR &= ~(1U << pin);
if(config->direction == kGPIO_DigitalInput){
base->GDIR &=~ (1 << pin);
}else{
base->GDIR |= (1 << pin);
gpio_pinWrite(base,pin,config->outputLogic);
//設置默認輸出電平
}
gpio_intconfig(base,pin,config->interruptMode);
}
//控制GPIO高低電平
void gpio_pinWrite(GPIO_Type *base,int pin, int value){
if (value == 0U)
{
base->DR &=~ (1<<pin); /* code */
}else{
base->DR |= (1<<pin);
}
}
//讀取指定IO電平
int gpio_pinRead(GPIO_Type *base,int pin){
return (((base->DR) >> pin)&0x01);
}
void gpio_enable(GPIO_Type *base,int pin)
{
base->IMR |= (1<<pin);
}
void gpio_Disable(GPIO_Type *base,int pin)
{
base->IMR &=~ (1<<pin);
}
void gpio_ClearintFlags(GPIO_Type *base,int pin){
base->ISR|= (1<<pin);
}
//對icr寄存器操作
void gpio_intconfig(GPIO_Type *base,int pin,gpio_interrupt_mode_t pin_int_mode){
volatile uint32_t *icr; //用於讀寫的臨時變量
uint32_t icrShift; //用於控制具體操作icr1還是icr2 icr1:0-15 icr2:16-31 兩個bit控制一個pin
icrShift = pin; //先等於pin
base->EDGE_SEL &=~ (1<<pin); //關閉EDGE_SEL對應的pin,不然會覆蓋icr的配置
if(pin<16){ //具體操作哪個icr寄存器
icr = &(base->ICR1); //控制icr1
}else{
icr = &(base->ICR2); //控制icr2
icrShift -= 16; //切換到相對位置
}
// 00 :kGPIO_IntLowLevel 01:kGPIO_IntHighLevel 10:kGPIO_IntRisingEdge 11:kGPIO_IntFallingEdge, 雙邊沿:寄存器EDGE_SEL寫一(會覆蓋icr寄存器)
switch (pin_int_mode)
{
case kGPIO_IntLowLevel:/* constant-expression */
*icr &=~(3<<(2*icrShift)); //一個pin對應兩個位 清零相應的位
/**
pin0: 0-1
pin1: 2-3
pin2: 4-5
pinx: 2x
在將2個位11(3)掩碼全部清除
**/
/* code */
break;
case kGPIO_IntHighLevel:
*icr &=~(3<<(2*icrShift));
*icr |= (1<<(2*icrShift));
break;
case kGPIO_IntRisingEdge:
*icr &=~(3<<(2*icrShift));
*icr |= (2<<(2*icrShift));
break;
case kGPIO_IntFallingEdge:
*icr &=~(3<<(2*icrShift));
*icr |= (3<<(2*icrShift));
break;
case kGPIO_IntRisingOrFallingEdge:
base->EDGE_SEL |= (1<<pin);
break;
default:
break;
}
}
GIC配置
- 使能相應的中斷ID
- 設置中斷優先級
- 註冊GPIO1_IO18的中斷處理函數
編寫exti文件,在裏面去調用剛寫的GPIO中斷相關設置。
exti.h
#ifndef __BSP_EXTI_H
#define __BSP_EXTI_H
#include "imx6ul.h"
//初始化中斷
void exti_init(void);
//具體的中斷處理函數
void gpio1_io18_irqhandler(void);
#endif
exti.c
#include "bsp_exti.h"
#include "bsp_gpio.h"
#include "imx6ul.h"
#include "bsp_int.h"
#include "bsp_delay.h"
#include "bsp_beep.h"
//初始化外部中斷
void exti_init(void){
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
//初始化GPIO中斷模式
gpio_pin_config_t config;
config.direction = kGPIO_DigitalInput;
//配置為下降沿觸發
config.interruptMode = kGPIO_IntFallingEdge;
config.outputLogic = 1;
gpio_init(GPIO1,18 ,&config);
//使能GIC中斷、註冊中斷服務函數、使能GPIO中斷
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
//指向了gpio1_io18_irqhandler函數GPIO1_Combined_16_31_IRQn觸發,都會允許該函數
system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_io18_irqhandler, NULL) ;
gpio_enable(GPIO1,18);
}
//中斷處理函數
void gpio1_io18_irqhandler(void){
static unsigned int state= 0;
delay(10);//原則上使用定時器實現。
if(gpio_pinRead(GPIO1,18)==0){ //具體是哪個 IO 引起的中斷,那就需要在中斷處理函
數中判斷了
state = !state;
beep_switch(state);
}
//清楚中斷標誌位,一定要去除中斷標誌位。ISR寄存器清楚
gpio_ClearintFlags(GPIO1,18);
}
編寫mian函數
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_int.h"
#include "key.h"
#include "bsp_exti.h"
int main(void){
int_Init(); //中斷初始化必須要開頭
imx6u_clkinit();
clk_enable();
led_init();
Beep_Init();
Key_Init();
exti_init();
while (1)
{
led_switch(LED0,0);
delay(500);
led_switch(LED0,1);
delay(500);
}
return 0;
}
最後在Makefile中添加文件路徑,編譯下載即可