引言

        前面几章试验都是讲解如何使用 I.MX6U 的 GPIO 输出控制功能,I.MX6U IO 不仅能作为输出,而且也可以作为输入。I.MX6U-ALPHA 开发板上有一个按键,按键连接了一个 IO,将这个 IO 配置为输入功能,读取这个 IO 的值即可获取按键的状态(按下或松开)。本章通过这个按键来控制蜂鸣器的开关,通过本章的学习你将掌握如何将 I.MX6UL IO 作为输入来使用。

7.1 按键输入简介

        按键就两个状态:按下或弹起,将按键连接到一个 IO 上,通过读取这个 IO 的值就知道按键是按下的还是弹起的。至于按键按下的时候是高电平还是低电平要根据实际电路来判断。前面几章我们都是讲解 I.MX6U 的 GPIO 作为输出使用,当 GPIO 连接按键的时候就要做为输入使用。关于 I.MX6U GPIO 已经在第八章详细的讲解了,本章我们的主要工作就是配置按键所连接的 IO 为输入功能,然后读取这个 IO 的值来判断按键是否按下。
        I.MX6U-ALPHA 开发板上有一个按键 KEY0,本章我们将会编写代码通过这个 KEY0 按键
来控制开发板上的蜂鸣器,按一下 KEY0 蜂鸣器打开,再按一下蜂鸣器就关闭。

7.2 硬件原理分析

本试验我们用到的硬件有:
1LED LED0
2)蜂鸣器。
31 个按键 KEY0
按键 KEY0 的原理图如图 15.2.1 所示:

 图 7.2.1 按键原理图

        从图 7.2.1 可以看出,按键 KEY0 是连接到 I.MX6U UART1_CTS 这个 IO 上的,KEY0
接了一个 10K 的上拉电阻,因此 KEY0 没有按下的时候 UART1_CTS 应该是高电平,当 KEY0
按下以后 UART1_CTS 就是低电平。

7.3 实验程序编写

        本试验在上一章试验例程的基础上完成,重新创建 VSCode 工程,工作区名字为“key”,在工程目录的 bsp 文件夹中创建名为“key”和“gpio”两个文件夹。按键相关的驱动文件都放到“key”文件夹中,本章试验我们对 GPIO 的操作编写一个函数集合,也就是编写一个 GPIO驱动文件,GPIO 的驱动文件放到“gpio”文件夹里面。
        新建 bsp_gpio.c bsp_gpio.h 这两个文件,将这两个文件都保存到刚刚创建的 bsp/gpio
件夹里面,然后在 bsp_gpio.h 文件夹里面输入如下内容:
示例代码 7.3.1 bsp_gpio.h 文件代码
1 #ifndef _BSP_GPIO_H
2 #define _BSP_GPIO_H
3 #define _BSP_KEY_H
4 #include "imx6ul.h"
5 /***************************************************************
6 
7 文件名 : bsp_gpio.h
8 
9 版本 : V1.0
10 描述 : GPIO 操作文件头文件。
11 其他 :
12 
13 
14 ***************************************************************/
15
16 /* 枚举类型和结构体定义 */
17 typedef enum _gpio_pin_direction
18 {
19 kGPIO_DigitalInput = 0U, /* 输入 */
20 kGPIO_DigitalOutput = 1U, /* 输出 */
21 } gpio_pin_direction_t;
22
23 /* GPIO 配置结构体 */
24 typedef struct _gpio_pin_config
25 {
26 gpio_pin_direction_t direction; /* GPIO 方向:输入还是输出
*/
27 uint8_t outputLogic; /* 如果是输出的话,默认输出电平 */
28 } gpio_pin_config_t;
29
30
31 /* 函数声明 */
32 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config);
33 int gpio_pinread(GPIO_Type *base, int pin);
34 void gpio_pinwrite(GPIO_Type *base, int pin, int value);
35
36 #endif
        bsp_gpio.h 中定义了一个枚举类型 gpio_pin_direction_t 和结构体 gpio_pin_config_t,枚举类
gpio_pin_direction_t 表示 GPIO 方向,输入或输出。结构体 gpio_pin_config_t GPIO 的配置
结构体,里面有 GPIO 的方向和默认输出电平两个成员变量。在 bsp_gpio.c 中输入如下所示内容:
示例代码 7.3.2 bsp_gpio.c 文件代码
1 #include "bsp_gpio.h"
2 /***************************************************************
3 
4 文件名 : bsp_gpio.h
5 
6 版本 : V1.0
7 描述 : GPIO 操作文件。
8 其他 :
9 
10 
11 ***************************************************************/
12
13 /*
14 * @description : GPIO 初始化。
15 * @param - base : 要初始化的 GPIO 组。
16 * @param - pin : 要初始化 GPIO 在组内的编号。
17 * @param - config : GPIO 配置结构体。
18 * @return :
19 */
20 void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
21 {
22
if(config->direction == kGPIO_DigitalInput) /* 输入 */
23
{
24
base->GDIR &= ~( 1 << pin);
25
}
26 else
/* 输出 */
27
{
28
base->GDIR |= 1 << pin;
29
gpio_pinwrite(base,pin, config->outputLogic);/* 默认输出电平 */
30
}
31 }
32
33 /*
34 * @description : 读取指定 GPIO 的电平值 。
35 * @param – base : 要读取的 GPIO 组。
36 * @param - pin : 要读取的 GPIO 脚号。
37 * @return :
38 */
39 int gpio_pinread(GPIO_Type *base, int pin)
40 {
41 return (((base->DR) >> pin) & 0x1);
42 }
43
44 /*
45 * @description : 指定 GPIO 输出高或者低电平 。
46 * @param – base : 要输出的的 GPIO 组。
47 * @param - pin : 要输出的 GPIO 脚号。
48 * @param – value : 要输出的电平,1 输出高电平, 0 输出低低电平
49 * @return :
50 */
51 void gpio_pinwrite(GPIO_Type *base, int pin, int value)
52 {
53 if (value == 0U)
54 {
55 base->DR &= ~(1U << pin); /* 输出低电平 */
56 }
57 else
58 {
59 base->DR |= (1U << pin); /* 输出高电平 */
60 }
61 }
        文件 bsp_gpio.c 中有三个函数:gpio_initgpio_pinread gpio_pinwrite,函数 gpio_init
于初始化指定的 GPIO 引脚,最终配置的是 GDIR 寄存器,此函数有三个参数,这三个参数的
含义如下:
base: 要初始化的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。
pin要初始化 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18
config: 要初始化的 GPIO 配置结构体,用来指定 GPIO 配置为输出还是输入。
        函数 gpio_pinread 是读取指定的 GPIO 值,也就是读取 DR 寄存器的指定位,此函数有两个
参数和一个返回值,参数含义如下:
base: 要读取的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。
pin要读取的 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18
返回值:读取到的 GPIO 值,为 0 或者 1
        函数 gpio_pinwrite 是控制指定的 GPIO 引脚输入高电平(1)或者低电平(0),就是设置 DR
存器的指定位,此函数有三个参数,参数含义如下:
base: 要设置的 GPIO 所属于的 GPIO 组,比如 GPIO1_IO18 就属于 GPIO1 组。
pin要设置的 GPIO 在组内的标号,比如 GPIO1_IO18 在组内的编号就是 18
value: 要设置的值,1(高电平)或者 0(低电平)
        我们以后就可以使用函数 gpio_init 设置指定 GPIO 为输入还是输出,使用函数 gpio_pinread
gpio_pinwrite 来读写指定的 GPIO,文件 bsp_gpio.c 文件就讲解到这里。
接下来编写按键驱动文件,新建 bsp_key.c bsp_key.h 这两个文件,将这两个文件都保存
到刚刚创建的 bsp/key 文件夹里面,然后在 bsp_key.h 文件夹里面输入如下内容:
示例代码 7.3.3 bsp_key.h 文件代码
1 #ifndef _BSP_KEY_H
2 #define _BSP_KEY_H
3 #include "imx6ul.h"
4 /***************************************************************
5 
6 文件名 : bsp_key.h
7 
8 
9 描述 : 按键驱动头文件。
10 其他 :
11 
12 
13 ***************************************************************/
14
15 /* 定义按键值 */
16 enum keyvalue{
17
KEY_NONE = 0,
18
KEY0_VALUE,
19 };
20
21 /* 函数声明 */
22 void key_init(void);
23 int key_getvalue(void);
24
25 #endif
        bsp_key.h 文件中定义了一个枚举类型:keyvalue,此枚举类型表示按键值,因为 I.MX6U
ALPHA 开发板上只有一个按键,因此枚举类型里面只到 KEY0_VALUE。在 bsp_key.c 中输入
如下所示内容:
示例代码 7.3.4 bsp_key.c 文件代码
1 #include "bsp_key.h"
2 #include "bsp_gpio.h"
3 #include "bsp_delay.h"
4 /***************************************************************
5 
6 文件名 : bsp_key.c
7 
8 
9 描述 : 按键驱动文件。
10 其他 :
11 
12 
13 ***************************************************************/
14
15 /*
16 * @description : 初始化按键
17 * @param :
18 * @return :
19 */
20 void key_init(void)
21 {
22 gpio_pin_config_t key_config;
23
24
/* 1、初始化 IO 复用, 复用为 GPIO1_IO18 */
25
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
26
27
/* 2、、配置 UART1_CTS_B IO 属性
28 *bit 16:0 HYS 关闭
29 *bit [15:14]: 11 默认 22K 上拉
30 *bit [13]: 1 pull 功能
31 *bit [12]: 1 pull/keeper 使能
32 *bit [11]: 0 关闭开路输出
33 *bit [7:6]: 10 速度 100Mhz
34 *bit [5:3]: 000 关闭输出
35 *bit [0]: 0 低转换率
36 */
37
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
38
39
/* 3、初始化 GPIO GPIO1_IO18 设置为输入*/
40
key_config.direction = kGPIO_DigitalInput;
41
gpio_init(GPIO1,18, &key_config);
42
43 }
44
45 /*
46 * @description : 获取按键值
47 * @param :
48 * @return : 0 没有按键按下,其他值:对应的按键值
49 */
50 int key_getvalue(void)
51 {
52 int ret = 0;
53 static unsigned char release = 1; /* 按键松开 */
54
55 if((release==1)&&(gpio_pinread(GPIO1, 18) == 0)) /* KEY0 按下 */
56 {
57 delay(10); /* 延时消抖
*/
58 release = 0; /* 标记按键按下 */
59 if(gpio_pinread(GPIO1, 18) == 0)
60 ret = KEY0_VALUE;
61 }
62 else if(gpio_pinread(GPIO1, 18) == 1)
/* KEY0 未按下 */
63 {
64 ret = 0;
65 release = 1; /* 标记按键释放 *
66
}
67
68
return ret;
69 }
        bsp_key.c 中一共有两个函数:key_init key_getvaluekey_init 是按键初始化函数,用来
初始化按键所使用的 UART1_CTS 这个 IO。函数 key_init 先设置 UART1_CTS 复用为
GPIO1_IO18,然后配置 UART1_CTS 这个 IO 为速度为 100MHz,默认 22K 上拉。最后调用函
gpio_init 来设置 GPIO1_IO18 为输入功能。
        函数 key_getvalue 用于获取按键值,此函数没有参数,只有一个返回值,返回值表示按键
值,返回值为 0 的话就表示没有按键按下,如果返回其他值的话就表示对应的按键按下了。获
取按键值其实就是不断的读取 GPIO1_IO18 的值,如果按键按下的话相应的 IO 被拉低,那么
GPIO1_IO18 值就为 0,如果按键未按下的话 GPIO1_IO18 的值就为 1。此函数中静态局部变量
release 表示按键是否释放。
        “示例代码 7.3.4”中的 57 行是按键消抖延时函数,延时时间大约为 10ms,用于消除按
键抖动。理想型的按键电压变化过程如图 7.3.1 所示:

图 7.3.1 理想的按键电压变化过程
        在图 7.3.1 中,按键没有按下的时候按键值为 1,当按键在 t1 时刻按键被按下以后按键值
就变为 0,这是最理想的状态。但是实际的按键是机械结构,加上刚按下去的一瞬间人手可能
也有抖动,实际的按键电压变化过程如图 7.3.2 所示:

图 7.3.2 实际的按键电压变化过程
        在图 7.3.2 t1 时刻按键被按下,但是由于抖动的原因,直到 t2 时刻才稳定下来,t1
t2 这段时间就是抖动。一般这段时间就是十几 ms 左右,从图 7.3.2 中可以看出在抖动期间会
有多次触发,如果不消除这段抖动的话软件就会误判,本来按键就按下了一次,结果软件读取
IO 值发现电平多次跳变以为按下了多次。所以我们需要跳过这段抖动时间再去读取按键的 IO
值,也就是至少要在 t2 时刻以后再去读 IO 值。在“示例代码 7.3.4”中的 57 行是延时了大约
10ms 后再去读取 GPIO1_IO18 IO 值,如果此时按键的值依旧是 0,那么就表示这是一次有
效的按键触发。
        按键驱动就讲解到这里,最后就是 main.c 文件的内容了,在 main.c 中输入如下代码:
示例代码 7.3.5 main.c 文件代码
/**************************************************************
文件名 : main.c
描述 : I.MX6U 开发板裸机实验 7 按键输入实验
其他 : 本实验主要学习如何配置 I.MX6U GPIO 作为输入来使用,通过
开发板上的按键控制蜂鸣器的开关。
**************************************************************/
1 #include "bsp_clk.h"
2 #include "bsp_delay.h"
3 #include "bsp_led.h"
4 #include "bsp_beep.h"
5 #include "bsp_key.h"
6
7 /*
8 * @description : main 函数
9 * @param :
10 * @return :
11 */
12 int main(void)
13 {
14 int i = 0;
15 int keyvalue = 0;
16 unsigned char led_state = OFF;
17 unsigned char beep_state = OFF;
18
19 clk_enable(); /* 使能所有的时钟 */
20 led_init(); /* 初始化 led */
21 beep_init(); /* 初始化 beep */
22 key_init(); /* 初始化 key */
23
24 while(1)
25 {
26 keyvalue = key_getvalue();
27 if(keyvalue)
28 {
29 switch (keyvalue)
30 {
31 case KEY0_VALUE:
32 beep_state = !beep_state;
33 beep_switch(beep_state);
34 break;
35 }
36 }
37 i++;
38 if(i==50)
39 {
40 i = 0;
41 led_state = !led_state;
42 led_switch(LED0, led_state);
43 }
44 delay(10);
45 }
46 return 0;
47 }
        main.c 函数先初始化 led 灯、蜂鸣器和按键,然后在 while(1)循环中不断的调用函数
key_getvalue 来读取按键值,如果 KEY0 按下的话就打开/关闭蜂鸣器。LED0 作为系统提示指
示灯闪烁,闪烁周期大约为 500ms。本章例程的软件编写就到这里结束了,接下来就是编译下
载验证了。

7.4 编译下载验证

Makefile 使用第十三章编写的通用 Makefile,修改变量 TARGET key,在变量 INCDIRS
SRCDIRS 中追加“bsp/gpio”和“bsp/key”,修改完成以后如下所示:
示例代码 7.4.1.1 Makefile 文件代码
1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= key
3
4 /* 省略掉其它代码...... */
5
6 INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay \
10  bsp/beep \
11 bsp/gpio \
12 bsp/key
13
14 SRCDIRS := project \
15 bsp/clk \
16 bsp/led \
17 bsp/delay \
18 bsp/beep \
19 bsp/gpio \
20 bsp/key
21
22 /* 省略掉其它代码...... */
23
24 clean:
25 rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
2 行修改变量 TARGET 为“key”,也就是目标名称为“key”。
1112 行在变量 INCDIRS 中添加 GPIO 和按键驱动头文件(.h)路径。
1920 行在变量 SRCDIRS 中添加 GPIO 和按键驱动文件(.c)路径。
链接脚本就使用第十三章试验中的链接脚本文件 imx6ul.lds 即可。
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐