前言:
本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。
引用:
正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》
正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档
正文:
本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第15.1, 15.2,15.3 讲” 的读书笔记。第15讲主要是介绍I.MX6U处理器GPIO中断控制实验。本节将参考正点原子的视频教程第15讲和配套的正点原子开发指南文档进行学习。
0. 概述
中断系统是一个处理器的重要的组成部分,中断系统极大的提高了CPU的执行效率,在学习STM32的时候就经常用到中断。本章就是通过与STM32的对比来学习一下 Cortex-A7(I.MX6U)中断系统和Cortex-M(STM32)中断系统的异同,同时,本章会将I.MX6U的一个IO作为输入中断,借此来讲解如何对I.MX6U的中断系统进行编程。
1. Conrtex-A7 中断系统详解
1.1 STM32中断系统回顾
STM32的中断系统主要有以下几个关键点:
- 中断向量表。
- NVIC(内嵌向量中断控制器,Nested Vector Interrupt Controller)。
- 中断使能。
- 中断服务函数。
1. 中断向量表
中断向量表是一个表,这个表里面存放的是中断向量。中断服务程序的入口地址或存放中断服务程序的首地址称为中断向量表,因此中断向量表是一些列中断服务程序入口地址组成的表。这些中断服务程序(函数)在中断向量表中的位置是由半导体厂商定好的,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。中断向量表在整个程序的组前面,比如 STM32F103 的中断向量表如下所示:
上图示例代码就是 STM32F103 的中断向量表,中断向量表都是链接到代码的最前面,比如一般ARM处理器都是从 0x00000000 开始执行指令的,那么中断向量表就是从 0x0000_0000 开始存放的。示例代码中的第一行 “__initial_sp” 就是第一条中断向量,存放的是栈顶指针,接下来第2行复位中断函数 Reset_Handler 的入口地址,依次类推,知道第27行的最后一个中断服务函数 DMA2_Channel4_5_IRQHandler 的入口地址,这样 STM32F103的中断向量表就建好了。
我们说ARM处理器都是从地址 0x0000_0000 开始运行的,但是我们学习STM32的时候代码是下载到 0x80_0000 开始的存储区中。因此中断向量表是存放到 0x80_0000 地址处的,而不是 0x0000_0000 ,这样不就出错了么?为了解决这个问题,Cortex-M 架构引入了一个新的概念-中断向量表偏移,通过中断向量表偏移就可以将中断向量表存放到任意地址处,中断向量表偏移配置在函数 SystemInit 中完成,通过向 SCB_VTOR 寄存器写入新的中断向量表首地址即可,代码如下所示:
第8行和第10行就是设置中断向量表偏移,第八行是将中断向量表设置到 RAM 中,第10行是将中断向量表设置到 ROM 中,也就是地址 0x80_0000 处。第10行用到了 FLAS_BASSE 和 VEC_TAB_OFFSET ,这两个都是宏,定义如下所示:
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0
第10行的代码就是:SCB->VTOR = 0x0800_0000,中断向量表偏移设置完成。通过上面的讲解我们了解了STM32中断有关的概念:中断向量表和中断偏移,那么这跟 I.MX6U 有什么关系呢?因为I.MX6U 使用的是 Cortex-A7 内核也有中断向量表和中断向量表偏移,而且其含义和 STM32 是一模一样的!指示用到的寄存器不同而已,概念完全相同。
2. NVIC(内嵌向量中断控制器)
中断系统得有个管理结构,对于 STM32 这种 Cortex-M 内核的单片机来说这个管理机构叫做 NVIC,全程叫做 Nested Vector Interrupt Controller。关于 NVIC 本教程不做详细的讲解,既然 Corex-M 内核有个中断系统的管理结构--NVIC,那么 I.MX6U 所使用的 Corex-A7 内核是不是也应该有一个中断管理机构?答案是肯定的,不过 Cortex-A7 内核的中断管理机构不叫做 NVIC ,而是叫做 GIC,全程是 Geneal Interrupt Controller ,后面我们会详细的讲解 Cortex-A7 内核的 GIC。
3. 中断使能
要使用某个外设的中断,肯定要先使能这个外设的中断,以 STM32F103 的 PE2 这个IO为例,假如我们要使用 PE2 的输入中断肯定要使用下面的代码来使能对应的中断:
NVIC_InitStructure.NVIC_IRQChannel = EXIT2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = x002; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENALBE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
上述代码就是使能 PE2 对应的 EXIT2 中断,同理,如果要使用 I.MX6U 的某个中断的话也需要使能其对应的中断。
4. 中断服务函数
我们使用中断的目的就是为了使用中断服务函数,当中断发生以后中断服务函数就会被调用,我们要处理的工作就可以放到中断服务函数中去完成。同样以 STM32F103 的 PE2 为例,其中断服务函数如下图所示:
/* 外部中断2 服务程序 */
void EXIT2_IRQHandler(void)
{
/* 中断处理代码 */
}
当PE2引脚的中断触发以后就会调用其对应的中断服务函数 EXIT2_IRQHandler,我么可以在函数 EXIT2_IRQHandler 中提那家中断处理代码。同理,I.MX6U 也有中断服务函数,当某个外设发生中断以后就会调用其对应的中断服务函数。
通过对 STM32 中断系统的回顾,我们知道了 Cortex-M 内核的中断处理过程,那么 Cortex-A7 内核的中断处理过程是否是一样的,有什么异同呢?接下来我们就带着这样的疑问来学习 Cortex-A7 内核的中断处理系统。
1.2 Cortex-A7 中断系统简介
跟STM32一样,Cortex-A7 内核也有中断向量表,中断向量表也是在代码的最前面。Cortex-A7 内核有8个异常中断,这8个异常中断的中断向量表如下表所示:
向量地址 | 中断类型 | 中断模式 |
0x00 | 复位中断(Reset) | 特权模式(SVC) |
0x04 | 未定义指令中断(Undefined Instruction) | 未定义指令终止模式(Undef) |
0x08 | 软中断(Software Interrupt, SWI) | 特权模式(SVC) |
0x0C | 指令预取中止中断(Prefetch Abort) | 中止模式 |
0x10 | 数据访问中止 (Data Abort) | 中止模式 |
0x14 | 未使用(Note Used) | 未使用 |
0x18 | IRQ中断(IRQ Interrupt) | 外部中断模式(IRQ) |
0x1C | FIRQ中断(FIRQ Interrupt) | 快速中断模式(FIRQ) |
中断向量表里都是中断服务函数的入口地址,因此一款新芯片有什么中断都是可以从中断向量表看出来的。从上表中可以看出,Cortex-A7 内核一共有 8 个中断,而且还有一个中断向量未使用,实际只有7个中断。和“示例代码 17.1.1.1”中的 STM32F103 中断向量表比起来少了很多!难道一个能跑 Linux 的芯片只有这7个中断?明显是不可能的!那类似 STM32 中的 EXIT9-5_IRQHandler, TIM2_IRQHandler 这样的中断向量在哪里?I2C, SPI,定时器等等的中断怎么处理呢?
这个就是 Cortex-A 和 Cortex-M 在中断向量表这一块的区别,对于 Cortex-M 内核来说,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。对于 Cortex-A 内核来说并没有这么做,在上表中有个 IRQ 中断,Cortex-A 内核CPU的所有外部中断都属于这个 IRQ 中断,当任意一个外设中断发生的时候都会触发 IRQ 中断。在IRQ中断服务函数中就可以读取指定的寄存器来判断发生的具体事什么中断,进而根据具体的中断做出相应的处理。这些外部中断和IRQ的关系如下图所示:
在图 17.1.2.1 中,,左侧的 Softwre0_IRQn~PMU_IRQ2_IRQ 这些都是 I.MX6U 的中断,它们都属于 IRQ 中断。当图 17.1.2.1左侧这些中断中任意一个发生的时候 IRQ 中断都会被触发,所以我们需要在 IRQ 中断服务函数中判断究竟是左侧的哪个中断发生了,然后在坐车具体的处理。
在表 17.1.2.1 中一共有7个中断,简单介绍一下这7个中断:
- 复位中断(Reset),CPU复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化SP指针,DDR等等。
- 未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
- 软中断(Software Interrupt,SWI),由SWI指令引起的中断,Linux系统调用会用 SWI 指令来一起软中断,通过软中断来陷入到内核空间。
- 指令预取中止中断(Prefetch Abort),预取指令出错的时候会产生此中断。
- 数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断
- IRQ中断(IRQ Interrupt),外部中断,前面已经说过了,芯片内部的外设中断都会引起此中断的发生。
- FIQ中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。
在上面的7个中断中,我们常用的就是复位中断和IRQ中断,所以我们需要编写这两个中断的中断服务函数,稍后我们会讲解如何编写对应的中断服务函数。首先我们要将表 17.1.2.1 的内容来创建中断向量表,中断向量表处于程序最开始的地方,比如我们前面例程 Start.S 的文件最前面,中断向量表如下:
示例代码 17.1.1.1 Cortex-A7 向量表模板
.global _start
_start:ldr pc, =Reset_Handler /*复位中断*/
ldr pc, =Undefined_Handler /*未定义指令中断*/
ldr pc, =SVC_Handler /*SVC(SuperVisor)中断*/
ldr pc, =PrefAbort_Handler /*预取中止中断*/
ldr pr, =DataAbort_Handler /*数据中止中断*/
ldr pc, =NotUsed_Hanlder /*未使用中断*/
ldr pc, =IRQ_Handler /*IRQ中断*/
ldr pc, =FIQ_Handler /*FIQ(快速中断)*/
/* 复位中断 */Reset_Handler:
/*复位中断具体处理过程*/
/*未定义中断*/Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0
/*SVC中断*/SVC_Handler:
ldr r0, =SVC_Handler
bx r0
/*预取中止中断*/PrefAbort_Handler:
ldr r0, =PerfAbort_Handler
bx r0
/*数据中止中断*/
DataAbort_Handler:
ldr r0, =DataAbort_Handler
bx r0
/*未使用的中断*/NotUsed_Handler:
ldr r0, =NotUserd_Handler
bx r0
/* IRQ中断!重点!!!!*/IRQ_Handler:
/*IRQ中断具体处理过程*/
/*FIQ中断*/
FIQ_Handler:ldr r0, =FIQ_Handler
bx r0
第4到11行时中断向量表,当指定的中断发生以后就会调用对应的中断服务函数,比如复位中断发生以后会执行第4行的代码,也就是调用 Reset_Handler,函数 Reset_Handler 就是复位中断的中断服务函数,其它中断同理。
第14到50行就是对应的中断服务函数,中断服务函数都是用汇编编写的,我们实际需要编写的只有复位中断服务函数 Reset_Handler 和 IRQ中断服务函数 IRQ_Handler,其它的中断本教程没有用到,所以是死循环。在编写复位(Reset)中断服务函数和IRQ中断服务函数之前我们还需要了解一些其他的知识,否则的话就没法编写。