市面上一些无驱的usbkey 一直是技术卖点,象招商银行的usbkey 就一直以无需安装驱动自豪。无驱动的usbkey确是比较方便,尤其在使用别人的计算机的时候。
刚好最近在研究一款HID 设备,在这方面做了一些探索。
首先工具一定要利索,最好能武装到牙齿,下面的三个工具最好都有,否则就回到了石器时代
bus hound
usb device viewer
hid descriptor tool
文档是一定要仔细看,
Universal Serial Bus Revision 2.0 specification ,尤其是那个著名的chap. 9
Device Class Definition for HID 1.11 
google , 一定要多用,你碰到的问题,别人肯定已经碰到过,不过别太相信中文文章,绝大部分没有自己实验过,会误导

我们看一下招行的usbkey 抓包结果
25.0 CTL    80 06 00 01 00 00 12 00                                                                                GET DESCRIPTOR           1.1.0        
25.0 DI     12 01 10 01 00 00 00 08 6e 09 10 a0 05 20 01 02 00 01                                               ........n.... ..         1.2.0        
25.0 CTL    80 06 00 02 00 00 09 00                                                                                GET DESCRIPTOR           2.1.0        
25.0 DI     09 02 1b 00 01 01 00 80 0f                                                                            .........                2.2.0        
25.0 CTL    80 06 00 02 00 00 1b 00                                                                                GET DESCRIPTOR           3.1.0        
25.0 DI     09 02 1b 00 01 01 00 80 0f 09 04 00 00 00 03 00 00 00 09 21 00 01 00 01 22 a6 00                  ................         3.2.0        
25.0 CTL    00 09 01 00 00 00 00 00                                                                                SET CONFIG               4.1.0        
25.0 CTL    21 0a 00 00 00 00 00 00                                                                                SET IDLE                 5.1.0        
25.0 USTS   04 00 00 c0                                                                                             stall pid                5.2.0        
25.0 CTL    81 06 00 22 00 00 e6 00                                                                                GET DESCRIPTOR           6.1.0        
25.0 DI     06 a0 ff 09 a2 a1 01 09 a3 a1 03 06 a1 ff 09 c1 09 c2 09 c3 09 da 09 da 09 da 09 da 09 da 09 da ................         6.2.0        
               09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da ................         6.2.32       
               09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da ................         6.2.64       
               09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da ................         6.2.96       
               09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 da 09 c4 15 00 25 ff 35 00 45 ff 75 08 ................         6.2.128      
               95 44 b1 02 c0 c0                                                                                      .D....                   6.2.160

对应的描述符表
usb_device_descriptor ROM_DATA dev_desc
    = {
       0x12,    //bDefineLength
       0x01,    //bDescriptorType,设备描述符
       0x10,    //bcdUSBL
       0x01,    //bcdUSBH
       0x00,    //bDeviceClass
       0x00,    //bDeviceSubClass
       0x00,    //bDeviceProtocol
       0x08,    //bMaxPacketSize0, 包长
       0x6e,    //idVendorL 0x6e , VID=0x096E
       0x09,    //             0x09
       0x10,    //idProductL 0x10 , PID=0xA010
       0xa0,    //             0xa0
       0x05,    //bcdDevice , SN=0x2005
       0x20,    //
       0x01,    //iManufacturer
       0x02,    //iProduct
       0x00,    //iSerialNumber
       0x01        //bNumConfigurattions,
       };

unsigned char cfg_desc [] =
    {
    //configuration desc
    0x09,           // bLength
   02, // bDescriptorType, CONFIGURATION,配置描述符
    0x1b,           // wTotalLength (low byte)
    0x00,           // wTotalLength (high byte)
    0x01, // bNumInterfaces, 接口数
    0x01,           // bConfigurationValue
    0x00,           // iConfiguration (none)
    0x80,           // bmAttributes
    0x0f,           // bMaxPower (30 mA)
    //interface desc
    0x09,           // bLength (Interface1 descriptor starts here)
    0x04,        // bDescriptorType
    0x00,           // bInterfaceNumber
    0x00,             // bAlternateSetting
    0x00,             // bNumEndpoints (excluding EP0),注意这样配置是不符合规范的,HID设备要求至少一个In endpoint, windows 下可以运行, linux 下不可以
    0x03,           // bInterfaceClass (HID code)
    0x00,           // bInterfaceSubClass ( not specified)
    0x00,           // bInterfaceProtocol ( not specified)
    0x00,           // iInterface (none)
   // hid 
    0x09,            // bLength (HID1 descriptor starts here)
    0x21,            // bDescriptorType, hid 描述
    0x00,            // bcdHID (low byte)
    0x01,            // bcdHID (high byte)
    0x00,            // bCountryCode (none)
    0x01,            // bNumDescriptors
    0x22,            // bDescriptorType   report 描述
    a6            // wDescriptorLength (low byte)
    0x00,            // wDescriptorLength (high byte)
};

很奇怪没有按要求至少有一个 in endpoint,而windows 下竟然能运行
再看那个很变态的report 表, 去掉那些奇怪的 09 开始的定义, 用dt 表示大致如下

char ReportDescriptor[] = {
    0x06, 0xa0, 0xff,              // USAGE_PAGE (Vendor Defined Page 1)
    0x09, 0x01,                    // USAGE (Vendor Usage 1)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x02,                    //   USAGE (Vendor Usage 2)
    0xa1, 0x02,                    //   COLLECTION (Logical)
    0x06, 0xa0, 0xff,              //     USAGE_PAGE (Vendor Defined Page 1)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0xff,                    //     LOGICAL_MAXIMUM (255)
    0x35, 0x00,                    //     PHYSICAL_MINIMUM (0)
    0x45, 0xff,                    //     PHYSICAL_MAXIMUM (255)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x44,                    //     REPORT_COUNT (68)
    0xb1,0x02,                       // 不知道用途
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION
};
即 in/out 的长度都是 0x44* 8 bit, 即68 byte

有了这几个描述表,只要应答正确, 基本上windows系统就能识别出usb hid 设备了,linux 下需要提供一个 in endpoint 才不会出错

windows 下打开文件句柄如下
HANDLE connectToIthUSBHIDDevice (DWORD deviceIndex)
{
    GUID hidGUID;
    HDEVINFO hardwareDeviceInfoSet;
    SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
    PSP_INTERFACE_DEVICE_DETAIL_DATA deviceDetail;
    ULONG requiredSize;
    HANDLE deviceHandle = INVALID_HANDLE_VALUE;
    DWORD result;

//Get the HID GUID value - used as mask to get list of devices
    HidD_GetHidGuid (&hidGUID);

//Get a list of devices matching the criteria (hid interface, present)
    hardwareDeviceInfoSet = SetupDiGetClassDevs (&hidGUID,
        NULL, // Define no enumerator (global)
        NULL, // Define no
        (DIGCF_PRESENT | // Only Devices present
        DIGCF_DEVICEINTERFACE)); // Function class devices.

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

//Go through the list and get the interface data
    result = SetupDiEnumDeviceInterfaces (hardwareDeviceInfoSet,
        NULL, //infoData,
        &hidGUID, //interfaceClassGuid,
        deviceIndex, 
        &deviceInterfaceData);

/* Failed to get a device - possibly the index is larger than the number of devices */
    if (result == FALSE)
    {
        SetupDiDestroyDeviceInfoList (hardwareDeviceInfoSet);
        return INVALID_HANDLE_VALUE;
    }

//Get the details with null values to get the required size of the buffer
    SetupDiGetDeviceInterfaceDetail (hardwareDeviceInfoSet,
        &deviceInterfaceData,
        NULL, //interfaceDetail,
        0, //interfaceDetailSize,
        &requiredSize,
        0); //infoData))

//Allocate the buffer
    deviceDetail = (PSP_INTERFACE_DEVICE_DETAIL_DATA)malloc(requiredSize);
    deviceDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);

//Fill the buffer with the device details
    if (!SetupDiGetDeviceInterfaceDetail (hardwareDeviceInfoSet,
        &deviceInterfaceData,
        deviceDetail,
        requiredSize,
        &requiredSize,
        NULL)) 
    {
        SetupDiDestroyDeviceInfoList (hardwareDeviceInfoSet);
        free (deviceDetail);
        return INVALID_HANDLE_VALUE;
    }

//Open file on the device
    deviceHandle = CreateFile (deviceDetail->DevicePath,
        GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,        // no SECURITY_ATTRIBUTES structure
        OPEN_EXISTING, // No special create flags
        0, 
        NULL);       // No template file

SetupDiDestroyDeviceInfoList (hardwareDeviceInfoSet);
    free (deviceDetail);
    return deviceHandle;
}

HANDLE connectToUSBHIDDevice (DWORD vendorID, DWORD productID, DWORD versionNumber)
{
    HANDLE deviceHandle = INVALID_HANDLE_VALUE;
    DWORD index = 0;
    HIDD_ATTRIBUTES deviceAttributes;
    BOOL matched = FALSE;

while (!matched &&index <20 )
    {
        if((deviceHandle = connectToIthUSBHIDDevice (index)) != INVALID_HANDLE_VALUE)
        {
            if (!HidD_GetAttributes (deviceHandle, &deviceAttributes))
                return INVALID_HANDLE_VALUE;

if ((vendorID == 0 || deviceAttributes.VendorID == vendorID) &&
                (productID == 0 || deviceAttributes.ProductID == productID) &&
                (versionNumber == 0 || deviceAttributes.VersionNumber == versionNumber))
                return deviceHandle; /* matched */

CloseHandle (deviceHandle); /* not a match - close and try again */
        }
        index++;
    }

return INVALID_HANDLE_VALUE;
}

对数据的访问,如果是有 in endpoint 和 out endpoint, 那么用常规的 ReadFile/WriteFile, 如果只有control endpoint, 用DeviceIoControl, 注意in 和out 是从device 的角度定义的
// in: PC ->Device
// out: Device->PC
发送数据给usb
    memset(inbuffer,0,REPORT_PACKET_SIZE+1);
    inbuffer[0]= 0x0 ;

memcpy(inbuffer+1,data);
    bResult = DeviceIoControl(hDevice,IOCTL_HID_SET_OUTPUT_REPORT,
        &inbuffer[0], REPORT_PACKET_SIZE+1,&outbuffer[0], 0,
        &numBytesReturned,(LPOVERLAPPED) NULL);

按规范inbuffer[0] 填 report id, 所以数据长度要0x44+1

接收数据
        outbuffer[0] = 0;   
        bResult = DeviceIoControl(hDevice,IOCTL_HID_GET_INPUT_REPORT,
                &inbuffer[0], 0,&outbuffer[0],0x44,
                &numBytesReturned,(LPOVERLAPPED) NULL); 
按msdn 的文档,outbuffer[0] 填 reportid
按hid 的规范,返回的数据中outbuffer[0]要填reportid, 可是招行的usbkey 好象没有这样做,也能正确解释

linux 下就要严格得多,首先至少要有 中断类型的 in endpoint, 然后数据要严格的第一个byte填 reportid
首先建立hid 的文件节点
#!/bin/sh
mkdir -p /dev/usb
mknod /dev/usb/hiddev0 c 180 96
mknod /dev/usb/hiddev1 c 180 97
mknod /dev/usb/hiddev2 c 180 98
mknod /dev/usb/hiddev3 c 180 99
mknod /dev/usb/hiddev4 c 180 100
mknod /dev/usb/hiddev5 c 180 101
mknod /dev/usb/hiddev6 c 180 102
mknod /dev/usb/hiddev7 c 180 103
mknod /dev/usb/hiddev8 c 180 104
mknod /dev/usb/hiddev9 c 180 105
mknod /dev/usb/hiddev10 c 180 106
mknod /dev/usb/hiddev11 c 180 107
mknod /dev/usb/hiddev12 c 180 108
mknod /dev/usb/hiddev13 c 180 109
mknod /dev/usb/hiddev14 c 180 110
mknod /dev/usb/hiddev15 c 180 111

发送和接收的程序如下
HANDLE connectToUSBHIDDevice (DWORD vendorID, DWORD productID, DWORD versionNumber)
{
    char evdev[50];
    int fd = -1, index;
    struct hiddev_event ev[64];
    struct hiddev_devinfo dinfo;
    char name[256] = "Unknown";
    int matched = 0;
    const char hid_dir[] = "/dev/usb";
    index = 0 ;
    while(!matched&&index <10)
    {
         sprintf(evdev, "%s/hiddev%d",hid_dir, index);
         if ((fd = open(evdev, O_RDONLY)) > 0) 
         {
            ioctl(fd, HIDIOCGDEVINFO, &dinfo);
            if ((vendorID == 0 ||dinfo.vendor == vendorID) &&
                (productID == 0 ||dinfo.product == (__s16)productID) &&
                (versionNumber == 0 || dinfo.version == versionNumber))
                return fd; /* matched */
             close(fd); /* not a match - close and try again */
        }
         index++ ;
    }
    return INVALID_HANDLE_VALUE;
}

int SendOutputReport(int fd, unsigned char reportID, unsigned char* vals, int num_vals, int delay)
{
int ret = 0, i;
char sval;
struct hiddev_report_info out_report;
struct hiddev_usage_ref uref;

uref.report_type = HID_REPORT_TYPE_OUTPUT;
uref.report_id = reportID;
uref.field_index = 0;
uref.usage_index = 0;

/* usage code for this for this usage */
ret = ioctl(fd, HIDIOCGUCODE, &uref);
if(0 > ret){
    perror("SendCommandFS: ioctl to get usage code for out report");
    return ret;
}

/* fill in usage values. */
for( i = 0; (i < num_vals) && vals; i++){
    uref.usage_index = i;
    sval = vals[i];
    uref.value = sval;
    ioctl(fd, HIDIOCSUSAGE, &uref);
    if(0 > ret){
      perror("SendCommandFS:ioctl to set usage value for out report");
      return ret;
    }
}

/* tell the driver about the usage values */
out_report.report_type = HID_REPORT_TYPE_OUTPUT;
out_report.report_id = reportID;
ioctl(fd, HIDIOCGREPORTINFO, &out_report);
if (0 > ret) {
    perror("SendCommandFS: ioctl to get out report info");
    return ret;
}
/* this ioctl puts the report on the wire */
ret = ioctl(fd, HIDIOCSREPORT, &out_report);
if (0 > ret) {
    perror("SendCommandFS: ioctl to send output report");
    return ret;
}

usleep(delay);
return ret;
}

int GetInputReport(int fd, unsigned char reportID, unsigned char *vals, int num_vals, int delay)
{
int ret = 0, i;
/*struct hiddev_usage_ref_multi in_usage_multi;*/
struct hiddev_usage_ref uref;
struct hiddev_report_info in_report;

/* tell the driver about the usage values */
in_report.report_type = HID_REPORT_TYPE_INPUT;
in_report.report_id = reportID;
    /* this ioctl gets the report on the wire */
ret = ioctl(fd, HIDIOCGREPORT, &in_report);

uref.report_type = HID_REPORT_TYPE_INPUT;
uref.report_id = reportID;
uref.field_index = 0;
uref.usage_index = 0;
uref.usage_code = 0xff000001;
ret = ioctl(fd, HIDIOCGUCODE, &uref);

for( i = 0; i < num_vals; i++){
      uref.usage_index = i;
      ioctl(fd, HIDIOCGUSAGE, &uref);
      usleep(delay);
      vals[i] = (unsigned char)(uref.value & 0x000000FF) ;
}
return ret;
}
数据好象是一个一个byte 发送, 最后再发一条触发的命令HIDIOCSREPORT
接收也是一样,先发一个控制命令HIDIOCGREPORT,然后一个一个byte 接收,可能都是先缓冲的缘故

HID 设备PC端软件的开发相关推荐

  1. 7款必须下载的PC端软件,必看

    7款必须下载的PC端软件,必看!  如今的职场内卷程度,看看互联网公司的996作息就能大致明白.想要在如此内卷的情况下脱颖而出,光靠个人的努力是相当困难的,我们必须借助一些外力才行.一个顺手好用的设 ...

  2. 完美实现PC端软件控制手机(无需安装任何APK)(一)

    (需要了解开发细节和demo的朋友可以私信我或者加微信Kingthink) 一.背景 有一天,手机屏幕摔坏掉,我只能看那寥寥可数的开机.Power键,欲哭无泪, 我想要有一款PC端控制软件在必要时就能 ...

  3. 好用PC端软件分享,来看看有没有你的心头好

    今天我汇总了几个好用的PC端软件,朋友们来看一下有没有同款吧. 1.AnyDesk AnyDesk 是一个特别小巧实用的远程桌面控制软件.我们都知道QQ 有自带的远程协助,但是呢,勉强凑合.AnyDe ...

  4. 整理!这10款PC端软件,设计师必备!

    相信有很多人都很羡慕那些设计大神能够做出杰出的设计,但你知不知道那些大神是用什么软件做出来的呢?下面介绍的这10款软件都是设计大神钟爱的,仔细看一看,总有一款适合你. 1.CorelDRAW Core ...

  5. 微软认真了!微软Surface平板强来袭击,采用IntelX86 I5 CPU 支持Windows8 Pro版本 全面兼容PC端软件

    背景:不久之前微软在一连串的"大动作"其中之一就是发布了在所有人意料之外的微软牌产品,那就是微软本厂非OEM的平板 -- Surface Tablet:苹果的ipad,在谷歌发布了 ...

  6. 微软认真了!微软Surface平板强来袭击,采用IntelX86 I5 CPU 支持Windows8 Pro版本 全面兼容PC端软件...

    背景:不久之前微软在一连串的"大动作"其中之一就是发布了在所有人意料之外的微软牌产品,那就是微软本厂非OEM的平板 -- Surface Tablet:苹果的ipad,在谷歌发布了 ...

  7. 推荐3款实用的PC端软件,工作生活两不误,每天5分钟悄悄成长

    Piti插件 Piti插件,是一款实用且免费的PPT插件. Piti插件 有了它,可以让你的PPT制作,变得十分简单. 在它这里,拥有着诸多人性化的功能.如果你深入了解了它,你会发现,Piti提供丰富 ...

  8. 非常好用的PC端软件,个个都是宝藏!

    电脑浏览器 Edge Edge,是微软推出的基于 Chromium 内核的浏览器,用起来和谷歌的 Chrome 浏览器几乎一模一样,而且 Chrome 上已有的插件,换到 Edge 同样可以使用. 和 ...

  9. 尽收眼底,3款常用的PC端软件,简洁实用又良心

    星愿浏览器 星愿浏览器,是一款基于chromium内核的开源浏览器. 星愿浏览器 虽然其面向的群体主要为大学生,但这与它强大的功能并不冲突. 如果你深入使用了它,你就会发现,它不仅兼容Chrome插件 ...

最新文章

  1. 2018/8/24阅读文献 A Unified Model for Multi-Objective Evolutionary Algorithms with Elitism
  2. [Leedcode][第215题][JAVA][数组中的第K个最大元素][快排][优先队列]
  3. 负载均衡策略_常见的负载均衡策略
  4. 用计算机写作ppt文库,计算机专业英语Unit 19 计算机专业英语写作.pptx
  5. vue用html做报表,Vue配置生成无限分割的表格,可快速实现任意复杂报表
  6. cuda Synchronization
  7. 镜头上的四线电机怎么驱动_MS3988/N双路步进电机驱动MS4982内置16细分单路步进电机驱动MS41908M摄像机用镜头聚焦、变倍、自动...
  8. 【EasyNetQ】- 发布
  9. D2 日报 2019年6月5日
  10. 《码出高效》个人总结1.1 二进制,按位运算
  11. 对象可以创建数组吗_淘宝联盟平台可以创建淘礼金吗?相关问题解答
  12. 分布式事务CAP理论
  13. Python---贪心的狗熊
  14. 从阿里离职10天后,疯狂投简历面了4家公司,我的感受——不可名状的痛
  15. kf.qq.com.lol.html,英雄联盟安全信用星级,英雄联盟封号查询中心
  16. 四元数和旋转轴及旋转角度之间的转换理解实例
  17. 终端数据防泄漏解决方案
  18. 基于GMM模型的图像分割与颜色迁移算法
  19. 数据分析面试、笔试题汇总+解析(二)
  20. 《未来已来- 马云》笔记

热门文章

  1. Spring5注解编程基础组件
  2. 动力环境监控消防子系统
  3. 影子模型(Java篇)
  4. 成都市等市、州 《2015工程量清单计价定额》 人工费调整〔2016〕17
  5. python解题软件哪个好用_一些好用的Python工具整理(持续更新中)
  6. 百度地图高德地图横向评测nbsp;出…
  7. Houdini通过随机UV实现无限不重复贴图
  8. 如何做一个可实施的故障预案?
  9. Text Template 模板
  10. ai怎么渐变颜色_Illustrator如何调渐变颜色 调渐变颜色方法分享