梧州市建设局官方网站,娱乐网站建设,外贸营销员职业技能证书,房地产开发公司管理制度中断系统是一个处理器重要的组成部分#xff0c;中断系统极大的提高了 CPU 的执行效率#xff0c;本章会将 I.MX6U 的一个 IO 作为输入中断#xff0c;借此来讲解如何对 I.MX6U 的中断系统进行编程。 GIC 控制器简介 1、GIC 控制器总览 I.MX6U(Cortex-A)的中断控制器… 中断系统是一个处理器重要的组成部分中断系统极大的提高了 CPU 的执行效率本章会将 I.MX6U 的一个 IO 作为输入中断借此来讲解如何对 I.MX6U 的中断系统进行编程。 GIC 控制器简介 1、GIC 控制器总览 I.MX6U(Cortex-A)的中断控制器叫做 GIC GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器类似 Cortex-M 内核中的 NVIC。目前 GIC 有 4 个版本:V1~V4V1 是最老的版本已经被废弃了。V2~V4 目前正在大 量的使用。GIC V2 是给 ARMv7-A 架构使用的比如 Cortex-A7、Cortex-A9、Cortex-A15 等V3 和 V4 是给 ARMv8-A/R 架构使用的也就是 64 位芯片使用的。I.MX6U 是 Cortex-A 内核的因此我们主要讲解 GIC V2。GIC V2 最多支持 8 个核。ARM 会根据 GIC 版本的不同研发出不同的 IP 核那些半导体厂商直接购买对应的 IP 核即可比如 ARM 针对 GIC V2 就开发出了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况VFIQ、VIRQ、FIQ 和 IRQ他们之间的关系如图 17.1.3.1 所示 在图 17.1.3.1 中 GIC 接收众多的外部中断然后对其进行处理最终就只通过四个信号报给 ARM 内核这四个信号的含义如下 VFIQ:虚拟快速 FIQ。 VIRQ:虚拟外部 IRQ。 FIQ:快速中断 IRQ。 IRQ:外部中断 IRQ。 VFIQ 和 VIRQ 是针对虚拟化的我们不讨论虚拟化剩下的就是 FIQ 和 IRQ 了我们前面都讲了很多次了。本教程我们只使用 IRQ 所以相当于 GIC 最终向 ARM 内核就上报一个 IRQ信号。那么 GIC 是如何完成这个工作的呢 GICV2 的逻辑图如图 17.1.3.2 所示 图 17.1.3.2 中左侧部分就是中断源中间部分就是 GIC 控制器最右侧就是中断控制器向 处理器内核发送中断信息。我们重点要看的肯定是中间的 GIC 部分 GIC 将众多的中断源分为分为三类 ①、SPI(Shared Peripheral Interrupt),共享中断顾名思义所有 Core 共享的中断这个是最常见的那些外部中断都属于 SPI 中断(注意不是 SPI 总线那个中断) 。比如按键中断、串口中断等等这些中断所有的 Core 都可以处理不限定特定 Core。 ②、PPI(Private Peripheral Interrupt)私有中断我们说了 GIC 是支持多核的每个核肯定 有自己独有的中断。这些独有的中断肯定是要指定的核心处理因此这些中断就叫做私有中断。 ③、SGI(Software-generated Interrupt)软件中断由软件触发引起的中断通过向寄存器GICD_SGIR 写入数据来触发系统会使用 SGI 中断来完成多核之间的通信。 2、中断 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 中断、串口中断等这些外部中断 至于具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。比如 I.MX6U 的总共 使用了 128 个中断 ID加上前面属于 PPI 和 SGI 的 32 个 IDI.MX6U 的中断源共12832160个这 128 个中断 ID 对应的中断在《I.MX6ULL 参考手册》的“3.2 CortexA7 interrupts”小节中断源如表 17.1.3.1 所示 限于篇幅原因表 17.1.3.1 中并没有给出 I.MX6U 完整的中断源完整的中断源自行查阅《I.MX6ULL 参考手册》的 3.2 小节。打开裸机例程“ 9_int ”我们前面移植了 NXP 官方 SDK 中的文件 MCIMX6Y2.h 在此文件中定义了一个枚举类型 IRQn_Type 此枚举类型就枚举出了 I.MX6U 的所有中断代码如下所示 /** Interrupt Number Definitions */
#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 */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、GIC 逻辑分块 GIC 架构分为了两个逻辑块 Distributor 和 CPU Interface 也就是分发器端和 CPU 接口端。这两个逻辑块的含义如下 Distributor(分发器端 ) 从图 17.1.3.2 可以看出此逻辑块负责处理各个中断事件的分发问 题也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源可以控制每个中断的优先级它总是将优先级最高的中断事件发送到 CPU 接口端。分发器端要做的主要工作如下 ①、全局中断使能控制。 ②、控制每一个中断的使能或者关闭。 ③、设置每个中断的优先级。 ④、设置每个中断的目标处理器列表。 ⑤、设置每个外部中断的触发模式电平触发或边沿触发。 ⑥、设置每个中断属于组 0 还是组 1。 CPU Interface(CPU 接口端 ) CPU 接口端听名字就知道是和 CPU Core 相连接的因此在 图 17.1.3.2 中每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface 。 CPU 接口端 就是分发器和 CPU Core 之间的桥梁 CPU 接口端主要工作如下 ①、使能或者关闭发送到 CPU Core 的中断请求信号。②、应答中断。 ③、通知中断处理完成。 ④、设置优先级掩码通过掩码来设置哪些中断不需要上报给 CPU Core。 ⑤、定义抢占策略。 ⑥、当多个中断到来的时候选择优先级最高的中断通知给 CPU Core。 例程“ 9_int ”中的文件 core_ca7.h 定义了 GIC 结构体此结构体里面的寄存器分为了分 发器端和 CPU 接口端寄存器定义如下所示 /** GIC寄存器描述结构体* GIC分为分发器端和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; 上列代码中的结构体 GIC_Type 就是 GIC 控制器列举出了 GIC 控制器的所有寄存器可以通过结构体 GIC_Type 来访问 GIC 的所有寄存器。 第 5 行是 GIC 的分发器端相关寄存器其相对于 GIC 基地址偏移为 0X1000 因此我们获取到 GIC 基地址以后只需要加上 0X1000 即可访问 GIC 分发器端寄存器。 第 51 行是 GIC 的 CPU 接口端相关寄存器其相对于 GIC 基地址的偏移为 0X2000 同样的获取到 GIC 基地址以后只需要加上 0X2000 即可访问 GIC 的 CPU 接口段寄存器。 CP15 协处理器 关于 CP15 协处理器和其相关寄存器的详细内容请参考下面两份文档 《 ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》 第 1469 页“ B3.17Oranization of the CP15 registers in a VMSA implementation”。 《 Cortex-A7 Technical ReferenceManua.pdf 》 第 55 页“ Capter 4 System Control ”。 CP15 协处理器一般用于存储系统管理但是在中断中也会使用到 CP15 协处理器一共有16 个 32 位寄存器。 CP15 协处理器的访问通过如下另个指令完成 MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。 MCR: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。 MRC 就是读 CP15 寄存器MCR 就是写 CP15 寄存器MCR 指令格式如下 MCR{cond} p15, opc1, Rt, CRn, CRm, opc2 cond:指令执行的条件码如果忽略的话就表示无条件执行。opc1协处理器要执行的操作码。 RtARM 源寄存器要写入到 CP15 寄存器的数据就保存在此寄存器中。 CRnCP15 协处理器的目标寄存器。 CRm协处理器中附加的目标寄存器或者源操作数寄存器如果不需要附加信息就将 CRm 设置为 C0否则结果不可预测。 opc2可选的协处理器特定操作码当不需要的时候要设置为 0。 MRC 的指令格式和 MCR 一样只不过在 MRC 指令中 Rt 就是目标寄存器也就是从CP15 指定寄存器读出来的数据会保存在 Rt 中。而 CRn 就是源寄存器也就是要读取的写处理器寄存器。 假如我们要将 CP15 中 C0 寄存器的值读取到 R0 寄存器中那么就可以使用如下命令 MRC p15, 0, r0, c0, c0, 0 CP15 协处理器有 16 个 32 位寄存器 c0~c15 本章来看一下 c0 、 c1 、 c12 和 c15 这四个寄 存器因为我们本章实验要用到这四个寄存器其他的寄存器大家参考上面的两个文档即可。 1、c0 寄存器 CP15 协处理器有 16 个 32 位寄存器 c0~c15 在使用 MRC 或者 MCR 指令访问这 16 个寄存器的时候指令中的 CRn 、 opc1 、 CRm 和 opc2 通过不同的搭配其得到的寄存器含义是不同的。比如 c0 在不同的搭配情况下含义如图 17.1.4.1 所示 在图 17.1.4.1 中当 MRC/MCR 指令中的 CRnc0 opc10 CRmc0 opc20 的时候就表示此时的 c0 就是 MIDR 寄存器也就是主 ID 寄存器这个也是 c0 的基本作用。对于 Cortex-A7内核来说c0 作为 MDIR 寄存器的时候其含义如图 17.1.4.2 所示 在图 17.1.4.2 中各位所代表的含义如下 bit31:24 厂商编号 0X41 ARM 。 bit23:20 内核架构的主版本号 ARM 内核版本一般使用 rnpn 来表示比如 r0p1 其中 r0 后面的 0 就是内核架构主版本号。 bit19:16 架构代码 0XF ARMv7 架构。 bit15:4 内核版本号 0XC07 Cortex-A7 MPCore 内核。 bit3:0 内核架构的次版本号 rnpn 中的 pn 比如 r0p1 中 p1 后面的 1 就是次版本号。 2、c1 寄存器 c1 寄存器同样通过不同的配置其代表的含义也不同如图 17.1.4.3 所示 在图 17.1.4.3 中当 MRC/MCR 指令中的 CRnc1 opc10 CRmc0 opc20 的时候就表示 此时的 c1 就是 SCTLR 寄存器也就是系统控制寄存器这个是 c1 的基本作用。 SCTLR 寄存 器主要是完成控制功能的比如使能或者禁止 MMU、I/D Cache 等c1 作为 SCTLR 寄存器的时 候其含义如图 17.1.4.4 所示 SCTLR 的位比较多我们就只看本章会用到的几个位 bit13V , 中断向量表基地址选择位为 0 的话中断向量表基地址为 0X00000000软件可以使用 VBAR 来重映射此基地址也就是中断向量表重定位。为 1 的话中断向量表基地址为0XFFFF0000此基地址不能被重映射。 bit12II Cache 使能位为 0 的话关闭 I Cache为 1 的话使能 I Cache。 bit11Z分支预测使能位如果开启 MMU 的话此位也会使能。 bit10SWSWP 和 SWPB 使能位当为 0 的话关闭 SWP 和 SWPB 指令当为 1 的时候就使能 SWP 和 SWPB 指令。 bit9:3未使用保留。 bit2CD Cache 和缓存一致性使能位为 0 的时候禁止 D Cache 和缓存一致性为 1 时使能。 bit1A内存对齐检查使能位为 0 的时候关闭内存对齐检查为 1 的时候使能内存对齐检查。 bit0MMMU 使能位为 0 的时候禁止 MMU为 1 的时候使能 MMU。 如果要读写 SCTLR 的话就可以使用如下命令 MRC p15, 0, Rt, c1, c0, 0 ;读取 SCTLR 寄存器数据保存到 Rt 中。
MCR p15, 0, Rt, c1, c0, 0 ;将 Rt 中的数据写到 SCTLR(c1)寄存器中。 3、c12 寄存器 c12 寄存器通过不同的配置其代表的含义也不同如图 17.1.4.4 所示 在图 17.1.4.4 中当 MRC/MCR 指令中的 CRnc12 opc10 CRmc0 opc20 的时候就表 示此时 c12 为 VBAR 寄存器也就是向量表基地址寄存器。设置中断向量表偏移的时候就需要将新的中断向量表基地址写入 VBAR 中比如在前面的例程中代码链接的起始地址为0X87800000而中断向量表肯定要放到最前面也就是 0X87800000 这个地址处。所以就需要 设置 VBAR 为 0X87800000 设置命令如下 ldr r0, 0X87800000 ; r00X87800000
MCR p15, 0, r0, c12, c0, 0 ;将 r0 里面的数据写入到 c12 中即 c120X87800000 4、c15 寄存器 c15 寄存器也可以通过不同的配置得到不同的含义参考文档 《 Cortex-A7 Technical ReferenceManua.pdf》 第 68 页“ 4.2.16 c15 registers ”其配置如图 17.1.4.5 所示 在图 17.1.4.5 中我们需要 c15 作为 CBAR 寄存器因为 GIC 的基地址就保存在 CBAR中我们可以通过如下命令获取到 GIC 基地址 MRC p15, 4, r1, c15, c0, 0 ; 获取 GIC 基础地址基地址保存在 r1 中。 获取到 GIC 基地址以后就可以设置 GIC 相关寄存器了比如我们可以读取当前中断 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 的值 关于 CP15 协处理器就讲解到这里简单总结一下通过 c0 寄存器可以获取到处理器内核信息通过 c1 寄存器可以使能或禁止 MMU 、 I/D Cache 等通过 c12 寄存器可以设置中断向量偏移通过 c15 寄存器可以获取 GIC 基地址。关于 CP15 的其他寄存器大家自行查阅本节前面列举的 2 份 ARM 官方资料。 中断使能 中断使能包括两部分一个是 IRQ 或者 FIQ 总中断使能另一个就是 ID0~ID1019 这 1020 个中断源的使能。 1、IRQ 和 FIQ 总中断使能 IRQ 和 FIQ 分别是外部中断和快速中断的总开关就类似家里买的进户总电闸然后ID0~ID1019 这 1020 个中断源就类似家里面的各个电器开关。要想开电视那肯定要保证进户总电闸是打开的因此要想使用 I.MX6U 上的外设中断就必须先打开 IRQ 中断 ( 本教程不使用FIQ)。在“ 6.3.2 程序状态寄存器”小节已经讲过了寄存器 CPSR 的 I1 禁止 IRQ 当 I0 使能 IRQ F1 禁止 FIQ F0 使能 FIQ 。我们还有更简单的指令来完成 IRQ 或者 FIQ 的使能和禁止图表 17.1.5.1 所示 2、ID0~ID1019 中断使能和禁止 GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止对于 Cortex-A7 内核来说中断 ID 只使用了 512 个。一个 bit 控制一个中断 ID 的使能那么就需要 512/3216 个 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 中断的。 中断优先级设置 1、优先级数配置 学过 STM32 都知道 Cortex-M 的中断优先级分为抢占优先级和子优先级两者是可以配置 的。同样的 Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级两者同样是可以配置的。GIC 控制器最多可以支持 256 个优先级数字越小优先级越高 Cortex-A7 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器此寄存器用来决定使用几级优先级寄存器结构如图 17.1.6.1 所示 GICC_PMR 寄存器只有低 8 位有效这 8 位最多可以设置 256 个优先级其他优先级数设置如表 17.1.6.1 所示 I.MX6U 是 Cortex-A7 内核所以支持 32 个优先级因此 GICC_PMR 要设置为 0b11111000 。 2、抢占优先级和子优先级位数设置 抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的 GICC_BPR 寄存器结构如图 17.1.6.2 所示 寄存器 GICC_BPR 只有低 3 位有效其值不同抢占优先级和子优先级占用的位数也不同配置如表 17.1.6.2 所示 为了简单起见一般将所有的中断优先级位都配置为抢占优先级比如 I.MX6U 的优先级位数为5(32 个优先级 ) 所以可以设置 Binary point 为 2 表示 5 个优先级位全部为抢占优先级。 3、优先级设置 前面已经设置好了 I.MX6U 一共有 32 个抢占优先级数字越小优先级越高。具体要使用某个中断的时候就可以设置其优先级为 0~31 。某个中断 ID 的中断优先级设置由寄存器D_IPRIORITYR 来完成前面说了 Cortex-A7 使用了 512 个中断 ID 每个中断 ID 配有一个优先级寄存器所以一共有 512 个 D_IPRIORITYR 寄存器。如果优先级个数为 32 的话使用寄存器 D_IPRIORITYR 的 bit7:4 来设置优先级也就是说实际的优先级要左移 3 位。比如要设置ID40 中断的优先级为 5 示例代码如下 GICD_IPRIORITYR[40] 5 3; 有关优先级设置的内容就讲解到这里优先级设置主要有三部分 ①、设置寄存器 GICC_PMR 配置优先级个数比如 I.MX6U 支持 32 级优先级。 ②、设置抢占优先级和子优先级位数一般为了简单起见会将所有的位数都设置为抢占优先级。 ③、设置指定中断 ID 的优先级也就是设置外设优先级 硬件原理分析 本试验我们用到的硬件有 1 LED 灯 LED0。 2蜂鸣器。 31 个按键 KEY0。 按键 KEY0 的原理图如图所示 按键 KEY0 是连接到 I.MX6U 的 UART1_CTS 这个 IO 上的KEY0接了一个 10K 的上拉电阻因此 KEY0 没有按下的时候 UART1_CTS 应该是高电平当 KEY0按下以后 UART1_CTS 就是低电平。 试验程序编写 本章试验的功能和按键输入一样只是按键采用中断的方式处理。当按下按键 KEY0 以后就打开蜂鸣器再次按下按键 KEY0 就关闭蜂鸣器。所以是在按键输入篇章的基础上完成本次实验 1、移植 SDK 包中断相关文件 将 SDK 包中的文件 core_ca7.h 拷贝到本章试验工程中的“ imx6ul ”文件夹中参考试验 “ 9_int ”中 core_ca7.h 进行修改。主要留下和 GIC 相关的内容我们重点是需要 core_ca7.h 中的 10 个 API 函数这 10 个函数如表 17.3.1.1 所示 移植好 core_ca7.h 以后修改文件 imx6ul.h 在里面加上如下一行代码 #include core_ca7.h 2 、重新编写 start.S 文件 重新在 start.S 中输入如下内容 .global _start /* 全局标号 *//** 描述 _start函数首先是中断向量表的创建* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P423 ARM Processor Modes and RegistersARM处理器模型和寄存器* 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, 0X87800000dsbisbmcr p15, 0, r0, c12, c0, 0dsbisb
#endif/* 设置各个模式下的栈指针* 注意IMX6UL的堆栈是向下增长的* 堆栈指针地址一定要是4字节地址对齐的* DDR范围:0X80000000~0X9FFFFFFF*//* 进入IRQ模式 */mrs r0, cpsrbic 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, cpsrbic 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, cpsrbic 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中 */
#endifb main /* 跳转到main函数 *//* 未定义中断 */
Undefined_Handler:ldr r0, Undefined_Handlerbx r0/* SVC中断 */
SVC_Handler:ldr r0, SVC_Handlerbx r0/* 预取终止中断 */
PrefAbort_Handler:ldr r0, PrefAbort_Handler bx r0/* 数据终止中断 */
DataAbort_Handler:ldr r0, DataAbort_Handlerbx r0/* 未使用的中断 */
NotUsed_Handler:ldr r0, NotUsed_Handlerbx r0/* IRQ中断重点 */IRQ_Handler:push {lr} /* 保存lr地址 */push {r0-r3, r12} /* 保存r0-r3r12寄存器 */mrs r0, spsr /* 读取spsr寄存器 */push {r0} /* 保存spsr寄存器 */mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49* Cortex-A7 Technical ReferenceManua.pdf P68 P138*/ 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语言中断处理函数带有一个参数保存在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 *//* FIQ中断 */
FIQ_Handler:ldr r0, FIQ_Handler bx r0 第 6 到 14 行是中断向量表。 第 17 到 81 行是复位中断服务函数 Reset_Handler 第 19 行先调用指令“ cpsid i ”关闭 IRQ 第 24 到 30 行是关闭 I/D Cache 、 MMU 、对齐检测和分支预测。第 33 行到 42 行是汇编版本的中断向量表重映射。第 50 到 68 行是设置不同模式下的 sp 指针分别设置 IRQ 模式、 SYS 模 式和 SVC 模式的栈指针每种模式的栈大小都是 2MB 。第 70 行调用指令“ cpsie i ”重新打开IRQ 中断第 72 到 79 行是操作 CPSR 寄存器来打开 IRQ 中断。当初始化工作都完成以后就可以进入到 main 函数了第 81 行就是跳转到 main 函数。 第 110 到 144 行是中断服务函数 IRQ_Handler这个是本章的重点因为所有的外部中断最终都会触发 IRQ 中断所以 IRQ 中断服务函数主要的工作就是区分当前发生的什么中断(中断 ID)然后针对不同的外部中断做出不同的处理。第 111 到 115 行是保存现场第 117 到 122行是获取当前中断号中断号被保存到了 r0 寄存器中。第 131 和 132 行才是中断处理的重点这两行相当于调用了函数 system_irqhandler函数 system_irqhandler 是一个 C 语言函数此函数有一个参数这个参数中断号所以我们需要传递一个参数。汇编中调用 C 函数如何实现参数传递呢根据 ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则在汇编调用 C 函数的时候建议形参不要超过 4 个形参可以由 r0~r3 这四个寄存器来传递如果形参大于 4 个那么大于 4 个的部分要使用堆栈进行传递。所以给 r0 寄存器写入中断号就可以了函数 system_irqhandler 的参数传递在 136 行已经向 r0 寄存器写入了中断号了。中断的真正处理过程其实是在函数 system_irqhandler 中完成稍后需要编写函数 system_irqhandler。 第 137 行向 GICC_EOIR 寄存器写入刚刚处理完成的中断号当一个中断处理完成以后必须向 GICC_EOIR 寄存器写入其中断号表示中断处理完成。 第 139 到 143 行就是恢复现场。 第 144 行中断处理完成以后就要重新返回到曾经被中断打断的地方运行这里为什么要将lr-4 然后赋给 pc 呢而不是直接将 lr 赋值给 pc ARM 的指令是三级流水线取指、译指、执行pc 指向的是正在取值的地址这就是很多书上说的 pc 当前执行指令地址 8 。比如下面代码示例 0X2000 MOV R1, R0 ;执行
0X2004 MOV R2, R3 ;译指
0X2008 MOV R4, R5 ;取值 PC 上面示例代码中左侧一列是地址中间是指令最右边是流水线。当前正在执行 0X2000 地址处的指令“MOV R1, R0 ”但是 PC 里面已经保存了 0X2008 地址处的指令“ MOV R4, R5 ”。假设此时发生了中断中断发生的时候保存在 lr 中的是 pc 的值也就是地址 0X2008 。当中断处理完成以后肯定需要回到被中断点接着执行如果直接跳转到 lr 里面保存的地址处 (0X2008)开始运行那么就有一个指令没有执行那就是地址 0X2004 处的指令“ MOV R2, R3 ”显然这是一个很严重的错误所以就需要将 lr-4 赋值给 pc 也就是 pc0X2004 从指令“ MOV R2 R3”开始执行。 3、通用中断驱动文件编写 在 start.S 文件中我们在中断服务函数 IRQ_Handler 中调用了 C 函数 system_irqhandler 来处理具体的中断。此函数有一个参数参数是中断号但是函数 system_irqhandler 的具体内容还没有实现所以需要实现函数 system_irqhandler 的具体内容。不同的中断源对应不同的中断处理函数I.MX6U 有 160 个中断源所以需要 160 个中断处理函数我们可以将这些中断处理函数放到一个数组里面中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。在 bsp 目录下新建名为“ int ”的文件夹在 bsp/int 文件夹里面创建 bsp_int.c 和 bsp_int.h 这两个文件。在 bsp_int.h 文件里面输入如下内容 #ifndef _BSP_INT_H
#define _BSP_INT_H
#include imx6ul.h/* 中断服务函数形式 */
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);/* 中断服务函数结构体*/
typedef struct _sys_irq_handle
{system_irq_handler_t irqHandler; /* 中断服务函数 */void *userParam; /* 中断服务函数参数 */
} sys_irq_handle_t;/* 函数声明 */
void int_init(void);
void system_irqtable_init(void);
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam);
void system_irqhandler(unsigned int giccIar);
void default_irqhandler(unsigned int giccIar, void *userParam); #endif第 16~23 行是中断处理结构体结构体 sys_irq_handle_t 包含一个中断处理函数和中断处理函数的用户参数。一个中断源就需要一个 sys_irq_handle_t 变量 I.MX6U 有 160 个中断源因此需要 160 个 sys_irq_handle_t 组成中断处理数组。 在 bsp_int.c 中输入如下所示代码 #include bsp_int.h/* 中断嵌套计数器 */
static unsigned int irqNesting;/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];/** description : 中断初始化函数* param : 无* return : 无*/
void int_init(void)
{GIC_Init(); /* 初始化GIC */system_irqtable_init(); /* 初始化中断表 */__set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移偏移到起始地址 */
}/** description : 初始化中断服务函数表 * param : 无* return : 无*/
void system_irqtable_init(void)
{unsigned int i 0;irqNesting 0;/* 先将所有的中断服务函数设置为默认值 */for(i 0; i NUMBER_OF_INT_VECTORS; i){system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);}
}/** description : 给指定的中断号注册中断服务函数 * param - irq : 要注册的中断号* param - handler : 要注册的中断处理函数* param - usrParam : 中断服务处理函数参数* return : 无*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{irqTable[irq].irqHandler handler;irqTable[irq].userParam userParam;
}/** description : C语言中断服务函数irq汇编中断服务函数会调用此函数此函数通过在中断服务列表中查找指定中断号所对应的中断处理函数并执行。* param - giccIar : 中断号* return : 无*/
void system_irqhandler(unsigned int giccIar)
{uint32_t intNum giccIar 0x3FFUL;/* 检查中断号是否符合要求 */if ((intNum 1023) || (intNum NUMBER_OF_INT_VECTORS)){return;}irqNesting; /* 中断嵌套计数器加一 *//* 根据传递进来的中断号在irqTable中调用确定的中断服务函数*/irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);irqNesting--; /* 中断执行完成中断嵌套寄存器减一 */}/** description : 默认中断服务函数* param - giccIar : 中断号* param - usrParam : 中断服务处理函数参数* return : 无*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{while(1) {}
} 第 14 行定义了一个变量 irqNesting 此变量作为中断嵌套计数器。 第 17 行定了中断服务函数数组 irqTable 这是一个 sys_irq_handle_t 类型的结构体数组数组大小为 I.MX6U 的中断源个数即 160 个。 第 24~28 行是中断初始化函数 int_init 在此函数中首先初始化了 GIC 然后初始化了中断服务函数表最终设置了中断向量表偏移。 第 36~46 行是中断服务函数表初始化函数 system_irqtable_init 初始化 irqTable 给其赋初值。 第 55~59 行是注册中断处理函数 system_register_irqhandler 此函数用来给指定的中断号注册中断处理函数。如果要使用某个外设中断那就必须调用此函数来给这个中断注册一个中断处理函数。 第 68~86 行就是前面在 start.S 中调用的 system_irqhandler 函数此函数根据中断号在中断处理函数表 irqTable 中取出对应的中断处理函数并执行。 第 94~99 行是默认中断处理函数 default_irqhandler 这是一个空函数主要用来给初始化中断函数处理表。 4、修改 GPIO 驱动文件 在前几章节试验中我们只是使用到了 GPIO 最基本的输入输出功能本章我们需要使用GPIO 的中断功能。所以需要修改文件 GPIO 的驱动文件 bsp_gpio.c 和 bsp_gpio.h 加上中断相关函数。关于 GPIO 中断内容已经在 8.1.5 小节进行了详细的讲解这里就不赘述了。打开 bsp_gpio.h 文件重新输入如下内容 #ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#define _BSP_KEY_H
#include imx6ul.h/* * 枚举类型和结构体定义 */
typedef enum _gpio_pin_direction
{kGPIO_DigitalInput 0U, /* 输入 */kGPIO_DigitalOutput 1U, /* 输出 */
} gpio_pin_direction_t;/** GPIO中断触发类型枚举*/
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 struct _gpio_pin_config
{gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */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);
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);#endif相比前面试验的 bsp_gpio.h 文件“示例代码 17.3.3.2 ”中添加了一个新枚举类型 gpio_interrupt_mode_t枚举出了 GPIO 所有的中断触发类型。还修改了结构体 gpio_pin_config_t在里面加入了 interruptMode 成员变量。最后就是添加了一些跟中断有关的函数声明bsp_gpio.h文件的内容总体还是比较简单的。 打开 bsp_gpio.c 文件重新输入如下代码 #include bsp_gpio.h/** description : GPIO初始化。* param - base : 要初始化的GPIO组。* param - pin : 要初始化GPIO在组内的编号。* param - config : GPIO配置结构体。* return : 无*/
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{base-IMR ~(1U pin);if(config-direction kGPIO_DigitalInput) /* GPIO作为输入 */{base-GDIR ~( 1 pin);}else /* 输出 */{base-GDIR | 1 pin;gpio_pinwrite(base,pin, config-outputLogic); /* 设置默认输出电平 */}gpio_intconfig(base, pin, config-interruptMode); /* 中断功能配置 */
}/** description : 读取指定GPIO的电平值 。* param - base : 要读取的GPIO组。* param - pin : 要读取的GPIO脚号。* return : 无*/int gpio_pinread(GPIO_Type *base, int pin){return (((base-DR) pin) 0x1);}/** description : 指定GPIO输出高或者低电平 。* param - base : 要输出的的GPIO组。* param - pin : 要输出的GPIO脚号。* param - value : 要输出的电平1 输出高电平 0 输出低低电平* return : 无*/
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{if (value 0U){base-DR ~(1U pin); /* 输出低电平 */}else{base-DR | (1U pin); /* 输出高电平 */}
}/** description : 设置GPIO的中断配置功能* param - base : 要配置的IO所在的GPIO组。* param - pin : 要配置的GPIO脚号。* param - pinInterruptMode: 中断模式参考枚举类型gpio_interrupt_mode_t* return : 无*/
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode)
{volatile uint32_t *icr;uint32_t icrShift;icrShift pin;base-EDGE_SEL ~(1U pin);if(pin 16) /* 低16位 */{icr (base-ICR1);}else /* 高16位 */{icr (base-ICR2);icrShift - 16;}switch(pin_int_mode){case(kGPIO_IntLowLevel):*icr ~(3U (2 * icrShift));break;case(kGPIO_IntHighLevel):*icr (*icr (~(3U (2 * icrShift)))) | (1U (2 * icrShift));break;case(kGPIO_IntRisingEdge):*icr (*icr (~(3U (2 * icrShift)))) | (2U (2 * icrShift));break;case(kGPIO_IntFallingEdge):*icr | (3U (2 * icrShift));break;case(kGPIO_IntRisingOrFallingEdge):base-EDGE_SEL | (1U pin);break;default:break;}
}/** description : 使能GPIO的中断功能* param - base : 要使能的IO所在的GPIO组。* param - pin : 要使能的GPIO在组内的编号。* return : 无*/
void gpio_enableint(GPIO_Type* base, unsigned int pin)
{ base-IMR | (1 pin);
}/** description : 禁止GPIO的中断功能* param - base : 要禁止的IO所在的GPIO组。* param - pin : 要禁止的GPIO在组内的编号。* return : 无*/
void gpio_disableint(GPIO_Type* base, unsigned int pin)
{ base-IMR ~(1 pin);
}/** description : 清除中断标志位(写1清除)* param - base : 要清除的IO所在的GPIO组。* param - pin : 要清除的GPIO掩码。* return : 无*/
void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
{base-ISR | (1 pin);
} 在 bsp_gpio.c 文件中首先修改了 gpio_init 函数在此函数里面添加了中断配置代码。另外也新增加了 4 个函数如下 gpio_intconfig配置 GPIO 的中断功能。 gpio_enableintGPIO 中断使能函数。 gpio_disableintGPIO 中断禁止函数。 gpio_clearintflagsGPIO 中断标志位清除函数。 bsp_gpio.c 文件重点就是增加了一些跟 GPIO 中断有关的函数都比较简单。 5、按键中断驱动文件编写 本例程的目的是以中断的方式编写 KEY 按键驱动当按下 KEY 以后触发 GPIO 中断然后在中断服务函数里面控制蜂鸣器的开关。所以接下来就是要编写按键 KEY 对应的UART1_CTS 这个 IO 的中断驱动在 bsp 文件夹里面新建名为“ exit ”的文件夹然后在 bsp/exit里面新建 bsp_exit.c 和 bsp_exit.h 两个文件。在 bsp_exit.h 文件中输入如下代码 #ifndef _BSP_EXIT_H
#define _BSP_EXIT_H
#include imx6ul.h/* 函数声明 */
void exit_init(void); /* 中断初始化 */
void gpio1_io18_irqhandler(void); /* 中断处理函数 */#endifbsp_exit.h 就是函数声明很简单。接下来在 bsp_exit.c 里面输入如下内容 #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、设置IO复用 */IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);/* 2、初始化GPIO为中断模式 */key_config.direction kGPIO_DigitalInput;key_config.interruptMode kGPIO_IntFallingEdge;key_config.outputLogic 1;gpio_init(GPIO1, 18, key_config);GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
}/** description : GPIO1_IO18最终的中断处理函数* param : 无* return : 无*/
void gpio1_io18_irqhandler(void)
{ static unsigned char state 0;/**采用延时消抖中断服务函数中禁止使用延时函数因为中断服务需要*快进快出这里为了演示所以采用了延时函数进行消抖后面我们会讲解*定时器中断消抖法*/delay(10);if(gpio_pinread(GPIO1, 18) 0) /* 按键按下了 */{state !state;beep_switch(state);}gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */
} bsp_exit.c 文件只有两个函数 exit_init 和 gpio1_io18_irqhandler exit_init 是中断初始化函数。第 14~24 行都是初始化 KEY 所使用的 UART1_CTS 这个 IO 设置其复用为 GPIO1_IO18 然后配置 GPIO1_IO18 为下降沿触发中断。重点是第 26~28 行在 26 行调用函数 GIC_EnableIRQ来使能 GPIO_IO18 所对应的中断总开关 I.MX6U 中 GPIO1_IO16~IO31 这 16 个 IO 共用 ID99 。 27 行调用函数 system_register_irqhandler 注册 ID99 所对应的中断处理函数 GPIO1_IO16~IO31这 16 个 IO 共用一个中断处理函数至于具体是哪个 IO 引起的中断那就需要在中断处理函数中判断了。28 行通过函数 gpio_enableint 使能 GPIO1_IO18 这个 IO 对应的中断。函数 gpio1_io18_irqhandler 就是 27 行注册的中断处理函数也就是我们学习 STM32 的时 候某个 GPIO 对应的中断服务函数。在此函数里面编写中断处理代码第 50 行就是蜂鸣器开关控制代码也就是我们本试验的目的。当中断处理完成以后肯定要清除中断标志位第 53 行调用函数 gpio_clearintflags 来清除 GPIO1_IO18 的中断标志位。 6、编写 main.c 文件 在 main.c 中输入如下代码 #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函数* param : 无* return : 无*/
int main(void)
{unsigned char state OFF;int_init(); /* 初始化中断(一定要最先调用) */imx6u_clkinit(); /* 初始化系统时钟 */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;
}main.c 很简单重点是第 18 行调用函数 int_init 来初始化中断系统第 24 行调用函数exit_init 来初始化按键 KEY 对应的 GPIO 中断。 编译下载验证 1、编写 Makefile 和链接脚本 在上一章实验的 Makefile 基础上修改变量 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 中断和通用中断驱动头文件 (.h) 路径。 第 23 、 24 行在变量 SRCDIRS 中添加 GPIO 中断和通用中断驱动文件 (.c) 路径。 链接脚本保持不变。 2、编译下载 使用 Make 命令编译代码编译成功以后使用软件 imxdownload2 将编译完成的 bsp.bin 文件生成可执行的img文件命令如下 make
./imxdownload2 int.bin 如果 imxdownload2无权限可用以下命令添加权限 chmod 777 imxdownload2 利用Win32DiskImager软件将load.img执行文件写入SD卡SD卡插入开发板上即可正常运行。如果代码运行正常的话 LED0 会以大约 500ms 周期闪烁 按下开发板上的 KEY0 按键蜂鸣器打开再按下 KEY0按键蜂鸣器关闭。 例程 【免费】Linux学习笔记13-GPIO中断实验例程资源-CSDN文库