易方数码笔缺省是一个标准的USB HID设备(Mouse),在OS为Android的平板电脑上接入时,基于USB Mouse形式输出的笔迹精度不够,为此,我们将其切换至hiddev设备类型,通过发送相关命令来获得笔输出的原始坐标信息,这样精度就大大提升了。
基于USB HID协议,除可实现为普通的输入设备外,也可用作为有自定义通讯功能的设备。事实上,当前Linux 内核中已提供了这两种HID事件的接口,即输入子系统和hiddev接口(具体请参见Linux内核根目录下的Documentation/hid/hiddev.txt文档)。
为使用hiddev设备,我们只需在配置内核时要打开hiddev选项,这样就可以编写应用级代码来读取笔的原始坐标信息了。hiddev驱动是一个字符型驱动,其访问节点一般为/dev/usb/hiddev[0~15],在应用程序中打开这个设备节点后,即可调用hiddev API来与hiddev设备进行通讯。
hiddev API有两个调用接口,read和ioctl调用。read只用于获取hiddev设备的状态变化,而主机与设备间进行数据交换是通过ioctl调用来实现的,写数据时传入ioctl的命令字为HDIOCSREPORT,读数据时则传入HDIOCGREPORT,传送的数据封装在report中,每个report分成多个filed,而每个filed又有多个usage。
访问数码笔时,我们要向设备发命令数据来通知设备切换输出模式(输出原始坐标信息),同时,我们也要实时地读取出数据笔输出的原始坐标信息。
1. 打开设备
int digitalpen_open(void) { int index; int fd; char hid_dev_node[50]; struct hiddev_devinfo dinfo; for(index = 0; index < 15; index ++) { sprintf(hid_dev_node, "/dev/usb/hiddev%d", index); fd = open(hid_dev_node, O_RDONLY); if(fd > 0) { memset(&dinfo, 0, sizeof(dinfo)); ioctl(fd, HIDIOCGDEVINFO, &dinfo); if( (dinfo.vendor == 0x0e20) && (dinfo.product == 0x0101)) break; close(fd); fd = -1; } } return fd; }
打开函数中通过对USB的VID和PID信息来确认所打开的设备是否为数码笔(可通过以下命令查到设备的VID和PID)。如打开成功,返回的是数码笔设备的文件描述符。
hfhe@hfhe:~$ lsusb Bus 003 Device 003: ID 0e20:0101 Pegasus Technologies Ltd. NoteTaker Bus 003 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 001 Device 008: ID 04b3:3107 IBM Corp. ThinkPad 800dpi Optical Travel Mouse Bus 001 Device 006: ID 04f2:b217 Chicony Electronics Co., Ltd Bus 001 Device 005: ID 0a5c:217f Broadcom Corp. Bluetooth Controller Bus 001 Device 004: ID 147e:2016 Upek Biometric Touchchip/Touchstrip Fingerprint Sensor Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
2. 获得数码笔的配置信息
在正式读写数据前,先获得数码笔设备的配置信息。我从Google上没有搜到较具体描述获知hiddev设备配置信息的文档,只能看Kernel中的代码和前面提及到的hiddev.txt文件,在此将与此相关的IOCTL命令整理如下:
可见,除HIDIOCGNAME和HIDIOCGDEVINFO调用外,其他的均是枚举过程。相关结构体的定义就不附上了,下面直接给出获得配置信息的代码。
int digitalpen_getconfiginfo(int fd) { char name[1024]; int index; int report_type,report_id,field_index,usage_index; struct hiddev_devinfo dinfo; struct hiddev_collection_info cinfo; struct hiddev_report_info rinfo; struct hiddev_field_info finfo; struct hiddev_usage_ref uref; const char *collection_type_name[] = {"Physical", "Application", "Logical"}; const char *report_type_name[] = {"Input", "Output", "Feature"}; // get device name memset(name, 0, sizeof(name)); if( ioctl(fd, HIDIOCGNAME(sizeof(name)), name) < 0) {; return -1; } // get device info memset(&dinfo, 0, sizeof(dinfo)); if( ioctl(fd, HIDIOCGDEVINFO, &dinfo) < 0) { return -1; } printf("\n[Device Information]\n"); printf("device name = %s\n", name); printf("vendor id = 0x%04x\n", dinfo.vendor & 0xffff); printf("product id = 0x%04x\n", dinfo.product & 0xffff); printf("version = 0x%04x\n", dinfo.version & 0xffff); printf("num_applications = 0x%x\n", dinfo.num_applications); // get collection info printf("\n[Collections Information]\n"); index = 0; while(1) { memset(&cinfo, 0, sizeof(cinfo)); cinfo.index = index; if(ioctl(fd, HIDIOCGCOLLECTIONINFO, &cinfo) < 0) break; index++; printf("index = %d\n", cinfo.index); if(cinfo.type >= 0 && cinfo.type <= 2) printf("type = %s\n", collection_type_name[cinfo.type]); else printf("type = %d\n", cinfo.type); printf("usage = 0x%x\n", cinfo.usage); printf("level = %d\n\n", cinfo.level); } // get reports info printf("[Report Information]\n"); for(report_type = HID_REPORT_TYPE_MIN; report_type <= HID_REPORT_TYPE_MAX; report_type++) { for(report_id = HID_REPORT_ID_FIRST; ;report_id++) { memset(&rinfo, 0, sizeof(rinfo)); rinfo.report_type = report_type; rinfo.report_id = report_id; if(ioctl(fd, HIDIOCGREPORTINFO, &rinfo) < 0) break; printf("report_type = %s\n", report_type_name[report_type - 1]); printf("report_id = %d\n", report_id); printf("num_fields = %d\n", rinfo.num_fields); // get field info printf("\n [field information]\n"); for(field_index = 0; field_index < rinfo.num_fields; field_index++) { memset(&finfo, 0, sizeof(finfo)); finfo.report_type = report_type; finfo.report_id = report_id; finfo.field_index = field_index; if(ioctl(fd, HIDIOCGFIELDINFO, &finfo) < 0) break; printf(" field_index = %d\n", finfo.field_index); printf(" maxusage = %d\n", finfo.maxusage); printf(" flags = %d\n", finfo.flags); printf(" physical = %d\n", finfo.physical); printf(" logical = %d\n", finfo.logical); printf(" application = 0x%x\n", finfo.application); printf(" unit_exponent = %d\n", finfo.unit_exponent); printf(" unit = %d\n", finfo.unit); printf(" logical_minimum = %d; logical_maximum = %d\n", finfo.logical_minimum, finfo.logical_maximum); printf(" physical_minimum = %d; physical_maximum = %d\n", finfo.physical_minimum, finfo.physical_maximum); // get usage info printf("\n [Usage code information]\n"); for(usage_index = 0; usage_index < finfo.maxusage; usage_index++) { memset(&uref, 0, sizeof(uref)); uref.report_type = report_type; uref.report_id = report_id; uref.field_index = field_index; uref.usage_index = usage_index; if(ioctl(fd, HIDIOCGUCODE, &uref) < 0) break; printf(" usage_index = %d, usage_code = 0x%x, value = %d\n", uref.usage_index, uref.usage_code, uref.value); } } report_id = report_id | HID_REPORT_ID_NEXT; printf("\n"); } } }
[Device Information] device name = Pegasus Technologies Ltd. EN202 Ver 3.02 vendor id = 0x0e20 product id = 0x0101 version = 0x0303 num_applications = 0x1 [Collections Information] index = 0 type = Application usage = 0xffa00001 level = 0 [Report Information] report_type = Input report_id = 256 num_fields = 1 [field information] field_index = 0 maxusage = 64 flags = 2 physical = 0 logical = 0 application = 0xffa00001 unit_exponent = 0 unit = 0 logical_minimum = -128; logical_maximum = 127 physical_minimum = 0; physical_maximum = 0 [Usage code information] usage_index = 0, usage_code = 0xffa00002, value = 0 usage_index = 1, usage_code = 0xffa00002, value = 0 usage_index = 2, usage_code = 0xffa00002, value = 0 usage_index = 3, usage_code = 0xffa00002, value = 0 usage_index = 4, usage_code = 0xffa00002, value = 0 usage_index = 5, usage_code = 0xffa00002, value = 0 usage_index = 6, usage_code = 0xffa00002, value = 0 usage_index = 7, usage_code = 0xffa00002, value = 0 usage_index = 8, usage_code = 0xffa00002, value = 0 usage_index = 9, usage_code = 0xffa00002, value = 0 usage_index = 10, usage_code = 0xffa00002, value = 0 usage_index = 11, usage_code = 0xffa00002, value = 0 usage_index = 12, usage_code = 0xffa00002, value = 0 usage_index = 13, usage_code = 0xffa00002, value = 0 usage_index = 14, usage_code = 0xffa00002, value = 0 usage_index = 15, usage_code = 0xffa00002, value = 0 usage_index = 16, usage_code = 0xffa00002, value = 0 usage_index = 17, usage_code = 0xffa00002, value = 0 usage_index = 18, usage_code = 0xffa00002, value = 0 usage_index = 19, usage_code = 0xffa00002, value = 0 usage_index = 20, usage_code = 0xffa00002, value = 0 usage_index = 21, usage_code = 0xffa00002, value = 0 usage_index = 22, usage_code = 0xffa00002, value = 0 usage_index = 23, usage_code = 0xffa00002, value = 0 usage_index = 24, usage_code = 0xffa00002, value = 0 usage_index = 25, usage_code = 0xffa00002, value = 0 usage_index = 26, usage_code = 0xffa00002, value = 0 usage_index = 27, usage_code = 0xffa00002, value = 0 usage_index = 28, usage_code = 0xffa00002, value = 0 usage_index = 29, usage_code = 0xffa00002, value = 0 usage_index = 30, usage_code = 0xffa00002, value = 0 usage_index = 31, usage_code = 0xffa00002, value = 0 usage_index = 32, usage_code = 0xffa00002, value = 0 usage_index = 33, usage_code = 0xffa00002, value = 0 usage_index = 34, usage_code = 0xffa00002, value = 0 usage_index = 35, usage_code = 0xffa00002, value = 0 usage_index = 36, usage_code = 0xffa00002, value = 0 usage_index = 37, usage_code = 0xffa00002, value = 0 usage_index = 38, usage_code = 0xffa00002, value = 0 usage_index = 39, usage_code = 0xffa00002, value = 0 usage_index = 40, usage_code = 0xffa00002, value = 0 usage_index = 41, usage_code = 0xffa00002, value = 0 usage_index = 42, usage_code = 0xffa00002, value = 0 usage_index = 43, usage_code = 0xffa00002, value = 0 usage_index = 44, usage_code = 0xffa00002, value = 0 usage_index = 45, usage_code = 0xffa00002, value = 0 usage_index = 46, usage_code = 0xffa00002, value = 0 usage_index = 47, usage_code = 0xffa00002, value = 0 usage_index = 48, usage_code = 0xffa00002, value = 0 usage_index = 49, usage_code = 0xffa00002, value = 0 usage_index = 50, usage_code = 0xffa00002, value = 0 usage_index = 51, usage_code = 0xffa00002, value = 0 usage_index = 52, usage_code = 0xffa00002, value = 0 usage_index = 53, usage_code = 0xffa00002, value = 0 usage_index = 54, usage_code = 0xffa00002, value = 0 usage_index = 55, usage_code = 0xffa00002, value = 0 usage_index = 56, usage_code = 0xffa00002, value = 0 usage_index = 57, usage_code = 0xffa00002, value = 0 usage_index = 58, usage_code = 0xffa00002, value = 0 usage_index = 59, usage_code = 0xffa00002, value = 0 usage_index = 60, usage_code = 0xffa00002, value = 0 usage_index = 61, usage_code = 0xffa00002, value = 0 usage_index = 62, usage_code = 0xffa00002, value = 0 usage_index = 63, usage_code = 0xffa00002, value = 0 report_type = Output report_id = 256 num_fields = 1 [field information] field_index = 0 maxusage = 8 flags = 8 physical = 0 logical = 0 application = 0xffa00001 unit_exponent = 0 unit = 0 logical_minimum = -128; logical_maximum = 127 physical_minimum = 0; physical_maximum = 255 [Usage code information] usage_index = 0, usage_code = 0xffa00003, value = 0 usage_index = 1, usage_code = 0xffa00003, value = 0 usage_index = 2, usage_code = 0xffa00003, value = 0 usage_index = 3, usage_code = 0xffa00003, value = 0 usage_index = 4, usage_code = 0xffa00003, value = 0 usage_index = 5, usage_code = 0xffa00003, value = 0 usage_index = 6, usage_code = 0xffa00003, value = 0 usage_index = 7, usage_code = 0xffa00003, value = 0
3. 向数码笔设备写入数据
与hiddev设备进行数据交换不是通过read和write这两个函数来完成的,而是通过调用IOCTL(命令字为HIDIOCGREPORT和HIDIOCSREPORT)并将传入传出的数据封装在report中来实现的。
写数据比较容易实现,根据hiddev.txt中的描述,就是在发出报告之间先通过调用HIDIOCSUSAGE(传给IOCTL)来填写将要传送的数据,其后调用HIDIOCSREPORT 让Linux内核发报告给设备。
int hiddev_send_report(int fd, unsigned int report_id, unsigned int num_fields, unsigned int field_index, unsigned int usage_index, unsigned int usage_code, char *buf, unsigned int size) { unsigned int index; struct hiddev_usage_ref_multi urefm; struct hiddev_report_info rinfo; if(size > HID_MAX_MULTI_USAGES) return -1; memset(&urefm, 0, sizeof(urefm)); urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT; urefm.uref.report_id = report_id; urefm.uref.field_index = field_index; urefm.uref.usage_index = usage_index; urefm.uref.usage_code = usage_code; urefm.num_values = size; for(index = 0; index < size; index++) urefm.values[index] = (unsigned int)buf[index]; if(ioctl(fd, HIDIOCSUSAGES, &urefm) < 0) return -2; memset(&rinfo, 0, sizeof(rinfo)); rinfo.report_type = HID_REPORT_TYPE_OUTPUT; rinfo.report_id = report_id; rinfo.num_fields = num_fields; if(ioctl(fd, HIDIOCSREPORT, &rinfo) < 0) return -3; return 0; }
const char cmd_opmode_pen[] = { 0x02, 0x04, 0x80, 0xB5, 0x01, 0x01, 0x00, 0x00}; if(hiddev_send_report(fd, HID_REPORT_ID_FIRST, 1, 0, 0, 0xffa00003, cmd_opmode_pen, sizeof(cmd_opmode_pen)) < 0) printf("Fail to send report\n");