嵌入式Linux开发24——Linux 按键输入实验
文章目录Linux 下按键驱动原理程序编写1.修改设备树文件1.1 添加 pinctrl 节点1.2 添加 KEY 设备节点1.3 检查 PIN 是否被其他外设使用2.按键驱动程序编写3.编写测试 APP运行测试 之前章我们都是使用的 GPIO 输出功能,还没有用过 GPIO 输入功能,本章我们就来学习一下如果在 Linux 下编写 GPIO 输入驱动程序,我们使用上一篇博客讲述的原子操作来对按
文章目录
之前章我们都是使用的 GPIO 输出功能,还没有用过 GPIO 输入功能,本章我们就来学习一下如果在 Linux 下编写 GPIO 输入驱动程序,我们使用上一篇博客讲述的原子操作来对按键值进行保护。
Linux 下按键驱动原理
按键驱动和 LED 驱动原理上来讲基本都是一样的,都是操作 GPIO,只不过一个是读取GPIO 的高低电平,一个是从 GPIO 输出高低电平。本文我们实现按键输入,在驱动程序中使用一个整形变量来表示按键值,应用程序通过 read 函数来读取按键值,判断按键有没有按下。在这里,这个保存按键值的变量就是个共享资源,驱动程序要向其写入按键值,应用程序要读取按键值。所以我们要对其进行保护,对于整形变量而言我们首选的就是原子操作,使用原子操作对变量进行赋值以及读取。 Linux 下的按键驱动原理很简单,接下来开始编写驱动。
注意,本篇文章内容只是为了演示 Linux 下 GPIO 输入驱动的编写,实际中的按键驱动并不会采用本文中所讲解的方法, Linux 下的 input 子系统专门用于输入设备!
程序编写
1.修改设备树文件
1.1 添加 pinctrl 节点
开发板上的KEY 使用了 UART1_CTS_B 这个 PIN,打开 imx6ull-14x14-evk.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点,节点内容如下所示:
pinctrl_key: keygrp {
fsl,pins = <
MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */
>;
};
1.2 添加 KEY 设备节点
在根节点“/”下创建 KEY 节点,节点名为“key”,节点内容如下:
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
status = "okay";
};
1.3 检查 PIN 是否被其他外设使用
在本文实验中蜂鸣器使用的 PIN 为 UART1_CTS_B,因此先检查 PIN 为 UART1_CTS_B 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO1_IO18这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。
设备树编写完成以后使用“ make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-14x14-evk.dtb 文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中查看“key”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图所示:
2.按键驱动程序编写
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define KEY_CNT 1 /* 设备号个数 */
#define KEY_NAME "key" /* 名字 */
/* 定义按键值 */
#define KEY0VALUE 0XF0 /* 按键值 */
#define INVAKEY 0X00 /* 无效的按键值 */
/* key设备结构体 */
struct key_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int key_gpio; /* key所使用的GPIO编号 */
atomic_t keyvalue; /* 按键值 */
};
struct key_dev keydev; /* key设备 */
/*
* @description : 初始化按键IO,open函数打开驱动的时候
* 初始化按键所使用的GPIO引脚。
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
keydev.nd = of_find_node_by_path("/key");
if (keydev.nd== NULL) {
return -EINVAL;
}
keydev.key_gpio = of_get_named_gpio(keydev.nd ,"key-gpio", 0);
if (keydev.key_gpio < 0) {
printk("can't get key0\r\n");
return -EINVAL;
}
printk("key_gpio=%d\r\n", keydev.key_gpio);
/* 初始化key所使用的IO */
gpio_request(keydev.key_gpio, "key0"); /* 请求IO */
gpio_direction_input(keydev.key_gpio); /* 设置为输入 */
return 0;
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int key_open(struct inode *inode, struct file *filp)
{
int ret = 0;
filp->private_data = &keydev; /* 设置私有数据 */
ret = keyio_init(); /* 初始化按键IO */
if (ret < 0) {
return ret;
}
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
int value;
struct key_dev *dev = filp->private_data;
if (gpio_get_value(dev->key_gpio) == 0) { /* key0按下 */
while(!gpio_get_value(dev->key_gpio)); /* 等待按键释放 */
atomic_set(&dev->keyvalue, KEY0VALUE);
} else {
atomic_set(&dev->keyvalue, INVAKEY); /* 无效的按键值 */
}
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf, &value, sizeof(value));
return ret;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int key_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations key_fops = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release = key_release,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init mykey_init(void)
{
/* 初始化原子变量 */
atomic_set(&keydev.keyvalue, INVAKEY);
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (keydev.major) { /* 定义了设备号 */
keydev.devid = MKDEV(keydev.major, 0);
register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME); /* 申请设备号 */
keydev.major = MAJOR(keydev.devid); /* 获取分配号的主设备号 */
keydev.minor = MINOR(keydev.devid); /* 获取分配号的次设备号 */
}
/* 2、初始化cdev */
keydev.cdev.owner = THIS_MODULE;
cdev_init(&keydev.cdev, &key_fops);
/* 3、添加一个cdev */
cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);
/* 4、创建类 */
keydev.class = class_create(THIS_MODULE, KEY_NAME);
if (IS_ERR(keydev.class)) {
return PTR_ERR(keydev.class);
}
/* 5、创建设备 */
keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
if (IS_ERR(keydev.device)) {
return PTR_ERR(keydev.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit mykey_exit(void)
{
/* 注销字符设备驱动 */
cdev_del(&keydev.cdev);/* 删除cdev */
unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */
device_destroy(keydev.class, keydev.devid);
class_destroy(keydev.class);
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiajia2020");
结构体 key_dev 为按键的设备结构体,原子变量 keyvalue 用于记录按键值。函数 keyio_init 用于初始化按键,从设备树中获取按键的 gpio 信息,然后设置为输入。将按键的初始化代码提取出来,将其作为独立的一个函数有利于提高程序的模块化设计。
key_read 函数,应用程序通过 read 函数读取按键值的时候此函数就会执行。读取按键 IO 的电平时,如果为 0 的话就表示按键按下了,如果按键按下的话就等待按键释放。按键释放以后标记按键值为KEY0VALUE。
3.编写测试 APP
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/* 定义按键值 */
#define KEY0VALUE 0XF0
#define INVAKEY 0X00
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
int keyvalue;
if(argc != 2){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
/* 打开key驱动 */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
/* 循环读取按键值数据! */
while(1) {
read(fd, &keyvalue, sizeof(keyvalue));
if (keyvalue == KEY0VALUE) { /* KEY0 */
printf("KEY0 Press, value = %#X\r\n", keyvalue); /* 按下 */
}
}
ret= close(fd); /* 关闭文件 */
if(ret < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
循环读取/dev/key 文件,也就是循环读取按键值,并且将按键值打印出来。
运行测试
将编译出来的 key.ko 和 keyApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入如下命令加载 key.ko 驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe key.ko //加载驱动
驱动加载成功以后如下命令来测试:
./keyApp /dev/key
输入上述命令以后终端显示如图所示:
按下开发板上的 KEY0 按键, keyApp 就会获取并且输出按键信息,如图所示:
当我们按下 KEY0 以后就会打印出“KEY0 Press, value = 0XF0”,表示按键按下。但是有时候按下一次 KEY0 但是会输出好几行“KEY0 Press,value = 0XF0”,这是因为我们的代码没有做按键消抖处理。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)