vi设计公司[本源百纳设计,专业搜索引擎seo合作,python基础教程期末考试,新网站怎么做流畅输入设备介绍
鼠标、键盘、按键、触摸屏等提供输入支持的设备都属于输入设备#xff0c;在Linux也提供了一套驱动框架“input 子系统”与之对应#xff0c;用于抽象输入设备#xff0c;并提供管理输入设备驱动和输入事件处理程序的功能
input 子系统
input 子系统用于管理…输入设备介绍
鼠标、键盘、按键、触摸屏等提供输入支持的设备都属于输入设备在Linux也提供了一套驱动框架“input 子系统”与之对应用于抽象输入设备并提供管理输入设备驱动和输入事件处理程序的功能
input 子系统
input 子系统用于管理各种输入设备的驱动程序和各种输入事件的事件处理程序input 子系统分为3层
输入设备驱动层包含各种各样的输入设备驱动程序用于驱动输入设备。input 核心层承上起下用于管理输入设备驱动程序和输入事件处理程序并提供驱动层和事件处理层相互匹配、相互通信的功能。输入事件处理层包含各种各样的输入事件处理程序用于对输入设备的硬件进行了抽象以便应用层能更方便的和输入设备进行交互其中 evdev 是一个通用输入事件处理程序它能与所有输入设备进行匹配此外对于鼠标、键盘、触摸屏等常用设备内核中也有相应的输入事件驱动程序。 注意输入设备和输入事件驱动是多对多的关系即一个输入设备可能有多个输入事件处理程序一个输入事件处理程序也可能有多个输入设备
struct input_dev 对象
input_dev 对象表示一个 input 设备它的核心成员如下 //输入设备的名字通过命令 cat /proc/bus/input/devices 可以查看const char *name;//在系统层次结构中设备的物理路径通过命令 cat /proc/bus/input/devices 可以查看const char *phys;//设备的唯一识别码通过命令 cat /proc/bus/input/devices 可以查看const char *uniq;//设备ID包含总线类型、作者、产品和版本相关消息它用于输入设备和事件处理程序间的匹配此外它通过命令 cat /proc/bus/input/devices 可以查看struct input_id id;//设备属性位图记录输入设备的一些属性unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];//输入设备支持的事件类型位图表示输入设备可以支持哪些事件unsigned long evbit[BITS_TO_LONGS(EV_CNT)];//表示相应事件可以上报的事件码位图//按键事件unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];//相对坐标事件unsigned long relbit[BITS_TO_LONGS(REL_CNT)];//绝对坐标事件unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//其他事件unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];//led事件unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];//声音事件unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];//压力反馈事件unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];//压力状态事件unsigned long swbit[BITS_TO_LONGS(SW_CNT)];//触摸屏相关是一个动态分配的数组用于存储触摸点的状态struct input_mt *mt;//操作函数一般由事件处理程序调用用于控制输入设备如 evdev 事件处理程序则是通过字符设备接口开放到应用层供应用层进行调用int (*open)(struct input_dev *dev);void (*close)(struct input_dev *dev);int (*flush)(struct input_dev *dev, struct file *file);int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);//使用 input_handle 中的 h_node 成员构成的链表 Linux 通过 input_handle 对象关联输入设备和输入事件处理程序struct list_head h_list;事件码、事件类型、设备属性定义参考 input_event_codes.h 文件可以通过函数 void __set_bit (int nr, volatile void *addr) 直接设置位图中的某一位也可以通过函数 void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code) 函数完成事件码位图和事件类型位图的设置。 当设备支持重复事件后内核会为其开启一个定时器生成重复事件无需设备驱动层重复上报。
注册和注销输入设备
注册输入设备
分配输入设备使用函数 struct input_dev *input_allocate_device(void)初始化 input_dev 对象主要是设置设备属性、支持的事件类型、对应的事件码注册输入设备使用函数 int input_register_device(struct input_dev *dev) 注销输入设备注销输入设备使用函数 void input_unregister_device(struct input_dev *dev)释放输入设备使用函数 void input_free_device(struct input_dev *dev)
struct input_handler 对象
input_handler 对象表示一个输入事件处理程序其核心成员如下 //事件处理函数用于处理输入设备上报的事件void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//事件处理函数用于处理输入设备上报的事件可处理多个事件未定义时系统默认使用event实现void (*events)(struct input_handle *handle, const struct input_value *vals, unsigned int count);//事件过滤函数用于处理输入设备上报的事件bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value);//自定义匹配函数当系统匹配函数执行成功后才会调用此函数bool (*match)(struct input_handler *handler, struct input_dev *dev);//用于建立事件处理程序和输入设备的联系通过调用 input_register_handle 实现int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);//用于断开事件处理程序和输入设备的联系通过调用 input_unregister_handle 实现void (*disconnect)(struct input_handle *handle);//事件处理程序和输入设备建立联系后执行void (*start)(struct input_handle *handle);//名字const char *name;//设备匹配表用于与输入设备匹配const struct input_device_id *id_table;//使用 input_handle 中的 d_node 成员构成的链表 Linux 通过 input_handle 对象关联输入设备和输入事件处理程序struct list_head h_list;注册和注销输入事件处理程序
注册输入事件处理程序
初始化 input_handler 对象主要包括各种事件处理函数、输入设备驱动关联函数、设备匹配表等使用 int input_register_handler(struct input_handler *handler) 函数注册输入事件处理程序 注销输入事件处理程序使用 void input_unregister_handler(struct input_handler *handler) 注销输入事件处理程序
struct input_handle 对象
input_handle 用于关联输入设备和事件处理程序其核心成员如下 //关联的输入设备struct input_dev *dev;//关联的事件处理程序struct input_handler *handler;//用于构建输入设备驱动程序中的 h_list 链表struct list_head d_node;//用于构建输入事件处理程序中的 h_list 链表struct list_head h_node;建立或断开输入设备和输入事件处理程序的联系
建立输入设备驱动程序和输入事件处理程序的联系
在注册输入设备驱动过的程中会遍历 input_handler_list 并调用 input_attach_handler 进行输入设备驱动程序和输入事件处理程序的匹配同样在注册输入事件处理程序的过程中也会遍历 input_dev_list 并调用 input_attach_handler 进行输入设备驱动程序和输入事件处理程序的匹配利用输入设备驱动程序的 id 和输入事件处理程序的 id_table 进行匹配。默认匹配函数执行成功后会调用输入事件处理程序提供的 match 函数匹配完成后会调用事件处理程序提供 connect 函数在 connect 函数中分配并初始化 input_handle 对象这里需要使用 input_get_device 进行获取输入设备使用 input_register_handle 建立输入设备和输入事件处理程序的联系 断开输入设备和输入事件处理程序的联系当注销输入设备驱动程序或输入事件处理程序时会遍历其 h_list 管理的 input_handle 对象链表执行输入事件处理程序的 disconnect 函数在 disconnect 函数中使用 input_unregister_handle 断开输入设备和输入事件处理程序的联系这里还需要对输入设备进行 put_device 操作
输入设备上报事件
通过函数 input_event 可以上报输入设备产生的输入事件 /*** dev 需要上报的 input_dev。* type 上报的事件类型比如 EV_KEY。* code 事件码如 KEY_0、 KEY_1 等等。* value 事件值如按键事件 1 表示按键按下 0 表示按键松开。*/void input_event(struct input_dev *dev,unsigned int type,unsigned int code,int value)另外针对常见类型的事件还有专门的事件上报函数这些函数都是通过 input_event 函数进行了二次封装实现如 input_report_key 可用于上报按键事件
事件同步
输入设备事件上报完成后需要调用 input_sync 告诉内核此次上报结束 /*** 需要上报同步事件的 input_dev。*/void input_sync(struct input_dev *dev)输入事件处理程序下发事件
输入事件处理程序可以通过 input_inject_event 下发事件给输入设备它最终会调用到输入设备驱动的 event 函数如 evdev 驱动在 connect 中注册了一个字符设备并在字符设备的 write中调用了此函数使得应用层可以向输入设备驱动程序发送事件。 /*** handle 需要下发事件的 input_handle 在 input_handle 关联了事件处理程序和输入设备* type 下发的事件类型* code 事件码* value 事件值*/void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)在应用层通过evdev访问输入设备
evdev 是一个通用的输入事件处理程序它能与所有输入设备进行匹配在匹配成功后会创建一个字符设备自然应用层也可以通过它创建的字符设备访问到对应的输入设备其步骤如下
包含头文件 #include linux/input.h。打开设备evdev 支持阻塞、非阻塞、 IO 多路复用、异步通知等机制 IO 多路复用、异步通知建议采用非阻塞方式打开避免 IO 错误或连续读写时导致程序意外阻塞。通过 ioctl 获取设备信息控制命令即参数参考内核中的 \drivers\input\evdev.c 中的 evdev_do_ioctl 函数。通过 read 读取输入设备上报的事件通过 write 发送事件给输入设备如控制LED等。使用完成关闭输入设备。 通过read函数读取到的是一些列的输入事件其数据类型如下 struct timeval {__kernel_time_t tv_sec;__kernel_suseconds_ tv_usec;};struct input_event {//事件上报时间struct timeval time;//表示哪类事件比如 EV_KEY 表示按键类、EV_REL 表示相对位移EV_ABS 表示绝对位置__u16 type;//表示该类事件下的事件码比如对于 EV_KEY 类事件它表示按键值对于触摸屏它表示是 X 还是Y 或是压力值__u16 code;//表示事件值对于按键它表示按键状态对于触摸屏它表示坐标值 x 值或 y 值或压力值__s32 value;};在读取据时可以得到一个或多个数据比如一个触摸屏的一个触点会上报 X 、Y 位置信息还可能会上报压力值 应用层可通过同步事件确定此次上报是否结束对于同步事件其 type 、 code 、 value 三项都是 0
确定设备信息
通过命令 cat /proc/bus/input/devices 可以查看输入设备的详细信息其内容格式如下 //设备 IDI: Bus0019 Vendor0000 Product0001 Version0000//设备名称N: NamePower Button//系统层次结构中设备的物理路径P: PhysLNXPWRBN/button/input0//位于 sys 文件系统的路径S: Sysfs/devices/LNXSYSTM:00/LNXPWRBN:00/input/input0//设备的唯一标识码U: Uniq//与输入设备关联的输入事件处理程序H: Handlerskbd event0//设备属性B: PROP0//设备支持的事件类型B: EV3//可上报的按键位图B: KEY10000000000000 0I: Bus0011 Vendor0001 Product0001 Versionab41N: NameAT Translated Set 2 keyboardP: Physisa0060/serio0/input0S: Sysfs/devices/platform/i8042/serio0/input/input1U: UniqH: Handlerssysrq kbd event1 ledsB: PROP0B: EV120013B: KEY402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe//设备支持的其他事件B: MSC10//设备上的指示灯B: LED7......使用命令读取数据
调试输入设备时可使用 hexdump 读取输入设备上报的数据其数据格式如下 |序号 | |秒 | |微秒 | |type| |code| |value |0000340 0000 0000 0000 0000 508b 62dd 0000 00000000350 a54d 0009 0000 0000 0003 0000 9d3f 00000000360 508b 62dd 0000 0000 a54d 0009 0000 0000GPIO按键输入驱动
原理图
硬件原理图如下所示在引脚PG3上接了一个按键KEY0按下按键时PG3为低电平松开按键时PG3为高电平
设备树编写
设备树如下所示 intr_key {compatible intr_key;status okay;key-gpio gpiog 3 GPIO_ACTIVE_LOW;interrupt-parent gpiog;interrupts 3 IRQ_TYPE_EDGE_BOTH;};驱动代码编写
驱动代码主要包括以下部分
分配、初始化、注册输入设备 //分配输入设备key-input_dev devm_input_allocate_device(pkey_dev-dev);if(!key-input_dev){printk(alloc input device failed);return -ENOMEM;}//设置输入设备名称key-input_dev-name virtual_input_Device;//设置事件类型按键事件、重复事件__set_bit(EV_KEY, key-input_dev-evbit);__set_bit(EV_REP, key-input_dev-evbit);//设置事件码__set_bit(KEY_0, key-input_dev-keybit);//注册输入设备result input_register_device(key-input_dev);if(result){printk(register input device failed);return result;}延时消抖定时器注册在按下按键时可能会产生抖动导致误触发我们可以等其稳定后在来读取按键值 //初始化消抖定時器timer_setup(key-timer, timer_func, 0);设置GPIO为输入获取并注册GPIO的中断处理函数 //获取中断号key-irq of_irq_get(pkey_dev-dev.of_node, 0);if(key-irq 0){input_unregister_device(key-input_dev);printk(irq get failed);return key-irq;}//获取中断触发方式irq_flags irq_get_trigger_type(key-irq);if(irq_flags IRQF_TRIGGER_NONE)irq_flags IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;irq_flags | IRQF_SHARED;//注册中断result devm_request_irq(pkey_dev-dev, key-irq, key_handler, irq_flags, key, (void*)key);if(result ! 0){input_unregister_device(key-input_dev);printk(request irq failed\r\n);return result;}在中断函数中设置消抖定时器触发时间然后再消抖定时器函数中上报按键状态
static irqreturn_t key_handler(int irq, void *dev)
{struct key_handle *key (struct key_handle *)dev;//设置定时器到期时间mod_timer(key-timer, jiffies msecs_to_jiffies(10));//返回IRQ_HANDLED表示中断被成功处理return IRQ_HANDLED;
}static void timer_func(struct timer_list *tm)
{struct key_handle *key container_of(tm, struct key_handle, timer);//上报GPIO状态input_report_key(key-input_dev, KEY_0, gpiod_get_value(key-gpio));//上报完成input_sync(key-input_dev);
}完整的按键输入驱动程序如下所示
#include linux/init.h
#include linux/kernel.h
#include linux/module.h
#include linux/fs.h
#include linux/cdev.h
#include linux/slab.h
#include linux/device.h
#include linux/uaccess.h
#include linux/io.h
#include linux/ioport.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_gpio.h
#include linux/interrupt.h
#include linux/of_irq.h
#include linux/miscdevice.h
#include linux/sched.h
#include linux/input.hstruct key_handle {//GPIO描述符struct gpio_desc *gpio;//GPIO中断号unsigned int irq;//软件定时器用于消抖struct timer_list timer;//输入设备struct input_dev *input_dev;
};static irqreturn_t key_handler(int irq, void *dev)
{struct key_handle *key (struct key_handle *)dev;//设置定时器到期时间mod_timer(key-timer, jiffies msecs_to_jiffies(10));//返回IRQ_HANDLED表示中断被成功处理return IRQ_HANDLED;
}static void timer_func(struct timer_list *tm)
{struct key_handle *key container_of(tm, struct key_handle, timer);//上报GPIO状态input_report_key(key-input_dev, KEY_0, gpiod_get_value(key-gpio));//上报完成input_sync(key-input_dev);
}static int key_probe(struct platform_device *pkey_dev)
{int result;uint32_t irq_flags;struct key_handle *key;printk(%s\r\n, __FUNCTION__);//分配设备句柄devm表示模块卸载时自动释放key devm_kzalloc(pkey_dev-dev, sizeof(struct key_handle), GFP_KERNEL);if(!key){printk(alloc memory failed\r\n);return -ENOMEM;}//复位key设备句柄memset(key, 0, sizeof(struct key_handle));//分配输入设备key-input_dev devm_input_allocate_device(pkey_dev-dev);if(!key-input_dev){printk(alloc input device failed);return -ENOMEM;}//设置输入设备名称key-input_dev-name virtual_input_Device;//设置事件类型按键事件、重复事件__set_bit(EV_KEY, key-input_dev-evbit);__set_bit(EV_REP, key-input_dev-evbit);//设置事件码__set_bit(KEY_0, key-input_dev-keybit);//注册输入设备result input_register_device(key-input_dev);if(result){printk(register input device failed);return result;}//初始化消抖定時器timer_setup(key-timer, timer_func, 0);//获取GPIO并设置为输入(devm表示模块卸载时自动释放)key-gpio devm_gpiod_get_index(pkey_dev-dev, key, 0, GPIOD_IN);if(IS_ERR(key-gpio)){input_unregister_device(key-input_dev);printk(get gpio failed\r\n);return PTR_ERR(key-gpio);}//获取中断号key-irq of_irq_get(pkey_dev-dev.of_node, 0);if(key-irq 0){input_unregister_device(key-input_dev);printk(irq get failed);return key-irq;}//获取中断触发方式irq_flags irq_get_trigger_type(key-irq);if(irq_flags IRQF_TRIGGER_NONE)irq_flags IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;irq_flags | IRQF_SHARED;//注册中断result devm_request_irq(pkey_dev-dev, key-irq, key_handler, irq_flags, key, (void*)key);if(result ! 0){input_unregister_device(key-input_dev);printk(request irq failed\r\n);return result;}//设置平台设备的驱动私有数据pkey_dev-dev.driver_data (void*)key;return 0;
}static int key_remove(struct platform_device *pkey_dev)
{struct key_handle *key;printk(%s\r\n, __FUNCTION__);//提取平台设备的驱动私有数据key (struct key_handle*)pkey_dev-dev.driver_data;//注销输入设备input_unregister_device(key-input_dev);return 0;
}/* 匹配列表用于设备树和驱动进行匹配 */
static struct of_device_id key_of_match[] {{.compatible intr_key},{},
};static struct platform_driver key_drv { .driver {.name intr_key,.of_match_table key_of_match,},.probe key_probe,.remove key_remove,
};static int __init mykey_init(void)
{return platform_driver_register(key_drv);
}static void __exit mykey_exit(void)
{platform_driver_unregister(key_drv);
}module_init(mykey_init);
module_exit(mykey_exit);MODULE_LICENSE(GPL);
MODULE_AUTHOR(csdn);
MODULE_DESCRIPTION(key test);
MODULE_ALIAS(key_input);驱动测试程序
按键驱动加载成功后会与event事件处理程序匹配event事件处理程序会在/dev/input/目录中创建一个event*的设备文件通过此设备文件可以读取到按键驱动上报的数据其测试代码如下
#include stdio.h
#include unistd.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
#include string.h
#include linux/input.hint main(int argc, char *argv[])
{int fd, ret;struct input_event ev;if(argc 2){printf(Usage:\r\n\t./keyinputApp /dev/input/eventX Open Key\r\n);return -1;}//打开输入设备fd open(argv[1], O_RDWR);if(0 fd){printf(Error: file %s open failed!\r\n, argv[1]);return -1;}while(1){//读取输入设备ret read(fd, ev, sizeof(struct input_event));if (ret){//处理输入事件switch (ev.type){/* 按键事件 */case EV_KEY:if (KEY_0 ev.code){/* 判断是不是 KEY_0 按键 */if (ev.value){/* 按键按下 */ printf(Key0 Press\n);}else{/* 按键松开 */ printf(Key0 Release\n);}}break;/* 重复事件 */case EV_REL:if (KEY_0 ev.code){/* 按键一直按下 */printf(Key0 repetition\n);}break;/* 其他类型的事件自行处理 */case EV_ABS:break;case EV_MSC:break;case EV_SW:break;};}else{printf(Error: file %s read failed!\r\n, argv[1]);break;}}/* 关闭设备 */close(fd);return 0;
}上机测试
根据硬件原理图对设备树进行修改然后用命令make ARCHarm CROSS_COMPILEarm-none-linux-gnueabihf- dtbs -j8编译设备树并用新的设备树启动目标板。从这里下载代码并进行编译然后将编译出来的.ko文件和.out文件拷贝到目标板根文件系统的root目录中。执行命令insmod key.ko加载按键驱动此时通过命令cat /proc/bus/input/devices可以看到按键的基本信息 执行命令./app.out /dev/input/event0运行测试程序测试程序会读取按键状态并输出在按下或松开时终端都会输出相应字符串长按时还会重复输出按下字符串。