Linux输入子系统:
前面章节讲解按键设备驱动,实际上,在Linux系统中,一种更值得推荐的实现这类设备驱动的方法是利用input子系统。Linux系统提供了input子系统,按键、触摸屏、鼠标等都可以利用input接口函数来实现设备驱动。
在Linux内核中,input设备用input_dev结构体描述,使用input子系统实现输入设备驱动的时候,驱动的核心工作是向系统报告按键、触摸屏、键盘、鼠标等输入事件(event,通过input_event结构体描述),不再需要关心文件操作接口,因为input子系统已经完成了文件操作接口。驱动报告的事件经过InputCore和 Eventhandler最终到达用户空间。
设备注册/注销:
注册输入设备的函数为:
int input_register_device(struct input_dev *dev)
注销输入设备的函数为:
void input_unregister_device(struct input_dev *dev)
驱动实现-事件支持
设备驱动通过set_bit()告诉input子系统它支持哪些事件,
如下所示:
set_bit(EV_KEY, button_dev.evbit)
struct input_dev中有两个成员,一个是evbit;一个是keybit。分别用来表示设备所支持的事件类型和按键类型。
事件类型:
EV_RST Reset、EV_KEY 按键、EV_REL 相对坐标、EV_ABS 绝对坐标、EV_MSC 其它、EV_LED LED、EV_SND 声音、EV_REP Repeat、EV_FF 力反馈
驱动实现-报告事件
用于报告EV_KEY、EV_REL和EV_ABS事件的函数分别为:
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)
code:
事件的代码。如果事件的类型是EV_KEY,该代码code为设备键盘代码。代码值0~127为键盘上的按键代码,0x110~0x116 为鼠标上按键代码,其中0x110(BTN_LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键。其它代码含义请参看include/linux/input.h文件
value:
事件的值。如果事件的类型是EV_KEY,当按键按下时值为1,松开时值为0。
驱动实现-报告事件
input_sync()用于事件同步,它告知事件的接收者:驱动已经发出了一个完整的报告。例如,在触摸屏设备驱动中,一次坐标及按下状态的整个报告过程如下:
input_report_abs(input_dev, ABS_X, x); //X坐标
input_report_abs(input_dev, ABS_Y, y);//Y坐标
input_report_abs(input_dev, ABS_PRESSURE, pres); //压力
input_sync(input_dev);//同步
实例分析
/*在按键中断中报告事件*/
static void button_interrupt(int irq, void *dummy , struct pt_regs *fp)
{
input_report_key(&button_dev, BTN_0, inb(BUTTON_PORT0));
input_report_key(&button_dev, BTN_0, inb(BUTTON_PORT1));
input_sync(&button_dev);
}
staticint _ _init button_init(void){
/*申请中断*/
if (request_irq(BUTTON_IRQ, button_interrupt, 0, "button", NULL))
return - EBUSY;
set_bit(EV_KEY, button_dev.evbit)//支持EV_KEY事件
set_bit(BTN_0, button_dev.keybit);//设备支持两个键
set_bit(BTN_1, button_dev.keybit);
input_register_device(&button_dev); //注册input设备
}
应用程序
struct input_event
{
struct timeval time;//按键时间
__u16 type;//类型,在下面有定义
__u16 code;//要模拟成什么按键
__s32 value;//是按下还是释放
}
struct input_event ev_mouse[2];
fd = open ("/dev/input/event3",O_RDWR);
while(1)
{
count=read(fd, ev_mouse, sizeof(struct input_event));
for(i=0;i<(int)count/sizeof(struct input_event);i++)
{
if(EV_REL==ev_mouse[i].type)
{
printf(“time:%ld.%d”,ev_mouse[i].time.tv_sec,ev_mouse[i].time.tv_usec);
printf(“ type:%d code:%d value:%d\n",ev_mouse[i].type,ev_mouse[i].code,ev_mouse[i].value);
}
if(EV_KEY==ev_mouse[i].type)
{
printf("time:%ld. %d",ev_mouse[i].time.tv_sec,ev_mouse[i].time.tv_usec);
printf(" type:%d code:%d value:%d\n",ev_mouse[i].type,ev_mouse[i].code,ev_mouse[i].value);
}
}
}
输入子系统按键实例:
开发环境:
Cent OS 6.4、TQ2440开发板、内核版本linux-2.30.4
硬件描述:
四个按键所在端口分别为EINT1/GPF1、EINT4/GPF4、EINT2/GPF2、EINT0/GPF0
驱动程序:
#include <linux/input.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
//#include <linux/init.h>
//#include <asm/irq.h>
//#include <asm/io.h>
struct input_dev *button_dev;
struct button_irq_desc {
int irq;
int pin;
int pin_setting;
int number;
char *name;
};
static struct button_irq_desc button_irqs [] = {
{IRQ_EINT1, S3C2410_GPF1 , S3C2410_GPF1_EINT1 , 0, "KEY1"},
{IRQ_EINT4, S3C2410_GPF4 , S3C2410_GPF4_EINT4 , 1, "KEY2"},
{IRQ_EINT2, S3C2410_GPF2 , S3C2410_GPF2_EINT2 , 2, "KEY3"},
{IRQ_EINT0, S3C2410_GPF0 , S3C2410_GPF0_EINT0 , 3, "KEY4"},
};
static int key_values = 0;
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;
int down;
udelay(0);
down = !s3c2410_gpio_getpin(button_irqs->pin); //down: 1(按下),0(弹起)
if (!down) {
/*报告事件*/
key_values = button_irqs->number;
//printk("====>rising key_values=%d\n",key_values);
if(key_values==0)
input_report_key(button_dev, KEY_1, 0);
if(key_values==1)
input_report_key(button_dev, KEY_2, 0);
if(key_values==2)
input_report_key(button_dev, KEY_3, 0);
if(key_values==3)
input_report_key(button_dev, KEY_4, 0);
input_sync(button_dev);
}
else {
key_values = button_irqs->number;
//printk("====>falling key_values=%d\n",key_values);
if(key_values==0)
input_report_key(button_dev, KEY_1, 1);
if(key_values==1)
input_report_key(button_dev, KEY_2, 1);
if(key_values==2)
input_report_key(button_dev, KEY_3, 1);
if(key_values==3)
input_report_key(button_dev, KEY_4, 1);
input_sync(button_dev);
}
return IRQ_RETVAL(IRQ_HANDLED);
}
static int s3c24xx_request_irq(void)
{
int i;
int err = 0;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0) {
continue;
}
/* IRQ_TYPE_EDGE_FALLING,IRQ_TYPE_EDGE_RISING,IRQ_TYPE_EDGE_BOTH */
err = request_irq(button_irqs[i].irq,buttons_interrupt,IRQ_TYPE_EDGE_BOTH,button_irqs[i].name, (void *)&button_irqs[i]);
if (err)
break;
}
if (err) {
i--;
for (; i >= 0; i--) {
if (button_irqs[i].irq < 0) {
continue;
}
disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
return -EBUSY;
}
return 0;
}
static int __init dev_init(void)
{
int error;
/*request irq*/
s3c24xx_request_irq();
/* Initialise input stuff */
button_dev = input_allocate_device();//得到一个struct input_dev的空间
if (!button_dev) {
printk(KERN_ERR "Unable to allocate the input device !!\n");
return -ENOMEM;
}
button_dev->name = "s3c2440_button";
/*button_dev->id.bustype = BUS_RS232;
button_dev->id.vendor = 0xDEAD;
button_dev->id.product = 0xBEEF;
button_dev->id.version = 0x0100;*/
set_bit(EV_KEY, button_dev->evbit);//支持EV_KEY事件或者用button_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT(EV_SYN);
set_bit(KEY_1, button_dev->keybit);
set_bit(KEY_2, button_dev->keybit);
set_bit(KEY_3, button_dev->keybit);
set_bit(KEY_4, button_dev->keybit);
error=input_register_device(button_dev);
if(error){
printk(KERN_ERR "button.c: Failed to register device\n");
//input_free_device(button_dev);
return error;
}
return 0;
}
static void __exit dev_exit(void)
{
int i;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
if (button_irqs[i].irq < 0) {
continue;
}
//disable_irq(button_irqs[i].irq);
free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
}
input_unregister_device(button_dev);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zbffff");
应用程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <linux/input.h>
int main(void)
{
int buttons_fd=-1;
int key_value,i=0,count;
struct input_event ev_key;
buttons_fd = open("/dev/event0", O_RDWR);//|O_NONBLOCK可以防止阻塞
if (buttons_fd < 0) {
perror("open device buttons\n");
exit(1);
}
for (;;) {
count = read(buttons_fd,&ev_key,sizeof(struct input_event));//如果没有信号会在此阻塞
//printf("readed\n");
if(count>0)
for(i=0; i<(int)count/sizeof(struct input_event); i++)
if(EV_KEY==ev_key.type)
printf("type:%d,keynum:%d,value:%d\n", ev_key.type,ev_key.code-1,ev_key.value);
else if(EV_SYN==ev_key.type)
printf("syn event\n");//每次按键一次EV_KEY的信息之后会再有一次EV_SYN类型的信号,分两次
}
close(buttons_fd);
return 0;
}
触摸屏驱动设计:
触摸屏分为电阻式、电容式、声表面波式和红外线扫描式等类型,使用得最多的是4线电阻式触摸屏。 S3C2440触摸屏由横向电阻比和纵向电阻线组成,由nYPON、YMON、nXPON、XMON四个控制信号控制4个MOS管(S1、S2、S3、S4)的通断。
S3C2440触摸屏控制器有2种处理模式:
①X/Y位置分别转换模式。触摸屏控制器包括两个控制阶段,X坐标转换阶段和Y坐标转换阶段。
②X/Y位置自动转换模式。触摸屏控制器将自动转换X和Y坐标。
1. Select Separate X/Y Position Conversion Mode or Auto (Sequential) X/Y Position ConversionModeto get X/Y position.
2. Set Touch ScreenInterfaceto Waiting Interrupt Mode,
3. If interrupt occurs, then appropriate conversion(Separate X/Y Position Conversion Mode or Auto (Sequential) X/Y Position Conversion Mode) is activated.
4. After get the proper value about X/Y position, return to Waiting for Interrupt Mode.
When Touch Screen Controller is in Waiting for Interrupt Mode, it waits for Stylus down. The controller, generates Interrupt(INT_TC) signals when the Stylus is down on Touch Screen Panel. After an interruptoccurs, X and Yposition can be read by the proper conversion mode (SeparateX/Y position conversion Mode or Auto X/Y Position conversion Mode).