参考:Linux之ARM Cortex-A7 中断系统详解
作者:一只青木呀
发布时间: 2020-09-16 16:07:22
网址:https://blog.csdn.net/weixin_45309916/article/details/108290225
目录
- 1、中断是什么
- 2、回顾STM32中断系统
- 2.1、中断向量表(对应的中断服务函数)
- 2.2、NVIC(ARM对应的GIC)
- 2.3、中断使能
- 2.4、中断服务函数
- 3、Cortex-A7 中断系统详解
- 3.1、中断向量表
- 中断向量表介绍
- 创建中断向量表
- 3.2、GIC 中断控制器简介
- 3.2.1、GIC 控制器总览
- 3.2.2、中断 ID(中断源共有128+32=160个)
- 3.3.3、GIC 逻辑分块(分发器端和CPU接口端,CP15协处理器寄存器可以访问到)
- 3.3、CP15 协处理器寄存器
- 3.3.1、c0 寄存器
- 3.3.2、c1 寄存器(使能MMU和Cache)
- 3.3.3、c12 寄存器(中断向量偏移)
- 3.3.4、c15 寄存器
- 3.3.5、 总结
- 3.4、中断使能
- 3.4.1、IRQ 和 FIQ 总中断使能
- 3.4.2、 ID0~ID1019 中断使能和禁止
- 3.5、中断优先级设置
- 3.5.1、优先级数配置
- 3.5.2、抢占优先级和子优先级位数设置
- 3.6.3、优先级设置
- 3.6.4、总结
- 4.GPIO按键中断实验编写
- 课堂笔记
- 移植SDK包中断相关文件
- 修改汇编start.S(编写中断向量表 -> Rest中断 -> SVC模式跳转main函数 -> IRQ中断)
- 通用中断驱动编写
- bsp_int.h(汇编调用这里的system_irqhandler中断处理函数)
- bsp_int.c(初始化GIC及中断向量偏移、中断处理函数组->定义、初始化<注册>、实现)
- 修改GPIO 驱动文件(下降沿触发方式、使能、清除位)
- GIC也要配置(使能中断ID、优先级、注册中断处理函数、使能GPIO中断功能)
- bsp_gpio.h
- bsp_gpio.c
- 外部按键中断驱动文件编写(类似STM32某个GPIO对应的具体中断服务函数编写 )
- bsp_exit.h
- bsp_exit.c(初始化中断触发方式、配置GIC使能注册中断处理服务函数)
- main.c主函数
- 5.实验程序的编译与下载验证
- 编译
- 下载验证
1、中断是什么
中断系统是一个处理器重要的组成部分,中断系统极大的提高了 CPU 的执行效率
2、回顾STM32中断系统
STM32 的中断系统主要有以下几个关键点:
- ①、 中断向量表。
- ②、 NVIC(内嵌向量中断控制器)。
- ③、 中断使能。
- ④、 中断服务函数。
课堂笔记:
2.1、中断向量表(对应的中断服务函数)
中断向量表是一个表,这个表里面存放的是中断向量。中断服务程序的入口地址或存放中断服务程序的首地址(就是中断服务函数名)成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。这些中断服务程序(函数)在中断向量表中的位置是由半导体厂商定好的,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。中断向量表在整个程序的最前面,比如 STM32F103 的中断向量表如下所示:
__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD NMI_Handler ; NMI HandlerDCD HardFault_Handler ; Hard Fault HandlerDCD MemManage_Handler ; MPU Fault HandlerDCD BusFault_Handler ; Bus Fault HandlerDCD UsageFault_Handler ; Usage Fault HandlerDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVC_Handler ; SVCall HandlerDCD DebugMon_Handler ; Debug Monitor HandlerDCD 0 ; ReservedDCD PendSV_Handler ; PendSV HandlerDCD SysTick_Handler ; SysTick HandlerExternal InterruptsDCD WWDG_IRQHandler ; Window WatchdogDCD PVD_IRQHandler ; PVD through EXTI Line detectDCD TAMPER_IRQHandler ; TamperDCD RTC_IRQHandler ; RTCDCD FLASH_IRQHandler ; Flash/* 省略掉其它代码 */DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & l5
上表就是 STM32F103 的中断向量表,中断向量表都是链接到代码的最前面,比如一般 ARM 处理器都是从地址 0X00000000 开始执行指令的,那么中断向量表就是从 0X00000000 开始存放的。上表中第 1 行的“__initial_sp”就是第一条中断向量,存放的是栈顶指针,接下来是第 2 行复位中断复位函数 Reset_Handler 的入口地址,依次类推,直到第 27 行的最后一个中断服务函数 DMA2_Channel4_5_IRQHandler 的入口地址,这样 STM32F103 的中断向量表就建好了。
我们说 ARM 处理器都是从地址 0X00000000 开始运行的,但是我们学习 STM32 的时候代码是下载到 0X8000000 开始的存储区域中。因此中断向量表是存放到 0X8000000 地址处的,而不是 0X00000000,这样不是就出错了吗?为了解决这个问题, Cortex-M 架构引入了一个新的概念——中断向量表偏移,通过中断向量表偏移就可以将中断向量表存放到任意地址处,中断向量表偏移配置在函数 SystemInit 中完成,通过向 SCB_VTOR 寄存器写入新的中断向量表首地址即可,代码如下所示:
void SystemInit (void)
{RCC->CR |= (uint32_t)0x00000001;/* 省略其它代码 */#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;#elseSCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;#endif
}
第 8 行和第 10 行就是设置中断向量表偏移,第 8 行是将中断向量表设置到 RAM 中,第10 行是将中断向量表设置到 ROM 中,基本都是将中断向量表设置到 ROM 中,也就是地址0X8000000 处。第 10 行用到了 FALSH_BASE 和 VECT_TAB_OFFSET,这两个都是宏,定义如下所示:
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0
因此第 10 行的代码就是: SCB->VTOR=0X080000000,中断向量表偏移设置完成。
STM32中断向量表和中断向量偏移和I.MX6U中断向量表和中断向量偏移的关系?
I.MX6U 所使用的 Cortex-A7 内核也有中断向量表和中断向量表偏移,而且其含义和 STM32 是一模一样的!只是用到的寄存器不同而已,概念完全相同。
2.2、NVIC(ARM对应的GIC)
中断系统得有个管理机构,对于 STM32 这种 Cortex-M 内核的单片机来说这个管理机构叫做 NVIC,全称叫做 Nested Vectored Interrupt Controller。
作用就是中断管理机构,使能和关闭指定的中断、设置中断优先级。
关于 NVIC 本教程不作详细的讲解,既然 Cortex-M 内核有个中断系统的管理机构—NVIC,那么 I.MX6U 所使用的 Cortex-A7 内核是不是也有个中断系统管理机构?答案是肯定的,不过 Cortex-A 内核的中断管理机构不叫做NVIC,而是叫做 GIC,全称是 general interrupt controller。
2.3、中断使能
要使用某个外设的中断,肯定要先使能这个外设的中断,以 STM32F103 的 PE2 这个 IO 为例,假如我们要使用 PE2 的输入中断肯定要使用如下代码来使能对应的中断:
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
上述代码就是使能 PE2 对应的 EXTI2 中断,同理,如果要使用 I.MX6U 的某个中断的话也需要使能其对应的中断。
2.4、中断服务函数
我们使用中断的目的就是为了使用中断服务函数,当中断发生以后中断服务函数就会被调用,我们要处理的工作就可以放到中断服务函数中去完成。同样以 STM32F103 的 PE2 为例,其中断服务函数如下所示:
/* 外部中断 2 服务程序 */
void EXTI2_IRQHandler(void)
{
/* 中断处理代码 */
}
当PE2 引脚的中断触发以后就会调用其对应的中断处理函数 EXTI2_IRQHandler,我们可以在函数 EXTI2_IRQHandler 中添加中断处理代码。同理, I.MX6U 也有中断服务函数,当某个外设中断发生以后就会调用其对应的中断服务函数。
3、Cortex-A7 中断系统详解
3.1、中断向量表
中断向量表介绍
跟 STM32 一样, Cortex-A7 也有中断向量表,中断向量表也是在代码的最前面。 CortexA7 内核有 8 个异常中断,这 8 个异常中断的中断向量表如下表所示:
向量地址(相对于起始地址的偏移量) | 终端类型 | 中断模式 |
---|---|---|
0X00 | 复位中断(Rest) | 特权模式(SVC) |
0X04 | 未定义指令中断(Undefined Instruction) | 未定义指令中止模式(Undef) |
0X08 | 软中断(Software Interrupt,SWI) | 特权模式(SVC) |
0X0C | 指令预取中止中断(Prefetch Abort) | 中止模式 |
0X10 | 数据访问中止中断(Data Abort) | 中止模式 |
0X14 | 未使用(Not Used) | 未使用 |
0X18 | IRQ 中断(IRQ Interrupt) | 外部中断模式(IRQ) |
0X1C | FIQ 中断(FIQ Interrupt) | 快速中断模式(FIQ) |
中断向量表里面都是中断服务函数的入口地址,因此一款芯片有什么中断都是可以从中断向量表看出来的。从上表中可以看出, Cortex-A7 一共有 8 个中断,而且还有一个中断向量未使用,实际只有 7 个中断。和“示例代码的 STM32F103 中断向量表比起来少了很多!难道一个能跑 Linux 的芯片只有这 7 个中断?明显不可能的!那类似 STM32 中的EXTI9_5_IRQHandler、 TIM2_IRQHandler 这样的中断向量在哪里? I2C、 SPI、定时器等等的中断怎么处理呢?
这个就是 Cortex-A 和 Cotex-M 在中断向量表这一块的区别,对于 Cortex-M 内核来说,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。对于 CotexA 内核来说并没有这么做,在上表中有个 IRQ 中断, Cortex-A 内核 CPU 的所有外部中断都属于这个 IQR 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。这些外部中断和 IQR 中断的关系如图 所示:
在上图中,左侧的 Software0_IRQn~PMU_IRQ2_IRQ 这些都是 I.MX6U 的中断,他们都属于 IRQ 中断。当图左侧这些中断中任意一个发生的时候 IRQ 中断都会被触发,所以我们需要在 IRQ 中断服务函数中判断究竟是左侧的哪个中断发生了,然后再做出具体的处理。
在中断系统表中一共有 7 个中断,简单介绍一下这 7 个中断:
中断 | 描述 |
---|---|
①、复位中断(Rest) | CPU 复位以后(一上电)就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、 DDR 等等。 |
②、未定义指令中断(Undefined Instruction) | 如果指令不能识别的话就会产生此中断。 |
③、软中断(Software Interrupt,SWI) | 由 SWI 指令引起的中断, Linux 的系统调用会用 SWI指令来引起软中断,通过软中断来陷入到内核空间。 |
④、指令预取中止中断(Prefetch Abort) | 预取指令的出错的时候会产生此中断。 |
⑤、数据访问中止中断(Data Abort) | 访问数据出错的时候会产生此中断。 |
⑥、 IRQ 中断(IRQ Interrupt) | 外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。 |
⑦、 FIQ 中断(FIQ Interrupt) | 快速中断,如果需要快速处理中断的话就可以使用此中。 |
在上面的 7 个中断中,我们常用的就是复位中断和 IRQ 中断,所以我们需要编写这两个中断的中断服务函数。
创建中断向量表
首先我们要根据表的内容来创建中断向量表(需要用户自己来创建,而STM32提前帮我们创建好了),中断向量表处于程序最开始的地方,比如我们前面例程的 start.S 文件最前面,中断向量表如下:
.global _start /* 全局标号 */_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:
/* 复位中断具体处理过程 *//* 未定义指令中断服务函数实现 都写成死循环*/Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC 中断 */
SVC_Handler:ldr r0, =SVC_Handlerbx r0/* 预取终止中断 */
PrefAbort_Handler:ldr r0, =PrefAbort_Handlerbx r0/* 数据终止中断 */
DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0/* 未使用的中断 */
NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0/* IRQ 中断!重点!!!!! */
IRQ_Handler:
/* 复位中断具体处理过程 */FIQ_Handler:ldr r0, =FIQ_Handlerbx r0
第 4 到 11 行是中断向量表,当指定的中断发生以后就会调用对应的中断复位函数,比如复位中断发生以后就会执行第 4 行代码,也就是调用函数 Reset_Handler,函数 Reset_Handler就是复位中断的中断复位函数,其它的中断同理。
第 14 到 50 行就是对应的中断服务函数,中断服务函数都是用汇编编写的,我们实际需要编写的只有复位中断服务函数 Reset_Handler 和 IRQ 中断服务函数 IRQ_Handler,其它的中断本教程没有用到,所以都是死循环。在编写复位中断复位函数和 IRQ 中断服务函数之前我们还需要了解一些其它的知识,否则的话就没法编写。
课堂笔记:
后面会讲:
3.2、GIC 中断控制器简介
3.2.1、GIC 控制器总览
I.MX6U(Cortex-A)的中断控制器叫做 GIC
GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器(关于GIC 的详细内容请参考开发板光盘中的文档《ARM Generic Interrupt Controller(ARM GIC控制器)V2.0.pdf》,不同的架构对应的控制器版本不同 ),类似 Cortex-M 内核中的NVIC。
目前 GIC 有 4 个版本:V1~V4, V1 是最老的版本,已经被废弃了。 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,他们之间的关系如图所示:
在上图中, GIC 接收众多的外部中断,然后对其进行处理,最终就只通过四个信号报给 ARM 内核,这四个信号的含义如下:
- VFIQ:虚拟快速 FIQ。
- VIRQ:虚拟快速 IRQ。
- FIQ:快速中断 IRQ。
- IRQ:外部中断 IRQ
我们只使用 IRQ,所以相当于 GIC 最终向 ARM 内核就上报一个 IRQ信号。那么 GIC 是如何完成这个工作的呢? GICV2 的逻辑图如图下所示:
图中左侧部分就是中断源,中间部分就是 GIC 控制器,最右侧就是中断控制器向处理器内核发送中断信息。我们重点要看的肯定是中间的 GIC 部分, GIC 将众多的中断源分为分为三类:
-
①、SPI(Shared Peripheral Interrupt),共享中断(重点),顾名思义,所有核Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
-
②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
-
③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
3.2.2、中断 ID(中断源共有128+32=160个)
中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包含了 PPI、 SPI 和 SGI,那么这三类中断是如何分配这 1020 个中断 ID 的呢?这 1020 个 ID 分配如下:
- ID0~ID15:这 16 个 ID 分配给 SGI。
- ID16~ID31:这 16 个 ID 分配给 PPI。
- ID32~ID1019:这 988 个 ID 分配给 SPI(重点)。
I.MX6U 的总共使用了 128 个中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID, I.MX6U 的中断源共有 128+32=160个,这 128 个中断 ID 对应的中断在《I.MX6ULL 参考手册》的“3.2 Cortex A7 interrupts”小节,中断源如表所示:
IRQ | ID | 中断源 | 描述 |
---|---|---|---|
0 | 32 | boot | 用于在启动异常的时候通知内核。 |
1 | 33 | ca7_platform | DAP 中断,调试端口访问请求中断。 |
2 | 34 | sdma | SDMA 中断。 |
3 | 35 | tsc | TSC(触摸)中断。 |
4 | snvs_lp_wrapper 和snvs_hp_wrapper | SNVS 中断。 | |
… | … | …… | …… |
124 | 156 | 无 | 保留 |
125 | 157 | 无 | 保留 |
126 | 158 | 无 | 保留 |
127 | 159 | pmu | PMU中断 |
这里只列举了一小部分并没有给出 I.MX6U 完整的中断源
NXP 官方 SDK中的文件 MCIMX6Y2C.h,在此文件中定义了一个枚举类型 IRQn_Type,此枚举类型就枚举出了 I.MX6U 的所有中断,代码如下所示:
typedef enum IRQn {/* Auxiliary constants */NotAvail_IRQn = -128, /**< Not available device specific interrupt *//* Core interrupts */Software0_IRQn = 0, /**< Cortex-A7 Software Generated Interrupt 0 */Software1_IRQn = 1, /**< Cortex-A7 Software Generated Interrupt 1 */Software2_IRQn = 2, /**< Cortex-A7 Software Generated Interrupt 2 */Software3_IRQn = 3, /**< Cortex-A7 Software Generated Interrupt 3 */Software4_IRQn = 4, /**< Cortex-A7 Software Generated Interrupt 4 */Software5_IRQn = 5, /**< Cortex-A7 Software Generated Interrupt 5 */Software6_IRQn = 6, /**< Cortex-A7 Software Generated Interrupt 6 */Software7_IRQn = 7, /**< Cortex-A7 Software Generated Interrupt 7 */Software8_IRQn = 8, /**< Cortex-A7 Software Generated Interrupt 8 */Software9_IRQn = 9, /**< Cortex-A7 Software Generated Interrupt 9 */Software10_IRQn = 10, /**< Cortex-A7 Software Generated Interrupt 10 */Software11_IRQn = 11, /**< Cortex-A7 Software Generated Interrupt 11 */Software12_IRQn = 12, /**< Cortex-A7 Software Generated Interrupt 12 */Software13_IRQn = 13, /**< Cortex-A7 Software Generated Interrupt 13 */Software14_IRQn = 14, /**< Cortex-A7 Software Generated Interrupt 14 */Software15_IRQn = 15, /**< Cortex-A7 Software Generated Interrupt 15 */VirtualMaintenance_IRQn = 25, /**< Cortex-A7 Virtual Maintenance Interrupt */HypervisorTimer_IRQn = 26, /**< Cortex-A7 Hypervisor Timer Interrupt */VirtualTimer_IRQn = 27, /**< Cortex-A7 Virtual Timer Interrupt */LegacyFastInt_IRQn = 28, /**< Cortex-A7 Legacy nFIQ signal Interrupt */SecurePhyTimer_IRQn = 29, /**< Cortex-A7 Secure Physical Timer Interrupt */NonSecurePhyTimer_IRQn = 30, /**< Cortex-A7 Non-secure Physical Timer Interrupt */LegacyIRQ_IRQn = 31, /**< Cortex-A7 Legacy nIRQ Interrupt *//* Device specific interrupts */IOMUXC_IRQn = 32, /**< General Purpose Register 1 from IOMUXC. Used to notify cores on exception condition while boot. */DAP_IRQn = 33, /**< Debug Access Port interrupt request. */SDMA_IRQn = 34, /**< SDMA interrupt request from all channels. */TSC_IRQn = 35, /**< TSC interrupt. */SNVS_IRQn = 36, /**< Logic OR of SNVS_LP and SNVS_HP interrupts. */LCDIF_IRQn = 37, /**< LCDIF sync interrupt. */RNGB_IRQn = 38, /**< RNGB interrupt. */CSI_IRQn = 39, /**< CMOS Sensor Interface interrupt request. */PXP_IRQ0_IRQn = 40, /**< PXP interrupt pxp_irq_0. */SCTR_IRQ0_IRQn = 41, /**< SCTR compare interrupt ipi_int[0]. */SCTR_IRQ1_IRQn = 42, /**< SCTR compare interrupt ipi_int[1]. */WDOG3_IRQn = 43, /**< WDOG3 timer reset interrupt request. */Reserved44_IRQn = 44, /**< Reserved */APBH_IRQn = 45, /**< DMA Logical OR of APBH DMA channels 0-3 completion and error interrupts. */WEIM_IRQn = 46, /**< WEIM interrupt request. */RAWNAND_BCH_IRQn = 47, /**< BCH operation complete interrupt. */RAWNAND_GPMI_IRQn = 48, /**< GPMI operation timeout error interrupt. */UART6_IRQn = 49, /**< UART6 interrupt request. */PXP_IRQ1_IRQn = 50, /**< PXP interrupt pxp_irq_1. */SNVS_Consolidated_IRQn = 51, /**< SNVS consolidated interrupt. */SNVS_Security_IRQn = 52, /**< SNVS security interrupt. */CSU_IRQn = 53, /**< CSU interrupt request 1. Indicates to the processor that one or more alarm inputs were asserted. */USDHC1_IRQn = 54, /**< USDHC1 (Enhanced SDHC) interrupt request. */USDHC2_IRQn = 55, /**< USDHC2 (Enhanced SDHC) interrupt request. */SAI3_RX_IRQn = 56, /**< SAI3 interrupt ipi_int_sai_rx. */SAI3_TX_IRQn = 57, /**< SAI3 interrupt ipi_int_sai_tx. */UART1_IRQn = 58, /**< UART1 interrupt request. */UART2_IRQn = 59, /**< UART2 interrupt request. */UART3_IRQn = 60, /**< UART3 interrupt request. */UART4_IRQn = 61, /**< UART4 interrupt request. */UART5_IRQn = 62, /**< UART5 interrupt request. */eCSPI1_IRQn = 63, /**< eCSPI1 interrupt request. */eCSPI2_IRQn = 64, /**< eCSPI2 interrupt request. */eCSPI3_IRQn = 65, /**< eCSPI3 interrupt request. */eCSPI4_IRQn = 66, /**< eCSPI4 interrupt request. */I2C4_IRQn = 67, /**< I2C4 interrupt request. */I2C1_IRQn = 68, /**< I2C1 interrupt request. */I2C2_IRQn = 69, /**< I2C2 interrupt request. */I2C3_IRQn = 70, /**< I2C3 interrupt request. */UART7_IRQn = 71, /**< UART-7 ORed interrupt. */UART8_IRQn = 72, /**< UART-8 ORed interrupt. */Reserved73_IRQn = 73, /**< Reserved */USB_OTG2_IRQn = 74, /**< USBO2 USB OTG2 */USB_OTG1_IRQn = 75, /**< USBO2 USB OTG1 */USB_PHY1_IRQn = 76, /**< UTMI0 interrupt request. */USB_PHY2_IRQn = 77, /**< UTMI1 interrupt request. */DCP_IRQ_IRQn = 78, /**< DCP interrupt request dcp_irq. */DCP_VMI_IRQ_IRQn = 79, /**< DCP interrupt request dcp_vmi_irq. */DCP_SEC_IRQ_IRQn = 80, /**< DCP interrupt request secure_irq. */TEMPMON_IRQn = 81, /**< Temperature Monitor Temperature Sensor (temperature greater than threshold) interrupt request. */ASRC_IRQn = 82, /**< ASRC interrupt request. */ESAI_IRQn = 83, /**< ESAI interrupt request. */SPDIF_IRQn = 84, /**< SPDIF interrupt. */Reserved85_IRQn = 85, /**< Reserved */PMU_IRQ1_IRQn = 86, /**< Brown-out event on either the 1.1, 2.5 or 3.0 regulators. */GPT1_IRQn = 87, /**< Logical OR of GPT1 rollover interrupt line, input capture 1 and 2 lines, output compare 1, 2, and 3 interrupt lines. */EPIT1_IRQn = 88, /**< EPIT1 output compare interrupt. */EPIT2_IRQn = 89, /**< EPIT2 output compare interrupt. */GPIO1_INT7_IRQn = 90, /**< INT7 interrupt request. */GPIO1_INT6_IRQn = 91, /**< INT6 interrupt request. */GPIO1_INT5_IRQn = 92, /**< INT5 interrupt request. */GPIO1_INT4_IRQn = 93, /**< INT4 interrupt request. */GPIO1_INT3_IRQn = 94, /**< INT3 interrupt request. */GPIO1_INT2_IRQn = 95, /**< INT2 interrupt request. */GPIO1_INT1_IRQn = 96, /**< INT1 interrupt request. */GPIO1_INT0_IRQn = 97, /**< INT0 interrupt request. */GPIO1_Combined_0_15_IRQn = 98, /**< Combined interrupt indication for GPIO1 signals 0 - 15. */GPIO1_Combined_16_31_IRQn = 99, /**< Combined interrupt indication for GPIO1 signals 16 - 31. */GPIO2_Combined_0_15_IRQn = 100, /**< Combined interrupt indication for GPIO2 signals 0 - 15. */GPIO2_Combined_16_31_IRQn = 101, /**< Combined interrupt indication for GPIO2 signals 16 - 31. */GPIO3_Combined_0_15_IRQn = 102, /**< Combined interrupt indication for GPIO3 signals 0 - 15. */GPIO3_Combined_16_31_IRQn = 103, /**< Combined interrupt indication for GPIO3 signals 16 - 31. */GPIO4_Combined_0_15_IRQn = 104, /**< Combined interrupt indication for GPIO4 signals 0 - 15. */GPIO4_Combined_16_31_IRQn = 105, /**< Combined interrupt indication for GPIO4 signals 16 - 31. */GPIO5_Combined_0_15_IRQn = 106, /**< Combined interrupt indication for GPIO5 signals 0 - 15. */GPIO5_Combined_16_31_IRQn = 107, /**< Combined interrupt indication for GPIO5 signals 16 - 31. */Reserved108_IRQn = 108, /**< Reserved */Reserved109_IRQn = 109, /**< Reserved */Reserved110_IRQn = 110, /**< Reserved */Reserved111_IRQn = 111, /**< Reserved */WDOG1_IRQn = 112, /**< WDOG1 timer reset interrupt request. */WDOG2_IRQn = 113, /**< WDOG2 timer reset interrupt request. */KPP_IRQn = 114, /**< Key Pad interrupt request. */PWM1_IRQn = 115, /**< hasRegInstance(`PWM1`)?`Cumulative interrupt line for PWM1. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */PWM2_IRQn = 116, /**< hasRegInstance(`PWM2`)?`Cumulative interrupt line for PWM2. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */PWM3_IRQn = 117, /**< hasRegInstance(`PWM3`)?`Cumulative interrupt line for PWM3. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */PWM4_IRQn = 118, /**< hasRegInstance(`PWM4`)?`Cumulative interrupt line for PWM4. Logical OR of rollover, compare, and FIFO waterlevel crossing interrupts.`:`Reserved`) */CCM_IRQ1_IRQn = 119, /**< CCM interrupt request ipi_int_1. */CCM_IRQ2_IRQn = 120, /**< CCM interrupt request ipi_int_2. */GPC_IRQn = 121, /**< GPC interrupt request 1. */Reserved122_IRQn = 122, /**< Reserved */SRC_IRQn = 123, /**< SRC interrupt request src_ipi_int_1. */Reserved124_IRQn = 124, /**< Reserved */Reserved125_IRQn = 125, /**< Reserved */CPU_PerformanceUnit_IRQn = 126, /**< Performance Unit interrupt ~ipi_pmu_irq_b. */CPU_CTI_Trigger_IRQn = 127, /**< CTI trigger outputs interrupt ~ipi_cti_irq_b. */SRC_Combined_IRQn = 128, /**< Combined CPU wdog interrupts (4x) out of SRC. */SAI1_IRQn = 129, /**< SAI1 interrupt request. */SAI2_IRQn = 130, /**< SAI2 interrupt request. */Reserved131_IRQn = 131, /**< Reserved */ADC1_IRQn = 132, /**< ADC1 interrupt request. */ADC_5HC_IRQn = 133, /**< ADC_5HC interrupt request. */Reserved134_IRQn = 134, /**< Reserved */Reserved135_IRQn = 135, /**< Reserved */SJC_IRQn = 136, /**< SJC interrupt from General Purpose register. */CAAM_Job_Ring0_IRQn = 137, /**< CAAM job ring 0 interrupt ipi_caam_irq0. */CAAM_Job_Ring1_IRQn = 138, /**< CAAM job ring 1 interrupt ipi_caam_irq1. */QSPI_IRQn = 139, /**< QSPI1 interrupt request ipi_int_ored. */TZASC_IRQn = 140, /**< TZASC (PL380) interrupt request. */GPT2_IRQn = 141, /**< Logical OR of GPT2 rollover interrupt line, input capture 1 and 2 lines, output compare 1, 2 and 3 interrupt lines. */CAN1_IRQn = 142, /**< Combined interrupt of ini_int_busoff,ini_int_error,ipi_int_mbor,ipi_int_txwarning and ipi_int_waken */CAN2_IRQn = 143, /**< Combined interrupt of ini_int_busoff,ini_int_error,ipi_int_mbor,ipi_int_txwarning and ipi_int_waken */Reserved144_IRQn = 144, /**< Reserved */Reserved145_IRQn = 145, /**< Reserved */PWM5_IRQn = 146, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */PWM6_IRQn = 147, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */PWM7_IRQn = 148, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */PWM8_IRQn = 149, /**< Cumulative interrupt line. OR of Rollover Interrupt line, Compare Interrupt line and FIFO Waterlevel crossing interrupt line */ENET1_IRQn = 150, /**< ENET1 interrupt */ENET1_1588_IRQn = 151, /**< ENET1 1588 Timer interrupt [synchronous] request. */ENET2_IRQn = 152, /**< ENET2 interrupt */ENET2_1588_IRQn = 153, /**< MAC 0 1588 Timer interrupt [synchronous] request. */Reserved154_IRQn = 154, /**< Reserved */Reserved155_IRQn = 155, /**< Reserved */Reserved156_IRQn = 156, /**< Reserved */Reserved157_IRQn = 157, /**< Reserved */Reserved158_IRQn = 158, /**< Reserved */PMU_IRQ2_IRQn = 159 /**< Brown-out event on either core, gpu or soc regulators. */
} IRQn_Type;
课堂笔记:
3.3.3、GIC 逻辑分块(分发器端和CPU接口端,CP15协处理器寄存器可以访问到)
GIC 架构分为了两个逻辑块: Distributor 和 CPU Interface(CP15协处理器寄存器可以访问到),也就是分发器端和 CPU 接口端。这两个逻辑块的含义如下:
Distributor(分发器端): 从GICV2 的逻辑图中可以看出,此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。分发器端要做的主要工作如下:
- ①、全局中断使能控制。
- ②、控制每一个中断的使能或者关闭。
- ③、设置每个中断的优先级。
- ④、设置每个中断的目标处理器列表。
- ⑤、设置每个外部中断的触发模式:电平触发或边沿触发。
- ⑥、设置每个中断属于组 0 还是组 1。
CPU Interface(CPU 接口端): CPU 接口端听名字就知道是和 CPU Core 相连接的,因此在图中每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface。 CPU 接口端就是分发器和 CPU Core 之间的桥梁, CPU 接口端主要工作如下:
- ①、使能或者关闭发送到 CPU Core 的中断请求信号。
- ②、应答中断。
- ③、通知中断处理完成。
- ④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。
- ⑤、定义抢占策略。
- ⑥、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。
构体里面的寄存器分为了分发器端和 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 接口段寄存器。那么问题来了? GIC 控制器的寄存器基地址在哪里呢?这个就需要用到 Cortex-A 的 CP15 协处理器了
3.3、CP15 协处理器寄存器
关 于 CP15 协处理 器和其 相关寄存 器的详细 内容 请参考下 面两份文 档: 《 ARMArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》 第 1469 页“B3.17 Oranizationof the CP15 registers in a VMSA implementation”。《Cortex-A7 Technical ReferenceManua.pdf》 第55 页“Capter 4 System Control”。
CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到, CP15 协处理器一共有16 个 32 位寄存器。 CP15 协处理器的访问通过如下几个指令完成:
指令 | 描述 |
---|---|
MRC | 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。 |
MCR | 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。 |
指令格式:
MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>
参数 | 描述 |
---|---|
cond | 指令执行的条件码,如果忽略的话就表示无条件执行。 |
opc1 | 协处理器要执行的操作码。 |
Rt | ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。 |
CRn | CP15 协处理器的目标寄存器。 |
CRm | 协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将CRm |
opc2 | 可选的协处理器特定操作码,当不需要的时候要设置为 0。 |
MRC 的指令格式和 MCR 一样,只不过在 MRC 指令中 Rt 就是目标寄存器,也就是从CP15 指定寄存器读出来的数据会保存在 Rt 中。而 CRn 就是源寄存器,也就是要读取的写处理器寄存器。
假如我们要将 CP15 中 C0 寄存器的值读取到 R0 寄存器中,那么就可以使用如下命令:
MRC p15, 0, r0, c0, c0, 0
CP15 协处理器有 16 个 32 位寄存器, c0~c15
3.3.1、c0 寄存器
CP15 协处理器有 16 个 32 位寄存器, c0~c15,在使用 MRC 或者 MCR 指令访问这 16 个寄存器的时候,指令中的 CRn、 opc1、 CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是不同的。比如 c0 在不同的搭配情况下含义如下图所示:
在图中当 MRC/MCR 指令中的 CRn=c0, opc1=0, CRm=c0, opc2=0 的时候就表示此时的 c0 就是 MIDR 寄存器,也就是主 ID 寄存器,这个也是 c0 的基本作用。对于 Cortex-A7内核来说, c0 作为 MDIR 寄存器的时候其含义如图所示:
在图中各位所代表的含义如下:
位 | 含义 |
---|---|
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 就是次版本号。 |
3.3.2、c1 寄存器(使能MMU和Cache)
c1 寄存器同样通过不同的配置,其代表的含义也不同,如图所示:
在图中当 MRC/MCR 指令中的 CRn=c1, opc1=0, CRm=c0, opc2=0 的时候就表示此时的 c1 就是 SCTLR 寄存器,也就是系统控制寄存器,这个是 c1 的基本作用。 SCTLR 寄存器主要是完成控制功能的,比如使能或者禁止 MMU、 I/D Cache 等, c1 作为 SCTLR 寄存器的时候其含义如下图所示:
SCTLR 的位比较多,我们就只看本次会用到的几个位:
位 | 描述 |
---|---|
bit13 | V , 中断向量表基地址选择位,为 0 的话中断向量表基地址为 0X00000000,软件可以使用 VBAR 来重映射此基地址,也就是中断向量表重定位。为 1 的话中断向量表基地址为0XFFFF0000,此基地址不能被重映射。 |
bit12 | I, I Cache 使能位,为 0 的话关闭 I Cache,为 1 的话使能 I Cache。 |
bit11 | Z,分支预测使能位,如果开启 MMU 的话,此位也会使能。 |
bit10 | SW, SWP 和 SWPB 使能位,当为 0 的话关闭 SWP 和 SWPB 指令,当为 1 的时候就使能 SWP 和 SWPB 指令。 |
bit9:3 | 未使用,保留。 |
bit2 | C, D(D代表date) Cache 和缓存一致性使能位,为 0 的时候禁止 D Cache 和缓存一致性,为 1 时使能。 |
bit1 | A,内存对齐检查使能位,为 0 的时候关闭内存对齐检查,为 1 的时候使能内存对齐检查。 |
bit0 | M, MMU 使能位,为 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.3.3、c12 寄存器(中断向量偏移)
c12 寄存器通过不同的配置,其代表的含义也不同,如图所示:
在上图中当 MRC/MCR 指令中的 CRn=c12, opc1=0, CRm=c0, opc2=0 的时候就表示此时 c12 为 VBAR 寄存器,也就是向量表基地址寄存器。设置中断向量表偏移的时候就需要将新的中断向量表基地址写入 VBAR 中,比如在前面的例程中,代码链接的起始地址为0X87800000,而中断向量表肯定要放到最前面,也就是 0X87800000 这个地址处。所以就需要设置 VBAR 为 0X87800000,设置命令如下:
ldr r0, =0X87800000
MCR p15, 0, r0, c12, c0, 0 ;将 r0 里面的数据写入到 c12 中,即 c12=0X87800000
3.3.4、c15 寄存器
c15 寄存器也可以通过不同的配置得到不同的含义
在图中,我们需要 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 的值
3.3.5、 总结
寄存器 | 作用 |
---|---|
c0 | 寄存器可以获取到处理器内核信息; |
c1 | 寄存器可以使能或禁止 MMU、 I/D Cache 等; |
c12 | 寄存器可以设置中断向量偏移; |
c15 | 寄存器可以获取 GIC 基地址。 |
3.4、中断使能
中断使能包括两部分,一个是 IRQ 或者 FIQ 总中断使能,另一个就是 ID0~ID1019 这 1020个中断源的使能。
3.4.1、IRQ 和 FIQ 总中断使能
IRQ 和 FIQ 分别是外部中断和快速中断的总开关,就类似家里买的进户总电闸,然后ID0~ID1019 这 1020 个中断源就类似家里面的各个电器开关。要想开电视,那肯定要保证进户总电闸是打开的,因此要想使用 I.MX6U 上的外设中断就必须先打开 IRQ 中断(本教程不使用FIQ)。在“6.3.2 程序状态寄存器”小节已经讲过了,寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使能 IRQ; F=1 禁止 FIQ, F=0 使能 FIQ。我们还有更简单的指令来完成 IRQ 或者 FIQ 的使能和禁止,图表所示:
指令 | 描述 |
---|---|
cpsid i | 禁止 IRQ 中断。 |
cpsie i | 使能 IRQ 中断。 |
cpsid f | 禁止 FIQ 中断。 |
cpsie f | 使能 FIQ 中断 |
3.4.2、 ID0~ID1019 中断使能和禁止
GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止,对于 Cortex-A7 内核来说中断 ID 只使用了 512 个。一个 bit 控制一个中断 ID 的使能,那么就需要 512/32=16 个 GICD_ISENABLER 寄存器来完成中断的使能。同理,也需要 16 个GICD_ICENABLER 寄存器来完成中断的禁止。其中 GICD_ISENABLER0 的 bit[15:0]对应ID15~0 的 SGI 中断, GICD_ISENABLER0 的 bit[31:16]对应 ID31~16 的 PPI 中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。
3.5、中断优先级设置
3.5.1、优先级数配置
学过 STM32 都知道 Cortex-M 的中断优先级分为抢占优先级和子优先级,两者是可以配置的。同样的 Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级,两者同样是可以配置的。 Cortex-A7 最多可以支持 256 个优先级,数字越小,优先级越高!半导体厂商自行决定选择多少个优先级。 I.MX6U 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级,寄存器结构如图所示:
GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级,其他优先级数设置如下表所示:
bit[7:0] | 优先级数 |
---|---|
11111111 | 256 个优先级。 |
11111110 | 128 个优先级。 |
11111100 | 64 个优先级。 |
11111000 | 32 个优先级 |
11110000 | 16 个优先级。 |
I.MX6U 支持 32 个优先级,所以 GICC_PMR 要设置为 0b11111000。
3.5.2、抢占优先级和子优先级位数设置
抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的, GICC_BPR 寄存器结构如下图所示:
寄存器 GICC_BPR 只有低 3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同,配置如下表所示:
Binary Point | 抢占优先级域 | 子优先级域 | 描述 |
---|---|---|---|
0 | [7:1] | [0] | 7 级抢占优先级, 1 级子优先级。 |
1 | [7:2] | [1:0] | 6 级抢占优先级, 2 级子优先级。 |
2 | [7:3] | [2:0] | 5 级抢占优先级, 3 级子优先级。 |
3 | [7:4] | [3:0] | 4 级抢占优先级, 4 级子优先级。 |
4 | [7:5] | [4:0] | 3 级抢占优先级, 5 级子优先级。 |
5 | [7:6] | [5:0] | 2 级抢占优先级, 6 级子优先级。 |
6 | [7:7] | [6:0] | 1 级抢占优先级, 7 级子优先级。 |
7 | 无 | [7:0] | 0 级抢占优先级, 8 级子优先级 |
为了简单起见,一般将所有的中断优先级位都配置为抢占优先级,比如 I.MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级。
3.6.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;
3.6.4、总结
优先级设置主要有三部分:
- ①、设置寄存器 GICC_PMR,配置优先级个数,比如 I.MX6U 支持 32 级优先级。
- ②、设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占优先级。
- ③、设置指定中断 ID 的优先级,也就是设置外设优先级。
4.GPIO按键中断实验编写
课堂笔记
这里我们用中断的方式来读取按键状态,改变之前用轮询的方式,释放CPU的资源。
移植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"
实现方式是利用C语言内嵌汇编,就是使用C语言来实现汇编指令,从而实现一些中断功能的API,这样我们就可以调用了。
修改汇编start.S(编写中断向量表 -> Rest中断 -> SVC模式跳转main函数 -> IRQ中断)
8个中断所在手册的位置。
.global _start /* 全局标号 *//** 描述: _start函数,首先是中断向量表的创建* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)* ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)*/
_start: /* 中 断 向 量 表 ldr执行指令 pc就是内核寄存器中的程序计数器R15 保存函数的首地址*/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 /* 关闭全局中断 防止其他的中断造成干扰 跳转main函数之前再次打开中断*//* 关闭I,D Cache和MMU 其实内部bootroom已经帮我们完成了 我们自己再实现一遍* 修改CP15协处理器的 SCTLR寄存器 采取读-改-写的方式。*/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//条件编译屏蔽掉 /* 汇编版本设置中断向量偏移 C语言也可以写 要保证发生在中断之前*/ldr r0, =0X87800000 //链接首地址dsb /* 指令、数据同步的指令(确保前面的指令执行完毕) */isbmcr p15, 0, r0, c12, c0, 0dsbisb
#endif /* 视频这里紧接着清除BSS段 这里没写!!不写也能运行 内部bootroom管理? *//* 设置9种模式下的SP栈指针,* 注意: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模式下的栈(SP)首地址为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模式下的栈(SP)首地址为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模式下的栈(SP)首地址为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 /* reset中断执行完跳转到main函数,后面的IRQ外部中断在执行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-r3,r12寄存器 其他的寄存器在中断机制下自动保存了*/mrs r0, spsr /* 读取spsr寄存器到RO */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这个C0寄存器读取CBAR寄存器放到R1寄存器,CBAR寄存器保存了GIC控制器寄存器组首地址*/ add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址(查手册) */ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,将其保存到R0寄存器* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据* 这个中断号来绝对调用哪个中断服务函数其实还保存了CPU的ID,多核多CPU,这里不用关心*/push {r0, r1} /* 保存r0,r1 */cps #0x13 /* 进入SVC模式,后面要运行C程序,其他的中断也可以继续发生了 */push {lr} /* 保存SVC模式的lr寄存器 */ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中 最终目的就是这个 注意不要放到R0和R1里,已经被使用了*/ blx r2 /* 跳转到R2执行C程序这是汇编和C混合编程 C语言中断处理函数在bsp_int.c里实现这个C程序带有一个参数,这个参数保存在R0寄存器中(中断号) */pop {lr} /* 执行完C语言中断服务函数,lr出栈 */cps #0x12 /* 重新进入到IRQ模式 前面中断处理函数结束重新返回到IRQ模式*/pop {r0, r1} str r0, [r1, #0X10] /* 中断执行完成,将R0保存的中断ID值写到EOIR寄存器里面(手册上要求的) */pop {r0} msr spsr_cxsf, r0 /* 恢复spsr */pop {r0-r3, r12} /* r0-r3,r12出栈 */pop {lr} /* lr出栈 */subs pc, lr, #4 /* 将lr-4赋给pc 三级流水线结构要求减4*//* FIQ中断 */
FIQ_Handler:ldr r0, =FIQ_Handler bx r0
第6 到14 行是中断向量表,17.1.2 小节已经讲解过了。
第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 的指令是三级流水线:取指、译指、执行,这三级流水线循环执行,比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在 R15(PC)中。
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,也就是pc=0X2004,从指令“MOV R2,R3”开始执行。
通用中断驱动编写
在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 文件里面输入如下内容:
bsp_int.h(汇编调用这里的system_irqhandler中断处理函数)
#ifndef _BSP_INT_H
#define _BSP_INT_H
#include "imx6ul.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_int.c
作者 : 左忠凯
版本 : V1.0
描述 : 中断驱动头文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************//* 定义中断处理函数 */ //中断ID 传递给中断处理函数的参数//
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
第20~24 行是中断处理结构体,结构体sys_irq_handle_t 包含一个中断处理函数和中断处理函数的用户参数。一个中断源就需要一个sys_irq_handle_t 变量,I.MX6U 有160 个中断源,因此需要160 个sys_irq_handle_t 组成中断处理数组。
bsp_int.c(初始化GIC及中断向量偏移、中断处理函数组->定义、初始化<注册>、实现)
#include "bsp_int.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_int.c
作者 : 左忠凯
版本 : V1.0
描述 : 中断驱动文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************//* 中断嵌套计数器 记录当前嵌套了多少个中断*/
static unsigned int irqNesting;/* 中断服务函数表 保留多个结构体*/ //这个宏里面定义了160个中断ID号
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); /* 中断向量偏移,偏移到起始地址 要在中断发生之前完成 也可以用汇编写 参考上面汇编文件 这里调用SDK很方便,不用看那么多寄存器、地址 */
}/* 初始化中断服务函数表 对160个中断服务函数初始化一个默认值* @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;//传过来default_irqhandler函数名irqTable[irq].userParam = userParam;//传过来空指针
}/* 具体的中断处理函数实现* @description : C语言中断服务函数,irq汇编中断服务函数会调用此函数,此函数通过在中断服务列表中查找指定中断号所对应的中断处理函数并执行。* @param - giccIar : 中断号* @return : 无*/ //中断ID 前面汇编文件R0寄存器传递来的参数
void system_irqhandler(unsigned int giccIar)
{//bit0-bit9全为1 获取低十位uint32_t intNum = giccIar & 0x3ff;/* 检查中断号是否符合要求 */ //大于160 宏定义if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)){ //低十位全部为1 1023 (作者也忘记为啥就要判断是否1023)return;//返回 不要处理了}irqNesting++; /* 中断嵌套计数器加一 *//* 根据传递进来的中断ID号,在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 中取出对应的中断处理函数并执行。此函数有一个参数,参数是中断号,就是前面汇编文件R0寄存器传过来的。
第94~99 行是默认中断处理函数default_irqhandler,这是一个空函数,主要用来给初始化中断函数处理表。
修改GPIO 驱动文件(下降沿触发方式、使能、清除位)
在前几章节试验中我们只是使用到了GPIO 最基本的输入输出功能,本章我们需要使用GPIO 的中断功能。所以需要修改文件GPIO 的驱动文件bsp_gpio.c 和bsp_gpio.h,加上中断相关函数。
GIC也要配置(使能中断ID、优先级、注册中断处理函数、使能GPIO中断功能)
bsp_gpio.h
相比前面试验的bsp_gpio.h 文件,“示例代码17.3.3.2”中添加了一个新枚举类型:gpio_interrupt_mode_t,枚举出了GPIO 所有的中断触发类型。还修改了结构体gpio_pin_config_t,在里面加入了interruptMode 成员变量。最后就是添加了一些跟中断有关的函数声明,bsp_gpio.h文件的内容总体还是比较简单的。
#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#define _BSP_KEY_H
#include "imx6ul.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_gpio.h
作者 : 左忠凯
版本 : V1.0
描述 : GPIO操作文件头文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建V2.0 2019/1/4 左忠凯修改添加GPIO中断相关定义***************************************************************//* * 枚举类型和GPIO结构体定义 */
typedef enum _gpio_pin_direction
{kGPIO_DigitalInput = 0U, /* 输入 */kGPIO_DigitalOutput = 1U, /* 输出 */
} gpio_pin_direction_t;/** GPIO中断触发类型枚举 由GPIO_ICR寄存器配置*/
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.c
在bsp_gpio.c 文件中首先修改了gpio_init 函数,在此函数里面添加了中断配置代码。另外也新增加了4 个函数,如下:
- gpio_intconfig:配置GPIO 的中断初始化(触发方式)功能。
- gpio_enableint:GPIO 中断使能函数。
- gpio_disableint:GPIO 中断禁止(不使能)函数。
- gpio_clearintflags:GPIO 中断标志位清除函数。
bsp_gpio.c 文件重点就是增加了一些跟GPIO 中断有关的函数,都比较简单。
#include "bsp_gpio.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_gpio.h
作者 : 左忠凯
版本 : V1.0
描述 : GPIO操作文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建V2.0 2019/1/4 左忠凯修改:修改gpio_init()函数,支持中断配置.添加gpio_intconfig()函数,初始化中断添加gpio_enableint()函数,使能中断添加gpio_clearintflags()函数,清除中断标志位***************************************************************//** @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); /* 输出高电平 */}
}/*GPIO中断初始化函数* @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);//EDGE_SEL寄存器要清零,否则ICR寄存器无效(手册上有讲)if(pin < 16) /* 低16位 */ //不同的引脚使用不同的ICR寄存器{icr = &(base->ICR1);}else /* 高16位 */{icr = &(base->ICR2);icrShift -= 16; //ICR使用0~15来操作}switch(pin_int_mode){case(kGPIO_IntLowLevel)://低电平触发*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);
}
外部按键中断驱动文件编写(类似STM32某个GPIO对应的具体中断服务函数编写 )
bsp_exit.h
本例程的目的是以中断的方式编写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
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_exit.c
作者 : 左忠凯
版本 : V1.0
描述 : 外部中断驱动头文件。
其他 : 配置按键对应的GPIP为中断模式
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************/
#include "imx6ul.h"/* 函数声明 */
void exit_init(void); /* 中断初始化 */
void gpio1_io18_irqhandler(unsigned int giccIar, void *userParam); /* 中断处理函数 */#endif
bsp_exit.c(初始化中断触发方式、配置GIC使能注册中断处理服务函数)
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_exit.c
作者 : 左忠凯
版本 : V1.0
描述 : 外部中断驱动。
其他 : 配置按键对应的GPIP为中断模式
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************/
#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;//下降沿gpio_init(GPIO1, 18, &key_config);//将GPIO1 IO18初始为下降沿GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);/* 使能GIC中对应的中断ID */system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
}/** @description : GPIO1_IO18最终的中断处理函数* @param : 无* @return : 无*/ //中断ID
void gpio1_io18_irqhandler(unsigned int giccIar, void *userParam)
{ 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 的中断标志位。
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;
}
5.实验程序的编译与下载验证
编译
在第十六章实验的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)路径。
链接脚本保持不变
下载验证
使用Make 命令编译代码,编译成功以后使用软件imxdownload 将编译完成的int.bin 文件下载到SD 卡中,命令如下:
chmod 777 imxdownload //给予imxdownload 可执行权限,一次即可
./imxdownload int.bin /dev/sdd //烧写到SD 卡中,不能烧写到/dev/sda 或sda1 设备里面!
烧写成功以后将SD 卡插到开发板的SD 卡槽中,然后复位开发板。本试验效果其实和试验“8_key”一样,按下KEY 就会打开蜂鸣器(之前的轮询模式就改成了中断模式),再次按下就会关闭蜂鸣器。LED0 会不断闪烁,周期大约500ms。