易方数码笔缺省是一个标准的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");