IMX6UL裸机实现C语言按键输入实验

2019-12-02 12:13发布

按键就两个状态:按下或弹起,将按键连接到一个 IO 上,通过读取这个 IO 的值就知道按键是按下的还是弹起的。至于按键按下的时候是高电平还是低电平要根据实际电路来判断。当 GPIO 连接按键的时候就要做为输入使用。我们的主要工作就是配置按键所连接的 IO 为输入功能,然后读取这个 IO 的值来判断按键是否按下。

I.MX6U-ALPHA 开发板上有一个按键KEY0,我们将会编写代码通过这个 KEY0 按键来控制开发板上的蜂鸣器,按一下KEY0 蜂鸣器打开,再按一下蜂鸣器就关闭。

本试验我们用到的硬件有:

1) LED 灯 LED0。

2)蜂鸣器。

3)1 个按键KEY0。

按键KEY0 的原理图如图所示:

111

按键原理图

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

本试验在上一篇文章《IMX6UL裸机实现C语言蜂鸣器实验》中例程的基础上完成,重新创建 VSCode 工程,工作区名字为“key”,在工程目录的bsp 文件夹中创建名为“key”和“gpio”两个文件夹。按键相关的驱动文件都放到“key”文件夹中,本章试验我们对 GPIO 的操作编写一个函数集合,也就是编写一个 GPIO驱动文件,GPIO 的驱动文件放到“gpio”文件夹里面。

新建 bsp_gpio.c 和 bsp_gpio.h 这两个文件,将这两个文件都保存到刚刚创建的 bsp/gpio 文件夹里面,然后在bsp_gpio.h 文件夹里面输入如下内容:

11 #ifndef _BSP_GPIO_H

12 #define _BSP_GPIO_H

13 #define _BSP_KEY_H

14 #include "imx6ul.h"

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 中输入如下所示内容:

11 #include "bsp_gpio.h"

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 {

50 return (((base->DR) >> pin) & 0x1);

42 }

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_init、gpio_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 文件夹里面输入如下内容:

11 #ifndef _BSP_KEY_H

12 #define _BSP_KEY_H

13 #include "imx6ul.h"

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,此枚举类型表示按键值,因为开发板上只有一个按键,因此枚举类型里面只到 KEY0_VALUE。在 bsp_key.c 中输入如下所示内容:

11 #include "bsp_key.h"

12 #include "bsp_gpio.h"

13 #include "bsp_delay.h"

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_getvalue,key_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 表示按键是否释放。

示例代码中的 57 行是按键消抖延时函数,延时时间大约为 10ms,用于消除按键抖动。理想型的按键电压变化过程如图所示:

按键按下

222

理想的按键电压变化过程

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

按键按下

333

实际的按键电压变化过程

在图中 t1 时刻按键被按下,但是由于抖动的原因,直到 t2 时刻才稳定下来,t1 到t2 这段时间就是抖动。一般这段时间就是十几 ms 左右,从图中可以看出在抖动期间会有多次触发,如果不消除这段抖动的话软件就会误判,本来按键就按下了一次,结果软件读取IO 值发现电平多次跳变以为按下了多次。所以我们需要跳过这段抖动时间再去读取按键的 IO值,也就是至少要在 t2 时刻以后再去读IO 值。在示例代码中的 57 行是延时了大约10ms 后再去读取 GPIO1_IO18 的 IO 值,如果此时按键的值依旧是 0,那么就表示这是一次有效的按键触发。

按键驱动就讲解到这里,最后就是 main.c 文件的内容了,在 main.c 中输入如下代码:

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。例程的软件编写就到这里结束了,接下来就是编译下载验证了。

Makefile 使用前面文章编写的通用 Makefile,修改变量 TARGET 为 beep,在变量 INCDIRS和 SRCDIRS 中追加“bsp/beep”,修改完成以后如下所示:

1 CROSS_COMPILE ?= arm-linux-gnueabihf-

2 TARGET ?= key

3

4 /* 省略掉其它代码...... */

5

6 INCDIRS := imx6ul \

7 bsp/clk \

8 bsp/led \

9 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”。

第 11、12 行在变量 INCDIRS 中添加 GPIO 和按键驱动头文件(.h)路径。第 19、20 行在变量SRCDIRS 中添加 GPIO 和按键驱动文件(.c)路径。

链接脚本就使用上一篇文章《IMX6UL裸机实现C语言蜂鸣器实验》中的链接脚本文件 imx6ul.lds 即可。