IMX6UL开发板中断实验(三)

        在上一节我们编写完成了中断驱动文件和中断驱动头文件,那么这一讲我们将继续中断实验

        下面就是GPIO的中断设置,第一步要设置中断GPIO的触发方式,首先我们先看到寄存器,一共有GPIOx_ICR1和ICR2,

        

        图如上,ICR1或者ICR2可以看出图中都是由两个bit来描述一个IO口,相当于一个寄存器控制16个IO口,两个的话也就是32个IO口
        

        可以看出,bit31-bit30的作用翻译过来就是,此寄存器控制GPIO中断15功能的激活条件,然后像寄存器中写入00,01作用就是,中断的触发模式,分别是低电平,高电平,上升沿,下降沿触发
        对于我们需要完成的实验,首先我们看到开发板的原理图,既然是通过中断的方式点亮LED等,那么意思就是当按键按下的时候LED灯亮,那么按键按下是属于哪种形式的触发方式?

        这里需要说明的是,各种触发条件的条件以及特性和大致应用场景,在如下图按键按下额时候,如果产生低电平那么就可以触发条件,那么很明显对应的就是,下降沿触发,然后下面的表格对应的是,低电平触发,高电平触发,上升沿触发,下降沿触发

        KEY0按键对应的IO是,UART1 CTS,需要讲这个引脚设置为下降沿触发

        然后我们看到下一个寄存器,GPIO_IMR 包含了针对每个中断线的屏蔽位(masking bits),具体意思是,用于控制哪些GPIO引脚的中断信号应该被处理器接收和处理,哪些应该被忽略。
        GPIO_IMR寄存器中的每一位都对应一个GPIO引脚的中断屏蔽控制。如果某位被设置为1,则对应的GPIO引脚的中断请求将被屏蔽,即该引脚的中断信号不会被处理器响应;如果某位被设置为0,则对应的GPIO引脚的中断请求是使能的,即该引脚的中断信号可以被处理器接收和处理。

        然后这里继续看下一个我们需要用到的寄存器,GPIOx_ISR这个寄存器的作用就是,每个bit位对应一个IO口,一共也就是对应32的IO,如果某个IO口检测到中断产生,则将对应的bit位置1
        但是在中断产生过后,这个中断标志位肯定是需要被清0的,但是对于这个寄存器而言,这个寄存器是需要通过写1来清0,并不是直接写0

                

        UART1 CTS 按键这个引脚对应的是GPIO1_IO18,现在我们需要找到这个引脚对应的中断ID号,可以看到GPIO16-31是67这个ID号,但是这并不是他实际的ID号,他的实际ID号还需要加上32,因为PPI(私有中断ID)和SGI(软件中断),以及SPI共享外设中断,这些都是在之前提到过的,这里编号全是SPI共享外设中断,而PPI和SGI加起来一共有32个中断,所以需要在这里67的基础上再加上32,所以GPIO16-31引脚对应的中断是99

        然后在我们的代码文件中,也可以看到对应的中断号正好是99

        然后下面就开始编写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;
	

typedef struct _gpio_pin_config
{
    gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
    uint8_t outputLogic;            /* 如果是输出的话,默认输出电平 */
} 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);

#endif

        然后是.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)
{
	if(config->direction == kGPIO_DigitalInput) /* 输入 */
	{
		base->GDIR &= ~( 1 << pin);
	}
	else										/* 输出 */
	{
		base->GDIR |= 1 << pin;
		gpio_pinwrite(base,pin, config->outputLogic);/* 设置默认输出电平 */
	}
}

 /*
  * @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); /* 输出高电平 */
	 }
}



        然后在此基础上加入中断的函数,首先就是使能GPIO中断,前面讲到了GPIO引脚的中断是由引脚GPIOx_IMR寄存器所控制,那么中断使能函数如下

void gpio_enableint(GPIO_Type* base, unsigned int pin)
{ 
    base->IMR |= (1 << pin);
}

         有了使能函数,同样就有禁止函数引脚使用中断的函数

void gpio_disableint(GPIO_Type* base, unsigned int pin)
{ 
    base->IMR &= ~(1 << pin);
}

        以及对应的清楚中断标志位的函数,这些都是上面所提到的寄存器所控制的

void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
{
    base->ISR |= (1 << pin);
}

        然后揪下来就要开始编写 设置GPIO的中断配置功能的函数,首先代码如下

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

        代码看似很长,但其实很好理解,我们分两部分,以swtich语句为分割,首先swtich上面大致原理是这段代码的主要作用是根据pin号配置相应引脚的中断触发方式,并决定修改哪个中断控制寄存器(ICR1或ICR2)以及在该寄存器中的哪个位位置。它首先禁用当前pin号引脚的中断触发方式,然后根据pin号的大小选择合适的ICR寄存器,并计算出在该寄存器中应操作的位位置。
        先定义一个指向uint32_t类型的指针icr,大致作用就是访问中断控制寄存器。volatile关键字确保编译器在每次访问icr时都直接从内存中读取值,而不是使用可能已经缓存在寄存器中的值。
        定义一个无符号32位整数icrShift,用于存储将要操作的位的位置,在ICR寄存器中

        base->EDGE_SEL &= ~(1U << pin);就是如果在之后中断采取高电平或者低电平的方式触发的话,就禁用该引脚当前的边沿触发设置。
        然后ICR在前面也提高过,分别控制16个IO引脚,那么低16位就是ICR1,不低于16位就是ICR2,这里是对ICR寄存器的一个选择

        然后下面看到switch语句的部分,这部分是在完成什么事情呢,也就是选择每个GPIO的中断触发方式,以这个为例,比如我要让某个引脚低电平输出,那么我就将控制触发方式的寄存器(ICR),清零然后写入值,因为低电平触发是00,所以这里就写入00的值,以为着到时候就如果选择模式是低电平,那么就写入值00

case(kGPIO_IntLowLevel):
			*icr &= ~(3U << (2 * icrShift));
			break;

        然后下面的代码都是一样的道理,都是先将这个寄存器的值清零,也就是初始化,然后再根据选择的模式然后写入值进去,唯一有区别的地方就是最后一个,因为在前面ICR寄存器中可以看到,他只有低电平,高点平,上边沿和下边沿四种触发方式
        但是在我们的设置当中,却还有一种双边沿触发的方式,那么这个是通过控制哪个寄存器来实现的呢

case(kGPIO_IntRisingOrFallingEdge):
			base->EDGE_SEL |= (1U << pin);
			break;

        它的主要作用是,在GPIO(通用输入输出)接口中,GPIO_EDGE_SEL是一个特殊的寄存器,它允许开发者对中断的触发方式进行更细致的控制。通常,中断可以由多种条件触发,如电平变化(高电平、低电平)、边缘变化(上升沿、下降沿)等。GPIO_EDGE_SEL通过其各个位的状态,为GPIO引脚指定了是应该基于上升沿、下降沿还是两者都产生中断。
        所以双边沿以及边沿触发的不产生都是需要这个寄存器的控制

        然后剩下的函数只需要在头文件在中引用一下,然后GPIO的驱动文件我们就编写完成了。然后就开始创建我们的具体实验代码了,首先需要创建一个文件夹存放两个文件,分别名为exti,创建文件bsp_exti.c和bsp_exti.h,下面我会先放出两个文件的代码

#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); /* 清除中断标志位 */
}



        首先将按键的GPIO进行复用,然后先初始化GPIO,最后设置GIC的初始化,包括对于中断功能以及对应中断号的初始化,上面其实我们就是在编写自己的库,然后使用自己的库开始编写具体的外部中断函数,就和以前写STM32的标准库是一个道理
        然后下面就是中断处理函数,也就是我们在触发中断后,需要执行的函数,这个就不难理解了,按下 KEY 就会打开蜂鸣器,再次按下就会关闭蜂鸣器,最后再将中断位清除

        主函数里面只需要执行灯一直在闪烁的代码就可以了,然后等待之后我们按下按键产生蜂鸣器鸣叫的中断



#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;

}

        因为添加了文件,最后再修改一下Makefile,确保能找到每个文件的路径

INCDIRS 		:= imx6ul \

				   bsp/clk \

				   bsp/led \

				   bsp/delay  \

				   bsp/beep \

				   bsp/gpio \

				   bsp/key \

				   bsp/exit \

				   bsp/int

				   			   

SRCDIRS			:= project \

				   bsp/clk \

				   bsp/led \

				   bsp/delay \

				   bsp/beep \

				   bsp/gpio \

				   bsp/key \

				   bsp/exit \

				   bsp/int

        然后最后只需要将代码烧写进去,我们的本次实验就算是完成了,这个中断对于imx6ul确实是一个全新的板块,与STM32的NVIC不同,MPU芯片普遍采取的都是GIC的中断模式,也是一个很复杂的适用于多核的中断模式
        博主也是才接触,所以周期有点长,以及在讲解的过程当中可能会有很多地方表述的不够清楚,所以希望各位在阅读的时候保持谅解,如果有问题欢迎讨论或者指针,那么中断的实验到这里就结束了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值