当前位置: 首页 > news >正文

imx6ull芯片中断机制6.24-6.25

一、中断

摘要:

        1、中断是指cpu能够打断当前正在执行的程序,进而执行更为紧要的任务,执行完任务之后能够回到原来被打断的地方继续执行程序的一种机制。

        2、中断六步:中断源发出中断请求、处理器判断是否响应中断及该中断是否被屏蔽、判断中断优先级、保护现场、执行中断处理函数、恢复现场。

        3、51单片机的中断机制:首先系统创建了一个中断向量表,这个中断向量表其实是一个数组,数组在内存的地址是固定的。并且中断向量表中保存的是各个中断处理函数的入口地址。当某个中断需要被处理时,系统会根据中断向量表和中断源ID实现对中断服务函数的调用。并且系统会自动保护现场和恢复现场。因此在编写51单片机中断程序的时候,只需要简单地编写一个函数,并指定其中断向量即可。

        4、arm内核能响应1020个中断,每个中断对应一个ID,其中中断0-15是软件中断SGI。16-31是私有中断PPI。32-1019是共享中断SPI,所有外设中断。

在MCIMX6Y2.h头文件中就列出了imx6ull内核的中断,有160个,经过删除还剩128个。

        5、gic中断控制器。由分发器(distributer)和cpu接口(interface)组成。主要功能是接收不同外设中断并判断优先级,然后通过cpu接口分发给cpu。我们主要通过IRQ接口发给内核。

        6、之前按键引脚是GPIO1组的18号引脚,那么它的中断就可以通过99发送。

当中断发到cpu,cpu就会跳到异常向量表的irq位置执行,也就是0x18,但是我们编写链接代码时第一条指令是在0x87800000,那么怎么告诉cpu中断向量表放在这呢?

需要用core_ca7.h里面的两个函数:GIC_Init()初始化,通知cpu我们的板子只有1个内核,组0、_set_VBAR()异常向量表重定位。

这就是头文件里的_set_VBAR的实现机制。

7、协处理器cp0-cp15,位于内核

可以看到,要想配置异常向量表的地址,就要用到cp15。主要是用里面的寄存器:

如何访问这些寄存器,介绍两个指令:

mcr指令,从arm内核读数据到协处理器

mrc指令,把协处理器的内容写到arm内核。

MRC{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>

p15:即cp15协处理器

opc1:操作码:0-7

rt内核目标寄存器

crn:协处理器的目标寄存器1

m:2

opc2:协处理器操作码:0-7

我们来看看c1的sctlr寄存器:

可以看到13位就可以改变异常向量表基地址。12位是指令使能位,要打开。

8、

补充:

ARM A7内核为了能够适应各种厂商生产的不同型号的Soc,设计出更复杂的中断控制逻辑。上述的几乎所有步骤都需要程序员编写代码才能够实现。

1、异常向量表

        ARM A7有9种运行模式User 、 FIQ 、 IRQ 、 Supervisor 、Monitor 、 Abort 、 Hyp 、 Undefined 、 System 。当中断产生时, ARM内核会立即从当前模式切换到IRQ模式,并自动从异常向量中去调用函数,跟51单片机的中断向量表非常相似。不同之处在于所有的中断都会使系统触发这一过程。

地址空间从0x00处开始是异常向量表,当异常产生时, arm内核会自动执行异常向量表中的对应的指令。

        改进之前的start.s文件,第一条指令开始先是异常向量表。

第4~11行:异常向量表,里面都是指令跳转;第13~15行, undifined异常处理,这里没有真正处理,而是使系统进入一个死循环;第18~20行,中断异常处理,这是我们接下来要学习的重点;第22行开始就是复位异常处理了。我们先完善一下复位异常的处理,这里主要完成以下几个操作:

1、使能arm内核中断;

2、设置各模式的栈指针寄存器,这里我们主要设置svc、 irq和sys模式下的sp指针;

3、设置系统在svc模式下运行;

4、跳转进入主函数。

各模式下的sp只能在进入对应模式后才能设置,这是因为每种模式的sp都是分离的。这里分别设置irq、 sys、 svc模式下的大小都为2MB。

异常向量表搭建好了, ARM内核的工作模式也被设置好了。我们眼下还面临这这么几个问题:

1、我们的程序入口地址是0x87800000,即异常向量表经过编译器链接之后的地址其实是在0x87800000这个位置。但当中断产生后, ARM内核认为异常向量表是在0x00处。

2、 IMX外设中断源有128个之多,中断服务函数对应中断源是哪一个呢?

3、中断优先级怎么设置?

要说明这个问题,就不得不谈谈ARM公司为其ARM内核设计的中断控制器 GIC 。

2、GIC

        GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器。目前 GIC 有 4 个版本:V1~V4,GIC V2 是给 ARMv7-A 架构使用的,。 I.MX6U 是Cortex-A 内核的,因此我们主要讲解 GIC V2。 GIC V2 最多支持 8 个核。imx6ull只有一个核。 当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况: VFIQ、 VIRQ、 FIQ 和IRQ,他们之间的关系如图:

        GIC 接收众多的外部中断,然后对其进行处理,最终就只通过四个信号报给 ARM 内核,这四个信号的含义如下: VFIQ:虚拟快速 FIQ。 VIRQ:虚拟 IRQ。 FIQ:快速中断。 IRQ:中断 。本次我们只在异常向量表中设置了IRQ,所以相当于 GIC 最终向 ARM 内核就上报一个 IRQ信号。那么 GIC 是如何完成这个工作的呢? 下图为GIC V2 的逻辑图,我们只看最下面,因为我们只有一个核心,不需要全部看。

        GIC把中断分了个类:

1. SPI(Shared Peripheral Interrupt),共享中断,泛指所有的外设中断;

2. PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断;

3. SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

从图中可以明显看出, Distributor(分发器)可以分发所有共享中断给8个内核,但是私有中断和软件中断只出现自己独自的内核中。

        中断ID:为了区分不同中断源,给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。

ID0~ID15:SGI;

ID16~ID31: PPI;

ID32~ID1019:SPI.

具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。比如 I.MX6U 的总共使用了 128 个中断 ID,加上前面属于PPI和 SGI 的 32 个 ID, I.MX6U 的中断源共有 128+32=160个,这 128 个中断 ID 对应的中断《I.MX6ULL参考手册》 的3.2 Cortex A7 interrupts 第183页。此外, NXP 官方 SDK中的文件 MCIMX6Y2C.h中定义了一个枚举类型 IRQn_Type,此枚举类型就枚举出了 I.MX6U 的所有中断。

        分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到 CPU 接口端。分发器端要做的主要工作如下: 1、全局中断使能控制; 2、控制每一个中断的使能或者关闭; 3、设置每个中断的优先级; 4、设置每个中断的目标处理器列表; 5、设置每个外部中断的触发模式:电平触发或边沿触发; 6、设置每个中断属于组 0 还是组 1。

        CPU接口(CPU interfaces)是分发器和 CPU Core 之间的桥梁, CPU 接口的主要工作如下: 1、使能或者关闭发送到CPU Core 的中断请求信号; 2、应答中断; 3、通知中断处理完成; 4、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core; 5、定义抢占策略; 6、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。

很明显,要使用IMX的中断,上述两个东东都得使用。怎么使用呢?肯定是寄存器嘛!只不过这里的寄存器都在ARM内核中, NXP 官方 SDK中的文件core_ca7.h文件中定义了一个GIC 结构体,此结构体里面的寄存器定义了分发器端和 CPU 接口端

在GIC_Type结构体中,D_CTLR寄存器被称为分发器控制寄存器。要想使用这个寄存器,需要知道它所在结构体的基地址。

介绍ARM内核中的一个勤务兵, CP15,又被称为Cortex-A 的协处理器。
         ARM处理器支持16个协处理器, CP0~CP15。其中CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到。 CP15 协处理器一共有16 个 32 位寄存器(C0~C15)。

        对于CP15中的16个寄存器的读写访问使用的两条指令为: MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中; MCR: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。 以MRC指令为例,从CP15的某个寄存器读取数据到ARM寄存器的指令格式为:

MRC{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>

cond:指令执行的条件码,就是之前我们使用过的指令条件, eq,lt什么的。如果忽略的话就表示无条件执行;

p15:表示要读取的是CP15当中的某个寄存器; opc1:协处理器要执行的操作码1,其实就是一个数,要做什么将来查表; Rt: ARM 目标寄存器,读出来的数据放到哪个ARM寄存器里。

CRn: CP15 协处理器的目标寄存器,就是你要读取CP15的哪个寄存器(C0~C15);

CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将CRm 设置为 C0,否则结果不可预测。

opc2:可选的协处理器特定操作码2,使用时查表。

MCR与MRC类似的,只是Rt的作用变成了要写入CP15某个寄存器的值。

查表:

得出指令:mrc p15 0, r0, c0, c0, 0,于是就把MIDR读到r0寄存器里了
该手册第80页给出了MIDR的解释, 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 就是次版本号。

可见这些信息没有中断相关信息,所以查看其他寄存器,经过查找,找到所需寄存器:

SCTLR寄存器对应:CRn=c1, opc1=0, CRm=c0, opc2=0

我们用到的位:

bit13: V , 异常向量表基地址选择位,为 0 的话异常向量表基地址为 0X00000000,软件可以使用 VBAR 来重映射此基地址,也就是异常向量表重定位。为 1 的话异常向量表基地址为0XFFFF0000,此基地址不能被重映射。很明显,这个位我们应该设置位0,然后再把VBAR改成0x87800000;

bit12: I, I Cache 使能位,为 0 的话关闭 I Cache,为 1 的话使能 I Cache;I Cache即指令缓存
 

可以知道接下来要用VBAR

        VBAR: CRn=c12, opc1=0, CRm=c0, opc2=0

它是是向量表基地址寄存器。需要设置 VBAR 为 0X87800000;

        CBAR:还是刚才的手册第68页,称为配置基地址寄存器, GIC 的基地址就保存在 CBAR中。

关于 CP15 协处理器就讲解到这里,简单总结一下,通过 MIDR 寄存器可以获取到处理器内核信息;通过 SCTLR 寄存器可以使能或禁止 MMU、 I/D Cache 等;通过 VBAR 寄存器可以设置中断向量偏移;通过CBAR 寄存器可以获取 GIC 基地址。

中断使能需要解决两个问题,让cpu能够响应中断,让中断不会被屏蔽。

        cpsie i指令已经开启了ARM内核对中断的响应

        分发器能够使能或禁止任何一个中断。 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 中断的。

        最后就剩下一个中断优先级的问题了, GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高! Cortex-A7 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级, GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级,但是由于我们使用的Cortex-A7只有32个优先级,所以还不得不按照GIC手册第131页的说明对GICC_PMR 进行设置。

如图,既然只支持32的中断优先级,那么我们就需要把GICC_PMR设置为11111000。但是这一步仅仅是告诉GIC我们的优先级只有32级,这32级优先级又被ARM分为抢占优先级和子优先级。

首先抢占优先级比子优先级的优先权更高。抢占优先级占几位,子优先级占几位是需要我们来设置的,设置的寄存器是GICC_BPR。该寄存器只有低 3 位有效,能设置的值就是0~7之间。其值不同,抢占优先级和子优先级占用的位数也不同

如图,左边Binary Point就是GICC_BPR设置的数值,这里为了简单起见,一般将所有的中断优先级位都配置为抢占优先级,比如 I.MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point为 2,表示 5 个优先级位全部为抢占优先级(红圈)。

最后就是设置一个具体中断的优先级了,前面已经设置好了 I.MX6U 一共有 32 个抢占优先级,数字越小优先级越高。具体要使用某个中断的时候就可以设置其优先级为 0~31。某个中断 ID 的中断优先级设置由寄存器D_IPRIORITYR 来完成,前面说了 Cortex-A7 使用了 512 个中断 ID,每个中断 ID 配有一个优先级寄存器,因此一共有 512 个 D_IPRIORITYR 寄存器。如果优先级个数为 32 的话,使用寄存器D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位。比如要设置ID40 中断的优先级为 5,用c语言描述就是GICD_IPRIORITYR[40] = 5 << 3;

有了之前那些理论知识,接下俩就是付诸实践了。好在官方SDK包中的文件 core_ca7.h,主要是需要 10 个 API 函数:

此外,重定向异常向量表的函数__set_VBAR也在此头文件中。
 

3、编程实现中断

修改start.s中的reset_handler函数,在设置GIC的时候需要把一些不必要的功能关闭掉。程序很简单,先是通过CP15的读取SCTLR,分别关闭指令i Cache,数据d Cache。注意这里的位必须清零,一遍之后我们重新映射异常向量表。

接着改irq_handler函数,这是ARM内核响应中断的关键。在该函数中我们的主要任务是通过GIC查询当前中断ID,并把这个ID作为参数调用c语言编写的中断处理总函数,将来在中断处理总函数中我们根据这个ID再分别处理。这个ID保存在GIC的GICC_IAR寄存器中,地址偏移量为0x200C。此外GIC还有一个GICC_EOIR寄存器,当我们处理完中断之后,要通知GIC我们已经处理好中断了,否则, GIC会认为中断未处理,还会再次触发中断处理,我们通常把这个过程称为清除中断标记,只不过这里清除的是GIC的中断标志。清除的方法为向GICC_EOIR写入当前中断ID。这个ID刚好在r0中保存着,所以只需要把r0的值写入GICC_EOIR就行了。

异常处理返回地址偏移:

那么剩下的任务就是用c去编写中断处理总函数system_irqhandler了。为了提高代码的复用性,方便对之后所有中断的使用,我们封装一个总体的中断处理模块。

创建两个文件interrupt.c和interrupt.h。在头文件中声明两个数据类型: system_irq_handler_t是一个函数的指针,其实就是我们将来要为特定中断编写的中断服务函数。该函数传递两个参数,一个是中断id,另外一个是用户所需的个人参数。 sys_irq_handler_t就是把函数指针和个人参数封装在了一起,方便我们使用。

由于不同的中断源对应不同的中断处理函数, I.MX6U 有 160 个中断源,所以需要 160 个中断处理函数,我们可以将这些中断处理函数放到一个数组里面,中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。 在interrupt.c中定义这个数值NUMBER_OF_INT_VECTORS库函数已经定义好了,就是160。

sys_init_interrupt函数用于初始化GIC和重定位异常向量表,需要的函数SDK库函数已经为我们实现了。

system_register_irq_handler用于注册我们将来的中断服务函数,需要使用哪个函数,就把中断ID、中断服务函数入口地址和用户参数传进来。

最后的system_irqhandler就是在srart.s中调用的总中断服务函数了。其实就是按照中断产生时的ID查表(其实就是我们定义的数组),调用相关的中断服务函数。别忘了一定要在start.s中调用这个函数。

接下来我们将使用外部中断来测试我们编写的中断相关程序。

二、外部中断eint

        之前我们编写的按键程序采用的是查询方式,这个按键被接在UART1_CTS引脚上,该引脚作为GPIO使用时是GPIO1_IO18。 IMX所有能够作为GPIO使用的引脚都可以用来触发外部中断。这里隐含的另外一层含义就是对于外部中断其实是GPIO这个外设的功能之一,因此我们就到GPIO章节中去查找有关外部中断需要使用的寄存器。

        寄存器GPIOx_ICR1和GPIOx_ICR2是用来配置引脚的中断模式的,就是上升沿,下降沿,高电平和低电平触发什么的。 GPIOx_ICR1用于配置引脚0~15, GPIOx_ICR2用于配置16~31。例如: GPIO1_IO18,使用GPIOx_ICR2,又因为是GPIO1组,所以使用的是GPIO1_ICR2。

IMX的GPIO引脚外部中断不仅可以上升沿,下降沿,高电平和低电平触发,还可以双边沿触发。由寄存器GPIOx_EDGE_SEL控制。

GPIOx_IMR用于屏蔽某个引脚的外部中断; GPIOx_ISR是中断挂起标志寄存器,这个寄存器在中断处理完毕之后一定要清除。

原理很简单,接下来我们通过扩展之前的gpio.h,把中断功能添加进去。

由于外部中断触发模式各不相同,先用一个枚举把所有模式都封装起来,再将其追加到Gpio_pin_config中,这一在配置引脚的时候就直接把中断模式配置好了。

在gpio.h中添加一个专门用于设置外部中断模式的函数,注意在设置icr寄存器时是每两个比特对应一个GPIO引脚

在key.c中定义外部中断服务函数,这里的延时是一种演示,中断服务函数一定要快进快出,不能这样做。按下按键产生中断后只是把Led切换以下。之后需要把这个函数注册到中断服务函数表中。设置外部中断优先级,使能GIC外部中断,使能GPIO中断

之后在主函数中调用这些初始化函数即可,注意, sys_init_interrupt一定要尽快调用,因为该函数重定向了异常向量表。

http://www.dtcms.com/a/263821.html

相关文章:

  • Python中字符串isalpha()函数详解
  • 设计模式-责任链, 责任链+ 模板方法模式相结合
  • 抽奖概率-数值练习题
  • AR衍射光波导设计遇瓶颈,OAS 光学软件来破局
  • 【Golang面试题】Go结构体的特点,与其它语言的区别
  • 学习昇腾开发的第11天--主要接口调用流程
  • 逐步构建高性能http服务器及聊天室服务器
  • 青否数字人直播再创新纪录!“人工智能+消费”开新篇?zhibo175
  • ABB CH-3185 3 bhl 000986 p 1006 ab ability 800 xa自动化系统
  • 【V6.0 - 听觉篇】当AI学会“听”:用声音特征捕捉视频的“情绪爽点”
  • 【开源项目】一款真正可修改视频MD5工具视频质量不损失
  • 【第二章:机器学习与神经网络概述】04.回归算法理论与实践 -(3)决策树回归模型(Decision Tree Regression)
  • UE5.6 官方文档笔记 [1]——虚幻编辑器界面
  • Python 单例模式与魔法方法:深度解析与实践应用
  • MySQL允许root用户远程连接
  • PDFBox + Tess4J 从PDF中提取图片OCR识别文字
  • 探秘阿里云Alibaba Cloud Linux:云时代的操作系统新宠
  • C语言学习笔记:深入解析结构体数组(附代码实践)
  • Qt QTableWidget多行多列复制粘贴
  • Android 网络全栈攻略(四)—— TCPIP 协议族与 HTTPS 协议
  • 安全左移(Shift Left Security):软件安全的演进之路
  • Spring Boot 2 多模块项目中配置文件的加载顺序
  • 智能交通信号灯
  • Django打造智能Web机器人控制平台
  • HarmonyOS应用开发高级认证知识点梳理 (三)状态管理V2装饰器核心规则
  • android车载开发之HVAC
  • 笔记本电脑怎样投屏到客厅的大电视?怎样避免将电脑全部画面都投出去?
  • 【蓝牙】Linux Qt4查看已经配对的蓝牙信息
  • 05【C++ 入门基础】内联、auto、指针空值
  • 算法-每日一题(DAY12)最长和谐子序列