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

10. GPIO中断

10. GPIO中断

  • 回顾stm32中断系统
    • STM32中断向量表
    • 中断向量偏移
    • NVIC中断控制器
  • Cortex_A7 中断系统
    • 中断向量表
    • GIC控制器
      • 中断ID
      • GIC逻辑分块
      • CP15协处理器
        • c0寄存器
        • c1寄存器
        • c12寄存器
        • c15寄存器
      • 中断使能
      • 中断优先级设置
        • 优先级数配置 GICC_PMR
        • 抢占优先级和子优先级位数设置 GICC_BPR
        • 优先级设置 D_IPRIORITYR
    • 代码实例
      • 移植 SDK 包中断相关文件
      • 修改start.s
      • 通用中断处理函数编写
      • 修改GPIO驱动文件
      • 按键中断驱动文件
      • 主函数

回顾stm32中断系统

STM32中断向量表

中断向量表存放的是中断向量。中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量。ARM芯片从0x00000000开始运行,执行指令。在程序开始的地方存放着中断向量表,中断向量表主要功能是描述中断对应的中断服务函数。对于stm32来说,代码最开始的地址是存放在栈顶指针

中断向量偏移

通过中断偏移可以将中断向量表放到任意位置。一般ARM从0x00000000地址开始运行,对于stm32我们设置连接首地址为0x80000000。如果代码一定要从该地址运行,一定要告诉soc内核,也就是设置中断向量偏移。设置SCB的VTOR寄存器为新的中断向量表起始地址即可

NVIC中断控制器

NVIC就是中断管理机构,例如使能和关闭指定的中断,设置中断优先级

Cortex_A7 中断系统

在这里插入图片描述

  • 复位中断:CPU复位后就会进入,可以在复位中断服务函数中做一些初始化工作
  • 未定义指令中断:如果指令不能识别的话就会产生
  • 软中断:由 SWI 指令引起的中断,Linux 的系统调用会用SWI指令来引起软中断,通过软中断可以进入内核空间
  • 指令预取中止中断:预取指令的出错的时候就会产生
  • 数据访问中止中断:访问数据出错的时候产生
  • IRQ中断:外部中断,芯片内部的外部中断都会引起此中断的发生
  • FIQ中断:快速中断,如果需要快速处理中断的话就可以使用
    上述中最常用的是复位中断和IRQ中断

中断向量表

Cortex-A内核CPU的所有外设中断都属于IRQ中断,当任意一个外部中断发生的时候都会触发IRQ中断。在IRQ中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而做出相应的处理。中断向量表处于程序最开始的地方,比如 start.s 文件最前面

.global _start_start:ldr pc, =Reset_Handler 			// 复位中断ldr pc, =Undefined_Handler		// 未定义指令中断ldr pc, =SVC_Handler			// SVC 中断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_Handler:ldr r0, =SVC_Handler			bx r0PrefAbort_Handler:ldr r0, =PrefAbort_Handler		bx r0DataAbort_Handler:ldr r0, =DataAbort_Handler		bx r0NotUsed_Handler:ldr r0, =NotUsed_Handler		bx r0IRQ_Handler:// IRQ中断具体处理过程FIQ_Handler:ldr r0, =FIQ_Handlerbx r0

GIC控制器

当 GIC 接收到外部中断信号后就会报给 ARM 内核,ARM 内核只提供了四个信号给GIC来汇报中断情况:VFIQ(虚拟快速FIQ)、VIFQ(虚拟外部IRQ)、FIQ和IRQ。这里只探讨IRQ
在这里插入图片描述
GICV2逻辑结构图
左侧部分是中断源,中间部分是GIC控制器,最右侧就是中断控制器向处理器内核发送中断信息。

  1. SPI,共享中断,所有Core共享的中断,外设中断都属于SPI中断,这些中断所有的Core都可以处理,不限定特定的Core
  2. PPI,私有中断,GIC是支持多核的,每个核有自己独立的中断,这些中断要指定的核心处理,所有这些中断就叫做私有中断
  3. SGI,网络中断,由软件触发引起的中孤单,通过向寄存器GICD_SGIR写入数据来触发,系统会使用SGI中断来完成多核之间的通信

中断ID

中断源有很多,为了区分这些不同的中断源就产出了一个唯一ID。每一个CPU最多支持1020个中断ID,包含了SGI(0 ~ 15)、PPI(16 ~ 31)和 SPI(32 ~ 1019)。I.MX6U 的中断源有32+128 = 160 个。

GIC逻辑分块

分了两个逻辑块:Distributor 和 CPU Interface,也就是分发器端和CPU接口端
分发器端: 负责处理各个中断事件的分发问题,就是中断事件应该发送到哪个CPU 接口端。分发器收集所有的中断源,可以控制每个中断的优先级,将优先级高的中断事件发送到CPU接口端。 主要工作:全局中断使能控制、控制每一个中断的使能或关闭、设置每个中断的优先级、设置每个中断的目标处理器列表、设置每个外部中断的触发模式(电平触发或边沿触发)、设置每个中断属于组0还是组1。分发器端相关寄存器相对于 GIC 基地址偏移为 0x1000
CPU接口端: 和 CPU Core 相连,每个 CPU Core 都可以在GIC中找到一个对应的CPU Interface。主要工作:使能或关闭发送到CPU Core 的中断请求信号、应答中断、通知中断处理完成、设置优先级掩码,并通过掩码来设置哪些中断不需要上报给CPU Core、定义抢占策略、当多个中断到来时选择优先级最高的。接口端相关寄存器相对于 GIC 基地址偏移为 0x2000。
GIC 基地址就需要用到 CP15 协处理器获取。

CP15协处理器

在这里插入图片描述
CP15协处理器一般用于存储系统管理,但是在中断中也会使用,一共有16个32位寄存器,该处理器的访问通过如下指令完成:

  1. MRC:将CP15协处理器中的寄存器数据读到ARM寄存器中
  2. MCR:将ARM寄存器的数据写入到CP15中
MCR{cond} p15,<opc1>,<Rt>,<CRn>,<CRm>,<opc2>
/**	cond: 指令执行的条件码,如果忽略就表示无条件执行*	opc1: 协处理器要执行的操作码*	Rt: ARM源寄存器,要写入到CP15寄存器的数据*	CRn: CP15的目标寄存器*	CRm: 协处理器中附加的目标寄存器或源操作数寄存器,如果不需要附加信息就将CRm设置为C0,否则表示不可预测*	opc2: 可选的协处理器特定操作码,不需要的时候要设置为0
*/
// MRC 指令格式和 MCR 一样
// 例如:将 CP15 中 C0 寄存器的值读取到 R0 中
MRC p15, 0, r0, c0, c0, 0
c0寄存器

在这里插入图片描述
上图是c0寄存器不同搭配的含义。当指令中的 CRn=c0,opc1=0,CRm=c0,opc2=0 的时候就表示此时的 c0 就是 MIDR 寄存器,也就是主 ID 寄存器。

c1寄存器

在这里插入图片描述
SCTLR是c1寄存器的基本作用,也就是系统控制寄存器,主要是完成控制功能,位结构图如下:
在这里插入图片描述
bit13: V,中断向量表基地址选择位,为0的话就是0x00000000,软件可使用VBAR来重映射此基地址,为1的话就是0xFFFF0000,此及地址不能被重新映射
bit12: I,I Cache使能位,为0就关闭,为1就使能
bit11: Z,分支预测使能位,如果开启 MMU 的话,此位也会使能。
bit10: SW, SWP 和 SWPB 使能位,当为 0 的话关闭 SWP 和 SWPB 指令,当为 1 的时候就使能 SWP 和 SWPB 指令。
bit9:3: 未使用,保留。
bit2: C, D 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)寄存器中。
c12寄存器

在这里插入图片描述
设置中断向量表偏移的时候就需要将新的中断向量表基地址写入VBAR中,也就是向量表基地址寄存器。

ldr r0, =0x87800000
MCR p15,0,r0,c12,c0,0	// c12=0x87800000
c15寄存器

在这里插入图片描述
需要c15作为CBAR寄存器,因为 GIC 的基地址就保存在 CBAR 中

MRC p15,4,r1,c15,c0,0 // 获取基地址,保存在r1中

获取到基地址后就可以设置相关寄存器,比如获取当前中断ID,中断ID保存在GICC_IAR中,属于CPU接口寄存器

MRC p15,4,r1,c15,c0,0 // 获取基地址
ADD r1,r1,#0x2000 // 基地址加0x2000得到CPU接口寄存器起始地址
LDR r0,[r1,#0xC] // 读取CPU接口端起始地址+0xC处的寄存器值,也就是寄存器GIC_IAR的值

中断使能

中断使能包括两部分,一个是IRQ或FIQ总中断使能,另一个就是 ID0 ~ ID1020 中断源的使能

  1. IRQ和FIQ总中断使能
    要想使用I.MX6U上的外设时钟,就必须先打开IRQ中断。CPSRI=1禁止IRQ,I=0使能IRQ;F=1禁止FIQF=0使能FIQ。也可以使用指令
    在这里插入图片描述
  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 中断的。

中断优先级设置

优先级数配置 GICC_PMR

中断优先级可以分为抢占优先级和子优先级。GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高! Cortex-A7 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级
在这里插入图片描述
在这里插入图片描述

抢占优先级和子优先级位数设置 GICC_BPR

也就是设置抢占优先级和子优先级各占多少位
在这里插入图片描述
一般将所有的中断优先级位配置为抢占优先级,比如I.MX6U的优先级位数为5(32个优先级),所以可以设置Binary point 为2

优先级设置 D_IPRIORITYR

某个中断 ID 的中断优先级是由寄存器 D_IPRIORITYR 来设置的,一共有512个,如果优先级个数为32,使用寄存器 bit7:4 来设置,也就是实际的优先级要左移 3 位

GICD_IPRIORITYR[40]=5<<3; // 设置ID40 中断优先级为5

代码实例

移植 SDK 包中断相关文件

将 core_ca7.h 拷贝到 imx6ul 文件夹中,主要留下 GIC 相关的内容,重点是10个接口函数
在这里插入图片描述

修改start.s

在这里插入图片描述
添加中断向量表,编写复位中断服务函数和IRQ中断服务函数

.global _start_start:ldr pc, =Reset_Handler 			// 复位中断ldr pc, =Undefined_Handler		// 未定义指令中断ldr pc, =SVC_Handler			// SVC 中断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/D Cache 和 MMU,采取读--写的方式,bic 指令就是清除特定位mrc p15, 0, r0, c1, c0, 0	// 读取 CP15 的 C1 寄存器到 R0 中bic r0, r0, #(0x1 << 12) 	// 清除 C1 的 I 位,关闭 I Cache,清除 r0 位并将结果保存到 r0 中bic r0, r0, #(0x1 << 2) 	// 清除 C1 的 C 位,关闭 D Cachebic r0, r0, #0x2			// 清除 C1 的 A 位,关闭对齐检查bic r0, r0, #(0x1 << 11)	// 清除 C1 的 Z 位,关闭分支预测bic r0, r0, #0x1			// 关闭 C1 的 M 位,关闭MMUmcr p15, 0, r0, c1, c0, 0	// 将 r0 的值写入到 CP15 的 C1 中// 设置各个模式下的栈指针// IMX6U 的堆栈是向下增长的,也要注意4字节内存对齐// DDR 的范围是 0x80000000 ~ 0x9FFFFFFF 或者 0x8FFFFFFF// 进入 IRQ 模式mrs r0, cpsrbic r0, r0, #0x1f			// 将 r0 的低五位清零orr r0, r0, #0x12			// r0 | 0x12,表示使用 IRQ 模式msr cpsr, r0 				// 将 r0 的数据写入到 cpsr 中ldr sp, =0x80600000			// IRQ 模式栈首地址为 0x80600000,大小为 2MB// 进入 SYS 模式mrs r0, cpsrbic r0, r0, #0x1f			// 将 r0 的低五位清零orr r0, r0, #0x1f			// r0 | 0x1f,表示使用 SYS 模式msr cpsr, r0 				// 将 r0 的数据写入到 cpsr 中ldr sp, =0x80600000			// SYS 模式栈首地址为 0x80400000,大小为 2MB// 进入 SVC 模式mrs r0, cpsrbic r0, r0, #0x1f			// 将 r0 的低五位清零orr r0, r0, #0x13			// r0 | 0x13,表示使用 SVC 模式msr cpsr, r0 				// 将 r0 的数据写入到 cpsr 中ldr sp, =0x80200000			// SVC 模式栈首地址为 0x80600000,大小为 2MB        cpsie i						// 打开全局中断b main						// 跳转到 main 函数                                
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0	// 跳转到未定义中断服务函数位置执行SVC_Handler:ldr r0, =SVC_Handler			bx r0PrefAbort_Handler:ldr r0, =PrefAbort_Handler		bx r0DataAbort_Handler:ldr r0, =DataAbort_Handler		bx r0NotUsed_Handler:ldr r0, =NotUsed_Handler		bx r0IRQ_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 寄存器中add r1, r1, #0x2000			// GIC 基地址加 0x2000,得到 CPU 接口地址ldr r0, [r1, #0xC]			// CPU 接口端基地址加 0x0c 就是 GICC_IAR 寄存器,保存着当前发生中断的中断号,要根据中断号来判断调用哪个中断服务函数push {r0, r1}				// 保存 r0, r1cps #0x13					// 进入 SVC 模式,允许其他中断再次进去push {lr}					// 保存 SVC 模式的 lr 寄存器ldr r2, =system_irqhandler	// 加载C语言中断处理函数到r2寄存器中blx r2						// 运行C语言中断处理函数,带有一个参数pop {lr} 					// 执行完C语言中断服务函数,lr 出栈cps #0x12					// 进入 IRQ 模式pop {r0, r1} 					str r0, [r1, #0x10]			// 中断执行完成,向 EOIR 寄存器写入刚刚处理完成的中断号pop {r0}msr spsr_cxsf, r0			// 恢复 spsrpop {r0-r3, r12}			// r0-r3,r12 出栈pop {lr}					// lr 出栈subs pc, lr, #4				// 将 lr-4 赋给 pc// 为什么要将 pc-4 后再赋值:ARM 指令是三级流水线,分别是取指、译指、执行,pc指向的是正在取值的地址
FIQ_Handler:ldr r0, =FIQ_Handlerbx r0

编写复位中断服务函数:
在这里插入图片描述

IRQ中断服务函数:
在这里插入图片描述
MRC p15, 4, r1, c15, c0, 0读取CP15的CBAR寄存器,CBAR寄存器保存了GIC控制器的寄存器组的首地址。0x1000 ~ 0x1ff是GIC的分发器,0x2000 ~ 0x3fff是CPU接口端。代码中,R1寄存器保存着GIC控制器的CPU接口端基地址,读取CPU接口端的GICC_IAR 寄存器的值保存到R0寄存器里面。可以从GICC_IAR的bit9 ~ 0读取中断ID,也就可以判断出是什么中断,就可以执行对应的中断处理函数。system_irqhandler就是具体的中断服务函数,只有一个参数就是GICC_IAR的值。处理完具体的中断后,需要将对应的中断ID值写入到GICC_EOIR

通用中断处理函数编写

bsp_int.h

不同的中断源对应不同的中断处理函数,IMX6U有160个中断源,可以将这些中断处理函数放在一个数组中,对应的下标就是中断号。

#pragma once
#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 ini_init();
void system_irqtable_init();
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam);// IRQn_Type 是 NXP 官方 SDK中的文件 MCIMX6Y2C.h 中定义的一个枚举类型,包含了IMX6U的所有中断
void system_irqhandler(unsigned int giccIar);
void default_irqhandler(unsigned int giccIar, void *userParam);

bsp_int.c

#include "bsp_int.h"
static unsigned int irqNesting; // 统计中断个数
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS]; // 中断处理函数表,这个宏是在MCIMX6Y2.h中定义的void int_init()
{GIC_init();							// 初始化 GICsystem_irqtable_init();				// 初始化中断表__set_VBAR((uint32_t)0x87800000);  	// 中断向量表偏移
}
// 初始化中断处理函数表
void system_irqtable_init()
{unsigned int i = 0;irqNesting=0;// 先将所有的中断服务函数设置为默认值for(i=0; i<NUMBER_OF_INT_VECTORS;i++){system_register_irqhandler((IRQn_Type)i, default_irqhandler, NULL);}
}
// 注册中断处理函数
void system_register_irqhandler(IRQn_Yype irq, system_irq_handler_t handler, void *userParam)
{irqTable[irq].irqHandler = handler;irqTable[irq].userParam = userParam;
}// 具体的中断处理函数,IRQ_Handler 会调用此函数
void system_irqhandler(unsigned int gicciar)
{uint32_t intNum = giccier & 0x3ff;// 检查中断IDif((intNum == 1020) || (intNum >= NUMBER_OF_INT_VECTORS)){return;}irqNesting++;irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);// 执行具体的中断处理函数irqNesting--;
}
// 默认中断处理函数
void default_irqhandler(unsigned int gicciar, void *UserParam)
{while(1){}
}

修改GPIO驱动文件

  1. 首先要设置GPIO中断触发方式,也就是GPIO_ICR1 或 GPIO_ICR2 寄存器。触发方式有0低电平、1高电平、2上升沿和3下降沿触发方式。对于本例程,设置KEY0,也就是UART1_CTS为下降沿触发。
  2. 使能GPIO对应的中断,设置GPIO_IMR 寄存器
  3. 处理完中断后,需要清除中断标志位,也就是GPIO_ISR寄存器相应的位,是通过写1清零
#pragma once
#define _BSP_KEY_H
#include "imx6ul.h"
// 枚举类型和结构体定义
typedef enum _gpio_pin_direction
{kGPIO_DigitalInput = 0U,  		/* 输入 */kGPIO_DigitalOutput = 1U, 		/* 输出 */
} gpio_pin_direction_t;// 触发中断类型枚举
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);
#include "bsp_gpio.h"
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);
}

按键中断驱动文件

#pragma once
#include "imx6ul.h"
void exit_init(void); 			  	// 中断初始化
void gpio1_io18_irqhandler(void);  	// 中断处理函数

按键所使用的是 UART_CTS 这个IO,设置复用为 GPIO1_IO18,设置为下降沿中断

#include "bsp_exit.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_delay.h"
#include "bsp_beep.h"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的中断功能 */
}
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); /* 清除中断标志位 */
}

主函数

#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"int main(void)
{unsigned char state = OFF;int_init(); 		imx6u_clkinit();	clk_enable();		led_init();			beep_init();		key_init();			exit_init();		while(1)			{	state = !state;led_switch(LED0, state);delay(500);}return 0;
}

相关文章:

10. GPIO中断

10. GPIO中断 回顾stm32中断系统STM32中断向量表中断向量偏移NVIC中断控制器 Cortex_A7 中断系统中断向量表GIC控制器中断IDGIC逻辑分块CP15协处理器c0寄存器c1寄存器c12寄存器c15寄存器 中断使能中断优先级设置优先级数配置 GICC_PMR抢占优先级和子优先级位数设置 GICC_BPR优先…...

【离散数学必刷题】谓词逻辑(第二章 左孝凌版)刷完包过!

专栏&#xff1a;离散数学必刷题 本章需要掌握的重要知识&#xff1a; 1.利用谓词表达式表示命题 2.变元的约束 3.谓词公式的定义、谓词公式的赋值 4.谓词公式的翻译&#xff08;注意在全总个体域时使用特性谓词&#xff09; 5.有限论域上量词的消去 6.谓词公式中关于量词的等价…...

SpringBoot系列-2 自动装配

背景&#xff1a; Spring提供了IOC机制&#xff0c;基于此我们可以通过XML或者注解配置&#xff0c;将三方件注册到IOC中。问题是每个三方件都需要经过手动导入依赖、配置属性、注册IOC&#xff0c;比较繁琐。 基于"约定优于配置"原则的自动装配机制为该问题提供了一…...

vue3+ts 前端实现打印功能

1.安装插件 npm install vue3-print-nb --save 2.全局引用 import { createApp } from ‘vue’ import App from ‘./App.vue’ import print from ‘vue3-print-nb’ const app createApp(App) app.use(print) app.mount(‘#app’) 例子 <template><div><el-…...

egg.js sequelize数据库操作配置

egg.js sequelize数据库操作配置 文章目录 egg.js sequelize数据库操作配置1. 数据库配置2. 迁移配置3.数据表设计和迁移4.模型创建 1. 数据库配置 安装并配置egg-sequelize插件&#xff08;它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上&#xff09;和mysql2模块&a…...

vagrant安装k8s集群

目录 概述前期准备安装virtualbox安装vagrant安装gitbash 集群架构集群安装集群初始化集群测试 概述 使用vagrant、virtualbox创建。 前期准备 安装virtualbox 访问官网安装&#xff0c;版本7.0.10 安装vagrant 访问官网安装&#xff0c;版本2.3.7 安装gitbash 访问官网…...

ArcGIS进阶:水源涵养功能分级评价操作

首先抛出水源涵养重要性评价的公式&#xff1a;水源涵养量降雨量-蒸散发量-地表径流量&#xff0c;其中地表径流量降雨量*平均地表径流系数 声明&#xff1a;以下数据来源于来自于牛强老师书籍&#xff08;城乡规划GIS技术&#xff09;。 以下给出重要性评价阈值表&#xff1…...

数据结构与算法 | 第四章:字符串

本文参考网课为 数据结构与算法 1 第四章字符串&#xff0c;主讲人 张铭 、王腾蛟 、赵海燕 、宋国杰 、邹磊 、黄群。 本文使用IDE为 Clion&#xff0c;开发环境 C14。 更新&#xff1a;2023 / 11 / 12 数据结构与算法 | 第四章&#xff1a;字符串 字符串概念字符串字符字符…...

2023-11-rust-struct

struct 类似 schema。 ts的interface 和type struct MyStruct {width: i32,height: i32, } 创建实例 let eg1 MyStruct {width: 23,height: 22,}; struct 可以有自己的方法&#xff0c;并且默认第一个参数是该实例 impl MyStruct {fn can_hold(&self, instance: &…...

Docker容器编排

文章目录 基本概念Docker ComposeSwarm分布式NodeTaskservice集群搭建弹性伸缩 基本概念 针对容器生命周期的管理&#xff0c;对容器生命周期进行更方便更快捷的方式进行管理。 依赖管理&#xff1a;当一个容器必须在另一个容器运行完成后&#xff0c;才能运行时&#xff0c;…...

计算机中丢失mfc140u.dll怎么解决

mfc140u.dll是一个Microsoft Visual C库文件&#xff0c;主要用于MFC&#xff08;Microsoft Foundation Class&#xff09;应用程序的开发。它包含了MFC应用程序所需的一些常用功能&#xff0c;如对话框、窗口、菜单等。当mfc140u.dll丢失时&#xff0c;可能会导致MFC应用程序无…...

postman设置动态token, 每次登录更新token

postman设置动态token, 每次登录更新token 文章目录 postman设置动态token, 每次登录更新token问题1. 设置全局变量2. 新建登录接口3. 设置脚本4. 切换环境5. 配置动态token 问题 token过期时间一般比较短, 每次使用postman调用接口都token非常麻烦 实现token过期后, 调用一次…...

架构师范文(AI写作)两篇

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 架构师范文-论区块链技术及应用 2022年3月&#xff0c;我参与了某集团内部一款基于区块链技术的数字资产管理平台&#xff0c;该平台是为了方便管理公司旗下的各种…...

基于SSM的电子病历系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…...

一次sougo workflow库的使用过程

安装就是常规的make install tutorial http_echoserver实现一下&#xff0c;在macos上实现 cmakelist.txt cmake_minimum_required(VERSION 3.6)set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Release")project(mainLANGUAGES C CXX )set(CMAKE_RUNTIME_OUTP…...

macOS Big Sur(macos11版本)

macOS Big Sur是苹果推出的最新操作系统&#xff0c;具有以下特点&#xff1a; 全新的设计风格&#xff1a;Big Sur采用了全新的设计语言&#xff0c;包括更加圆润的窗口和控件、更加鲜明的色彩和更加简洁的界面。这种设计风格使得操作系统更加美观和易用。强大的性能表现&…...

泛微E-Office信息泄露漏洞复现

简介 Weaver E-Office是中国泛微科技&#xff08;Weaver&#xff09;公司的一个协同办公系统。 Weaver E-Office 9.5版本存在安全漏洞。攻击者利用该漏洞可以访问文件或目录。 漏洞编号&#xff1a;CVE-2023-2766 漏洞复现 FOFA语法&#xff1a; app"泛微-EOffice&qu…...

-bash: sudo: command not found的解决方法

在 Linux 系统中&#xff0c;使用 sudo 命令时提示 “command not found”&#xff0c;首先执行以下命令看一下 /etc/sudoers.d 文件是否存在&#xff1a; find /etc/sudoers.d1&#xff09;如果返回 No such file or directory&#xff0c;就说明系统没有安装sudo&#xff0c…...

CMOS介绍

1 二极管 2 CMOS 2.1 栅极、源极、漏极 2.2 内部结构 2.2 导电原理 - 原理&#xff1a;1.通过门级和衬底加一个垂直电场Ev&#xff0c;从而在两口井之间形成反形层2.如果加的电场足够强&#xff0c;反形层就可以把source&#xff08;源极&#xff09;和drain&#xff08;漏极…...

《软件工程与计算》期末考试真题范例及答案

今天分享一套针对《软件工程与计算》这本书的真题案例&#xff0c;有关《软件工程与计算》23章内容的重点知识整理&#xff0c;已经总结在了博客专栏中&#xff0c;有需要的自行阅读&#xff1a; 《软件工程与计算》啃书总结https://blog.csdn.net/jsl123x/category_12468792.…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

地震勘探——干扰波识别、井中地震时距曲线特点

目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波&#xff1a;可以用来解决所提出的地质任务的波&#xff1b;干扰波&#xff1a;所有妨碍辨认、追踪有效波的其他波。 地震勘探中&#xff0c;有效波和干扰波是相对的。例如&#xff0c;在反射波…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

【JavaWeb】Docker项目部署

引言 之前学习了Linux操作系统的常见命令&#xff0c;在Linux上安装软件&#xff0c;以及如何在Linux上部署一个单体项目&#xff0c;大多数同学都会有相同的感受&#xff0c;那就是麻烦。 核心体现在三点&#xff1a; 命令太多了&#xff0c;记不住 软件安装包名字复杂&…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

webpack面试题

面试题&#xff1a;webpack介绍和简单使用 一、webpack&#xff08;模块化打包工具&#xff09;1. webpack是把项目当作一个整体&#xff0c;通过给定的一个主文件&#xff0c;webpack将从这个主文件开始找到你项目当中的所有依赖文件&#xff0c;使用loaders来处理它们&#x…...