ARM -IMX6ULL的中断系统、GIC、CP15协处理器详细讲解

目录

[一. 知识回顾](#一. 知识回顾)

[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 指令)

1、c0

2、c1

3、c12

4、c15

[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的上拉电阻

还要配置与这个引脚相关的中断触发方式与中断屏蔽寄存器

有关 ICRIMR 寄存器的介绍参考此文章: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);
}
相关推荐
碎光拾影4 小时前
i.MX6ULL时钟系统配置指南
c语言·arm开发·嵌入式硬件
简单电磁炮6 小时前
硬件8 arm
arm开发
简单电磁炮8 小时前
硬件9 arm时钟树
arm开发
武汉万象奥科9 小时前
万象奥科OpenHarmony 6.1 LTS适配完成,RK3576核心板再添新生态
arm开发·嵌入式硬件·物联网·rk3576·arm核心板
治愈系贝壳13 小时前
ARM --- day 6 时钟
linux·arm开发·学习
Dlrb121113 小时前
ARM---I.MX6ULL时钟系统详解、时钟树、系统时钟配置等
单片机·嵌入式硬件·arm·imx6ull·pll·时钟树·cortex-a7
华清远见IT开放实验室1 天前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
执迷不悟8231 天前
ARM--按键/中断
arm开发
维基框架1 天前
汽车座舱开发上云 Google的Arm实例解决了什么实际问题
arm开发·人工智能·汽车