在嵌入式系统中,会使用到多的输入设备,像按键、鼠标、键盘、触摸屏等都属于输入设备。对于这些输入设备,如果都按照之前字符设备注册模型进行驱动程序的编写,那么编程过程会比较复杂,会出现许多重复性的代码。而且又因为不同的人编写出来的设备驱动也不同,向应用层上报的数据也不同,应用开发人员访问这些设备驱动也是比较麻烦。
由于这些输入设备最终的功能都是将数据上报给用户,因此,Linux内核专门做了一个叫做input子系统来统一处理这些输入设备。输入子系统的作用有以下几点:
** 1.兼容所有输入设备**
统一了物理形态各异的相似的输入设备的处理功能,像鼠标、键盘、按键、触摸屏等都被同样处理。
2.统一了编程驱动方法
抽取了输入设备驱动的通用部分,交由内核中的输入子系统处理,驱动编写者只需要按照要求上报输入事件即可。
3.统一了应用操作接口
使用输入子系统进行编程会自动创建设备文件节点/dev/input/eventx,应用层访问eventx设备文件就可以获取来自输入设备中的数据。
总结: 输入子系统使得应用编程人员和驱动编程人员在编程的时候变得简单统一。
input子系统分为input驱动层、input核心层、input事件处理层,最终给用户空间提供可访问的设备节点,input子系统框架如下如所示:
驱动层:输入设备的具体驱动程序,初始化输入设备硬件,获取数据并上报数据。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:提供file_operation操作集合,实现xxx_open,xxx_read等函数接口,将数据传输给应用层。
注:对于编写一个底层的输入设备驱动, 并不需要关心这几层关联实现的细节, 因为 Linux 系统为我们做了很好的分层,对于核心层和事件处理层,代码都已经实现好,不需要驱动开发者实现。我们实现设备驱动层代码即可,因此编写一个输入设备驱动就变得很简单。
Linux使用struct input_dev结构体来描述一个输入设备。该结构体定义在include/linux/input.h文件中,定义如下:
struct input_dev {
const char *name; //设备名,用于标识已注册的输入设备
const char *phys;
const char *uniq;
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)]; //杂项事件的位图
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; // LED相关的位图
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; // sound相关的位图
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)]; //压力反馈的位图
unsigned long swbit[BITS_TO_LONGS(SW_CNT)]; //开关状态的位图
……
};
对于input_dev结构体,只需要实现三个成员:设备名、输入事件类型、输入事件码,就可以用来描述一个输入设备了。其中设备名即name成员,输入事件类型即evbit成员。输入事件码可以是keybit、relbit、absbit等这些位图成员。设备的事件码是根据输入事件类型的不同而不同!因此对于不同的输入设备,要实现的input_event结构体成员就不同。
evbit:表示输入事件类型, evbit本质是一个long类型数组,所占用的内存每个位表示一种输入事件类型,evbit是用来记录本设备所支持的事件类型。可选的事件类型定义在include/linux/input.h文件中
#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 //键盘声音事件
#define EV_REP 0x14 //重复事件
#define EV_FF 0x15 //压力事件
#define EV_PWR 0x16 //电源事件
#define EV_FF_STATUS 0x17//压力状态事件
如果支持EV_KEY,则把evbit[0]的第1个位设置为1;
如果支持EV_REL,则把evbit[0]的第2个位设置为1;
如果支持EV_REP,则把evbit[0]的第20个位设置为1;
对于按键输入设备,就需要支持EV_KEY,即将evbit[0]的第2个位置1。
keybit:表示按键键码,本质是一个long类型数组,使用一个位表示一个按键,keybit用于记录本设备所支持的键码。可选的键码定义在include/linux/input.h文件中
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
……
#define BTN_TRIGGER_HAPPY39 0x2e6
#define BTN_TRIGGER_HAPPY40 0x2e7
示例:
如果绑定KEY_1,则把keybit[0]的第2个位设置为1
如果绑定KEY_Q,则把keybit[0]的第16个位设置为1
如果绑定KEY_D,则把keybit[1]的第0个位设置为1
我们可以选择将按键设备的按键数值设置为以上任意一个。
其他成员稍微了解即可。
relbit:和keybit性质是一样的,只是当设备支持EV_REL事件(相对事件)时候才需要设置,表示本设备支持EV_REL的哪一个轴(REX_X,REL_Y等)。可选值如下:
#define REL_X 0x00
#define REL_Y 0x01
#define REL_Z 0x02
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)
absbit:和relbit性质是一样的,只是当设备支持EV_ABS事件(绝对事件)时候才需要设置,表示本设备支时持EV_ABS 的哪一个轴(ABS_X,ABS_Y等)。可选值如下:
#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ABS_THROTTLE 0x06
#define ABS_RUDDER 0x07
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
#define ABS_HAT0X 0x10
……
注意:
很显然,对于evbit、keybit等这些成员进行赋值时需要使用到位操作。但是内核也提供了一个set_bit函数,使用该函数可以很方便的实现对evbit、keybit等成员赋值操作。
函数原型:void set_bit(int nr, volatile unsigned long * addr);
示例: set_bit(EV_KEY,evbit); //支持EV_KEY事件
set_bit(KEY_D,keybit); //把keybit数组占用的内存空间的第32位设置为1
编写一个输入设备的驱动程序首先要创建一个struct input_event结构体,创建或释放该结构体变量需要调用以下两个函数:
头文件:include /linux/input.h
函数原型:struct input_dev *input_allocate_device(void);
函数功能:为struct input_dev结构体分配内存,并初始化该结构体的部分成员
函数参数:无
函数返回值:分配失败返回NULL;分配成功返回分配到的内存空间首地址
头文件:include /linux/input.h
函数原型:void input_free_device(struct input_dev *dev);
函数功能:释放input_dev结构空间。和input_allocate_device功能相反的
函数参数:dev--要释放的输入设备结构体地址
函数返回值:无
当输入设备初始化好后,我们就要对该输入设备进行注册,对输入设备进行注册或注需要使用以下两个函数。
头文件:include /linux/input.h
函数原型:int input_register_device(struct input_dev *dev);
函数功能:注册一个输入设备
函数参数:dev--要注册的输入设备结构体地址
函数返回值:0--注册成功,负数失败
头文件:include /linux/input.h
函数原型:void input_unregister_device(struct input_dev *dev);
函数功能:注销一个输入设备
函数参数:dev--要注销的输入设备结构体地址
函数返回值:无
当向Linux内核注册好输入设备(input_dev对象)后,我们还需要获取具体的输入值或输入事件,然后将输入事件上报给Linux内核。比如对于按键设备,需要在按键中断处理函数将按键值上报给Linux内核。Linux内核给我们提供了以下API函数,实现上报按键事件:
头文件:include /linux/input.h
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
函数作用:上报指定的事件以及对应的值
函数参数:
dev:需要上报的input_dev
type:上报的事件类型,比如EV_KEY
code:事件码,也就是我们注册的按键码,比如KEY_0、KEY_1等。
value:事件值,比如1表示按键按下,0表示按键松开。
函数返回值:无
示例:input_event(key_input, EV_KEY, KEY_1,1);//输入设备上报EV_KEY类型的事件,KEY_1的值为1。
注:input_event函数可以上报所有的事件类型和事件值,Linux内核也提供了其他的针对具体事件的上报函数,这些函数其实都用到了input_event函数。
void input_report_key(struct input_dev *dev, unsigned int code, int value);//按键事件上报
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);//绝对坐标事件上报
另外还要注意的是,当我们上报事件以后还需要使用input_sync函数来告诉Linux内核input子系统上报结束。input_sync函数本质上是上报一个同步事件。此函数原型如下所示:
void input_sync(struct input_dev *dev);
按键中断处理函数上报数据示例:
irqreturn_t keyIRQ_Handler(int irq , void *arg)
{
/* 上报输入事件 */
if(*keydat & (1<<2)){ //IO为高电平,按键未按下
input_report_key(key_input, KEY_1, 0);
input_sync(key_input);
}else{ //IO为低电平,按键已按下
input_report_key(key_input, KEY_1, 1);
input_sync(key_input);
}
return IRQ_HANDLED;
}
输入设备的设备文件一般是位于:/dev/eventx或/dev/input/eventx,x是0~31,主设备号固定是13,我们可以使用使用cat(以ASCII码查看)或hexdump(以16进制查看)命令来查看来自输入设备上报的事件。
ubuntu系统下自带的输入设备如下:
event1:键盘
event2:鼠标滚动以及左右键,触控板
event3:鼠标滑轮
在虚拟机/dev/input/目录下,输入hexdump event1,查看结果:
Linux内核使用input_event这个结构体来表示所有的输入事件,该结构体定义在include\linux\input.h文件中。结构体原型如下:
struct input_event {
struct timeval time; //存放时间戳,占8字节
__u16 type; //表示事件类型,如EV_KEY,占2个字节
__u16 code; //事件码,如KEY_0、KEY_1,占2个字节
__s32 value; //事件值,如0、1,占4个字节
};
input_event这个结构体非常重要,因为所有的输入设备最终都是按照input_event结构体呈现给用户的,用户应用程序可以通过input_event来获取到具体的输入事件或相关的值。
示例:
int main(int argc,char *argv[])
{
int fd;
struct input_event inputevent;
fd = open("/dev/input/event3",O_RDWR);
if(fd < 0){
perror("open");
return -1;
}
while(1){
read(fd,&inputevent,sizeof(inputevent));
switch(inputevent.type){
case EV_KEY:
printf("key%d %s\n",inputevent.code,inputevent.value?"press":"release");
default:break;
}
}
close(fd);
return 0;
}
1)为输入设备分配一个input_dev结构体(调用input_allocate_device)
2)填充输入设备支持的事件类型:evbit(调用set_bit)
3)根据输入设备支持的事件类型填充相应的子事件:keybit,relbit,absbit(调用set_bit)
4)注册输入设备(调用input_register_device)
5)在适当的地方上报事件(调用input_event,一般在中断函数内调用,别忘了上报同步事件)
6)不需要使用输入的时候注销输入设备(调用input_unregister_device)
7)释放核心数据结构(调用input_free_device)