当前位置: 首页 > news >正文

imx6ull-驱动开发篇39——Linux INPUT 子系统实验

目录

硬件原理图

实验程序编写

修改设备树文件

添加 pinctrl 节点

添加 KEY 设备节点

按键驱动程序

keyinput.c

keyinputApp.c

Makefile文件

运行测试


在上一讲内容里,Linux INPUT 子系统,我们学习了input驱动编写的方法和函数,这一讲就是驱动源码。

硬件原理图

硬件原理图和芯片资料参考:裸机学习实验6——按键输入实验。

实验程序编写

修改设备树文件

正点原子I.MX6U-ALPHA 开发板上的 KEY 使用了 UART1_CTS_B 这个 PIN。

添加 pinctrl 节点

打开 imx6ull-alientek-emmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_key”的子节点。

“pinctrl_key ” 节点内容如下所示:

pinctrl_key: keygrp {fsl,pins = <MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0xF080 /* KEY0 */>;
};

添加 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";
};

最后在设备树文件中,检查PIN脚是否被其它外设使用,若有则屏蔽相关代码。

按键驱动程序

keyinput.c

使用input框架的按键驱动文件,代码如下:

#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/input.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define KEYINPUT_CNT		1			/* 设备号个数 	*/
#define KEYINPUT_NAME		"keyinput"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*//* 中断IO描述结构体 */
struct irq_keydesc {int gpio;								/* gpio */int irqnum;								/* 中断号     */unsigned char value;					/* 按键对应的键值 */char name[10];							/* 名字 */irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};/* keyinput设备结构体 */
struct keyinput_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;	/* 类 		*/struct device *device;	/* 设备 	 */struct device_node	*nd; /* 设备节点 */struct timer_list timer;/* 定义一个定时器*/struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键描述数组 */unsigned char curkeynum;				/* 当前的按键号 */struct input_dev *inputdev;		/* input结构体 */
};struct keyinput_dev keyinputdev;	/* key input设备 *//* @description		: 中断服务函数,开启定时器,延时10ms,*				  	  定时器用于按键消抖。* @param - irq 	: 中断号 * @param - dev_id	: 设备结构。* @return 			: 中断执行结果*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{struct keyinput_dev *dev = (struct keyinput_dev *)dev_id;dev->curkeynum = 0;dev->timer.data = (volatile long)dev_id;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */return IRQ_RETVAL(IRQ_HANDLED);
}/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后*				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。* @param - arg	: 设备结构变量* @return 		: 无*/
void timer_function(unsigned long arg)
{unsigned char value;unsigned char num;struct irq_keydesc *keydesc;struct keyinput_dev *dev = (struct keyinput_dev *)arg;num = dev->curkeynum;keydesc = &dev->irqkeydesc[num];value = gpio_get_value(keydesc->gpio); 	/* 读取IO值 */if(value == 0){ 						/* 按下按键 *//* 上报按键值 *///input_event(dev->inputdev, EV_KEY, keydesc->value, 1);input_report_key(dev->inputdev, keydesc->value, 1);/* 最后一个参数表示按下还是松开,1为按下,0为松开 */input_sync(dev->inputdev);} else { 									/* 按键松开 *///input_event(dev->inputdev, EV_KEY, keydesc->value, 0);input_report_key(dev->inputdev, keydesc->value, 0);input_sync(dev->inputdev);}	
}/** @description	: 按键IO初始化* @param 		: 无* @return 		: 无*/
static int keyio_init(void)
{unsigned char i = 0;char name[10];int ret = 0;keyinputdev.nd = of_find_node_by_path("/key");if (keyinputdev.nd== NULL){printk("key node not find!\r\n");return -EINVAL;} /* 提取GPIO */for (i = 0; i < KEY_NUM; i++) {keyinputdev.irqkeydesc[i].gpio = of_get_named_gpio(keyinputdev.nd ,"key-gpio", i);if (keyinputdev.irqkeydesc[i].gpio < 0) {printk("can't get key%d\r\n", i);}}/* 初始化key所使用的IO,并且设置成中断模式 */for (i = 0; i < KEY_NUM; i++) {memset(keyinputdev.irqkeydesc[i].name, 0, sizeof(name));	/* 缓冲区清零 */sprintf(keyinputdev.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */gpio_request(keyinputdev.irqkeydesc[i].gpio, name);gpio_direction_input(keyinputdev.irqkeydesc[i].gpio);	keyinputdev.irqkeydesc[i].irqnum = irq_of_parse_and_map(keyinputdev.nd, i);}/* 申请中断 */keyinputdev.irqkeydesc[0].handler = key0_handler;keyinputdev.irqkeydesc[0].value = KEY_0;for (i = 0; i < KEY_NUM; i++) {ret = request_irq(keyinputdev.irqkeydesc[i].irqnum, keyinputdev.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, keyinputdev.irqkeydesc[i].name, &keyinputdev);if(ret < 0){printk("irq %d request failed!\r\n", keyinputdev.irqkeydesc[i].irqnum);return -EFAULT;}}/* 创建定时器 */init_timer(&keyinputdev.timer);keyinputdev.timer.function = timer_function;/* 申请input_dev */keyinputdev.inputdev = input_allocate_device();keyinputdev.inputdev->name = KEYINPUT_NAME;
#if 0/* 初始化input_dev,设置产生哪些事件 */__set_bit(EV_KEY, keyinputdev.inputdev->evbit);	/* 设置产生按键事件          */__set_bit(EV_REP, keyinputdev.inputdev->evbit);	/* 重复事件,比如按下去不放开,就会一直输出信息 		 *//* 初始化input_dev,设置产生哪些按键 */__set_bit(KEY_0, keyinputdev.inputdev->keybit);	
#endif#if 0keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endifkeyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);/* 注册输入设备 */ret = input_register_device(keyinputdev.inputdev);if (ret) {printk("register input device failed!\r\n");return ret;}return 0;
}/** @description	: 驱动入口函数* @param 		: 无* @return 		: 无*/
static int __init keyinput_init(void)
{keyio_init();return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit keyinput_exit(void)
{unsigned int i = 0;/* 删除定时器 */del_timer_sync(&keyinputdev.timer);	/* 删除定时器 *//* 释放中断 */for (i = 0; i < KEY_NUM; i++) {free_irq(keyinputdev.irqkeydesc[i].irqnum, &keyinputdev);}/* 释放input_dev */input_unregister_device(keyinputdev.inputdev);input_free_device(keyinputdev.inputdev);
}module_init(keyinput_init);
module_exit(keyinput_exit);
MODULE_LICENSE("GPL");

关键代码分析如下:

keyinput_dev设备结构体中,定义一个 input_dev 指针变量。

struct input_dev *inputdev;		/* input结构体 */

timer_function函数,上报输入事件,使用 input_report_key函数上报按键事件以及按键值,用input_sync 函数上报一个同步事件。

if(value == 0)  /* 按下按键 */
{ 	/* 上报按键值 *///input_event(dev->inputdev, EV_KEY, keydesc->value, 1);input_report_key(dev->inputdev, keydesc->value, 1);/* 最后一个参数表示按下还是松开,1为按下,0为松开 */input_sync(dev->inputdev);}
else { 									/* 按键松开 *///input_event(dev->inputdev, EV_KEY, keydesc->value, 0);input_report_key(dev->inputdev, keydesc->value, 0);input_sync(dev->inputdev);}	

keyio_init函数中,使用 input_allocate_device 函数申请 input_dev,然后设置相应的事件以及事件码。最后使用 input_register_device函数向 Linux 内核注册 input_dev。

	/* 申请input_dev */keyinputdev.inputdev = input_allocate_device();keyinputdev.inputdev->name = KEYINPUT_NAME;
#if 0/* 初始化input_dev,设置产生哪些事件 */__set_bit(EV_KEY, keyinputdev.inputdev->evbit);	/* 设置产生按键事件          */__set_bit(EV_REP, keyinputdev.inputdev->evbit);	/* 重复事件,比如按下去不放开,就会一直输出信息 		 *//* 初始化input_dev,设置产生哪些按键 */__set_bit(KEY_0, keyinputdev.inputdev->keybit);	
#endif#if 0keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |= BIT_MASK(KEY_0);
#endifkeyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);/* 注册输入设备 */ret = input_register_device(keyinputdev.inputdev);

keyinput_exit函数中,当注销 input 设备驱动的时候使用 input_unregister_device 函数注销掉前面注册的 input_dev,最后使用 input_free_device 函数释放掉前面申请的 input_dev。

	/* 释放input_dev */input_unregister_device(keyinputdev.inputdev);input_free_device(keyinputdev.inputdev);

keyinputApp.c

测试app文件,代码如下:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <linux/input.h>/* 定义一个input_event变量,存放输入事件信息 */
static struct input_event inputevent;/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;int err = 0;char *filename;filename = argv[1];if(argc != 2) {printf("Error Usage!\r\n");return -1;}fd = open(filename, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", filename);return -1;}while (1) {err = read(fd, &inputevent, sizeof(inputevent));if (err > 0) { /* 读取数据成功 */switch (inputevent.type) {case EV_KEY:if (inputevent.code < BTN_MISC) { /* 键盘键值 */printf("key %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");} else {printf("button %d %s\r\n", inputevent.code, inputevent.value ? "press" : "release");}break;/* 其他类型的事件,自行处理 */case EV_REL:break;case EV_ABS:break;case EV_MSC:break;case EV_SW:break;}} else {printf("读取数据失败\r\n");}}return 0;
}

当我们向 Linux 内核成功注册 input_dev 设备以后,会在/dev/input 目录下生成一个名为“eventX(X=0….n)”的文件,这个/dev/input/eventX 就是对应的 input 设备文件。

我们读取这个文件就可以获取到输入事件信息,使用 read函数读取输入设备文件,也就是/dev/input/eventX,读取到的数据按照 input_event 结构体组织起来。

Makefile文件

makefile文件只需要修改 obj-m 变量的值,改为keyinput.o。

代码如下:

KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := keyinput.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

运行测试

编译代码:

make -j32 //编译makefile文件
arm-linux-gnueabihf-gcc keyinputApp.c -o keyinputApp  //编译测试app

编译成功以后,就会生成一个名为“keyinput.ko”的驱动模块文件,和keyinputApp 这个应用程序。

将编译出来 keyinput.ko 和 keyinputApp 这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录 lib/modules/4.1.15 中。

在加载 keyinput.ko 驱动模块之前,先看一下/dev/input 目录下都有哪些文件,输入如下命令:

ls /dev/input -l

看/dev/input 目录下是否有名为“eventX(X=0….n)”的文件。

输入如下命令加载 keyinput.ko 这个驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe keyinput.ko //加载驱动模块

当驱动模块加载成功,以后再来看一下/dev/input 目录下有哪些文件:

ls /dev/input -l

名为“eventX(X=0….n)”的文件,是否有新增?新增的eventX文件,就是我们注册的驱动所对应的设备文件。

然后测试我们的驱动,比如设备文件是event1,输入如下测试命令:

./keyinputApp /dev/input/event1

按下开发板上的 KEY 按键,结果如图:

我们按下或者释放开发板上的按键以后都会在终端上输出相应的内容,提示我们哪个按键按下或释放了,在 Linux 内核中 KEY_0 为 11。

也可以直接使用 hexdump 命令来查看/dev/input/event1 文件内容,输入如下命令:

hexdump /dev/input/event1

按下按键值,打印如下:

这就是input_event 类型的原始事件数据值,采用十六进制表示,这些原始数据的含义如下:

  • type 为事件类型,EV_KEY 事件值为 1, EV_SYN 事件值为0。
  • code 为事件编码,KEY_0 这个按键编号为 11,对应的十六进制为 0xb。

前四行的意思就是:

  • 按键(KEY_0) 按下事件。
  •  EV_SYN 同步事件,因为每次上报按键事件以后都要同步的上报一个 EV_SYN 事件。
  • 按键(KEY_0) 松开事件
  •  EV_SYN 同步事件。
http://www.xdnf.cn/news/1360585.html

相关文章:

  • 【基础算法】初识搜索:递归型枚举与回溯剪枝
  • 【ElasticSearch】springboot整合es案例
  • Smooze Pro for mac 鼠标手势增强软件
  • 【C语言练习】青蛙跳台阶
  • Vue状态管理工具pinia的使用以及Vue组件通讯
  • 强光干扰下检出率↑93%!陌讯多模态融合算法在充电桩车位占用检测的实战解析
  • 力扣【1277. 统计全为1的正方形子矩阵】——从暴力到最优的思考过程
  • 【网络运维】Shell脚本编程:函数
  • 深度学习之第二课PyTorch与CUDA的安装
  • AOSP构建指南:从零开始的Android源码之旅
  • Docker 容器(一)
  • 【Docker基础】Docker-compose常用命令实践(三):镜像与配置管理
  • 【零代码】OpenCV C# 快速开发框架演示
  • 电路学习(四)二极管
  • 【计算机视觉】CaFormer
  • Java:LinkedList的使用
  • 【Protues仿真】基于AT89C52单片机的温湿度测量
  • 【文献阅读】生态恢复项目对生态系统稳定性的影响
  • 在JavaScript中,比较两个数组是否有相同元素(交集)的常用方法
  • 解决编译osgEarth中winsocket2.h找不到头文件问题
  • Node.js自研ORM框架深度解析与实践
  • C++11新特性全面解析(万字详解)
  • Starlink第三代终端和第二代终端的差异性有哪些?
  • Flink SQL执行SQL错误排查
  • MySQL的安装和卸载指南(入门到入土)
  • ZKmall模块商城的推荐数据体系:从多维度采集到高效存储的实践
  • 从“小麻烦”到“大难题”:Spring Boot 配置文件的坑与解
  • 04-ArkTS编程语言入门
  • 使用UE5开发《红色警戒3》类战略养成游戏的硬件配置指南
  • 源码导航页