USB HID在系统下通信的一些总结

news2024/12/24 3:10:49

前言

这篇文章主要介绍在PC(上位机,Host)端,通过HID与硬件进行通信的一些总结,像很多同学肯定和我一样压根不想 去了解什么USB相关的资料,毕竟USB太复杂了,只想有个API给我们进行下数据就好了,像这里主要是我在进行hid通信的总结。

以下理解只是站在PC开发HID软件时的角度,所以讲述的一些USB知识不会很详细

USB 简单介绍

这里只是简单描述一下USB,如果感兴趣的同学可以去查看《从零开始学USB》,作者:to-run-away,这是介绍USB一个系类的文章,讲的特别详细。

USB传输类型

详细介绍请参考从零开始学USB(十三、USB的四种传输类型(2))

USB协议规定了4种传输类型:批量传输、等时传输、中断传输和控制传输,像HID主要使用中断传输和控制传输。

USB描述符

详细请参考

USB有好几种描述符
在这里插入图片描述像我们只需要了解设备描述符,配置描述符,接口描述符,端点描述符,还有HID描述符就好了。

设备描述符

主要是描述USB设备的信息,比如VID,PID(VID是厂商向USB协会申请的一个ID,这个ID可以网上查的到的(查询链接),PID表示这款产品的ID,属于每个公司自己定义的。我们一般根据VID,PID去查找USB设备的)以及USB设备的名字,序列号,制造商这些。

        BYTE blength;                               //设备描述符的字节数大小   
        BYTE bDescriptorType;                      //设备描述符类型编号   
        WORD bcdUSB;                                //USB版本号   
        BYTE bDeviceClass;                          //USB分配的设备类代码   
        BYTE bDeviceSubClass;                       //USB分配的子类代码   
        BYTE bDeviceProtocol;                       //USB分配的设备协议代码   
        BYTE bMaxPacketSize0;                       //端点0的最大包大小   
        WORD idVendor;                              //厂商编号   
        WORD idProduct;                             //产品编号   
        WORD bcdDevice;                             //设备版本 
        BYTE iManufacturer;                         //设备厂商字符串的索引   
        BYTE iProduct;                              //描述产品字符串的索引   
        BYTE iSerialNumber;                         //描述设备序列号字符串的索引   
        BYTE bNumConfigurations;                    //可能的配置数量   

配置描述符

主要描述有多少个接口。

        BYTE bLength;                               //配置描述符的字节数大小   
        BYTE bDescriptorType;                       //配置描述符类型编号   
        WORD wTotalLength;                          //此配置返回的所有数据大小   
        BYTE bNumInterfaces;                        //此配置所支持的接口数量   
        BYTE bConfigurationValue;                   //Set_Configuration命令所需要的参数值   
        BYTE iConfiguration;                        //描述该配置的字符串的索引值   
        BYTE bmAttributes;                          //供电模式的选择   
        BYTE MaxPower;                              //设备从总线提取的最大电流   

接口描述符

主要描述这个接口下面有多个端点,以及这个接口做什么用的,比如这个接口是HID或者CDC,音频输入设备(UAC),视频输入设备(UVC)等。

端点描述符

主要描述这个端点最大一次可以传输多少数据,以及这个端点支持什么传输方式。在HID中支持控制传输和中断传输
控制传输 主要用来获取USB信息,以及可以用来下命令去控制设备,读取时需要主动去读,USB设备无法主动给我们发送数据。
中断传输 如果端点是OUT的话,那么我们PC可以往USB设备被发送数据,USB设备无法主动向这个端点发送数据,如果是端点是IN的话,那么PC无法往这个端点发送数据,但是USB设备可以主动像里面发送数据。

HID描述符

HID描述符特别复杂,想了解的大家可以去看这几篇文章USB HID设备报告描述符详解, USB HID报告描述符教程

HID描述符和端点一样也是在接口下面的,主要是描述PC和USB设备之间通信的数据是做什么用的,像我们只需要知道这几个东西Usage Page/Usage以及Report ID。

Usage Page/Usage可以理解为一个报表说明,里面会描述这个报表数据多长以及这些数据是干啥子用的,范围为0x00-0xFFFF,其中0x00-0xFEFF为预留的,用于描述一些标准规范的报表,比如鼠标指针,键盘按键值等,0xFF00-0xFFFF用于给开发商自定义使用,像在设备管理器中看到有设备名字为符合 HID 标准的供应商定义设备的表示为Usage Page为0xFF00-0xFFFF的HID报表。

Report ID 主要用户区分不同报表的,比如一个HID描述符里面可能有多个Usage Page/Usage,当PC和HID进行通信时,下的数据不知道对应哪个报表的,因此需要有个ID进行区分,这就是Report ID 。

比如有两个Usage Page,0xFF00,0xFF01,其中0xFF00描述数据用于设置灯光亮度,0xFF01描述数据用于设置音量大小,与0xFF00,0xFF01对应的ReportID分别为0x05,0x06,当我们发一段数据到设备,设备收到0x05开头的数据就知道是设置灯光亮度,收到0x06的数据是用来调节音量。

HID通信

HID通信我们使用 https://github.com/libusb/hidapi 这个开源库,这个支持跨平台,不建议去使用libusb去进行HID访问,太底层了,学习使用成本高。
在进行通信前,需要先和硬件了解USB HID设备的以下信息:

  1. 需要控制的Usage Page/Usage 是多少。
  2. 需要数据的Report ID是多少(不存在的话默认为0)。
  3. Usage Page/Usage 一次支持写入多少字节的数据。
    这些是通信的关键识别标志。

hidapi中有以下几个函数:

//写入数据到设备(支持控制传输以及中断传输),如果设备有ReportId,那么data首字节需要为此ID,如果没有,首字节默认需要为0
//返回写入的字节数,如果写入为-1说明写入失败
int  HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);
//从设备读取数据(只支持中断传输,这个数据是设备主动传给PC的),如果设备有ReportId,那么data首字节为此ID
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
//
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
//
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
//通过控制传输读取设备数据,如果设备有ReportId,那么data首字节需要为此ID,如果没有,首字节默认需要为0
//返回读取到的数据长度,小于0读取失败
int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length);

一般使用比较多的是hid_write,hid_read_timeout,hid_get_input_report,具体看设备端的定义。

HID通信

Windows下,Windows的hid驱动针对HID每个Usage Page/Usage都抽象出来了一个Path,如下图所示,因此我们想往某个Usage Page/Usage发送数据时,就需要去打开与之对应的HID对象,不然无法写入。
在这里插入图片描述
MAC端,MAC没有像Windows那样,只需要使用hidapi找到指定VID PID的设备,直接打开通信即可。

打开设备

hidapi里面这个函数用于打开设备,在Mac端使用时没有问题,但是当存在多个Usage Page,在Windows端使用就会存在问题,他只会打开hid_enumerate扫描到的第一个Usage Page,比如存在0x01和0xFF00,当想打开0xFF00时,它只会打开0x01的。

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

因此建议将上面的函数改成如下所示,通过指定Usage Page进行打开

HID_API_EXPORT hid_device* HID_API_CALL hid_open2(unsigned short vendor_id, unsigned short product_id, unsigned short usage_page)
{
    /* TODO: Merge this functions with the Linux version. This function should be platform independent. */
    struct hid_device_info* devs, * cur_dev;
    const char* path_to_open = NULL;
    hid_device* handle = NULL;
    devs = hid_enumerate(vendor_id, product_id);
    cur_dev = devs;
    while (cur_dev) {
        if (cur_dev->vendor_id == vendor_id &&
                cur_dev->product_id == product_id) {
            if (usage_page == cur_dev->usage_page) {
                path_to_open = cur_dev->path;
                break;
            }
        }
        cur_dev = cur_dev->next;
    }
    if (path_to_open) {
        /* Open the device */
        handle = hid_open_path(path_to_open);
    }
    hid_free_enumeration(devs);
    return handle;
}

写入数据

  /** @brief Write an Output report to a HID device.

    The first byte of @p data[] must contain the Report ID. For
    devices which only support a single report, this must be set
    to 0x0. The remaining bytes contain the report data. Since
    the Report ID is mandatory, calls to hid_write() will always
    contain one more byte than the report contains. For example,
    if a hid report is 16 bytes long, 17 bytes must be passed to
    hid_write(), the Report ID (or 0x0, for devices with a
    single report), followed by the report data (16 bytes). In
    this example, the length passed in would be 17.

    hid_write() will send the data on the first OUT endpoint, if
    one exists. If it does not, it will send the data through
    the Control Endpoint (Endpoint 0).

    @ingroup API
    @param dev A device handle returned from hid_open().
    @param data The data to send, including the report number as
      the first byte.
    @param length The length in bytes of the data to send.

    @returns
      This function returns the actual number of bytes written and
      -1 on error.
      Call hid_error(dev) to get the failure reason.
  */
  int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);

写入数据时,假如USB描述设置最大一次可以写入64字节,如果存在Report ID,那么第一个字节必须是Report ID,那么还可以写入63个字节的有效数据,一起64字节。如果不存在Report ID,那边第一个字节固件设置为0,后面还可能写64字节,一起写入65字节。(如果写入的数据不足建议补齐到64或者65)

例如:

	//写入数据到Report ID 0x12
	unsigned char data[64] = { 0 };
	data[0] = 0x12;
	hid_write(pDevice, data, sizeof(data));
	
	//没有Report ID
	unsigned char data[65] = { 0 };
	data[0] = 0;
	data[1] = 0x80;
	hid_write(pDevice, data, sizeof(data));

读取数据(控制传输)

  /** @brief Get a input report from a HID device.

    Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0)

    Set the first byte of @p data[] to the Report ID of the
    report to be read. Make sure to allow space for this
    extra byte in @p data[]. Upon return, the first byte will
    still contain the Report ID, and the report data will
    start in data[1].

    @ingroup API
    @param dev A device handle returned from hid_open().
    @param data A buffer to put the read data into, including
      the Report ID. Set the first byte of @p data[] to the
      Report ID of the report to be read, or set it to zero
      if your device does not use numbered reports.
    @param length The number of bytes to read, including an
      extra byte for the report ID. The buffer can be longer
      than the actual report.

    @returns
      This function returns the number of bytes read plus
      one for the report ID (which is still in the first
      byte), or -1 on error.
      Call hid_error(dev) to get the failure reason.
  */
  int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length);

读取数据时,如果存在Report ID,那么第一个字节必须先填写Report ID,才能读取到指定Report ID的数据,如果没有第一个字节固定设置为0
例如:

	//读取Report ID 0x12的数据
	unsigned char data[64] = { 0 };
	data[0] = 0x12;
	hid_get_input_report(pDevice, data, sizeof(data));
	
	//没有Report ID
	unsigned char data[65] = { 0 };
	data[0] = 0;
	hid_get_input_report(pDevice, data, sizeof(data));

读取数据(中断传输)

  /** @brief Read an Input report from a HID device with timeout.

    Input reports are returned
    to the host through the INTERRUPT IN endpoint. The first byte will
    contain the Report number if the device uses numbered reports.

    @ingroup API
    @param dev A device handle returned from hid_open().
    @param data A buffer to put the read data into.
    @param length The number of bytes to read. For devices with
      multiple reports, make sure to read an extra byte for
      the report number.
    @param milliseconds timeout in milliseconds or -1 for blocking wait.

    @returns
      This function returns the actual number of bytes read and
      -1 on error.
      Call hid_error(dev) to get the failure reason.
      If no packet was available to be read within
      the timeout period, this function returns 0.
  */
  int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);

读取数据时,如果存在Report ID,那么设备返回的数据第一个字节是Report ID。
中断传输设备主动给PC发送数据,因此如果设备发送过多,系统过有一个缓冲区进行缓冲,因此直接调用hid_read_timeout,可能可以连续调用hid_read_timeout读取多条数据,这是因为缓冲了多条数据在系统里面,如果全部读取完之后,设备也一直没有回复数据过来,再次调用会阻塞,超时时间通过milliseconds设置。
例如:

	unsigned char data[64] = { 0 };
	data[0] = 0x12;
	hid_read_timeout(pDevice, data, sizeof(data));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1181877.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D视觉引导纸箱拆码垛,助力物流行业转型升级

近年来,自动化和智能化技术在各行业的应用越来越广泛,特别是在物流和仓储领域。纸箱拆码垛是物流仓储中的一个重要环节。 人工分拣效率低、错误率高、成本高,传统的拆码垛设备存在兼容性差,对纸箱的识别率不高、操作不灵活等问题…

【分布式事务】初步探索分布式事务的概率和理论,初识分布式事的解决方案 Seata,TC 服务的部署以及微服务集成 Seata

文章目录 一、分布式服务案例1.1 分布式服务 demo1.2 演示分布式事务问题 二、分布式事务的概念和理论2.1 什么是分布式事务2.2 CAP 定理2.3 BASE 理论2.4 分布式事务模型 三、分布式事务解决方案 —— Seata3.1 什么是 Seata3.2 Seata 的架构3.3 Seata 的四种分布式事务解决方…

渗透实战靶机2wp

0x00 简介 1、测试环境 目标IP:10.xxxx 测试IP:192.168.139.128 测试环境:win10、kali等 测试时间:2021.7.22-2021.7.22 测试人员:ruanruan 2、测试过程 本次实战主要通过对收集到的端口、目录等信息进行持续整…

​怎么测试websocket接口

在部分业务中,我们需要使用长连接,我们可以使用http长连接或者websocket,开发结束后难免会遇到测试问题,这里推荐2个,一个是postman,一个是网站 postman 测试网站 测这边推荐测试网站,支持ws/w…

赴日工作赴日IT 如何找到一份日本IT工作?

IT在日本属于普通白领工作,那些想靠IT工作发财就不必考虑了。但是靠IT工作能安安稳稳的过个自己的小日子没问题,买房买车问题不大,作为一个普通人,在日本可以过的比较舒服。对有在日本长期发展的打算的还算是一个比较好的方向&…

Vue创建浅层响应式数据

shallowReactive:只处理对象第一层数据的响应式(浅响应式)。 shallowRef:只处理基本数据类型的响应式,不处理对象类型的响应式。 shallowReactive 适用于:如果有一个对象类型的数据,结构比较深…

【软考】2023下半年系统集成项目管理工程师案例分析真题(第五批次)

2023下半年系统集成项目管理工程师案例分析真题(第五批次) 案例一 (17分)-配置管理案例二 (20分)-进度管理案例三 (18分)-风险管理案例四 - 人力资源管理 系列文章版本记录 案例一 (17分)-配置管理 某游戏公…

【Spring】@Component组件

大前提&#xff1a; 添加了相关的约束文件以及注解支持 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:…

理解JavaScript模运算符

本文翻译自 Understanding the JavaScript Modulo Operator&#xff0c;作者&#xff1a;joshwcomeau&#xff0c; 略有删改。 当我第一次学习编码时&#xff0c;我记得发现模运算符&#xff08;%&#xff09;非常令人困惑。&#x1f62c; 当你不明白它的原理的时候&#xff0…

深度学习之基于YoloV5-Deepsort人物识别与追踪系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 YoloV5-Deepsort是一种基于深度学习的人物识别与追踪系统&#xff0c;具有较高的准确率和实时性能。 YoloV5是一种…

4.三大基础排序 -选择排序、冒泡排序、插入排序

排序算法 文章目录 冒泡排序算法步骤动图代码优化总结 选择排序算法步骤动图代码总结 插入排序算法步骤动图代码总结 排序算法&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。一般默认排序是按照由小到大即…

2.HTML中常用浏览器

2.常用浏览器 2.1 什么是浏览器 浏览器是网页显示&#xff0c;运行的平台。常用的浏览器有IE&#xff0c;火狐&#xff0c;谷歌&#xff0c;Safari和Opera等 平时成为五大浏览器 2.2 浏览器内核 浏览器内核&#xff08;渲染引擎&#xff09;&#xff1a;负责读取网页内容&…

用CMake编译项目 CMake和g++的区别

0.同样是编译c文件有了g为什么又出了个CMake&#xff1f; g适用于要编译的文件较少的情况。 而CMake像是写好的脚本&#xff0c;可以一键编译很多c文件。一.创建项目和CMakeLists.txt文件 1.1 创建一个项目&#xff0c;目录结构如下 1.2 在项目的根目录下创建一个CMakeLists.…

个人常用Linux命令

来自 linux命令学习-2023-8-1 153913.md等 1、切换目录 cd //切换目录 cd change directory cd 目录名 cd .. 返回上一级目录 pwd显示当前所处目录cd 绝对路径 cd ~ 表示一个用户的home目录 cd - 表示上一次访问的目录 cd / 表示进入根目录下//新建目录/data,并且进入/data…

001、Nvidia Jetson Nano Developer KIT(b01)-系统与登录

之——镜像烧录与远程登录 杂谈 Nvidia Jetson Nano Developer KIT&#xff08;b01&#xff0c;4G&#xff09;&#xff0c;系统配置全纪录&#xff0c;镜像烧录、系统安装、远程桌面安装、cuda与torch安装、pycharm、pycuda、tensorrt等等。 正文 1.开发板系统安装 1.1 开发…

创意涌动:CSDN·大学生博主14天创作挑战赛·第二期,正式开启报名!

文章目录 ⭐️ 活动介绍⭐️ 活动详情⭐️ 活动奖品⭐️ 活动流程⭐️ 评审规则⭐️ 报名&投稿注意事项⭐️ 关于活动官方 活动报名地址&#xff08;点击跳转&#xff09; 本次活动与官方活动及其他博主的创作型活动并不不冲突&#xff01; ⭐️ 活动介绍 亲爱的大学生博主…

【无标题】【教3妹学编程-算法题】2918. 数组的最小相等和

3妹&#xff1a;呜呜&#xff0c;烦死了&#xff0c; 脸上长了一个痘 2哥 : 不要在意这些细节嘛&#xff0c;不用管它&#xff0c;过两天自然不就好了。 3妹&#xff1a;切&#xff0c;你不懂&#xff0c;影响这两天的心情哇。 2哥 : 我看你是不急着找工作了啊&#xff0c; 工作…

Leo赠书活动-05期 【打造敏捷测试团队】文末送书5本

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

运动想象 EEG 信号分析

基于运动想象的公开数据集&#xff1a;Data set IVa (BCI Competition III)1 数据描述参考前文&#xff1a;https://blog.csdn.net/qq_43811536/article/details/134224005?spm1001.2014.3001.5501 本文使用公开数据集 Data set IVa 中的部分被试数据&#xff0c;数据已公开可…

Rust和Pytho写一段采集公众号代码

首先&#xff0c;我们需要安装Rust和Python的requests库。Rust的requests库可以用来发送HTTP请求&#xff0c;而Python的requests库可以用来处理HTTP响应。 // 导入所需的库 use std::io; use std::env;// 使用rustc命令来编译我们的程序 fn main() {// 获取命令行参数let args…