一、什么是输入子系统
输入子系统是 Linux 专门做的一套框架来处理输入事件的,像鼠标,键盘,触摸屏这些都是输入设备。 但是这些输入设备的类型又都不是一样的,所以为了统一这些输入设备驱动标准应运而生的。
统一了以后,在节点 /dev/input 下面则是我们输入设备的节点,如下图所示:
这些节点对应的则是我们当前系统的输入设备 , 我们要怎么查看当前系统都有哪些输入设 备呢?可以使用命令来查看:
cat /proc/bus/input/devices
那么要怎么确定哪个设备对应哪个节点呢?可以使用命令 hexdump 确定, hexdump 命令是 Linux 下查看二进制文本的工具。 举一个例子: 比如想确定键盘对应的是哪个节点,就可以使用命令:
hexdump /dev/input/event0 或者
hexdump /dev/input/event1 或者
hexdump /dev/input/event1 或者
输入完一条命令以后,按键盘的上的按键,如果有数据打印出来,则证明当前查看的这个节点是键盘这个设备对应的节点。
比如,在 Ubuntu 上输入命令:
hexdump /dev/input/event1
然后按键盘的按键,这时候有打印信息出现,则证明 /dev/input/event1 为键盘对应的节 点,如下图所示:
那么这些打印的信息都是什么意思呢?
上报的数据要按照具体的格式上报给输入子系统的核心层,应用就可以通过 设备节点来获得按照具体格式上报来的数据了。
封装数据是输入子系统的核心层来帮我们完成的,只需要按照指定的类型和这个类型对应的数据来上报给输入子系统的核心层即可。
那要怎么指定类型呢?这个就需要先了解一下 struct input_event 这个结构 体,这个结构体在 include/uapi/linux/input.h 文件中,如下图所示:
struct input_event {
struct timeval time; // 上报事件的时间
__u16 type; // 类型
__u16 code; // 编码
__s32 value; // 值
};
这里需要注意的是,当我们的 type 不同的时候, code 和 value 所代表的意义也是不一样的。
我们可以在这个 h 文件里面找到 tyep 的定义,每一个定义都是一个类型,如下图所示:
#define EV_SYN 0x00 /* 同步事件 */
#define EV_KEY 0x01 /* 按键事件 */
#define EV_REL 0x02 /* 相对坐标事件 */ 鼠标
#define EV_ABS 0x03 /* 绝对坐标事件 */ 触摸屏
#define EV_MSC 0x04 /* 杂项 ( 其他 ) 事件 */
#define EV_SW 0x05 /* 开关事件 */
#define EV_LED 0x11 /* LED */
#define EV_SND 0x12 /* sound( 声音 ) */
#define EV_REP 0x14 /* 重复事件 */
#define EV_FF 0x15 /* 压力事件 */
#define EV_PWR 0x16 /* 电源事件 */
#define EV_FF_STATUS 0x17 /* 压力状态事件 */
当使用命令
hexdump /dev/input/event1
后,按下键盘上的回车按键,打印以下内容:
然后获得回车打印的信息?
实践课
获取键盘按键信息:
应用层获取按键信息:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char **argv)
{
int fd;
struct input_event test_event;
fd = open("/dev/input/event1",O_RDWR);
if (fd<0)
{
perror("open error\n");
return fd;
}
while(1)
{
read(fd,&test_event,sizeof(test_event));
if(test_event.type == EV_KEY)
{
printf("type is %#x\n",test_event.type);
printf("type is %#x\n",test_event.value);
}
}
return 0;
}
使用输入子系统设计按键驱动
可以将开发板上的按键值设置为input.h 文件里面的宏定义的任意一个,比如本次实验将开发板上的 KEY 按键值设置为 KEY_0 。在编写 input 设备驱动的时候需要先申请一个 input_dev 结构体变量,使用 input_allocate_device 函数来申请一个 input_dev 。
此函数原型如下所示:
struct input_dev *input_allocate_device(void)
函数参数和返回值含义如下:
参数:无。
返回值: 申请到的input_dev 。
如果要注销的input 设备的话需要使用 input_free_device 函数来释放掉前面申请到的 input_dev,input_free_device 函数原型如下:
void input_free_device(struct input_dev *dev)
函数参数和返回值含义如下:
dev : 需要释放的 input_dev 。
返回值: 无。
申请好一个 input_dev 以后就需要初始化这个 input_dev ,需要初始化的内容主要为事件类型 (evbit) 和事件值 (keybit) 这两种。 input_dev 初始化完成以后就需要向 Linux 内核注册 input_dev 了,需要用到 input_register_device 函数, 此函数原型如下:
int input_register_device(struct input_dev *dev)
函数参数和返回值含义如下:
dev :要注册的 i nput_dev 。
返回值:0 , input_dev 注册成功;负值, input_dev 注册失败。
同样的,注销 input 驱动的时候也需要使用 input_unregister_device 函数来注销掉前面注册的 input_dev 。
input_unregister_device 函数原型如下:
void input_unregister_device(struct input_dev *dev)
函数参数和返回值含义如下:
dev :要注销的 i nput_dev 。
返回值: 无。
最终需要把事件上报上去,上报事件使用的函数要针对具体的时间来上报。比如,按键使用 input_report_key 函数。 同样的还有一些其他的事件上报函数,函数如下图所示:
void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)
当上报事件以后还需要使用 input_sync 函数来告诉 Linux 内核 input 子系统上报结束, input_sync 函数本质是上报一个同步事件,此函数原型如下所示:
void input_sync(struct input_dev *dev)
函数参数和返回值含义如下:
dev :需要上报同步事件的input_dev 。
实践课
在probe中添加如下申请分配函数:
test_dev = input_allocate_device();
在probe函数中添加如下代码:
test_device = input_allocate_device();
test_device->name = "test_key";
__set_bit(EV_KEY,test_device->evbit);//代表支持按键这个设备
__set_bit(KEY_1,test_device->keybit);//表示设备支持key这个按键
ret = input_register_device(test_device);//注册这个设备
if (ret<0)
{
printk("input_register_device is erron\n");
// goto error_input_register;
return -1;
}
在按键中断中添加如下代码:
DEFINE_TIMER(test_timer,timer_function,0,0);
static void timer_function(unsigned long data)
{
//超时时间处理函数
int value;
value = !gpio_get_value(gpio_num);
input_report_key(test_device,KEY_1,value);
//上报完成通知
input_sync(test_device);
}
irq_handler_t test_key(int irq, void *args)//中断处理函数
{
test_timer.expires = jiffies + msecs_to_jiffies(20) ;
//jiffies相当于手机的当前时刻!!!
add_timer(&test_timer);//启动定时器
return IRQ_HANDLED;
}