首先做个声明,以下内容是6月5日完成的,但是博客疏于打理,今天才贴上来,有任何问题可以询问我:tankery.chen@gmail.com

1.   整体简介

投票系统的软件对 HID 的读写操作被我封装成了一个 HID 驱动的类——QHidDevice。还有一个辅助类:QHidListener。

提供对HID设备的打开、关闭、监听(读取)、发送(写入)等接口。

具体的接口函数在这里不探讨,在PC端的类介绍中会有详细介绍。

这份文档将主要介绍我使用的第三方 HID 库。我原来蒙了头,用了libhid,后来发现在Windows下的使用极不方便——没有Windows工程——于是又找到一个功能不全的库,hidapi。

1.1. LIBHID简介

Libhid 是一个非常专业的第三方hid库。提供很丰富的接口来操作hid设备,其网址为:http://libhid.alioth.debian.org/。一看就是个Linux的产物……

它提供一份doxygen生成的文档(http://libhid.alioth.debian.org/doc/index.html ),不算完整,但可以大致看懂。

根据它提供的例子也可以开始hid的操作练习。

LIBHID提供的接口可以实现对HID设备的多接口、多端点的控制。比如一个HID设备,包含键盘、鼠标和自定义三种类型的接口,LIBHID可以打开其中的自定义接口(无论其接口序号为多少)。并且,可以指定读写此接口的特定端点。很强大,也很复杂。

1.2. HIDAPI简介

HIDAPI相比LIBHID来说就太简单、太不专业了,Linux、Windows和mac系统分别都只有一两个源文件…… 非常简洁。Mac的我不懂,我发现HIDAPI在Windows下调用了Windows自带的USB驱动函数,利用openfile、writefile 等函数来操纵HID(这类资料网上很多,不详述),而Linux则使用的LIBUSB,这点跟LIBHID一样,但显然没有LIBHID功能强大。

HIDAPI只能提供对单一接口、单一端点的控制,也就是说,它只能控制默认接口、默认端点的数据传输。功能若很多,也增加了很多限制。

另外,在使用过程中,你会发现很多不爽的地方,但是,因为它简单,你可以自己去改写它!

2.   详细使用

2.1. LIBHID的使用

虽然,在这个项目中,LIBHID似乎已经没有前途,在中国,不会有多少人用Linux作为教学仪器。而且,我们的投票器也暂 时不需要那么多复杂的接口和端点。所以,如果不想看可以直接跳到下一个主题:HIDAPI的使用,但,总结LIBHID还是可以作为一个积累,保不住将来 就会用到。

LIBHID,前文已经说过,封装了LIBUSB(http://www.libusb.org/ Windows版本叫做libusb-win32)。它在LIBUSB的基础上简化了数据的操作,使得对HID设备的读写变得更方便。

具体LIBHID是如何使用LIBUSB的,请看它的源代码文档:http://libhid.alioth.debian.org/doc/hid_8h.html

下面我们从LIBHID提供的例子来看,如何使用LIBHID。

 

2.2. HIDAPI的使用

2.2.1.    打开设备

HIDAPI的使用相比LIBHID就简单很多了,无需初始化,无需清理之前的连接,无需构造匹配结构体,直接根据VID和PID已经序列号打开HID设备即可进行对设备的读写。其打开设备的函数定义如下:

hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number);

传入VID和PID,以及一串序列号后,就可以返回一个HID设备指针了。

如果不关心序列号,最后一个参数可以为NULL。

如果打开失败,函数返回NULL。

2.2.2.    读设备

HID设备被打开后,就可以对其进行读写操作了。

HIDAPI提供两种读设备的方式,阻塞和非阻塞。

阻塞是指在进入读设备函数后,直到有数据被读取才退出,而非阻塞则不等待数据的到来,没有数据则返回0。

这两种方式通过下面这个函数来选择:

int hid_set_nonblocking(hid_device *device, int nonblock);

nonblock为1表示非阻塞,为0表示阻塞。默认为阻塞方式。

使用如下函数进行读操作:

int hid_read(hid_device *device, unsigned char *data, size_t length);

指定一个设备指针后,传入一个输出数据的指针和读取的长度。

返回实际读取的长度。

后面我们可以看到,我扩展了这个函数,使其能在阻塞模式下超时后退出。

2.2.3.    写设备

写设备函数定义如下:

int hid_write(hid_device *device, const unsigned char *data, size_t length);

将数据长度为length的数据data写入设备device,返回实际写入的长度。

值得注意的是,这个函数要求传入的数据中,第一个字节是一个“报告ID”,对报告ID是什么,我不太清楚,有兴趣可以自己查证。不过网上的资料(Google搜索)说,如果是单独的报告,直接设置为0即可。我也没看懂,尝试了下,附了个0,成功。

也就是说,在写入数据时,应该构造一个比实际数据长1个字节的数组。然后将这个数组的第一个字节赋值为0,接着调用该函数。

具体的代码可以参考我编写的QHidDevice类。

2.2.4.    增加等待超时

HIDAPI实在是太过简单了,功能很弱,于是我查看其源代码,做了一些修改,增强了hid_read 函数的功能,即增加了一个等待超时的参数,使得在非阻塞方式下,能够指定等待数据的超时时间。好处在QHidListener类中可以看到(CPU利用率 减低和数据读取成功率提升,可以设置超时为0做实验)。

在hid_read函数(Windows为例)中有如下一段:

if (!dev->blocking) {

       // See if there is any data yet.

       res = WaitForSingleObject(ev, 0);

       ……

可以看到,在非阻塞方式时,hid_read居然简单的等待0的时间,太浪费Windows API 了。。

于是我修改该函数为以下形式:

int hid_read(hid_device *device, unsigned char *data, size_t length, unsigned int timeout);

上文提到的那段代码改为:

if (!dev->blocking) {

       // See if there is any data yet.

       res = WaitForSingleObject(ev, timeout);

       ……

即实现了等待超时……

具体代码请看Google SVN(https://dian-zigbee-vote.googlecode.com/svn/trunk/PC/src)上的版本66到版本67的代码改变。

3.   总结

PC端对HID的读写操作就是这么些内容了,我研究得不深入,只是到了能用的程度,所以并不是很详细。代码也不是很好看。后期如果要考虑通用性的话,可能还得对HID设备的报告描述符进行读写操作,这样有利于数据长度、用途等等信息的获取,更具有通用性。

对以上内容有任何问题请邮件询问 Tankery(tankery.chen@gmail.com)。

posted on 2011-07-07 16:32  Tankery  阅读(12026)  评论(0编辑  收藏  举报