假设你现在正在写作业,突然电话响起,你需要停下写作业接电话,挂电话后继续写作业。突然由人按门铃,你需要先去开门,然后继续回来写作业。电话和门铃打断了写作业,能中断写作业的事情有很多,比如身体不舒服,口渴等。被打断后怎么做?身体不舒服就停下写作业休息一会,身体好了继续写作业。口渴就停下写作业喝水,喝完水继续写作业。如果你正在接一个很重要的电话,突然门铃响了,这是会优先处理其中一件事,比如先让按门铃的人等一下,挂电话后再去开门,或者先挂电话,等开门后再打电话过去。这就存在一个中断优先级的问题。
当有事件产生,处理事件之前我们需要记住现在作业写到第几页了,或者在作业上记一个标记,然后取处理事件,电话铃响了需要到放电话的地方去,门铃响了需要到门口去,口渴需要到放饮水机地方去,也就是说,不同的突发事件需要到不同的地方去处理。
嵌入式系统中也有类似的情况。CPU在运行过程中,也会被各种异常打断。这些异常有
① 指令未定义
② 指令、数据访问有问题
③ SWI(软中断)
④ 快中断
⑤ 中断
中断也属于一种异常,导致中断发生的中断源有很多,比如:
① 按键
② 定时器
③ ADC转换完成
④ UART发生完数据、接收数据
⑤ 等等
这些众多的中断源,汇集中中断管理器,由中断管理器选择优先级最高的中断并通知CPU。CPU会根据中断的类型到跳转到不同的地址处理中断。发生中断后,CPU并不是随便跳到一个地址处理中断,而是根据异常向量表,跳转到对应的地址处理中断。
1.2.1 GPIO中断 GPIO中断,指有GPIO模块产生的中断,有边沿触发中断或者电平翻转中断。GPIO模块能检测到引脚上的值是0还是1,并能通过外部拓展将电平从变为1或是从1变到0。CPU接收外部的中断请求,并进行处理,其实是一个被动接受的过程,这样的好处是己能保证主任务的执行效率,又能及时获取外部请求,从而处理重要的设备请求中断。
当GPIO模块检测到管脚电平变化且满足中断触发条件,就会触发中断,CPU会跳转到中断处理地址进行中断处理,为了避免破坏主任务数据,CPU会处理保存当前相关寄存器(保存现场)并进入中断服务函数,执行完中断服务函数后,CPU会恢复相关寄存器(恢复现场),回到主任务继续执行程序。
程序发生GPIO中断后会根据异常向量表强制跳转到0x18(IRQ中断地址)。如下图:
异常向量表并不总是从0地址开始,IMX6ULL可以设置vector base寄存器,指定向量表在其他位置,比如设置 vector base 为 0x80000000,指定为 DDR 的某个地 址。但是表中的各个异常向量的偏移地址,是固定的:复位向量偏移地址是 0,中断是 0x18。
本次实验使用GPIO中断方式实现按键控制LED亮灭,并通过串口把中断ID打印出来。
中断控制器和CP15协处理器
操作系统中,中断系统是很重要的一部分。有了中断系统,才不用一直轮询是否有事件发生,系统效率得以提高。中断系统一般分为三个部分:模块、中断管理器和处理器。模块通常有寄存器设置是否使能中断和中断触发方式。中断控制器可以管理中断优先级等。处理器则设置寄存器响应中断。
如上图所示,硬件中断信号发送GIC(Generic Interrupt Controller),GIC产生一个FIQ或IRQ信号给CPU。GPIO模块、UART模块均能产生硬件中断。在初始化中断时,要初始化GIC中断控制器,如果时GPIO中断则还要设置GPIO模块内相关的寄存器,如果时串口中断则还要设置UART模块内相关的寄存器。
1.2 GIC中断控制器介绍 1.2.1 IMX6ULL GIC中断控制器 IMX6ULL是Cortex-A7内核,采用GIC V2(Generic Interrupt Controller)中断控制器。在这里只简单的介绍一下GIC,具体可以参考arm文档。
GIC的主要作用可以归结为接受硬件中断信号,并进行简单的处理,按照一定的设置策略,分给对应的CPU处理。如下图:
ARM内核只提供了四个信号给GIC汇报中断情况:VIRQ(虚拟快速IRQ)、VFIQ(虚拟快速FIQ)、IRQ、FIQ。VIRQ、VFIQ是针对虚拟化,剩下就是IRQ和FIQ。GPIO中断属于IRQ中断,所以在本次实验中GIC上报IRQ信号给ARM内核。
接下来看一下GIC内部过程,如下图:
中断源分为SPI(Shared Peripheral Interrupt)、PPI(Private Peripheral Interrupt)、SGI request(Software-generated Interrupt)。外部中断都属于SPI中断源。
GIC控制器包括分发器(Distributor)和CPU接口端(CPU interface)。
分发器(Distributor)主要完成对整个中断控制器使能,设置中断优先级,设置中断触发方式,决定每个中断信号发送到哪一个具体的CPU上执行。
CPU接口端(CPU interface)主要完成使能和发送一个具体的中断信号到特定的CPU上,确认中断已被CPU接受、处理以及处理完成,设置CPU能接受中断的优先级以及基于级别的中断抢占。
中断信号先到分发器,根据设定CPU,发送到CPU对应的interface上,在这里判断是否优先级足够高,能否抢断或打断当前的终端处理,如果可以,CPU interface就会发送一个物理的signa到CPU的IRQ线上,CPU接收到中断信号,转到中断处理模式进行处理。
1.2.2 IMX6ULL GIC中断寄存器 GIC寄存器分为Distributor register和CPU interface register。寄存器数目较多,这里介绍本次实验中需要我们设置的寄存器。
1.2.2.1 GICC_IAR寄存器 GICC_IAR寄存器属于CPU interface register,作用是:保存中断ID,读取GICC_IAR寄存器可以获得中断ID,这个过程可以当作对中断的确认。
1.2.2.2 GICC_EOIR寄存器 GICC_EOIR寄存器属于CPU interface register,作用是:中断完成时,向GICC_EOIR写入中断ID,表示IRQ处理结束。
1.2.3 CP15协处理器 1.2.3.1 CP15协处理器介绍 在基于ARM的嵌入式系统中,存储系统通常是系统控制协处理器CP15完成的。ARM处理器使用协处理器指令MCR和MRC来读写寄存器,控制cache、MMU、配置时钟(在bootloader时钟初始化时会用到)等。CP15包含16个32位寄存器,编号为0~15。
在本次实验中,需要设置的寄存器有:SCTLR(System Control Register)寄存器,VBAR(Vector Base Address)寄存器。
1.2.3.2 SCTLR(System Control Register)寄存器 设置SCTLR寄存器可以控制cache、MMU等。
Bit[13]: 异常向量表地址设置位。我们设置为0,默认0x00000000地址,可以通过设置vector base寄存器映射到设置地址。
Bit[12]、Bit[2]: 指令cache、数据cache使能位。刚上电时,CPU还不能管理cache,指令cache可关闭也可不关闭,但数据cache一定要关闭,否争可能导致刚开始的代码里,去读取数据时到cache里读取,而这时候RAM数据还没有cache过来,导致数据预取错误。
Bit[11]: 分支预测使能位。分支预测技术是用来提高执行流水线指令效率。在本次实验中关闭分支预测技术。
Bit[1]: 字节对齐设置位。打开字节对齐,可以提高CPU访问效率,但会损失一部分内存空间。在本次实验中 CPU并不会做太多复杂的工作,所以关闭字节对齐。
Bit[0]: MMU使能位。上电后系统没有配置MMU,所以要先关掉MMU。
MRC p15, 0, < Rt >, c1, c0, 0: 把SCTLR寄存器的值读到ARM寄存器Rt中。
MRC p15, 0, < Rt >, c1, c0, 0: 把ARM寄存器Rt的值写入SCTLR寄存器。
1.2.3.3 VBAR(Vector Base Address)寄存器 设置VBAR寄存器,可以设置异常向量表的映射地址。如果不把异常向量表的映射地址告诉CPU,在发生异常时,CPU就找不到异常向量表,就无法处理异常。
MRC p15, 0, < Rt >, c12, c0, 0: 把VBAR寄存器的值读到ARM寄存器Rt中。
MRC p15, 0, < Rt >, c12, c0, 0: 把ARM寄存器Rt的值写入VBAR寄存器。
1.3 IMX6ULL的GPIO中断寄存器介绍 1.3.1 GPIO interrupt configuration register1 (GPIOx_ICR1) GPIO中断配置寄存器1
ICRn[1:0]决定中断类型:
00 低电平触发
01 高电平触发
10 上升沿触发
11 下降沿触发
ICR0~ICR15对应GPIO interrupt 0-15
1.3.2 GPIO interrupt configuration register2 (GPIOx_ICR2) GPIO中断配置寄存器2
与GPIOx_ICR1类似
ICR0~ICR15对应GPIO interrupt 16-31
1.3.3 GPIO interrupt mask register (GPIOx_IMR) GPIO中断屏蔽寄存器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qzczoE5q-1642060200367)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/11_GPIO_interrupt_image_018.png)]
Bit[n]对应interrupt n
0 interrupt n屏蔽
1 interrupt n 打开
1.3.4 GPIO interrupt status register (GPIOx_ISR) GPIO中断状态寄存器
中断状态位-当在GPIO输入上检测到有效状态(由相应的ICR位确定)时,该寄存器的位n置为有效(高电平有效)。该寄存器的值与GPIO_IMR中的值无关。
当检测到活动状态时,相应的位将保持置位状态,直到被软件清除为止。通过将1写入相应的位位置来清除状态标志。
1.3.5 GPIO edge select register (GPIOx_EDGE_SEL) GPIO中断边沿选择寄存器
设置GPIO_EDGE_SEL [n]时,GPIO会忽略ICR [n]设置,同时检测对应输入信号的上升沿和下降沿。
1.4 按键中断程序编程示例一 1.4.1 管脚设置和查询中断号 从上面的电路图可见KEY1接在GPIO5_1(SNVS_TAMPER1 pad,ALT5)上,KEY4接在GPIO4_14(NAND_CE1_B pad,ALT5)上。使用IOMUXC_SetPinMux设置这两个引脚为GPIO模式。如何获取这两个GPIO的中断号呢?查阅数据手册的chapter3,CORTEX A7interrupts章节,这两个GPIO的中断号如下表所示。对应到GIC的SPI中断号需要在此编号基础上加上32,所以KEY1对应的GIC interrupt ID为(74 + 32 = 106),KEY2对应的GIC interrupt ID为(72 + 32 = 104)。
直接查数据手册 Table 2-1. System memory map
可以知道gic的基地址是0xA0000
对于gic控制器还有另一种方法,通过 CP15查询:
mrc p15, 4, r0, c15, c0, 0
将gic的基地址通过mrc指令读取到r0寄存器。
1.4.3 GIC的初始化 通过CP15获取GIC的基地址,读取GICD_TYPER寄存器获得中断的数目,往GICD_ ICENABLERn寄存器写入0xFFFFFFFF禁用所有的SGI,PPI和SPI。通过GICC_PMR设置优先级等级,设置为0xF8;将GICC_BPR设置为2,这允许各个优先级进行抢占。 最后使能group0的distributor和CPU interface。
代码在**裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception/gic.c)**目录内:
void gic_init(void)
{
u32 i, irq_num;
GIC_Type *gic = get_gic_base();
/* the maximum number of interrupt IDs that the GIC supports */
irq_num = (gic->D_TYPER & 0x1F) + 1;
/* On POR, all SPI is in group 0, level-sensitive and using 1-N model */
/* Disable all PPI, SGI and SPI */
for (i = 0; i D_ICENABLER[i] = 0xFFFFFFFFUL;
/* The priority mask level for the CPU interface. If the priority of an
* interrupt is higher than the value indicated by this field,
* the interface signals the interrupt to the processor.
*/
gic->C_PMR = (0xFFUL D_CTLR = 1UL;
/* Enables the signaling of interrupts by the CPU interface to the connected processor
* Enable group0 signaling
*/
gic->C_CTLR = 1UL;
}
1.4.4 中断异常处理汇编部分
在异常向量表偏移为0x18的地方将pc设置为IRQ_Handler标号的位置,跳转到IRQ_Handler标号位置执行,处理器处于中断模式,lr_irq保存了被中断模式中的下一条即将执行的指令的地址,将lr减去4,将r0-r12和lr保存在栈上,用bl指令调用C函数handle_irq_c,C函数返回来后将r0-r12从栈上弹出,栈上的lr弹出到PC,并将SPSR拷贝到CPSR,返回被打断的指令继续执行。在reset handler里需要设置好irq模式的栈,这样在中断模式里才可以调用C函数,同时调用cpsie i打开中断。使用如下两条指令设置异常向量的基地址
ldr r0, =_vector_table
mcr p15, 0, r0, c12, c0, 0 /* set VBAR, Vector Base Address Register*/
汇编部分代码如下所示代码如下**裸机Git仓库 NoosProgramProject/(11_GPIO中断/008_exception/**008_exception\start.S)文件:
.text
.global _start, _vector_table
_start:
_vector_table:
ldr pc, =Reset_Handler /* Reset */
ldr pc, =Undefined_Handler /* Undefined instructions */
ldr pc, =SVC_Handler /* Supervisor Call */
b halt//ldr pc, =PrefAbort_Handler /* Prefetch abort */
b halt//ldr pc, =DataAbort_Handler /* Data abort */
.word 0 /* RESERVED */
ldr pc, =IRQ_Handler /* IRQ interrupt */
b halt//ldr pc, =FIQ_Handler /* FIQ interrupt */
………
.align 2
IRQ_Handler:
/* 执行到这里之前:
* 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
* 2. SPSR_irq保存有被中断模式的CPSR
* 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
* 4. 跳到0x18的地方执行程序
*/
/* 保存现场 */
/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
/* lr-4是异常处理完后的返回地址, 也要保存 */
sub lr, lr, #4
stmdb sp!, {r0-r12, lr}
/* 处理irq异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
.align 2
Reset_Handler:
/* Reset SCTlr Settings */
mrc p15, 0, r0, c1, c0, 0 /* read SCTRL, Read CP15 System Control register */
bic r0, r0, #(0x1
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?