Android 基于USB_BUIK 触摸驱动

1、概述

         Android 和PC系统本身是支持 usb hid输入设备的。不过由于业务的发展,需要采用高精度触摸框。重新设计框架,改变原来   串口+usb_hid的方式。采用俩路usb,一路usb_buik+一路usb_hid方式。具体架构如下图:       

Android  基于USB_BUIK 触摸驱动

2、触摸驱动

     该驱动是基于Android 8.0 内核 4.9版本上调试的。驱动主要从俩个方面去分析:1) USB 驱动框架  2)input 驱动

 2.1 USB驱动框架

   USB驱动基于Linux USB总线完成的。主要注册USB 设备和填充USB设备结构体相关信息。

 

static int __init usb_touch_init(void)
{
	
	int retval = usb_register(&XX_driver); //注册usb driver到系统中
	printk(KERN_ALERT "usb touch init\r\n");
	if(retval)
		printk(KERN_ALERT "usb touch init error %d\r\n",retval);
	return retval;
}

static void __exit usb_touch_exit(void)
{
	printk(KERN_ALERT "usb touch exit");
	usb_deregister(&XX_driver);
}

module_init(usb_touch_init);
module_exit(usb_touch_exit);

Linux 内核驱动都是从init函数开始执行,相当于应用程序里面的main函数。在init函数中主要做了usb_register(),注册usb driver到

系统中。usb_register() 参数为struct usb_driver 结构。

static struct usb_driver XX_driver = {
	.name		  	 	=	"XXXXXX", //usb driver name
	.probe				=	XX_probe, //usb 匹配后执行probe
	.disconnect			=	XX_disconnect, //usb 设备断开
	.id_table  			=	XX_id_table,   // 用于usb 匹配信息
};

 在usb_driver结构中重要的几个参数为 .probe 该函数用于usb 设备匹配正确后执行的。匹配规则后面会讲到 ,.disconnect该函数用于usb设备断开后执行的,主要做一些资源的释放。id_table 用于usb设备匹配信息,下面会讲。.name 用于usb driver name

static struct usb_device_id XX_id_table [] ={
	{
		.match_flags =  USB_DEVICE_ID_MATCH_VENDOR,//匹配规则,根据vid pid 去匹配设备
		.idVendor = XXXX,
		.idProduct = XXXXX,
	},
	
	{
		.match_flags =  USB_DEVICE_ID_MATCH_VENDOR, //可以匹配多个usb设备
		.idVendor = XXXXX,
		.idProduct = XXXXX,
	},
	
	 //{ USB_DEVICE(USB_TOUCH_VENDOR_ID, USB_TOUCH_PRODUCT_ID) },
	{}
};

MODULE_DEVICE_TABLE (usb, XX_id_table );

usb _device_id 写明usb 设备与驱动匹配的规则,这里采用的 是根据vid pid去匹配。每个usb设备的pid vid都是唯一的,当然也可以根据class Subclass (设备描述符)去匹配usb 设备.同时一个usb 驱动可以匹配多个usb 设备。MODULE_DEVICE_TABLE 宏将你写好的匹配规则带入到系统中.

注意 usb_device_id XX_id_table 要与 usb_driver 中的.id_table 关联起来,系统才可以识别相关的usb 设备。

static int usb_touch_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
    //获取usb_device 通过结构体成员指针获取结构体首地址
	struct usb_device *dev = interface_to_usbdev(intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint_in=NULL,*endpoint_out;
    //将接口描述符赋为当前接口
    interface = intf->cur_altsetting;
    //获取端点个数
	endpoint_num=interface->desc.bNumEndpoints;
	//申请touch 结构体空间
	touch = kzalloc(sizeof(struct usb_touch), GFP_KERNEL);
	//查找端点的属性,In端点还是Out端点
	for(i=0;i<interface->desc.bNumEndpoints;i++){
		if(!touch->input_ep &&  usb_endpoint_dir_in(&interface->endpoint[i].desc)){
			endpoint_in=&interface->endpoint[i].desc;
				
			touch->input_ep = interface->endpoint[i].desc.bEndpointAddress;
			maxp= usb_maxpacket(dev, touch->input_ep, usb_pipeout(touch->input_ep));
			printk(KERN_ALERT "In   bDescriptorType -> %x bEndpointAddress -> %x                                                        bmAttributes -> %x   wMaxPacketSize  ->%x\r\n",
					endpoint_in->bDescriptorType,endpoint_in->bEndpointAddress,endpoint_in->bmAttributes,endpoint_in->wMaxPacketSize);
		}
      
     if(!touch->output_ep &&  usb_endpoint_dir_out(&interface->endpoint[i].desc)){
			endpoint_out=&interface->endpoint[i].desc;
			touch->output_ep = interface->endpoint[i].desc.bEndpointAddress;
			maxp= usb_maxpacket(dev, touch->output_ep, usb_pipeout(touch->output_ep));
			printk(KERN_ALERT "Out  bDescriptorType -> %x bEndpointAddress -> %x  bmAttributes -> %x   wMaxPacketSize  ->%x\r\n",
					endpoint_out->bDescriptorType,endpoint_out->bEndpointAddress,endpoint_out->bmAttributes,endpoint_out->wMaxPacketSize);
		}
	}
    
    //申请data空间和buff空间
    touch->data = usb_alloc_coherent(dev,512*20,GFP_KERNEL,&touch->data_dma);
	touch->buff = kmalloc(512*20, GFP_ATOMIC);
	if(!touch->data || !touch->buff)
		goto fail1;
	
	/* 
     * 为 urb 结构体申请内存空间,第一个参数表示等时传输时需要传送包的数量,其它传输方式则为0。 
     * 申请的内存将通过下面即将见到的 usb_fill_int_urb 函数进行填充。  
     */ 
	touch->irq = usb_alloc_urb(0,GFP_KERNEL);
	if(!touch->irq)
		goto fail2;

    /* 填充 usb 设备结构体和输入设备结构体 */  
	touch->usbdev=dev;
	touch->usb_touch_input_dev=input_dev;
	
	/* 获取Usb_hid设备的名称 */
	printk(KERN_ALERT "usb touch probe manufacturer %s\r\n",dev->manufacturer);	
	if(dev->manufacturer)
		strlcpy(touch->name, dev->manufacturer, sizeof(touch->name));
	printk(KERN_ALERT "usb touch probe touch->name %s\r\n",touch->name);


    printk(KERN_ALERT "usb touch probe product %s\r\n",dev->product);	
	if (dev->product) {
		if (dev->manufacturer)
			strlcat(touch->name, " ", sizeof(touch->name));
		strlcat(touch->name, dev->product, sizeof(touch->name));
		printk(KERN_ALERT "usb touch probe touch->name22 %s\r\n",touch->name);
	}
	
	if (!strlen(touch->name))
		snprintf(touch->name, sizeof(touch->name),
			 "XXXX touch %04x:%04x",
			 le16_to_cpu(dev->descriptor.idVendor),
			 le16_to_cpu(dev->descriptor.idProduct));
	printk(KERN_ALERT "usb touch probe touch->name333 %s\r\n",touch->name);

    //初始化一个buik_urb块用于异步传输数据
    usb_fill_bulk_urb(touch->irq, dev, usb_rcvbulkpipe(dev, touch->input_ep), touch->data,
		512*20,
	    usb_touch_irq, touch);
	touch->irq->transfer_dma = touch->data_dma;//dma数据缓冲区指向设备的data_dma成员
	touch->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;// DMA 有效
    //提交urb到系统中
	retval = usb_submit_urb(touch->irq,GFP_KERNEL);
	if (retval) {
		printk(KERN_ALERT "retval %d\r\n",retval);
	}	
}

在probe函数主要是一些初始化,首先通过interface_to_usbdev函数获取到usb_dev.这种手法在内核中很常用。申请相关的结构体的空间,判断usb设备端点的属性和初始化一个urb用于传输数据。在初始化urb时 usb_fill_bulk_urb() 第六个参数是一个回调函数,当有数据是系统会调用这个函数。类似于中断函数。第五个参数是参数数据的buff。

static void usb_touch_irq(struct urb* urb)
{
	//根据上下文可获取到usb_touch 结构体 urb的私有成员。不过在这里设置usb_touch为全局变量,
	//struct usb_touch *touch = urb->context;
	int status=0;
    //判断urb 块传输数据的状态
	switch (urb->status) {
	case 0:	
		/*success */
		break;
	case -ECONNRESET:	/* unlink */
	case -ENOENT:
	case -ESHUTDOWN:
	printk(KERN_ALERT "ESHUTDOWN\r\n");
		return;
	/* -EPIPE:  should clear the halt */
	default:		/* error */
		goto resubmit;
	}
       //接收数据的长度
		urb->actual_length;
		//touch->data 接收的数据。
	
	//urb 状态未知时,再次提交urb到系统中			
resubmit:
	status = usb_submit_urb (urb, GFP_ATOMIC);
	if (status)
		printk(KERN_ALERT "can't resubmit intr, %s-%s/input0, status %d",
				touch->usbdev->bus->bus_name,
				touch->usbdev->devpath, status);
} 

在irq里判断urb的状态,然后就可以读到数据和数据的长度。

static void usb_touch_disconnect(struct usb_interface *intf)
{
	struct usb_touch *touch = usb_get_intfdata (intf);
	usb_set_intfdata(intf, NULL);
	if (touch) {
		usb_kill_urb(touch->irq);
		usb_free_urb(touch->irq);
		usb_free_coherent(interface_to_usbdev(intf), 512, touch->data, touch->data_dma);
		kfree(touch);
	}
}

在disconnect 中释放之前申请的资源。

 

2.2 input 部分

  Input部分相对来讲会简单一些,在probe函数里面做相关初始化,在disconnect里面释放相关资源。然后再irq函数中拿到数据后上报数据给系统即可。Android系统本事的input子系统会识别相关的event 事件,触摸正常。

  

static int usb_touch_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
    struct input_dev *input_dev;
    input_dev = input_allocate_device();//为input设备申请空间
    /* 
     * 填充usb设备结构体中的节点名。usb_make_path 用来获取 USB 设备在 Sysfs 中的路径,格式 
     * 为:usb-usb 总线号-路径名。 
     */ 
    usb_make_path(dev, touch->phys, sizeof(touch->phys));
    input_dev->name = touch->name;
	input_dev->phys=touch->phys;

    usb_to_input_id(dev, &input_dev->id);//设置输入设备的bustype,vendor,product,version
	input_dev->dev.parent = &intf->dev;//usb接口设备为输入设备的父设备
    
    //设备支持X Y ID 事件
    //支持同步事件
	set_bit(EV_SYN, input_dev->evbit);
    //设置绝对坐标,触摸屏采用绝对坐标,鼠标采用相对坐标
	set_bit(EV_ABS, input_dev->evbit);
    //设置点击事件
	set_bit(BTN_TOUCH, input_dev->keybit);
    //设置压力
    set_bit(ABS_MT_PRESSURE, input_dev->absbit);
	//点的ID ,多钱根据ID区分
	set_bit(ABS_MT_TRACKING_ID, input_dev->keybit);
    //设置X 事件
    set_bit(ABS_MT_POSITION_X, input_dev->absbit);
    //设置Y事件
	set_bit(ABS_MT_POSITION_Y, input_dev->absbit);
	//设置触摸笔的类型,粗细笔
	set_bit(ABS_MT_TOOL_TYPE, input_dev->absbit);
	//设置触摸屏设备
	set_bit(INPUT_PROP_DIRECT, input_dev->propbit); 

    //设置压力的范围
    input_set_abs_params(input_dev, ABS_MT_PRESSURE,0,255,0,0);
    //设置X Y坐标的范围
    input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, 0x7fff, 0, 0);
    input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, 0x7fff, 0, 0);
    //设置多点的ID个数 10点
	input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, 10, 0, 0);
	//设置面积的长宽
	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0);
	input_set_abs_params(input_dev, ABS_MT_TOUCH_MINOR, 0, 255, 0, 0);
    //设置粗细笔个数
	input_set_abs_params(input_dev, ABS_MT_TOOL_TYPE,0, 3, 0, 0);
	
    //关联input_dev与touch设备
	input_set_drvdata(input_dev, touch);
    //注册input驱动
    input_register_device(touch->usb_touch_input_dev);
}

 在probe中做相关的初始化,到此input相关已经初始化完成,在irq函数中直接上报函数即可。

for (i = 0; i < pointnum; i++) {
	if (((data->points[i].mPresstype & 0x07) == 0x07)) {
					input_report_abs(dev, ABS_MT_TRACKING_ID, data->points[i].mID);
					input_report_abs(dev, ABS_MT_PRESSURE, 1);
					input_report_key(dev, BTN_TOUCH,1);
					input_report_abs(dev, ABS_MT_POSITION_X, data->points[i].mX);
					input_report_abs(dev, ABS_MT_POSITION_Y, data->points[i].mY);
	                input_report_abs(dev, ABS_MT_TOUCH_MAJOR,max(data-     >points[i].mWidth,data->points[i].mHeight));
					input_report_abs(dev, ABS_MT_TOUCH_MINOR,min(data->points[i].mWidth,data->points[i].mHeight));
					
					input_report_abs(dev, ABS_MT_TOOL_TYPE,data->points[i].mPresstype>>5);	
				}
				input_mt_sync(dev);//以一个点的信息为结尾
			}
			input_sync(dev);//以一次事件为结尾
		}

这里只截取上报数据部分,直接上报相关数据即可。需要注意的是多点时,需要先上报每个点的数据,然后用 input_mt_sync()函数同步每个点数据,当多点都上报完以后还需要用input_sync()同步,对于系统来说,多点时同一个事件。

2.3 device_attribute的使用。

device_attribute 主要用于在sys子系统中,用户空间与内核空间的交互,在这里采用device_attribute 将版本信息输出到sys子系统中。用户空间用cat指令即可查看。

   //在sys子系统下Class目录下创建一个XXX目录
     touch->myclass = class_create(THIS_MODULE, "XXX");
	if(IS_ERR(touch->myclass))
	{
		printk("XXXtouchscreen class_create error\n");
		return 0;
	}
	//在XXX目录下创建一个XXX_touch目录
	touch->mydevice=device_create(touch->myclass, NULL, touch->chrdev_no, NULL, "XXX_touch");
	
	//在XXX_touch目录下创建drvinfo节点,通过读取节点即可获取信息版本
	device_create_file(touch->mydevice, &drvinfo);


//device_attribute  结构体中重要俩个函数 这里只用了.show .attr包括模式和名字
static struct device_attribute drvinfo = {
	.attr = {
		.name = "drvinfo",
		.mode = 0444,
	},
	.show = drvinfo_show,
};

在drvinfo_show中采用sprintf()直接将信息输出。用户层利用cat就可获取数据信息
static ssize_t drvinfo_show(struct device *dev,struct device_attribute * attr,char * buf)
{
	return sprintf();
}

3、驱动框架

        整体触摸驱动的流程。

Android  基于USB_BUIK 触摸驱动

 4、总结

  驱动主要分为俩大部分,USB驱动和INPUT驱动。利用USB通信,采集数据然后通过算法算出数据后,通过INPUT子系统直接上报系统。在Linux 中USB 驱动和INPUT驱动的相关的API内核已经提供,只需按照相应的流程去操作就可以了。USB驱动前期会难一些,你要理解明白USB的枚举过程和四大描述符的作用。这样写起来就会很快。USB的触摸驱动到这里就结束了,后面还会跟新串口触摸驱动。

上一篇:带你读《实分析(原书第4版)》之三:Lebesgue测度


下一篇:UVC 摄像头驱动(三)配置摄像头,实时数据采集