当前位置: 首页 > 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.dtcms.com/a/348044.html

相关文章:

  • 05-ArkUI界面开发
  • Solidity学习笔记
  • ZKmall模块商城的推荐数据体系:从多维度采集到高效存储的实践
  • 用 Bright Data MCP Server 构建实时数据驱动的 AI 情报系统:从市场调研到技术追踪的自动化实战
  • 青少年软件编程(python五级)等级考试试卷-客观题(2024年6月)
  • 数据库原理及应用_数据库基础_第2章关系数据库标准语言SQL_数据的维护
  • Adobe CS6所有系列绿色免安装版,Photoshop 6 Adobe Illustrator CS6 等绿色版
  • Spring创建的方式
  • 使用 Frida 运行时检测 Android 应用的真实权限状态 (App Ops)
  • 第4章栈和队列:顺序队——基本结构
  • Java 基础学习总结(211)—— Apache Commons ValidationUtils:让参数校验从 “体力活“ 变 “优雅事“
  • Vue状态管理工具pinia的使用以及Vue组件通讯
  • 一个byte表示多个bool属性的功能
  • 高并发AI服务部署方案:vLLM、TGI、FastChat性能压测报告
  • CSS 进阶用法
  • Read View是实现MVCC的三大前提之一,那么它是在什么时候建立的
  • frida安装配置及其使用方法
  • 一个奇怪的问题-Python会替代Java吗?技术语言之争的真相-优雅草卓伊凡
  • Java 大视界 -- Java 大数据在智能交通智能公交系统中的乘客流量预测与车辆调度优化
  • 程序里的依赖和中间件的依赖冲突,怎么解决
  • Docling:一个基于AI驱动的免费文档解析工具
  • Python性能优化实战(二):让循环跑得比博尔特还快
  • [身份验证脚手架] 应用布局如何构建
  • 初学python的我开始Leetcode题-16
  • CTFshow系列——命令执行web49-52
  • assert使用方法
  • Redis 高可用开发指南
  • 力扣594:最和谐子序列
  • 客流特征识别误报率↓76%!陌讯多模态时序融合算法在智慧零售的实战解析
  • Tesla智能座舱域控制器(MCU)的系统化梳理