目录
[一. 知识回顾](#一. 知识回顾)
[1.1 处理器工作模式](#1.1 处理器工作模式)
[1.2 不同工作模式的寄存器](#1.2 不同工作模式的寄存器)
[1.3 CPSR - 程序状态字寄存器](#1.3 CPSR - 程序状态字寄存器)
[1.4 ARM异常处理过程](#1.4 ARM异常处理过程)
[二. GIC--通用中断控制器](#二. GIC--通用中断控制器)
[2.1 GIC 的版本](#2.1 GIC 的版本)
[2.2 GIC 对中断的管理](#2.2 GIC 对中断的管理)
[2.3 有关 GIC 的寄存器](#2.3 有关 GIC 的寄存器)
[2.3.1 GICC_IAR -- 中断应答寄存器](#2.3.1 GICC_IAR -- 中断应答寄存器)
[2.3.2 GICC_EOIR -- 中断结束寄存器](#2.3.2 GICC_EOIR -- 中断结束寄存器)
[三. 协处理器--CP15寄存器](#三. 协处理器--CP15寄存器)
[3.1 协处理器的寄存器](#3.1 协处理器的寄存器)
[3.1.1 c0中的MIDR寄存器](#3.1.1 c0中的MIDR寄存器)
[3.2.2 c1中的SCTLR寄存器](#3.2.2 c1中的SCTLR寄存器)
[3.3.3 c12中的VBAR寄存器](#3.3.3 c12中的VBAR寄存器)
[3.4.4 c15中的CBAR寄存器](#3.4.4 c15中的CBAR寄存器)
[3.2 对协处理器寄存器的读写汇编指令](#3.2 对协处理器寄存器的读写汇编指令)
[3.2.1 MRC 与 MCR 指令](#3.2.1 MRC 与 MCR 指令)
[3.3 总结协处理器](#3.3 总结协处理器)
[四. 与中断配置相关的库函数](#四. 与中断配置相关的库函数)
[4.1 GIC_Init](#4.1 GIC_Init)
[4.1.1 内联函数的概念](#4.1.1 内联函数的概念)
[4.2 GIC_EnableIRQ](#4.2 GIC_EnableIRQ)
[五. 中断处理流程与程序编写](#五. 中断处理流程与程序编写)
[5.1 按键部分](#5.1 按键部分)
[5.2 主函数](#5.2 主函数)
[5.3 启动代码的编写--汇编](#5.3 启动代码的编写--汇编)
[5.3.1 CPS 命令](#5.3.1 CPS 命令)
[5.3.2 _irq_handler的编写](#5.3.2 _irq_handler的编写)
一. 知识回顾
1.1 处理器工作模式

1.2 不同工作模式的寄存器



1.3 CPSR - 程序状态字寄存器

1.4 ARM异常处理过程

二. GIC--通用中断控制器
**作用:**使能外设中断,设置中断优先级等等
当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是 ARM 内核只提供了四个信号给 GIC 来汇报中断情况:VFIQ、VIRQ、FIQ 和 IRQ,他们之间的关系如图所示:
VFIQ: 虚拟快速 FIQ VIRQ: 虚拟外部 IRQ FIQ: 快速中断 IRQ **IRQ:**外部中断 IRQ

2.1 GIC 的版本
早期:GIC V1
Cortex-A5处理器应用
已经淘汰
GIC V2
应用于32位处理器
Cortex-A7
IMX6ULL----GIC 400型号
GIC V3 - V4
64位处理器
Cortex-A53手机芯片
GIC V5
对之前版本进行了重新架构,支持数百核处理器
应用在对应算力要求比较高,服务器
2.2 GIC 对中断的管理
可管理 1020个中断,GIC将中断分为了三大类,imx6ull只使用了 0 - 159总共160个中断
GIC 将众多的中断源分为 分为三类:
①**SPI(Shared Peripheral Interrupt)(编号:32 - 1019),**共享中断,顾名思义,所有 Core 共享的中断,这个是 最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
②**PPI(Private Peripheral Interrupt)(编号:16 - 31),**私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断
③SGI(Software-generated Interrupt)(编号:0 - 15),软件中断,由软件触发引起的中断,通过向寄存器 GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
中断编号要在库里面看,**MCIMX6Y2.h,**是一个IRQn枚举类型
eg:GPIO1_Combined_16_31_IRQn = 99; 表示GPIO16 - 31引脚共用同一个中断号 99
cpp
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;
2.3 有关 GIC 的寄存器
2.3.1 GICC_IAR -- 中断应答寄存器
在头文件 core_ca7.h 中的定义:
cpp
__IM uint32_t C_IAR; /*!< Offset: 0x200C (R/ ) Interrupt Acknowledge Register */
访问权限:只读 (RO),仅 CPU 读操作有效,写无任何作用
地址:GIC 基地址 + 0x000C
作用: 获取当前中断的中断编号、当 GIC 向 CPU 拉高 IRQ/FIQ 信号后,CPU 进入异常模式,必须读取 GICC_IAR,才能拿到需要处理的中断号
问题: 要访问IAR寄存器拿到中断号,就要得到IAR的地址,就得知道 GIC 的基地址, 但在 Cortex-A7 内核中,GIC 的基地址是变换的,想要得到其基地址,就得借助coprocessor协处理器!这个后面会讲到
2.3.2 GICC_EOIR -- 中断结束寄存器
在头文件 core_ca7.h 中的定义:
cpp
__OM uint32_t C_EOIR; /*!< Offset: 0x2010 ( /W) End Of Interrupt Register */
访问权限:只写 (WO),读取返回未知无效值
地址:GIC 基地址 + 0x0010
作用: 向 GICC_EOIR 寄存器中写入中断编号,则清除相应的中断请求
三. 协处理器--CP15寄存器
在cp15中又分为了c0 - c15,15个寄存器组,每个组里面又有许多寄存器
CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到,CP15 协处理器一共有 16 个 32 位寄存器,这里挑几个比较重要的介绍
3.1 协处理器的寄存器
3.1.1 c0中的MIDR寄存器
**MIDR:**主 ID 寄存器
**作用:**该寄存器保存的是内核中的一些基本信息
如下图:存放的是内核的版本和指令集版本等信息

3.2.2 c1中的SCTLR寄存器
SCTLR( System Control Register**):**系统控制寄存器
作用:提供系统的最高级控制功能,包括其内存系统
重要的几位:
|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| V 13位 异常向量表设置位 | 此位用于选择异常向量的基地址: 0 :为正常异常向量 ,基地址为 0x00000000。但是可以重新映射异常向量表地址,要使用寄存器VBAR,向其写要映射的地址值 1 :为高级异常向量,基地址为 0xFFFF0000。此基地址永远不会被重新映射。 |
| I 12位 指令缓存 Icache 开关 | 指令缓存启用位。这是一个针对指令缓存的全局启用位: 0 表示指令缓存已禁用,这是复位值。 1 表示指令缓存已启用。 |

3.3.3 c12中的VBAR寄存器
VBAR:向量基地址寄存器
**作用:**若SCTLR寄存器的V位为0,则向此寄存器中写入异常向量表的基地址
3.4.4 c15中的CBAR寄存器
**CBAR(Configuration Base Address Register):**配置基地址寄存器
**作用:**保存着GIC的基地址
3.2 对协处理器寄存器的读写汇编指令
3.2.1 MRC 与 MCR 指令
**MCR:**指令会将协处理器的寄存器内容写入到一个 ARM 寄存器中。
**MRC:**将协处理器中的一个寄存器数据读取至 ARM 寄存器中。
用法:
bash
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
MCR<c> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
|------------|---------------------------------------------------------|
| coproc | 默认是p15 |
| cond | 指令执行的条件码,如果忽略的话就表示无条件执行 |
| opc1 | 协处理器要执行的操作码 |
| Rt | ARM 源寄存器,要写入 到 CP15 寄存器/读出的数据就保存在此寄存器中 |
| CRn | CP15 协处理器的目标寄存器 |
| CRm | 协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将 CRm 设置为 C0,否则结果不可预测。 |
| opc2 | 可选的协处理器特定操作码,当不需要的时候要设置为 0 |
示例:CP15 中 C0 寄存器的值读取到 R0 寄存器中
bash
MRC p15, 0, r0, c0, c0, 0
CP15 协处理器有 16 个 32 位寄存器 ,c0 ~ c15,在使用 MRC 或者 MCR 指令访问这 16 个 寄存器的时候,指令中的 CRn、opc1、CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是不同的。
1、c0
比如c0 在不同的搭配情况下含义如图所示:

2、c1
c1 寄存器同样通过不同的配置,其代表的含义也不同,如图所示:
当 MRC/MCR 指令中的 CRn=c1,opc1=0,CRm=c0,opc2=0 的时候就表示 此时的 c1 就是 SCTLR 寄存器,也就是系统控制寄存器,这个是 c1 的基本作用。SCTLR 寄存器主要是完成控制功能的,比如使能或者禁止 MMU、I/D Cache等


3、c12
c12 寄存器通过不同的配置,其代表的含义也不同,如图所示:
在图中当 MRC/MCR 指令中的 CRn=c12,opc1=0,CRm=c0,opc2=0 的时候就表 示此时 c12 为 VBAR 寄存器,也就是向量表基地址寄存器 。设置异常向量表偏移的时候就需要将新的中断向量表基地址写入 VBAR 中,比如在前面的例程中,代码链接的起始地址为 0X878 00000,而中断向量表肯定要放到最前面,也就是 0X87800000 这个地址处。所以就需要设置 V BAR 为 0X87800000,设置命令如下:
cpp
ldr r0, =0X87800000 /r0=0X87800000
MCR p15, 0, r0, c12, c0, 0 /将 r0 里面的数据写入到 c12 中,即 c12=0X87800000

于是在之前的汇编启动代码中,可以添加下面两个函数并且调用:
作用:使能icache + 允许异常向量表地址重映射 + 设置异常向量表的重映射地址
cpp
/设置SCTLR寄存器:使能icache + 允许异常向量表地址重映射
bl _enable_icache
/设置VBAR寄存器:设置异常向量表的重映射地址
bl _set_vbsr
_enable_icache:
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #(1 << 13) /可以映射异常向量
orr r0, r0, #(1 << 12) /使能指令缓存Icache
mcr p15, 0, r0, c1, c0, 0
bx lr
_set_vbsr:
ldr r0, =0x87800000
mcr p15, 0, r0, c12, c0, 0
bx lr
4、c15
c15 寄存器也可以通过不同的配置得到不同的含义
在图中,需要 c15 作为 CBAR 寄存器,因为 GIC 的基地址就保存在 CBAR 中,我们可以通过如下命令获取到 GIC 基地址:
cpp
MRC p15, 4, r1, c15, c0, 0 /获取 GIC 基础地址,基地址保存在 r1 中
获取到 GIC 基地址 以后就可以设置 GIC 相关寄存器 了,比如可以读取当前中断 ID , 当前中断 ID 保存在 GICC_IAR 中,寄存器 GICC_IAR 属于 CPU 接口端寄存器 ,寄存器地址相对于 CPU 接口端起始地址的偏移为 0X000C,因此获取当前中断 ID 的代码如下:
cpp
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 总结协处理器
简单总结一下:
通过c0 寄存器的MIDR 可以获取到处理器内核信息;
通过 c1 寄存器的SCTLR 可以使能或禁止 MMU、I/D Cache 等;
通过 c12 寄存器的VBAR可以设置中断向量偏移;
通过 c15 寄存器的CBAR 可以获取 GIC 基地址。
四. 与中断配置相关的库函数
可以在头文件:core_ca7.h 中找到
4.1 GIC_Init
**功能:**初始化GIC
定义如下:
cpp
FORCEDINLINE __STATIC_INLINE void GIC_Init(void)
{
.......
}
4.1.1 内联函数的概念
注意core_ca7.h 这个头文件所有的函数都加了 FORCEDINLINE __STATIC_INLINE 这样的修饰,宏定义如下:
cpp
#define FORCEDINLINE __attribute__((always_inline))
#define __STATIC_INLINE static inline
关键字:inline
修饰的函数,叫内联函数
调用内联函数前,必须有该函数的定义!!
多次调用头文件不会报错:不会,内联函数会就地展开,不是跳转调用
但是代码得少一点,精简
关键字:always_inline:强制内联,不管代码量多少,要强制展开
4.2 GIC_EnableIRQ
**功能:**使能GIC中断
参数:此函数需要传参,参数为中断号
中断号都定义在了IRQn_Type 这个枚举中
cpp
typedef enum IRQn {
/一系列中断号
} IRQn_Type;
cpp
FORCEDINLINE __STATIC_INLINE void GIC_EnableIRQ(IRQn_Type IRQn)
{
.......
}
五. 中断处理流程与程序编写
本小节以按键为例,说明中断程序的编写流程,先看按键的原理图:

中断处理流程:

5.1 按键部分
注意与之前输出不同的是,按键属于输入,并且要配置上拉电阻,故IOMUXC_SetPinConfig这个函数传参时:(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0)----配置22kohm的上拉电阻
还要配置与这个引脚相关的中断触发方式与中断屏蔽寄存器
有关 ICR 与 IMR 寄存器的介绍参考此文章:GPIO相关寄存器介绍
cpp
/短延时
void delay_short(volatile unsigned int n)
{
while(n--)
{
}
}
/长延时
void _delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7ff);
}
}
/按键初始化
void key_init()
{
/配置引脚复用
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
/配置电气属性
/注意这一块不同于引脚的输出模式,按键检测要配置上拉电阻,其他不变
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0);
/配置输入输出
GPIO1->GDIR &= ~(1 << 18);
/配置中断触发方式为下降沿
GPIO1->ICR2 |= (3 << 4);
/设置中断屏蔽---不屏蔽
GPIO1->IMR |= (1 << 18);
/使能 GIC 通用中断控制器
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
}
/检测按键
unsigned int key_check()
{
unsigned int ret = 0;
if( (GPIO1->PSR & (1 << 18)) == 0 )
{
_delay(10);
if( ((GPIO1->PSR >> 18) & 1) == 0 )
{
ret = 0;
}
}
else
{
ret = 1;
}
return ret;
}
5.2 主函数
在主函数中来初始化GIC,使能时钟,还有中断服务函数的编写等等
cpp
/延时函数
void delay(unsigned int n)
{
while(n--)
{
}
}
/初始化GIC
void gic_init(void)
{
GIC_Init();
}
/使能时钟
void enable_clocks(void)
{
CCM->CCGR0 = 0xFFFFFFFF;
CCM->CCGR1 = 0xFFFFFFFF;
CCM->CCGR2 = 0xFFFFFFFF;
CCM->CCGR3 = 0xFFFFFFFF;
CCM->CCGR4 = 0xFFFFFFFF;
CCM->CCGR5 = 0xFFFFFFFF;
CCM->CCGR6 = 0xFFFFFFFF;
}
/中断处理函数
void system_interrupt_handler(int num)
{
/先判断中断编号是不是 GPIO1_IO18 对应的中断编号
if(num = GPIO1_Combined_16_31_IRQn)
{
/再判断 ISR寄存器 的 D18 位是否置1,是否是GPIO1_IO18发出中断请求
if((GPIO1->ISR & (1 << 18)) != 0)
{
led_nor();
/清除外设中断标志位
GPIO1->ISR |= (1 <<18);
}
}
}
int main(void)
{
enable_clocks();
gic_init();
led_init();
beep_init();
key_init();
while(1)
{
delay(0x7FFFF);
}
return 0;
}
5.3 启动代码的编写--汇编
cpp
.global _start
_start:
/初始化异常向量表:存放异常跳转指令
ldr pc, =_reset_handler /0x00000000 复位异常跳转指令 --- 复位默认是SVC模式
ldr pc, =_undefined_handler /0x00000004 未定义指令异常跳转指令
ldr pc, =_svc_handler /0x00000008 复位或软中断异常跳转指令
ldr pc, =_prefetch_abort_handler /0x0000000C 指令存取错误异常跳转指令
ldr pc, =_data_abort_handler /0x00000010 数据存取错误异常跳转指令
ldr pc, =_reserved_handler /0x00000014 保留
ldr pc, =_irq_handler /0x00000018 普通中断异常跳转指令
ldr pc, =_fiq_handler /0x0000001C 快速中断异常跳转指令
_fiq_handler:
ldr pc, =_fiq_handler
_irq_handler:
subs lr, lr, #4
/得到GIC的基地址,读取 CBAR 寄存器
mrc p15, 4, r1, c15, c0, 0
/偏移 0x2000 到达 C_CTLR 寄存器
add r1, r1, #0x2000
/再偏移 0x0C 到达 C_IAR 寄存器,此寄存器里面存放的中断编号
ldr r0, [r1, #0x0C]
stmfd sp!, {r0-r12, lr}
/将中断编号传参给中断服务函数,用于判断
stmfd sp!, {r0, r1}
/实现中断嵌套
/切换工作模式,切换到Sys模式
cps #0x1F
/因为下面的bl指令会改变sys模式下的lr_sys寄存器,故需要先入栈保护lr_sys寄存器
stmfd sp!, {lr}
bl system_interrupt_handler
ldmfd sp!, {lr}
/切换工作模式到IRQ模式
cps #0x12
ldmfd sp!, {r0, r1}
/向 C_EOIR 寄存器中写入中断编号,目的:清除 GIC 中断标志
str r0, [r1, #0x10]
ldmfd sp!, {r0-r12, pc}^
_reserved_handler:
ldr pc, =_reserved_handler
_data_abort_handler:
ldr pc, =_data_abort_handler
_prefetch_abort_handler:
ldr pc, =_prefetch_abort_handler
_svc_handler:
ldr pc, =_svc_handler
_undefined_handler:
ldr pc, =_undefined_handler
_reset_handler:
/将工作模式切换到IRQ模式下
cps #0x12
cpsie i /使能IRQ中断
/设置IRQ模式下的栈底指针,大小为32M
ldr sp, =0x86000000
/将工作模式切换到System模式下
cps 0x1F
/设置System模式的栈底指针
ldr sp, =0x84000000
/设置SCTLR寄存器:使能icache + 允许异常向量表地址重映射
bl _enable_icache
/设置VBAR寄存器:设置异常向量表的重映射地址
bl _set_vbsr
/清零.bss段与COMMON段
bl _bss_clear
b main
b finish
_enable_icache:
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #(1 << 13) /正常异常向量,可以映射
orr r0, r0, #(1 << 12) /使能指令缓存
mcr p15, 0, r0, c1, c0, 0
bx lr
_set_vbsr:
ldr r0, =0x87800000
mcr p15, 0, r0, c12, c0, 0
bx lr
_bss_clear:
ldr r0, =__bss_start
ldr r1, =__bss_end
loop:
mov r2, #0
str r2, [r0] /STR默认一次写32位数据!!
add r0, r0, #4
cmp r0, r1
blt loop /这里一定是小于,不是小于等于
bx lr
finish:
b finish
5.3.1 CPS 命令
CPS( Change Processor State**):**可用于更改处理器模式
用法:
CPS #模式
CPSIE iflags
CPSID iflags
模式是指处理器进入的特定模式的编号,参考CPSR寄存器的后五位。
IE 为中断或中止启用。
ID 为中断或中止禁用。
iflags 指定以下一项或多项(这些也是CPSR寄存器中的位 ):
• a 表示异步中止。
• i 表示中断请求。
• f 表示高级中断请求。
eg:
cpp
/将工作模式切换到IRQ模式下
cps #0x12
cpsie i /使能IRQ中断

5.3.2 _irq_handler的编写
1、在 system_interrupt_handler 函数中,要对中断编号进行判断,故汇编要向c传参,这里通过寄存器r0来传参
2、为了实现中断嵌套 ,中断函数的执行必须在Sys模式下执行,故在跳转到 system_interrupt_handler 函数中时,一定要切换工作模式,并且一定要将lr寄存器入栈保护,因为bl system_interrupt_handler 这条 指令会改变sys模式下的 lr_sys寄存器 ,故需要先入栈保护 lr_sys寄存器(BL 指令会将函数返回地址保存在LR寄存器中)
3、最后一定要向 EOIR 寄存器中再次写入中断编号,类似清除中断标志,防止死循环
4、subs lr, lr, #4的原因:
异常状态返回地址偏移量
原因:ARM 状态(32 位 ARM 指令)读 PC:读到的值 = 当前正在执行指令地址 + 8 ,因为流水线结构:取指→译码→执行,执行阶段时 PC 已预取两条之后指令,间隔 8 字节(2 条 ARM 指令)

cpp
_irq_handler:
subs lr, lr, #4
/得到GIC的基地址,读取 CBAR 寄存器
mrc p15, 4, r1, c15, c0, 0
/偏移 0x2000 到达 C_CTLR 寄存器
add r1, r1, #0x2000
/再偏移 0x0C 到达 C_IAR 寄存器,此寄存器里面存放的中断编号
ldr r0, [r1, #0x0C]
stmfd sp!, {r0-r12, lr}
/将中断编号传参给中断服务函数,用于判断
stmfd sp!, {r0, r1}
/实现中断嵌套
/切换工作模式,切换到Sys模式
cps #0x1F
/因为下面的bl指令会改变sys模式下的lr_sys寄存器,故需要先入栈保护lr_sys寄存器
stmfd sp!, {lr}
bl system_interrupt_handler
ldmfd sp!, {lr}
/切换工作模式到IRQ模式
cps #0x12
ldmfd sp!, {r0, r1}
/向 C_EOIR 寄存器中写入中断编号,目的:清除 GIC 中断标志
str r0, [r1, #0x10]
ldmfd sp!, {r0-r12, pc}^
六. 中断优先级
GIC支持256级优先级
0 - 255, 256优先级
中断号越小优先级越高
在imx6ull只使用了32个优先级
0 - 31
11111000:32个优先级
优先级分类:
(组优先级)抢占优先级 + 子优先级
八位
最左边一位叫 组,组优先级:0/1
剩下七个是一部分,子优先级
另一种分法:
左边两位做组优先级,四种:00 01 10 11
剩下为子优先级
后面将8bit都作为子优先级

使用函数设置
GIC_SetPriority 函数定义如下:
传参:传要进行设置的对应的中断编号和要设置的优先级等级即可
cpp
/*
* 设置优先级
*/
FORCEDINLINE __STATIC_INLINE void GIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
GIC_Type *gic = (GIC_Type *)(__get_CBAR() & 0xFFFF0000UL);
gic->D_IPRIORITYR[((uint32_t)(int32_t)IRQn)] = (uint8_t)((priority << (8UL - __GIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
进一步优化中断处理函数
之前的中断处理函数:可以看到要对中断编号不断的进行判断,可知IMX6ULL支持160个中断源,所有中断都会进入这个中断处理函数,难道要判断160次??
cpp
/中断处理函数
void system_interrupt_handler(int num)
{
/先判断中断编号是不是 GPIO1_IO18 对应的中断编号
if(num = GPIO1_Combined_16_31_IRQn)
{
/再判断 ISR寄存器 的 D18 位是否置1,是否是GPIO1_IO18发出中断请求
if((GPIO1->ISR & (1 << 18)) != 0)
{
led_nor();
/清除外设中断标志位
GPIO1->ISR |= (1 <<18);
}
}
}
这里有一个开闭原则:
对代码的修改关闭
对代码增加开放--添加函数,不需要改源码
可以做如下改进:
cpp
//函数指针类型重命名
typedef void (*irq_handler)(void);
irq_handler interrupt_vector_table[160];
//中断注册函数
void system_interrupt_register(int num, irq_handler handler)
{
interrupt_vector_table[num] = handler;
}
//中断处理函数
void system_interrupt_handler(int num)
{
if(interrupt_vector_table[num] != NULL)
{
interrupt_vector_table[num]();
}
}
在按键初始化函数中,做如下改进:
这里 irq_handler interrupt_vector_table160; 就相当一个中断向量表,而中断编号就相当一个中断向量,注册了中断处理函数之后,在system_interrupt_handler这个函数里面去判断并且调用相应的处理函数,就方便了许多
cpp
//按键中断处理函数
void key_interrupt_handler(void)
{
if( (GPIO1->ISR & (1 << 18)) != 0)
{
led_nor();
beep_nor();
GPIO1->ISR |= (1 << 18);
}
}
//按键初始化
void key_init()
{
//配置引脚复用
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
//配置电气属性
//注意这一块不同于引脚的输出模式,按键检测要配置上拉电阻,其他不变
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF0B0);
//配置输入输出
GPIO1->GDIR &= ~(1 << 18);
//配置中断触发方式为下降沿
GPIO1->ICR2 |= (3 << 4);
//设置中断屏蔽---不屏蔽
GPIO1->IMR |= (1 << 18);
//按键中断注册
system_interrupt_register(GPIO1_Combined_16_31_IRQn, key_interrupt_handler);
//设置中断优先级
GIC_SetPriority(GPIO1_Combined_16_31_IRQn, 0);
//使能 GIC 通用中断控制器
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
}