前言:
本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。
引用:
正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》
正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档
正文:
本文是 “正点原子[第二期]Linux之ARM(MX6U)裸机篇--第15.6 讲” 的读书笔记。第15讲主要是介绍I.MX6U处理器GPIO中断控制实验。本节将参考正点原子的视频教程第15讲和配套的正点原子开发指南文档进行学习。
在第15.6讲视频教程中,正点原子会讲解了如何初始化gpio支持中断模式,并且如何启用gpio中断,如何关闭gpio中断,如何读取gpio的中断标志位,在中断处理结束之后如何清除gpio外设的中断标志位。
0. 概述
本章实验的功能和之前按键控制蜂鸣器的实验一样,只是按键采用中断的方式来处理。当按下KEY0以后就打开蜂鸣器,再次按下蜂鸣器KEY0就关闭蜂鸣器。
1. gpio按键电路原理分析
如之前按键KEY实验中分析的过的,正点原子I.MX6ULL ALPHA/Mini开发板的按键KEY0接 I.MX6ULL 处理器的 "UART1_CTS_B" 引脚。
当按键KEY0按下时,当默认按键松开时GPIO读取到高电平,当按键按下时GPIO读取到低电平。通过读取GPIO输入引脚的电平,当读取到低电平是说明按键KEY0被按下。
1.2 按键消抖
当然,如之前按键KEY0实验分析的那样还需要在检测到KEY0被按下时对按键进行消抖处理,防止按键按下瞬间的高低电平抖动操作按键被判断为多次误触发。
理想的按键的按键电平变化
实际按键的电平变化过程
2. 配置GPIO复用,GPIO方向
将I.MX6U处理器的 "UART1_CTS_B" 引脚配置为按键KEY0的gpio引脚输入input模式需要如下几步,我们在之前的按键实验里已经分析过,这里不再详细描述。
- 配置 "UART1_CTS_B" 引脚复用为 GPIO1_IO18 模式
- 配置"UART1_CTS_B" 引脚的电气特性,包括压摆率,下拉电阻,磁滞特性,等属性。
- 配置GPIOx_GIDR 寄存器,设置GPIO的输出方向是Output输出还是Input输入。
- 对于GPIO输出模式的话,写GPIOx_DR寄存器的对应bit位来控制GPIO的输出是低电平还是高高电平。
3. 配置GPIO中断模式
GPIO引脚作为input输入时可以根据GPIO引脚上的电平变化来触发中断,我们可以通过按键KEY0的 GPIO1_IO18 引脚的电平变化来触发gpio中断的方式来检测按键是否被按下。
在之前的按键KEY0实验里,我们通过处理器不断地循环来检测按键是否被按下,当处理器不停的循环检测按键的电平时处理器什么也不能干,这就造成了处理器资源的浪费。使用中断的方式可以极大的提高处理器系统的运行效率。
I.MX6U 处理器的GPIO输入接口中断通过寄存器 "GPIOx_ICR1, GPIOx_MR, GPIOx_ISR, GPIOx_EDGE_SEL" 来配置启用/关闭,GPIO输入中断触发类型(低电平触发,高电频触发,电平下降沿触发,电平上上沿触发,电平上升下降沿都触发)。
GPIO1_IO18 引脚的GPIO中断配置需要如下步骤:
- 设置GPIOx->ICR寄存器配置,对应引脚bit位的GPIO输入信号中断触发类型,选择是低电平触发,高电平触发,电平下降沿触发,还是电平上升沿触发。
- 设置GPIOx->MR寄存器,对应引脚bit位来使能或关闭gpio输入中断触发。
- 在gpio中断处理完成之后向 GPIOx->ISR 寄存器的对应引脚bit位写1,来清除gpio外设的中断标志位。
4. GPIO中断处理函数编写
如上面分析,GPIO中断驱动函数编写需要初始化GPIO引脚的IO口复用模式,IO口的电气特性,GPIO的工作模式为输入模式,GPIO口使能输入信号中断触发,GPIO口的输入信号中断触发方式,以及在处理完GPIO口的中断ID之后写1清楚对应的中断标志位。
在bsp/bsp_gpio.h中增加 GPIO 输入信号中断出发类型的枚举类型定义 ‘enum _gpio_int_mode’:
#ifndef __BSP_GPIO_H__
#define __BSP_GPIO_H__
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "cc.h"
typedef enum _gpio_int_mode
{
kGPIO_Noint = 0,
kGPIO_LowInterrupt = 1,
kGPIO_HighInterrupt = 2,
kGPIO_RaisingEdgeInt = 3,
kGPIO_FalllingEdgeInt = 4,
kGPIO_FallingOrRaisingEdgeInt = 5,
} gpio_int_mode_t;
typedef enum _gpio_pin_direction
{
kGPIO_DigitalOutput = 0U, /*输出*/
kGPIO_DigitalInput = 1U, /*输入*/
} gpio_pin_direction_t;
typedef struct _gpio_pin_config
{
gpio_pin_direction_t directioin; /* GPIO 方向:输入还是输出 */
int outputLogic; /* 如果是输出的话,默认输出电平 */
gpio_int_mode_t intMode; /* GPIO中断模式 */
} gpio_pin_config_t;
/* 初始化函数 */
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
void gpio_pinwrite(GPIO_Type *base, int pin, int value);
int gpio_pinread(GPIO_Type *base, int pin);
void gpio_intmode_config(GPIO_Type *base, int pin, gpio_int_mode_t mode);
void gpio_int_enable(GPIO_Type *base, int pin);
void gpio_int_disable(GPIO_Type *base, int pin);
void gpio_int_cleanFlag(GPIO_Type *base, int pin);
#endif
在 'gpio_init()’ 初始化函数总增加gpio输入信号中断触发模式配置函数,gpio中断触发启用函数,gpio中断触发关闭函数。
#include "bsp_gpio.h"
/*
* @description : GPIO初始化。
* @param - base : 要初始化的寄存器组。
* @param - pin : 要初始化的寄存器脚号。
* @param - config : GPIO 配置结构体。
* @return : 无
*/
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
if(config)
{
if(config->directioin == kGPIO_DigitalOutput){
base->GDIR |= (1<<pin); /* 输出 */
gpio_pinwrite(base, pin, config->outputLogic); /* 默认输出电平 */
}
else if(config->directioin == kGPIO_DigitalInput){
base->GDIR &= ~(1<<pin); /* 输入 */
}
if(config->directioin == kGPIO_DigitalInput){
gpio_intmode_config(base, pin, config->intMode); /* gpio中断触发方式配置 */
}
}
}
/*
* @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 == 0)
base->DR &= ~(1<<pin);
else
base->DR |= (1<<pin);
}
/*
* @description : 读取指定 GPIO 的电平值。
* @param – base : 要读取的 GPIO 组。
* @param – pin : 要读取的 GPIO 脚号。
* @return : 1 读取高电平, 0 读取低低电平。
*/
int gpio_pinread(GPIO_Type *base, int pin)
{
return ((base->DR >> pin) & 0x1);
}
/*
* @description : 启用GPIO指定pin的中断。
* @param – base : 要启用的 GPIO 组。
* @param – pin : 要启用的 GPIO 脚号。
* @return : 无。
*/
void gpio_int_enable(GPIO_Type *base, int pin)
{
base->IMR |= (1 << pin);
}
/*
* @description : 禁用GPIO指定pin的中断。
* @param – base : 要禁用的 GPIO 组。
* @param – pin : 要启用中断的 GPIO 脚号。
* @return : 无。
*/
void gpio_int_disable(GPIO_Type *base, int pin)
{
base->IMR &= ~(1 << pin);
}
/*
* @description : 清除GPIO指定pin的中断标志。
* @param – base : 要清除的 GPIO 组。
* @param – pin : 要清除中断的 GPIO 脚号。
* @return : 无。
*/
void gpio_int_cleanFlag(GPIO_Type *base, int pin)
{
/* 写1清除对应的中断标志位 */
base->ISR |= (1 << pin);
}
/*
* @description : 设置GPIO中断触发类型。
* @param – base : 要清除的 GPIO 组。
* @param – pin : 要清除中断的 GPIO 脚号。
* @return : 无。
*/
void gpio_intmode_config(GPIO_Type *base, int pin, gpio_int_mode_t mode)
{
volatile unsigned int *icr; /* volatile关键字要求立即写入内存,不要在寄存器中缓存 */
unsigned int icrShift = pin;
if(pin < 16)
{
icr = &base->ICR1;
}
else{
icr = &base->ICR2;
icrShift -= 16;
}
//我忘记了清理GPIO1->EDGESEL寄存器的对应pin
base->EDGE_SEL &= ~(1 << pin);
*icr &= ~(0x3 << (icrShift * 2)); /* 清零gpio->ICR 寄存器对应的位 */
switch(mode)
{
case kGPIO_Noint:
break;
case kGPIO_LowInterrupt:
*icr |= (0 << (icrShift * 2));
break;
case kGPIO_HighInterrupt:
*icr |= (1 << (icrShift * 2));
break;
case kGPIO_RaisingEdgeInt:
*icr |= (2 << (icrShift * 2));
break;
case kGPIO_FalllingEdgeInt:
*icr |= (3 << (icrShift * 2));
break;
case kGPIO_FallingOrRaisingEdgeInt:
base->EDGE_SEL |= (1 << pin);
break;
}
}
在 'gpio_intmode_config()' 中根据GPIO输入信号的中断触发类型设置对应的 GPIOx->CR1或者CPIOx->CR2寄存器对应bit位来选择GPIO输入信号中断触发类型。
4.1 KEY0 按键选择电平下降沿触发
根据正点原子I.MX6ULL ALPHA/Mini开发板的按键KEY0的电路原理图分析,当按下按键KEY0时GPIO1_IO18的输入信号从高电平变为低电平,所以我们的按键KEY0链接的GPIO1_IO18的输入中断触发方式选择为“电平下降沿触发”。
4.2 KEY0按键消抖
如之前KEY0按键实验中已经分析过得,我们物理按键不是理想的按键,在按键KEY0被按下的瞬间会有多次的电平高低跳变,按下按键一次可能或触发多次中断,所以需要在按键进行消抖。
5. 总结和实验遇到的问题记录
本节分析了将按键对应的GPIO口配置为gpio输入模式,并启用gpio口的中断触发,根据KEY0的电路原理图选择gpio口的中断触发类型。参考I.MX6ULL手册,将gpio输入模式的配置配置到 GPIOx->CR1, GPIOx->CRx, GPIOx->MR, GPIOx->ISR 寄存器里对应的bit位。