正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.7讲 GPIO中断实验-编写按键中断驱动

前言:

本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。

引用:

正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com

《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》

正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档

正文:

本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第15.7 讲” 的读书笔记。第15讲主要是介绍I.MX6U处理器GPIO中断控制实验。本节将参考正点原子的视频教程第15讲和配套的正点原子开发指南文档进行学习。

在第15.7讲视频教程中,正点原子会讲解对之前的15.1,15.2,15.3,15.4,15.5,15.6 讲的一个综合,将前面介绍过的Cortex-A7中断向量表,Reset中断服务函数,IRQ中断服务函数,通用IRQ中断驱动处理,GPIO输入信号中断触发方式,GPIO输入信号中断使能,GPIO中断状态标记位清除,等所有的东西综合到一起,来完成我们最后的按键GPIO中断实验。

1. 按键GPIO中断编写

在前面几讲的基础上,已经分析了按键GPIO作为输入模式时gpio input输入信号的中断触发类型,gpio输入信号中断使能,IRQ中断服务函数,IRQ通用中断ID驱动函数,等函数的编写,本节我们只需要调用这些函数,注册 GPIO1_IO18 的中断ID处理函数,并完成按键GPIO中断实验来控制蜂鸣器和LED灯的行为就可以了。

1.1 GPIO1_IO18输入信号对应的中断ID

《I.MX6ULL参考手册》章节“3.2 Cortex A7 interrupts”中查到 GPIO1 组的引脚GPIO1_IO16 ~ GPIO1_IO31 这16个gpio引脚触发的中断ID都是 99 (99=67+32,32个的SGI,PPI中断ID号偏移)。

1.2 编写GPIO1_IO18的gpio中断ID处理函数

'bsp/bsp_exti.c'源码如下

#include "bsp_exti.h"
#include "bsp_gpio.h"
#include "bsp_int.h"
#include "bsp_beep.h"
#include "bsp_delay.h"
#include "bsp_led.h"

/* 外部设备中断源初始化 */
void exti_init(void)
{
	/* GPIO1_IO18 */
	
	gpio_pin_config_t config;

	/* 1. 初始化IO复用,复用为GPIO1_IO18 */
	IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);

	/* 2. 设置 UART1_CTS_B IO 的电气特性 */
	IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xf080);


	/* 3. 初始化 GPIO1_IO18 设置为输入 */
	config.directioin = kGPIO_DigitalInput;
	config.intMode = kGPIO_FalllingEdgeInt;
	gpio_init(GPIO1, 18, &config);


	/* 启用GIC IRQ */
	GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);

	/* 注册IRQ处理函数 */
	system_irqhandler_register(GPIO1_Combined_16_31_IRQn, gpio1_io18_int_handler, NULL);

	/* 启用gpio中断 */
	gpio_int_enable(GPIO1, 18);
}


void gpio1_io18_int_handler(IRQn_Type irq, void *userParam)
{
	
	static unsigned char state = OFF;
	//static unsigned char led_state = ON;
	int n = 20;
	int led_state = 0;


	/* 检查 GPIO1_IO18 的ISR */
	if(GPIO1->ISR & (1<<18))
	{
		/* 消抖,等待10ms */
		delay(10);

		/* 等待10ms再次读取gpio input电平值 */
		if(gpio_pinread(GPIO1, 18) == 0)
		{
			state = !state;
			beep_switch(state);
		}
	}

	/* 清除外设中断标志位 */
	gpio_int_cleanFlag(GPIO1, 18);
}

在 'bsp/bsp_exti.c' 中的 'exti_init()' 函数中调用注册 GPIO1_IO18 对应中断ID的中断处理函数

在'bsp/bsp_exti.c' (extern interupt)文件里的初始化按键KEY0对应的 GPIO1_IO18 的GPIO引脚时按照如下的顺序:

  1. 设置UART1_CTS_B 引脚 IO 口复用为 GPIO,即 GPIO1_IO18。
  2. 设置UART1_CTS_B 引脚 IO 口的电气特性,包括压摆率,下拉电阻,磁滞特性,上拉/保持器 等特性。
  3. 设置GPIO1_IO18的 GPIO 口输出方向为 INPUT 输入模式,设置GPIO1_IO18 引脚的输入信号中断触发方式为电平下降沿触发。通过调用之前写的 'gpio_init()' 函数来配置GPIO的方向和GPIO口输入中断电平触发方式。
  4. 使能GIC中断控制器中GPIO1_IO18 外设对应的中断ID号。
  5. 注册GPIO1_IO18外设中断端号对应的中断处理函数。
  6. 使能GPIO1_IO18外设中断,通过GPIOx->MR 寄存器对应gpio引脚的bit位置1使能gpio输入信号中断触发。 

在使能GPIO中断的时候需要注意中断ID的使能顺序,应该先注册gpio外设中断ID的中断处理函数,然后再使能gpio外设输入中断,防止在还没有注册中断ID处理函数的时候外设中断发生了造成不可预知的后果。

在注册的按键GPIO1_IO18中断ID的中断处理函数中,延时了10ms来进行按键消抖,按键需要消抖的原因之前已经分析过多次。在收到按键GPIO1_IO18 电平下降沿中断之后,进入GPIO1_IO18的中断ID处理函数延时10ms后再次检查gpio引脚上的电平值,如果还是低电平说明此次是有效的按键按下输入。

注意:在中断处理函数中原则上应该禁止使用延时函数,我们这里仅仅是作为示例。

处理完GPIO的外设中断ID之后,必须向对应的GPIO中断标志寄存器位写1清除对应的中断标志。

2. 编译烧写SD卡验证按键GPIO中断实验结果

译修改主频后源码烧录SD卡验证主频修改是否生效,预期按下按键之后触发中断在中断中打开蜂鸣器,蜂鸣器鸣叫;再次按下按键之后触发中断在中断中关闭蜂鸣器,蜂鸣器停止鸣叫。

为本地实验验证的结果是,按下按键可以成功的控制蜂鸣器开启和关闭。

3. 总结和实验遇到的问题记录

本节GPIO中断驱动实验根据正点原子的指导文档和视频教程进行学习,中断实验的教程比较长也是目前在正点原理ARM I.MX6ULL 第二期裸机开发教程里最复杂的章节,同时也是我个人理解起来最难得一个章节。

但是,中断机制是每一种处理器中最核心的一个部分,不彻底的理解和搞明白中断(异常)处理机制的每一个细节,每一条中断服务函数的汇编语句的含义,中断里寄存器是怎么入栈保存的怎么出栈的,中断现场是怎么恢复了,中断返回又是怎么跳转的,就不能说理解了这个处理器。不仅如此,后面整个Linux设备驱动开发也是和中断紧密相关的,可以说中断是现代处理器和现代操作系统的底层基石和核心概念。只有理解清楚了中断,才能搞明白外设驱动,Linux系统调用,操作系统分时复用,进场调度,Linux内核可抢占,Linux内核互斥和同步的各种锁原语。

本节通过正点原子的ARM裸机开发GPIO中断实验教程,第一次上手并且直观的使用感受了GIC中断控制器,中断触发,中断服务函数,中断返回,等只是。这也是第一次屏蔽掉操作系统的干扰,从硬件最底层操作中断和中断处理函数以及中断返回,这个实验对我来讲还是收获很大的,一次难得的实验经验。

  • Cortex-A7 GIC中断控制器,GIC中断控制分文分发器端(Distributor)和CPU接口端(CPU Interface)两个逻辑模块。
  • Cortex-A7 的中断向量表,有8个中断向量
  • 所有的外设中断源都连接到GIC控制器,通过GIC控制触发IRQ中断给Cortex-A7内核,为了区分不同的外设中断源,定义了中断ID,中断ID的编号从0~1019共1020个中断ID号。中断ID分为SGI(软件生成中断),PPI(私有外设中断,某个CPU内核独有的中断),SPI(共享外设中断,所有的CPU内核共享的外设中断)。
  • IRQ中断服务函数中需要注意对中断现场的保护,在IRQ中断处理函数入口处需要将 lr, r0-r3,r12, spsr 寄存器入栈保存这样才能在IRQ中断处理函数出口处恢复被中断现场。IRQ中断服务函数里需要考虑到中断嵌套的情况,IRQ中断处理函数中每次切到新的处理器运行模式都应该注意中断现场保护将可能修改的寄存器入栈保护。

3.1 问题1:GPIO中断实验开始的时候,发现开发板运行异常LED灯不亮,发现是时钟实验里 PLL2 错误的初始化方式导致的位。

GPIO中断实验开始的时候,发现开发板运行异常LED灯不亮,经过反复的调试和对比正点原子示例源码发现是时钟实验里 PLL2 错误的初始化方式导致的位。

原来我是看完正点原子的视频教程,自己写的上面的那种方法来初始化PLL2的PLL2_PFD0,PLL2_PFD1,PLL2_PFD2,PLL2_PFD3,这样写之后发现编译烧录SD卡后上电验证开发板运行异常LED灯不能正常闪烁。

经过反复的调试并且对比正点原子提供的裸机开发示例源码,才发现应该使用下面的这种写法来配置PLL2的PFD0,1,2,3的分频值,先将需要写的值写到一个中间变量里最后将中间变量赋值给 PLL2 的 PFD_528 寄存器,这样次设置后系统才能正常执行。

我自己猜测原来那种写法错误的原因如下:

错误的写法,从写寄存器的方式来讲是写了5次 PLL2 PFD_528寄存器,并且在错误的写法里第42行 "PFD_528 &= ~(0x3F3F3F3F)" 一次性将PLL2 PFD0/1/2/3 的分频值都设置成了0,这是一个非法的分配值(合法的frac值范围为12~35),这样第一步就有可能造成了PLL2 PFD0/1/2/3分频全部配置异常和PFD0/1/2/3关联的系统总线时钟就异常了,进而造成整个处理器状态异常。

3.2 问题2:IRQ中断服务函数结尾使用 'sub pc, lr, #4' 无法返回到中断现场继续执行

 IRQ中断服务函数结尾使用 'sub pc, lr, #4' 无法返回到中断现场继续执行,对比正点原子提供的示例源码应该在IRQ中断服务函数的结尾使用 'subs, pc, lr, #4' 来返回被中断现场继续执行。那么这里为什么要使用 'subs' ,而不是 'sub' 呢?

问题原因分析:
IRQ中断服务函数返回时,不仅仅需要更新 pc=l4-4 跳回到被中断的指令现场继续执行也需要恢复被中断指令处现场之前的CPSR寄存器。'sub pc, lr, #4' 仅仅更新了pc寄存器的值 pc=lr-4 但是 cpsr 寄存器的值没有被回复到被中断之前现场的CPSR的状态,所以使用'sub pc, lr, #4'返回后程序执行异常。
百度搜索查阅博客,并参考ARMv7的指令手册,在ARMv7指令手册中明确说明在IRQ中断(异常)返回的时候需要使用 'subs pc, lr, #4' 注意这里用的是 'subs' 指令多了一个 's' 。 'subs pc, lr, #4' 不仅仅会跟新pc=lr-4 调到被中断指令现场急促执行同时也会讲 spsr 恢复到cpsr,这样被中断现场的CPSR寄存器就恢复了。
使用 'subs pc, lr, #4' 才能正确的从IRQ中断(异常)返回到被中断现场继续执行。

《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》章节B9.3.20 对这里有做解释,在IRQ中断(异常)返回的时候应该使用 'subs' ,'subs pc, lr, #<const>' 不仅仅会更新PC指针同时也会讲 spsr 恢复到 cpsr。 

 4. 结束

本文至此结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值