通过HIDDEV编程接口读取易方数码笔的坐标数据

易方数码笔缺省是一个标准的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 – 获得由结构体hiddev_devinfo描述的设备信息
  • HIDIOCGCOLLECTIONINFO – 获得由结构体hiddev_collection_info描述的设备所拥有的集合(包括应用集合),调用时需传入index域,如index超过最后一 个集合,返回值为-1。
  • HIDIOCGREPORTINFO – 调用时需填写结构体hiddev_report_info中的type和id域。当ID为HID_REPORT_ID_FIRST传回第一个报告信息,(HID_REPORT_ID_NEXT |report_id)传回下一个报告信息;而类型值在HID_REPORT_TYPE_MIN和HID_REPORT_TYPE_MAX间。
  • HIDIOCGFIELDINFO – 返回报告中的域(filed)信息。调用时需传入报告的id和type, 也要填入field index,filed的数目从调用 HIDIOCGREPORTINFO的返回结构体中获得。
  • HIDIOCGUCODE – 返回用途码,调用时需传入报告的id和type、域的index和用途的索引(最大用途索引值在调用HIDIOCGFIELDINFO的返回结构体中获得)

可见,除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;
}

调用时,要用到2小节中获取的一些参数,比较好的作法是将这些参数放进全司结构体中。当然,为图方便也可直接填入获取到的参数,例如要将数码笔切换至原始坐标输出模式,通过下面的调用即实现。

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");

通过所获得的配置信息可知,报告类型为输出模式时,report_id的值为256,即为宏HID_REPORT_ID_FIRST所定义的值。

你可能感兴趣的:(软件编程技术)