Linux学习第20天:Linux按键输入驱动开发: 大道至简 量入为出

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长


中国文化博大精深,太极八卦,阴阳交合,变化无穷。在程序的开发中也是这样,数字0和1也是同样的道理。就本节来说,输入和输出的万千变化才是程序驱动开发的基石。所以题目为大道至简,就是要说明这个道理。量入为出,不去纠结到底是先有输入还是先有输出。工作中接触最多的传感器就是输入,之后驱动开发根据采集到的输入进行判断比对后输出,也就是我要说的量入为出。

本节开始学习第一个输入驱动的开发---按键驱动开发。主要内容包括按键驱动原理、硬件设计原理、驱动开发和测试。其中最重要的是驱动的开发和测试。

本节的思维导图如下:

一、按键驱动原理

在驱动程序中使用一个整形变量来表示按键值,应用程序通过 read 函数来读取按键值,判断按键有没有按下。保存按键值的变量就是个共享资源,驱动程序要向其写入按键值,应用程序要读取按键值。我们使用原子操作对这个按键值【整型数】进行赋值及读取。

二、硬件原理图

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

三、驱动开发

1.修改设备树文件

cpp 复制代码
1 #ifndef _BSP_GPIO_H
2 #define _BSP_GPIO_H
3 #define _BSP_KEY_H
4 #include "imx6ul.h"
5 /***************************************************************
6 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
7 文件名 : bsp_gpio.h
8 作者 : 左忠凯
9 版本 : V1.0
10 描述 : GPIO 操作文件头文件。
11 其他 : 无
12 论坛 : www.openedv.com
13 日志 : 初版 V1.0 2019/1/4 左忠凯创建
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;

枚举类型 gpio_pin_direction_t 表示 GPIO 方向,输入或输出。结构体 gpio_pin_config_t 是 GPIO 的配置结构体,里面有 GPIO 的方向和默认输出电平两个成员变量。

cpp 复制代码
1 #include "bsp_gpio.h"
2 /***************************************************************
3 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
4 文件名 : bsp_gpio.h
5 作者 : 左忠凯
6 版本 : V1.0
7 描述 : GPIO 操作文件。
8 其他 : 无
9 论坛 : www.openedv.com
10 日志 : 初版 V1.0 2019/1/4 左忠凯创建
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 }

函数 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。

2.按键驱动

cpp 复制代码
15 /* 定义按键值 */
16 enum keyvalue{
17 KEY_NONE = 0,
18 KEY0_VALUE,
19 };

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

cpp 复制代码
1 #include "bsp_key.h"
2 #include "bsp_gpio.h"
3 #include "bsp_delay.h"
4 /***************************************************************
5 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
6 文件名 : bsp_key.c
7 作者 : 左忠凯
8 版本 : V1.0
9 描述 : 按键驱动文件。
10 其他 : 无
11 论坛 : www.openedv.com
12 日志 : 初版 V1.0 2019/1/4 左忠凯创建
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_initkey_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 行是按键消抖延时函数,这个很多地方都有讲的,很简单,就不赘述了。

cpp 复制代码
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,修改变量 TARGET 为 key,在变量 INCDIRS和 SRCDIRS 中追加" bsp/gpio" 和" bsp/key"。

cpp 复制代码
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)路径。

使用 Make 命令编译代码,编译成功以后使用软件 imxdownload 将编译完成的 key.bin 文件

下载到 SD 卡中,命令如下:

chmod 777 imxdownload //给予 imxdownload 可执行权限,一次即可

./imxdownload key.bin /dev/sdd //烧写到 SD 卡中

烧写成功以后将 SD 卡插到开发板的 SD 卡槽中,然后复位开发板。如果代码运行正常的

话 LED0 会以大约 500ms 周期闪烁, 按下开发板上的 KEY0 按键,蜂鸣器打开,再按下 KEY0

按键,蜂鸣器关闭。

五、总结

这一节是非常重要的一个内容,作为驱动开发基石的输入和输出一定要学会记牢。主要内容包括按键驱动原理、硬件设计原理、驱动开发和测试。其中最重要的是驱动的开发和测试。


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

相关推荐
正在努力的小河42 分钟前
Linux设备树简介
linux·运维·服务器
荣光波比44 分钟前
Linux(十一)——LVM磁盘配额整理
linux·运维·云计算
墨雨听阁1 小时前
8.18网络编程——基于UDP的TFTP文件传输客户端
网络·网络协议·学习·udp
小晶晶京京1 小时前
day35-负载均衡
运维·网络·网络协议·学习·负载均衡
LLLLYYYRRRRRTT1 小时前
WordPress (LNMP 架构) 一键部署 Playbook
linux·架构·ansible·mariadb
轻松Ai享生活1 小时前
crash 进程分析流程图
linux
清风6666662 小时前
基于51单片机自动智能浇花系统设计
stm32·单片机·嵌入式硬件·毕业设计·课程设计
大路谈数字化3 小时前
Centos中内存CPU硬盘的查询
linux·运维·centos
long3163 小时前
构建者设计模式 Builder
java·后端·学习·设计模式
luoqice4 小时前
linux下查看 UDP Server 端口的启用情况
linux