imx6ull-裸机学习实验6——按键输入实验

发布于:2025-07-09 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

 

回顾

硬件原理图

实验程序

gpio.h

gpio.c

函数gpio_init

函数 gpio_pinread

函数gpio_pinwrite

GPIO_TYPE结构体

key.h

key.c

GPIO配置

​编辑函数key_init

函数key_getvalue

按键消抖

main.c


 

回顾

从学习实验1-5我们驱动了LED灯和蜂鸣器,但它们都是GPIO作为输出来驱动的,这一个实验叫做按键输入实验,也就是将GPIO作为输入。按键连接了一个 IO,将这个 IO 配置为输入功能,读取这个 IO 的值即可获取按键的状态(按下或松开)。

硬件原理图

我们使用正点原子的I.MX6U-ALPHA 开发板上有一个按键 KEY0,按键就两个状态:按下或弹起,将按键连接到一个 IO 上,通过读取这个 IO 的值就知道按键是按下的还是弹起的。所以我们可以在检测到按键状态变化时,来控制开发板上的蜂鸣器,按一下 KEY0 ,蜂鸣器打开,再按一下蜂鸣器就关闭。

从图中可以看出:按键 KEY0接了一个 10K 的上拉电阻。

按键KEY0连接的GPIO口为UART1_CTS,KEY0接了一个 10K 的上拉电阻,因此 KEY0 没有按下的时候 UART1_CTS 应该是高电平,当 KEY0按下以后 UART1_CTS 就是低电平。

实验程序

在工程目录的 bsp 文件夹中创建名为“key”和“gpio”两个文件夹。按键相关的驱动文件都放到“key”文件夹中:

gpio.h

bsp_gpio.h 中定义了一个枚举类型 gpio_pin_direction_t 和结构体 gpio_pin_config_t

枚举类型 gpio_pin_direction_t 表示 GPIO 方向,输入或输出。

结构体 gpio_pin_config_t 是 GPIO 的配置结构体,里面有 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

gpio.c

文件 bsp_gpio.c 中有三个函数: gpio_initgpio_pinreadgpio_pinwrite。

函数gpio_init

函数 gpio_init 用于初始化指定的 GPIO 引脚,最终配置的是 GDIR 寄存器,有3个参数:

  • base: 要初始化的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。
  • pin: 要初始化 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18。
  • config: 要初始化的 GPIO 配置结构体,也就是在gpio.h中定义的GPIO 的配置结构体,用来指定 GPIO 配置为输出还是输入。
    /*
     * @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);/* 设置默认输出电平 */
    	}
    }

函数 gpio_pinread

函数 gpio_pinread 是读取指定的 GPIO 值,也就是读取 DR 寄存器的指定位。

此函数有两个参数和一个返回值:

  • base: 要读取的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。
  • pin: 要读取的 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18。
  • 返回值: 读取到的 GPIO 值,为 0 或者 1。
     /*
      * @description	 : 读取指定GPIO的电平值 。
      * @param - base	 : 要读取的GPIO组。
      * @param - pin	 : 要读取的GPIO脚号。
      * @return 		 : 无
      */
     int gpio_pinread(GPIO_Type *base, int pin)
     {
    	 return (((base->DR) >> pin) & 0x1);
     }

函数gpio_pinwrite

函数 gpio_pinwrite 是控制指定的 GPIO 引脚输入高电平(1)或者低电平(0),就是设置 DR 寄存器的指定位,有三个参数:

  • base: 要设置的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。
  • pin: 要设置的 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18。
  • value: 要设置的值, 1(高电平)或者 0(低电平)。
 /*
  * @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_TYPE结构体

可以看见上面三个函数的第一个传参都是这个类型的结构体指针,这个结构体也是SDK官方头文件里提供的,封装了我们在学习实验1中详细解释过的GPIO驱动的8个寄存器,原型如下:

/** GPIO - Register Layout Typedef */

typedef struct {

  __IO uint32_t DR;                                /**< GPIO data register, offset: 0x0 */
  __IO uint32_t GDIR;                              /**< GPIO direction register, offset: 0x4 */
  __I  uint32_t PSR;                               /**< GPIO pad status register, offset: 0x8 */
  __IO uint32_t ICR1;                              /**< GPIO interrupt configuration register1, offset: 0xC */
  __IO uint32_t ICR2;                              /**< GPIO interrupt configuration register2, offset: 0x10 */
  __IO uint32_t IMR;                               /**< GPIO interrupt mask register, offset: 0x14 */
  __IO uint32_t ISR;                               /**< GPIO interrupt status register, offset: 0x18 */
  __IO uint32_t EDGE_SEL;                          /**< GPIO edge select register, offset: 0x1C */
} GPIO_Type;

接下来编写按键驱动文件,新建 bsp_key.c 和 bsp_key.h 这两个文件。

key.h

bsp_key.h 文件中定义了一个枚举类型: keyvalue, 此枚举类型表示按键值。

#ifndef _BSP_KEY_H
#define _BSP_KEY_H
#include "imx6ul.h"

/* 定义按键值 */
enum keyvalue{
	KEY_NONE   = 0,
	KEY0_VALUE,
	KEY1_VALUE,
	KEY2_VALUE,
};

/* 函数声明 */
void key_init(void);
int key_getvalue(void);


#endif

key.c

bsp_key.c 中一共有两个函数: key_initkey_getvalue。用来初始化key按键对应的GPIO口作为输入,和获取按键值。

GPIO配置

由硬件原理图已经知道:按键KEY0连接的GPIO口为UART1_CTS。查阅手册:

将这个IO口的MUX_MODE的值设为0101,就是复用为GPIO1_IO18.

#define IOMUXC_UART1_CTS_B_GPIO1_IO18                        
        0x020E008CU, 0x5U, 0x00000000U, 0x0U, 0x020E0318U

然后我们要将这个IO口配置为输入:


函数key_init

key_init 是按键初始化函数,用来初始化按键所使用的 UART1_CTS 这个 IO。

函数 key_init 先设置 UART1_CTS 复用为GPIO1_IO18,然后配置 UART1_CTS 这个 IO 为速度为 100MHz,默认 22K 上拉。最后调用函数 gpio_init 来设置 GPIO1_IO18 为输入功能。

void key_init(void)
{	
	gpio_pin_config_t key_config;
	
	/* 1、初始化IO复用, 复用为GPIO1_IO18 */
	IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0);

	/* 2、、配置UART1_CTS_B的IO属性	
	  0xF080 = 1111 0000 1011 0000
	 */
	IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
	
	/* 3、初始化GPIO */
	//GPIO1->GDIR &= ~(1 << 18);	/* GPIO1_IO18设置为输入 */	
	key_config.direction = kGPIO_DigitalInput;
	gpio_init(GPIO1,18, &key_config);
	
}

函数key_getvalue

函数 key_getvalue 用于获取按键值,此函数没有参数,只有一个返回值,返回值表示按键值。

返回值为 0 的话就表示没有按键按下,如果返回其他值的话就表示对应的按键按下了。
 

/*
 * @description	: 获取按键值 
 * @param 		: 无
 * @return 		: 0 没有按键按下,其他值:对应的按键值
 */
int key_getvalue(void)
{
	int ret = 0;
	static unsigned char release = 1; /* 按键松开 */ 

	if((release==1)&&(gpio_pinread(GPIO1, 18) == 0)) 		/* KEY0 	*/
	{	
		delay(10);		/* 延时消抖 		*/
		release = 0;	/* 标记按键按下 */
		if(gpio_pinread(GPIO1, 18) == 0)
			ret = KEY0_VALUE;
	}
	else if(gpio_pinread(GPIO1, 18) == 1)
	{
		ret = 0;
		release = 1; 	/* 标记按键释放 */
	}

	return ret;	
}

按键消抖

理想型的按键电压变化过程如图:

但是现实和理想总是存在差距的,实际的按键是机械结构,加上刚按下去的一瞬间人手可能也有抖动,实际的按键电压变化过程如图:

在上图中, t1 时刻按键被按下,但是由于抖动的原因,直到 t2 时刻才稳定下来, t1 到t2 这段时间就是抖动。一般这段时间就是十几 ms 左右。

在抖动期间会有多次触发,如果不消除这段抖动的话软件就会误判,本来按键就按下了一次,结果软件读取IO 值发现电平多次跳变以为按下了多次。
所以我们需要跳过这段抖动时间再去读取按键的 IO值,也就是至少要在 t2 时刻以后再去读 IO 值。在代码中,延时了大约10ms 后再去读取 GPIO1_IO18 的 IO 值,如果此时按键的值依旧是 0,那么就表示这是一次有效的按键触发。


main.c

在main.c 主函数中,先初始化 led 灯、蜂鸣器和按键,然后在 while(1)循环中不断的调用函数key_getvalue 来读取按键值。

如果 KEY0 按下的话就打开/关闭蜂鸣器。 LED0 作为系统提示指示灯闪烁,闪烁周期大约为 500ms。

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"

/*
 * @description	: main函数
 * @param 		: 无
 * @return 		: 无
 */
int main(void)
{
	int i = 0;
	int keyvalue = 0;
	unsigned char led_state = OFF;
	unsigned char beep_state = OFF;
	
	clk_enable();		/* 使能所有的时钟 			*/
	led_init();			/* 初始化led 			*/
	beep_init();		/* 初始化beep	 		*/
	key_init();			/* 初始化key 			*/

	while(1)			
	{	
		keyvalue = key_getvalue();
		if(keyvalue)
		{
			switch ((keyvalue))
			{
				case KEY0_VALUE:
					beep_state = !beep_state;
					beep_switch(beep_state);
					break;
			}
		}

		i++;
		if(i==50)
		{
			i = 0;
			led_state = !led_state;
			led_switch(LED0, led_state);
		}
		delay(10);
	}

	return 0;
}

好的,代码就编写完成了,记得修改makefile文件哦~修改编译目标,添加上源文件和头文件路径。

然后就是下载验证啦。如果代码运行正常的话 LED0 会以大约 500ms 周期闪烁, 按下开发板上的 KEY0 按键,蜂鸣器打开,再按下 KEY0按键,蜂鸣器关闭。

 


网站公告

今日签到

点亮在社区的每一天
去签到