ARM裸机开发:输入中断

举报
JeckXu666 发表于 2022/01/18 01:29:40 2022/01/18
【摘要】 文章目录 ARM裸机开发:输入中断一、硬件平台:二、原理图分析三、程序编写3.1 移植相关文件3.2 编写启动文件3.3 中断处理程序3.4 开启输入中断3.5 按键中断编写3.6 编写Makef...

ARM裸机开发:输入中断

一、硬件平台:

正点原子I.MX6U阿尔法开发板

_533488159_IMG_20210803_235508_1628006109000_xg_0

二、原理图分析

输入中断是配置GPIO作为输入IO口,检测按键引脚电平,当目标电平来到时产生中断,进入中断服务函数处理程序,I.MX6U的按键引脚如下:

20211101202031

20211101202058

20211101202909

可以看到按键引脚接到 GPIO1_IO18 口,按键的原理就是默认接一个上拉电阻,按键按下接地,可以有效控制 IO 电平

三、程序编写

程序编写前先复制上一节按键输入的工程作为本小节的开始工程

3.1 移植相关文件

在 NXP 提供的 SDK 包内 core_ca7.h 有相关的定义文件,为了节省开发时间,我们将其移植到本地工程目录;注意该文件要做一些修改,删除一些不必要的内容,不然会保存,这里我直接复制正点原子修改后的文件到工程目录下:

20211116103443

该文件下面我们只需要注意 10 个API函数,函数如下:

函数 描述
GIC_Init 初始化 GIC
GIC_EnableIRQ 使能指定的外设中断
GIC_DisableIRQ 关闭指定的外设中断
GIC_AcknowledgeIRQ 返回中断号
GIC_DeactivateIRQ 无效化指定中断
GIC_GetRunningPriority 获取当前正在运行的中断优先级
GIC_SetPriorityGrouping 设置抢占优先级位数
GIC_GetPriorityGrouping 获取抢占优先级位数
GIC_SetPriority 设置指定中断的优先级
GIC_GetPriority 获取指定中断的优先级

文件添加后使用如下头文件调用

#include "core_ca7.h"

  
 
  • 1

3.2 编写启动文件

SDK 添加完成之后就是修改启动文件,定义系统中断服务函数,修改 IRQ 中断,判断中断类型,进入不同的中断服务函数,启动文件编写如下:

首先编写全局标号,进入 _start 函数,在里面创建中断向量表

.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(快速中断)未定义中断 			*/

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

编写对应的中断服务函数,这里除了 Reset_Handler 和 IRQ_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
/* FIQ中断 */
FIQ_Handler:
	ldr r0, =FIQ_Handler	
	bx r0									

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

这些中断服务函数是可以编写一些处理代码,方便用户判断错误的来源的,暂时先不研究

下面编写复位中断服务函数:

/* 复位中断 */	
Reset_Handler:
	/* 关闭全局中断 */
	cpsid i						
	/* 关闭I、DCache和MMU 采取读-改-写的方式*/
    /* 读取CP15的C1寄存器到R0中*/
	mrc     p15, 0, r0, c1, c0, 0    
	/* 清除C1寄存器的bit12位(I位),关闭I Cache*/
    bic     r0,  r0, #(0x1 << 12)     
    /* 清除C1寄存器的bit2(C位),关闭D Cache*/
    bic     r0,  r0, #(0x1 <<  2)     
	/* 清除C1寄存器的bit1(A位),关闭对齐*/
    bic     r0,  r0, #0x2             
	/* 清除C1寄存器的bit11(Z位),关闭分支预测*/
    bic     r0,  r0, #(0x1 << 11)     
	/* 清除C1寄存器的bit0(M位),关闭MMU*/
    bic     r0,  r0, #0x1             
	/* 将r0寄存器中的值写入到CP15的C1寄存器中*/
    mcr     p15, 0, r0, c1, c0, 0     
#if 0
	/* 汇编版本设置中断向量表偏移 */
	ldr r0, =0X87800000
	dsb
	isb
	mcr p15, 0, r0, c12, c0, 0
	dsb
	isb
#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函数 			 	*/

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

IRQ 中断服务函数,进入中断服务函数后,先进行现场保护,然后获取 GIC 的基地址,偏移后操作其寄存器,获取当前中断号,保存到寄存器 r0 和 r1,接着调用一个c语言中断处理函数,将参数从 r0-r3 四个寄存器传入函数

汇编调用 C 函数的时候建议形参不要超过 4 个,形参可以由 r0~r3 这四个寄存器来传递,如果形参大于 4 个,那么大于 4 个的部分要使用堆栈进行传递。

所以 r0 寄存器写入中断号就可以了传入到函数 system_irqhandler;接着该函数进行对应中断的调用和处理,处理完成后向 GICC_EOIR 寄存器写入其中断号表示中断处理完成

/* 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寄存器中*/						
	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}		
    # 向 GICC_EOIR 寄存器写入刚刚处理完成的中断号,
    # 当一个中断处理完成以后必须向 GICC_EOIR 寄存器
    # 写入其中断号表示中断处理完成
	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 */

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

之后就是进行现场恢复,返回到中断位置!注意,此处恢复现场传递的是 lr - 4 的寄存器值,而不是pc,因为 ARM 的指令是三级流水线:取指、译指、执 行pc 指向的是正在取值的地址,比如下面一段代码

0X2000 MOV R1, R0 ;执行
0X2004 MOV R2, R3 ;译指
0X2008 MOV R4, R5 ;取值 PC

  
 
  • 1
  • 2
  • 3

当前正在执行 0X2000 地址处的指令 “MOV R1, R0” ,但 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。若发生中断,中断发生的时候保存在 lr 中的是 pc 的值,即地址 0X2008。当中断处理完成如果直接跳转到 lr 里面保存的地址处(0X2008) 开始运行,那么就有一个指令没有执行,所以就需要将 lr-4 赋值给 pc,即 pc=0X2004,从第二级正在译指的指令 “MOV R2, R3” 开始执行

3.3 中断处理程序

我们在中断服务函数 IRQ_Handler 中调用了 C 函数 system_irqhandler 来处理具体的中断,该函数的具体细节需要我们自己实现,所以要编写中断处理程序来实现,同时因为中断数量较多,所以我们引入一些其他的数据结构单元辅助管理中断服务函数,编写如下:

新建一个新的模块文件

20211116141327

头文件插入如下代码

#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
{
    /* data */
    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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

代码解释:

typedef void (* system_irq_handler_t) (unsigned int giccIar,void *param);

  
 
  • 1

创建一个函数指针,用 typedef 定义修饰别名为 system_irq_handler_t

typedef struct _sys_irq_handle
{
    /* data */
    system_irq_handler_t irqHandler;
    void *userParam;
} sys_irq_handle_t;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

创建一个结构体,其有两个参数,一个是函数指针的入口指针,另外一个则是一个用户参数,创建这个结构体用于保存中断的信息,保存其中断处理函数入口因为有160个中断源,所以我们在.c文件中可以定义一个结构体数组用于存储所有中断的信息

其他的就是一些函数声明了:

// 中断系统(GIC)初始化
void int_init(void);
// 中断信息结构体数组初始化
void system_irqtable_init(void);
// 注册中断,修改目标中断的结构体的信息
//要使用某个外设中断,那就必须调用此函数来给这个中断注册一个中断处理函数
void system_register_irqhandler(IRQn_Type irq,
                                system_irq_handler_t handler,
                                void *userParam);
// _start 文件中调用的的中断号处理函数
void system_irqhandler(unsigned int giccIar);
// 默认中断处理函数
void default_irqhandler(unsigned int giccIar,void *userParam);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

.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) ;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

3.4 开启输入中断

这里 GPIO 配置代码直接使用正点原子的驱动方案,有关的注释我写在代码内

bsp_gpio.h

#ifndef _BSP_GPIO_H
#define _BSP_GPIO_H
#define _BSP_KEY_H
#include "imx6ul.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名	: 	 bsp_gpio.h
作者	   : 左忠凯
版本	   : V1.0
描述	   : GPIO操作文件头文件。
其他	   : 无
论坛 	   : www.openedv.com
日志	   : 初版V1.0 2019/1/4 左忠凯创建
	 	 V2.0 2019/1/4 左忠凯修改
	 	 添加GPIO中断相关定义

***************************************************************/

/* 枚举类型和结构体定义 */
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;

/* 函数声明 */
// GPIO 初始化
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
// GPIO 读IO电平
int gpio_pinread(GPIO_Type *base, int pin);
// GPIO 写GPIO电平
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
// GPIO 中断配置
void gpio_intconfig(GPIO_Type* base, unsigned int pin, 
                    gpio_interrupt_mode_t pinInterruptMode);
// 使能 GPIO 中断
void gpio_enableint(GPIO_Type* base, unsigned int pin);
// 失能 GPIO 中断
void gpio_disableint(GPIO_Type* base, unsigned int pin);
// 清除中断标志
void gpio_clearintflags(GPIO_Type* base, unsigned int pin);
#endif

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

bsp_gpio.c

#include "bsp_gpio.h"
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名	: 	 bsp_gpio.h
作者	   : 左忠凯
版本	   : V1.0
描述	   : GPIO操作文件。
其他	   : 无
论坛 	   : www.openedv.com
日志	   : 初版V1.0 2019/1/4 左忠凯创建
		 V2.0 2019/1/4 左忠凯修改:
		 修改gpio_init()函数,支持中断配置.
		 添加gpio_intconfig()函数,初始化中断
		 添加gpio_enableint()函数,使能中断
		 添加gpio_clearintflags()函数,清除中断标志位
		 
***************************************************************/

/*
 * @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);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151

3.5 按键中断编写

有了 GPIO 驱动代码后,我们就可以新建一个新的模块代码,用于配置外部触发中断,新建模块如下:

20211116150340

bsp_exit.h 代码:

#ifndef __BSP_EXIT_H
#define __BSP_EXIT_H
#include "imx6ul.h"
// 外部中断初始化
void exit_init(void);
// 外部中断回调函数
void gpio1_io18_irqhandler(void);
#endif

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

bsp_exit.c 代码如下:

#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模式
    gpio_pin_config_t key_config;
    IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
	//设定按键中断
    key_config.direction=kGPIO_DigitalInput;
    key_config.interruptMode=kGPIO_IntFallingEdge;
    key_config.outputLogic=1;
    gpio_init(GPIO1,18,&key_config);
	//使能GIC中断,注册按键触发中断
    GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
    system_register_irqhandler(GPIO1_Combined_16_31_IRQn,
                               (system_irq_handler_t)gpio1_io18_irqhandler,
                               NULL);
    //使能按键触发中断
    gpio_enableint(GPIO1, 18);
}

void gpio1_io18_irqhandler(void)
{
    static unsigned char state = 0;
    //延时消抖(中断中严禁使用死延时,这里是为了IO稳定)
    delay(10);
    if(gpio_pinread(GPIO1,18) == 0)
    {
        state = !state;
        beep_switch(state);
    }
    //清除中断标志
    gpio_clearintflags(GPIO1,18);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

以上代码准备完成后,我们在 main.c 中分别调用代码进行初始化

20211116155803

3.6 编写Makefile脚本

在 Makefile 里面添加上对应文件的文件夹就可以完成编译,添加位置如下:

20211116155909
编译一下,成功通过:

20211116155930

四、实验现象

按下按键 LED 的灯光效果切换

文章来源: blog.csdn.net,作者:JeckXu666,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq_45396672/article/details/121358321

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。