1、前言
在前面的文章《C语言裸机GPIO控制—基于I.MX6UL嵌入式SoC》中,链接如下:
https://www.cnblogs.com/Cqlismy/p/12445576.html
实现了I.MX6UL嵌入式SoC中通用输入/输出接口外设的输出功能,我们都知道I.MX6UL芯片上的IO口除了能作为输出,还能够作为输入,作为GPIO的输入功能后,能够读取到当前引脚的电平状态,最典型、最简单的应用就是按键,按键一般就两个状态,分别为按下和弹起,当我们将按键的另一端接到I.MX6UL上的IO引脚上,就可以通过读取这个IO引脚的电平值来判断当前按键状态是处于按下还是弹起状态。
2、GPIO按键输入原理
单个按键的硬件原理如下所示:
+E为目标板电源,为3.3V,按键K的另一端(Y)连接到I.MX6UL处理器的GPIO4_IO22这个IO引脚上面,GPIO4_IO22这个IO引脚复用功能为GPIO,方向为输入,默认上拉,当按键K未按下时,IO口的引脚状态为高电平,当按键K按下时,IO口直接和GND导通,所以此时IO口的引脚状态为低电平,由于按键的机械结构,当按键按下或者松开期间,会产生一定的抖动,所谓的抖动就是IO的电平会出现多次电平跳动,实际的按键波形图如上所示,如果我们不进行按键抖动消除处理的话,可能会产生误判的现象,有可能按键只按了一次,结果使用程序对IO口电平读取发现按键按下了多次。
使用软件进行消抖的最简单处理就是进行延时处理,延时跳过抖动时间后,再去读引脚的IO口电平,如果此时的IO电平为低,则说明按键确实是被按下了,有事件触发了,需要进行事件处理,一般的延时消抖时间大约10ms即可。
3、GPIO按键输入程序
对GPIO按键输入的实现原理有一定的了解后,接下来看看如何编程实现,编程思路如下:
- 使能相应的按键IO时钟;
- 设置IO口的复用模式为GPIO,设置GPIO方向为输入并设置IO引脚的电气属性;
- 主函数中读取GPIO的电平状态,判断是否有按键事件触发,如果有的话,进行相应的事件处理。
先修改gpio驱动模块的相关函数,bsp_gpio.h文件内容如下:
#ifndef __BSP_GPIO_H #define __BSP_GPIO_H #include "imx6ul.h" /* GPIO方向定义 */ typedef enum _gpio_pin_direction { kGPIO_DigitalInput = 0U, /* 表示GPIO方向输入 */ kGPIO_DigitalOutput = 1U, /* 表示GPIO方向输出 */ } gpio_pin_direction_t; typedef struct _gpio_pin_config { gpio_pin_direction_t direction; /* GPIO的方向 */ unsigned char value; /* GPIO输出时默认引脚电平值 */ } gpio_pin_config_t; /* GPIO操作函数相关声明 */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config); int gpio_pin_read(GPIO_Type *base, int gpio); void gpio_pin_write(GPIO_Type *base, int gpio, int value); #endif
gpio操作的相关函数定义在bsp_gpio.c文件中,内容如下所示:
#include "bsp_gpio.h" /** * gpio_init() - GPIO初始化函数 * * @base: 要初始化的GPIO组,例如:GPIO1、GPIO2 * @pin: 要初始化的GPIO组的pin编号 * @config: gpio引脚配置结构体 * * @return: 无 */ void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config) { if (config->direction == kGPIO_DigitalInput) /* GPIO方向为输入 */ base->GDIR &= ~(1 << pin); else { base->GDIR |= (1 << pin); gpio_pin_write(base, pin, config->value); } } /** * gpio_pin_read() - 读取GPIO引脚的电平 * * @base: 要读取的GPIO组,例如:GPIO1、GPIO2 * @pin: 要读取的GPIO组的pin编号 * * @return: 0表示低电平,1表示高电平 */ int gpio_pin_read(GPIO_Type *base, int pin) { return (((base->DR) >> pin) & 0x1); } /** * gpio_pin_write() - 设置GPIO引脚的电平 * * @base: 要设置的GPIO组,例如:GPIO1、GPIO2 * @pin: 要设置的GPIO组的pin编号 * @value: 引脚要设置的电平值:0->低电平,1->高电平 * * @return: 无 */ void gpio_pin_write(GPIO_Type *base, int pin, int value) { if (0 == value) base->DR &= ~(1 << pin); /* 引脚输出低电平 */ else base->DR |= (1 << pin); /* 引脚输出高电平 */ }
gpio_init()函数用完成GPIO的初始化,主要是设置GPIO的方向,输入或者输出,如果GPIO的方向是输出的话,还要设置GPIO的默认输出电平,gpio_pin_read()函数用来读取相应的IO引脚的当前电平状态,是通过读取GPIOx_DR这个寄存器来实现的,gpio_pin_write()函数用来设置GPIO引脚的电平值,也是通过设置GPIOx_DR这个寄存器来实现的。
接下来,进入到bsp目录下,新创建key子目录,用来存放和按键功能相关的驱动文件:
$ cd bsp/bsp $ mkdir key $ cd key $ touch bsp_key.h $ touch bsp_key.c
bsp_key.h文件用来定义按键的键值以及一些函数声明,内容如下:
#ifndef __BSP_KEY_H #define __BSP_KEY_H #include "imx6ul.h" #include "bsp_gpio.h" #include "bsp_delay.h" /* 定义按键值 */ enum _key_value { KEY_NONE = 0, KEY0_VALUE, } key_value; /* 和按键操作相关函数声明 */ void key_init(void); int key_get_value(void); #endif
按键驱动函数的实现在bsp_key.c中,内容如下:
#include "bsp_key.h" /** * key_init() - 按键初始化函数 * * @return: 无 */ void key_init(void) { gpio_pin_config_t key_config; /* 设置CSI_DATA01引脚IO复用为GPIO4_IO22 */ IOMUXC_SetPinMux(IOMUXC_CSI_DATA01_GPIO4_IO22, 0); /* 配置GPIO4_IO22引脚电气属性 * bit[16]: 0 关闭HYS * bit[15:14]: 11 pull up 22k * bit[13]: 1 pull * bit[12]: 1 pull/keeper使能 * bit[11]: 0 禁止开路输出 * bit[10:8] 000 reserved * bit[7:6]: 10 速度为100MHz * bit[5:3]: 000 关闭输出 * bit[2:1]: 00 reserved
* bit[0]: 0 低摆率 */ IOMUXC_SetPinConfig(IOMUXC_CSI_DATA01_GPIO4_IO22, 0xF080); /* 将按键相关的GPIO方向设置为输入 */ key_config.direction = kGPIO_DigitalInput;
key_config.value = 1; gpio_init(GPIO4, 22, &key_config); } /** * key_get_value() - 获取按键的键值 * * @return: 0表示没有按键按下,1表示按键按下 */ int key_get_value(void) { int ret = KEY_NONE; static unsigned char release = 1; /* 表示按键处于释放状态 */ if ((release == 1) && (gpio_pin_read(GPIO4, 22) == 0)) { /* 按键按下 */ delay(10); /* 延时消抖 */ if (gpio_pin_read(GPIO4, 22) == 0) { /* 再次判断按键是否按下 */ release = 0; ret = KEY0_VALUE; } } else if (gpio_pin_read(GPIO4, 22) == 1) { /* 按键未按下 */ release = 1; /* 标记按键处于释放状态 */ ret = KEY_NONE; } return ret; }
key_init()函数用来完成按键IO口引脚的初始化,主要是完成IO口引脚的复用功能配置以及IO引脚的电气属性配置,最后,需要设置GPIO的方向为输入,key_get_value()函数则是用来获取按键的键值,当按键按下后,该函数返回1,当按键处于松开状态时,该函数返回0,主要是通过读取IO口引脚的电平状态来进行判断的,这就是按键的驱动函数。
app.c文件内容如下:
#include "bsp_clk.h" #include "bsp_delay.h" #include "bsp_gpio.h" #include "bsp_led.h" #include "bsp_key.h" /** * main() - 主函数 */ int main(void) { int key_value = KEY_NONE; unsigned char led_state = OFF; system_clk_enable(); /* 外设时钟使能 */ led_init(); /* LED灯初始化 */ key_init(); /* 按键初始化 */ while (1) { key_value = key_get_value(); /* 获取按键状态 */ if (key_value == KEY0_VALUE) { led_state = !led_state; led_switch(led_state); key_value = KEY_NONE; } delay(10); } return 0; }
在循环里面不断获取按键的状态,也就是获取GPIO引脚的电平状态,如果按键按下,对应的LED灯状态会进行相应的翻转。
4、小结
本文主要简单介绍了I.MX6UL嵌入式SoC中的GPIO外设作为输入时,如何进行IO口电平状态的读取,并以一个简单的按键输入实例进行介绍。