完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1 条评论
3个回答
|
|
1. 中断向量表
中断向量表是一个表,这个表里面存放的是中断向量。 中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量,因此中断向量表是一系列中断服务程序入口地址组成的表。 这些中断服务程序(函数)在中断向量表中的位置是由半导体厂商定好的,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。 中断向量表在整个程序的最前面,比如 STM32F103 的中断向量表如下所示: __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler ; External Interrupts DCD WWDG_IRQHandler ; Window Watchdog DCD PVD_IRQHandler ; PVD through EXTI Line detect DCD TAMPER_IRQHandler ; Tamper DCD RTC_IRQHandler ; RTC DCD FLASH_IRQHandler ; Flash /* 省略掉其它代码 */ DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & l5 与其说是中断向量表,不如说是中断服务函数的表。向量的含义其实是表示一种对应关系,其中的每个中断服务函数都对应一个中断ID,当相应的中断产生时,就会根据这个中断的ID来选择对应的中断服务函数进行处理。 下面我们来看 I.MX6ULL所属的Cortex-A7架构的中断向量表 但是这里只有8个中断啊~其中一个还是未使用的。 那类似 STM32 中的EXTI9_5_IRQHandler、 TIM2_IRQHandler 这样的中断向量在哪里? I2C、 SPI、定时器等等的中断怎么处理呢?这个就是 Cortex-A 和 Cotex-M 在中断向量表这一块的区别,对于 Cortex-M 内核来说,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。对于 CotexA 内核来说并没有这么做,上表有个 IRQ 中断, Cortex-A 内核 CPU 的所有外部中断都属于这个 IQR 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器(获取到中断具体中断的ID)来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理(也就是说在IRQ中断服务函数中一定还有其他的对于不同ID的处理函数)。 我们仔细看一下上面的7个中断服务函数: 1) 复位中断(Rest), CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做 一些初始化工作,比如初始化 SP 指针、 DDR 等等。 2) 未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。 3) 软中断(Software Interrupt,SWI),由 SWI 指令引起的中断, Linux 的系统调用会用 SWI 指 令来引起软中断,通过软中断来陷入到内核空间。 4) 指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。 5) 数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。 6) IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。 7) FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中。 在上面的 7 个中断中,我们常用的就是复位中断和 IRQ 中断,所以我们需要编写这两个中断的中断服务函数,稍后会具体编写对应的中断服务函数。首先我们要根据这7个中断服务函数的内容来创建中断向量表,中断向量表处于程序最开始的地方,比如我们前面例程的 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_Handler bx r0 /* SVC 中断 */ SVC_Handler: ldr r0, =SVC_Handler bx r0 /* 预取终止中断 */ PrefAbort_Handler: ldr r0, =PrefAbort_Handler bx r0 /* 数据终止中断 */ DataAbort_Handler: ldr r0, =DataAbort_Handler bx r0 /* 未使用的中断 */ NotUsed_Handler: ldr r0, =NotUsed_Handler bx r0 /* IRQ 中断!重点!!!!! */ IRQ_Handler: /* 复位中断具体处理过程 */ /* FIQ 中断 */ FIQ_Handler: ldr r0, =FIQ_Handler bx r0 代码前边是中断向量表,当指定的中断发生以后就会调用相应的中断服务函数,比如发生了复位中断,就会调用Reset_Handler复位中断服务函数。其他中断同理。 代码后面一部分就是对应的各个中断服务函数的具体内容,都是用汇编写的。我们在本次实验中只会用到其中的1. 复位中断服务函数:Reset_Handler、2.IRQ中断服务函数:IRQ_Handler。其他都没用到所以是死循环。 上面的代码中我们需要完成的1、和2、并没有进行编写,我们的目的是在1、中完成reset的功能,在2、中完成保存现场、获取中断id号、调用中断服务函数、还原现场的任务。 那么为了完成这些,我们需要进一步了解中断~ 2. 中断控制器(GIC) GIC:general interrupt controller
目前 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内核发送4种类型的信号:
2.2 GIC的作用 首先我们看个图 咋一看乱七八糟,仔细一看,还挺有层次的。 我们先挑重点: 中间黑色框框中的就是GIC,以及它的作用;左侧红色标记的①②③代表不同的中断源;右侧是GIC向ARM内核发送的刚刚说的那4种信号与内核之间的交流。 我们从左向右说: 2.2.1)GIC的众多中断源(左): ①、 SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。 ②、 PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。 ③、 SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。 中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 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太多了,具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。 比如 I.MX6U 的总共使用了 128 个SPI中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID, I.MX6U 的中断源共有 128+32=160个。 也就是说,在i.imx6ull中IRQ中断类型一共就由128个,占用了ID 32 - 159 也就是说,官方已经定义了各个中断所使用的ID,那么这部分信息我们在代码中怎么使用呢,其实在NXP的官方SDK中MCIMX6Y2C.h中就有这部分的定义。 #define NUMBER_OF_INT_VECTORS 160 /* 中断源 160 个, SGI+PPI+SPI*/ typedef enum IRQn { /* Auxiliary constants */ NotAvail_IRQn = -128, /* Core interrupts */ Software0_IRQn = 0, Software1_IRQn = 1, Software2_IRQn = 2, Software3_IRQn = 3, Software4_IRQn = 4, Software5_IRQn = 5, Software6_IRQn = 6, Software7_IRQn = 7, Software8_IRQn = 8, Software9_IRQn = 9, Software10_IRQn = 10, Software11_IRQn = 11, Software12_IRQn = 12 , Software13_IRQn = 13, Software14_IRQn = 14, Software15_IRQn = 15, VirtualMaintenance_IRQn = 25, HypervisorTimer_IRQn = 26, VirtualTimer_IRQn = 27, LegacyFastInt_IRQn = 28, SecurePhyTimer_IRQn = 29, NonSecurePhyTimer_IRQn = 30, LegacyIRQ_IRQn = 31, /* Device specific interrupts */ IOMUXC_IRQn = 32, DAP_IRQn = 33, SDMA_IRQn = 34, TSC_IRQn = 35, SNVS_IRQn = 36, ...... ...... ENET2_1588_IRQn = 153, Reserved154_IRQn = 154, Reserved155_IRQn = 155, Reserved156_IRQn = 156, Reserved157_IRQn = 157, Reserved158_IRQn = 158, PMU_IRQ2_IRQn = 159 } IRQn_Type; 使用的时候我们只要引入这个头文件就ok了~ 2.2.2)GIC逻辑分块(中) 细心的盆友可能发现,在GIC部分(也就是中间的部分)还分为两块。 就是图中的DIstributor和CPU Interface GIC 架构分为了两个逻辑块: Distributor 和 CPU Interface,也就是分发器端和CPU 接口端。 这两个逻辑块的含义如下: Distributor(分发器端): 从图可以看出,此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个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 说了这么多功能,那么我们怎么使用它来完成我们的目的呢? 这就需要操作寄存器了。 2.3 如何使用GIC(操作什么寄存器,如何操作寄存器) 上面提到了外部中断源产生的中断信号进入到GIC中,Distributor 和 CPU Interface同心协力作用。 那么这两部分功能的实现必然需要我们通过设置某些寄存器,以及我们读取某些寄存器获得我们想要的参数,比如:我们如何获得外部中断源的ID呢? 在core_ca7.h中,定义了GIC结构体,其描述了GIC的所有寄存器,分为分发器端和CPU接口端。 /* * GIC 寄存器描述结构体, * GIC 分为分发器端和 CPU 接口端 */ typedef struct { /* 分发器端寄存器 */ uint32_t RESERVED0[1024]; __IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */ __IM uint32_t D_TYPER; /* Offset: 0x1004 (R/ ) */ __IM uint32_t D_IIDR; /* Offset: 0x1008 (R/ ) */ uint32_t RESERVED1[29]; __IOM uint32_t D_IGROUPR[16]; /* Offset: 0x1080 - 0x0BC (R/W) */ uint32_t RESERVED2[16]; __IOM uint32_t D_ISENABLER[16];/* Offset: 0x1100 - 0x13C (R/W) */ uint32_t RESERVED3[16]; __IOM uint32_t D_ICENABLER[16];/* Offset: 0x1180 - 0x1BC (R/W) */ uint32_t RESERVED4[16]; __IOM uint32_t D_ISPENDR[16]; /* Offset: 0x1200 - 0x23C (R/W) */ uint32_t RESERVED5[16]; __IOM uint32_t D_ICPENDR[16]; /* Offset: 0x1280 - 0x2BC (R/W) */ uint32_t RESERVED6[16]; __IOM uint32_t D_ISACTIVER[16];/* Offset: 0x1300 - 0x33C (R/W) */ uint32_t RESERVED7[16]; __IOM uint32_t D_ICACTIVER[16];/* Offset: 0x1380 - 0x3BC (R/W) */ uint32_t RESERVED8[16]; __IOM uint8_t D_IPRIORITYR[512];/* Offset: 0x1400 - 0x5FC (R/W) */ uint32_t RESERVED9[128]; __IOM uint8_t D_ITARGETSR[512];/* Offset: 0x1800 - 0x9FC (R/W) */ uint32_t RESERVED10[128]; __IOM uint32_t D_ICFGR[32]; /* Offset: 0x1C00 - 0xC7C (R/W) */ uint32_t RESERVED11[32]; __IM uint32_t D_PPISR; /* Offset: 0x1D00 (R/ ) */ __IM uint32_t D_SPISR[15]; /* Offset: 0x1D04 - 0xD3C (R/ ) */ uint32_t RESERVED12[112]; __OM uint32_t D_SGIR; /* Offset: 0x1F00 ( /W) */ uint32_t RESERVED13[3]; __IOM uint8_t D_CPENDSGIR[16];/* Offset: 0x1F10 - 0xF1C (R/W) */ __IOM uint8_t D_SPENDSGIR[16];/* Offset: 0x1F20 - 0xF2C (R/W) */ uint32_t RESERVED14[40]; __IM uint32_t D_PIDR4; /* Offset: 0x1FD0 (R/ ) */ __IM uint32_t D_PIDR5; /* Offset: 0x1FD4 (R/ ) */ __IM uint32_t D_PIDR6; /* Offset: 0x1FD8 (R/ ) */ __IM uint32_t D_PIDR7; /* Offset: 0x1FDC (R/ ) */ __IM uint32_t D_PIDR0; /* Offset: 0x1FE0 (R/ ) */ __IM uint32_t D_PIDR1; /* Offset: 0x1FE4 (R/ ) */ __IM uint32_t D_PIDR2; /* Offset: 0x1FE8 (R/ ) */ __IM uint32_t D_PIDR3; /* Offset: 0x1FEC (R/ ) */ __IM uint32_t D_CIDR0; /* Offset: 0x1FF0 (R/ ) */ __IM uint32_t D_CIDR1; /* Offset: 0x1FF4 (R/ ) */ __IM uint32_t D_CIDR2; /* Offset: 0x1FF8 (R/ ) */ __IM uint32_t D_CIDR3; /* Offset: 0x1FFC (R/ ) */ /* CPU 接口端寄存器 */ __IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */ __IOM uint32_t C_PMR; /* Offset: 0x2004 (R/W) */ __IOM uint32_t C_BPR; /* Offset: 0x2008 (R/W) */ __IM uint32_t C_IAR; /* Offset: 0x200C (R/ ) */ __OM uint32_t C_EOIR; /* Offset: 0x2010 ( /W) */ __IM uint32_t C_RPR; /* Offset: 0x2014 (R/ ) */ __IM uint32_t C_HPPIR; /* Offset: 0x2018 (R/ ) */ __IOM uint32_t C_ABPR; /* Offset: 0x201C (R/W) */ __IM uint32_t C_AIAR; /* Offset: 0x2020 (R/ ) */ __OM uint32_t C_AEOIR; /* Offset: 0x2024 ( /W) */ __IM uint32_t C_AHPPIR; /* Offset: 0x2028 (R/ ) */ uint32_t RESERVED15[41]; __IOM uint32_t C_APR0; /* Offset: 0x20D0 (R/W) */ uint32_t RESERVED16[3]; __IOM uint32_t C_NSAPR0; /* Offset: 0x20E0 (R/W) */ uint32_t RESERVED17[6]; __IM uint32_t C_IIDR; /* Offset: 0x20FC (R/ ) */ uint32_t RESERVED18[960]; __OM uint32_t C_DIR; /* Offset: 0x3000 ( /W) */ } GIC_Type; 根据结构体中元素的地址连续性,我们可以直接通过GIC_Type这个结构体来访问GIC的所有寄存器。不必担心首地址的问题,这是官方的sdk包中的文件,后面所标注的偏移量就是相对于GIC及地址的偏移。 因此,我们只需要在获取到GIC及地址后加上0x1000就能访问到GIC分发器端寄存器了、加上0x2000就能访问到GIC的CPU接口端寄存器了 那GIC的及地址在哪里得到呢?——>Cortex-A的CP15协处理器。 CP15协处理器 CP15 协处理器一般用于存储系统管理,但是在中断中也会使用到, CP15 协处理器一共有16 个 32 位寄存器(c0-c15)。 CP15 协处理器的访问通过其独特的语法指令完成,如下所示: (move to register from controller;move to controller to register) MRC: 将** CP15 协处理器中的寄存器数据读到 ARM 寄存器中**。 MCR: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。 MRC 就是读 CP15 寄存器, MCR 就是写 CP15 寄存器, MCR 指令格式如下: MCR{cond} p15, |
|
|
|
4.2.2 IRQ中断服务函数IRQ_Handler
.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, =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, =0X87800000 d*** i*** mcr p15, 0, r0, c12, c0, 0 d*** i*** #endif /* 设置各个模式下的栈指针, * 注意:IMX6UL的堆栈是向下增长的! * 堆栈指针地址一定要是4字节地址对齐的!!! * DDR范围:0X80000000~0X9FFFFFFF */ /* 进入IRQ模式 */ mrs r0, cpsr bic 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, cpsr bic 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, cpsr bic 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中 */ #endif b main /* 跳转到main函数 */ /* 未定义中断 */ Undefined_Handler: ldr r0, =Undefined_Handler bx r0 /* SVC中断 */ SVC_Handler: ldr r0, =SVC_Handler bx r0 /* 预取终止中断 */ PrefAbort_Handler: ldr r0, =PrefAbort_Handler bx r0 /* 数据终止中断 */ DataAbort_Handler: ldr r0, =DataAbort_Handler bx r0 /* 未使用的中断 */ NotUsed_Handler: ldr r0, =NotUsed_Handler bx r0 /* IRQ中断!重点!!!!! */ IRQ_Handler: push {lr} /* 保存lr地址 */ push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */ 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 至此,start.S文件编写完成,完成了系统初始化的过程。 4.3 通用中断驱动文件编写 在 start.S 文件中我们在中断服务函数 IRQ_Handler 中调用了 C 函数system_irqhandler 来处理具体的中断。此函数有一个参数,参数是中断号(这个参数在汇编文件中,默认传入的是r0),但是函数 system_irqhandler 的具体内容还没有实现,所以需要实现函数 system_irqhandler 的具体内容。不同的中断源对应不同的中断处理函数, I.MX6U 有 160 个中断源,所以需要 160 个中断处理函数,我们可以将这些中断处理函数放到一个数组里面,中断处理函数在数组中的标号就是其对应的中断号。 当中断发生以后函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。 在 bsp 目录下新建名为“int”的文件夹,在 bsp/int 文件夹里面创建 bsp_int.c 和 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 |
|
|
|
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); } 4.4 修改 GPIO 驱动文件 在之前的试验中我们使用到了 GPIO 最基本的输入输出功能,这里我们需要使用 GPIO 的中断功能。所以需要修改文件 GPIO 的驱动文件 bsp_gpio.c 和 bsp_gpio.h,加上中断相关函数。关于 GPIO 的配置工作,这里就不赘述了。 文件内容:(这里只针对终端需要的驱动来分析,基本配置不讲解,可以看我之前的文章)
#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.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); } 4.5 按键中断驱动文件编写 本节目的是以中断的方式编写 KEY 按键驱动,当按下 KEY 以后触发 GPIO 中断,然后在中断服务函数里面控制蜂鸣器的开关。所以接下来就是要编写按键 KEY 对应的UART1_CTS 这个 IO 的中断驱动,在 bsp 文件夹里面新建名为“exit”的文件夹,然后在 bsp/exit里面新建 bsp_exit.c 和 bsp_exit.h 两个文件。 简单描述一下文件的内容:
#ifndef _BSP_EXIT_H #define _BSP_EXIT_H #include "imx6ul.h" /* 函数声明 */ void exit_init(void); /* 中断初始化 */ void gpio1_io18_irqhandler(void); /* 中断处理函数 */ #endif 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); /* 清除中断标志位 */ } 4.6 编写 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; } |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1618 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1545 浏览 1 评论
979 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
683 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1597 浏览 2 评论
1864浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
645浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
516浏览 3评论
532浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
505浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 22:09 , Processed in 0.697404 second(s), Total 55, Slave 48 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号