当前位置: 首页 > news >正文

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

前言

这篇文章主要介绍在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. Fordevices which only support a single report, this must be setto 0x0. The remaining bytes contain the report data. Sincethe Report ID is mandatory, calls to hid_write() will alwayscontain one more byte than the report contains. For example,if a hid report is 16 bytes long, 17 bytes must be passed tohid_write(), the Report ID (or 0x0, for devices with asingle report), followed by the report data (16 bytes). Inthis example, the length passed in would be 17.hid_write() will send the data on the first OUT endpoint, ifone exists. If it does not, it will send the data throughthe 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 asthe first byte.@param length The length in bytes of the data to send.@returnsThis 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 0x12unsigned char data[64] = { 0 };data[0] = 0x12;hid_write(pDevice, data, sizeof(data));//没有Report IDunsigned 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 thereport to be read. Make sure to allow space for thisextra byte in @p data[]. Upon return, the first byte willstill contain the Report ID, and the report data willstart in data[1].@ingroup API@param dev A device handle returned from hid_open().@param data A buffer to put the read data into, includingthe Report ID. Set the first byte of @p data[] to theReport ID of the report to be read, or set it to zeroif your device does not use numbered reports.@param length The number of bytes to read, including anextra byte for the report ID. The buffer can be longerthan the actual report.@returnsThis function returns the number of bytes read plusone for the report ID (which is still in the firstbyte), 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 IDunsigned 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 returnedto the host through the INTERRUPT IN endpoint. The first byte willcontain 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 withmultiple reports, make sure to read an extra byte forthe report number.@param milliseconds timeout in milliseconds or -1 for blocking wait.@returnsThis 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 withinthe 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));

相关文章:

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

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

[java进阶]——方法引用改写Lambda表达式

🌈键盘敲烂,年薪30万🌈 目录 📕概念介绍: ⭐方法引用的前提条件: 1.引用静态方法 2.引用构造方法 ①类的构造: ②数组的构造: 3.引用本类或父类的成员方法 ①本类&#xff1…...

lvs dr+keepalived

基于keepalived(主从双主) LVS(DR模型) DNS实现http高可用集群 keepalived高可用主机IP:172.21.5.22和172.21.5.21 http服务高可用主机IP:172.21.5.16和172.21.5.18 VIP采用172.16.32.5 各虚拟机及主机名和IP对应关系如下所示: 虚拟机主机…...

如何使新手小白编码能力暴涨之Devchat-AI

在这个快速发展的时代,开发者的任务越来越繁重,要求他们快速、高效地完成开发任务。然而,传统的开发方式已经无法满足这个需求。在这种情况下,Devchat的出现给开发者带来了新的帮助。Devchat是一个研发效能分析平台,它…...

SAP ABAP基础语法-TCODE学习(八)

一、 SD-如何在订单中使用客户层次定价的配置和维护步骤 在SD中有时会用到按客户层次进行定价的策略,我这里就将配置和维护的步骤简单写出来,供大家参考. 1)定义层次类型(VOH1) 路径:销售和分销->主数据->业务合作伙伴->客户->客户层次->定义层次类型 (1)伙…...

stm32-arm固件开发

文章目录 前言1. 前言 ARM体系结构与程序设计【全68讲】 1....

LeetCode 面试题 16.17. 连续数列

文章目录 一、题目二、C# 题解 一、题目 给定一个整数数组,找出总和最大的连续数列,并返回总和。 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。…...

基于人工蜂鸟算法的无人机航迹规划-附代码

基于人工蜂鸟算法的无人机航迹规划 文章目录 基于人工蜂鸟算法的无人机航迹规划1.人工蜂鸟搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要:本文主要介绍利用人工蜂鸟算法来优化无人机航迹规划。 …...

51单片机汇编-点亮一个led

文章目录 前言1.打开IDE2.设置编辑器3.设置输出4. 原理图5.编写代码6 编译7.下载8.其它代码1.LED闪烁2.跑马灯 前言 51单片机基础 51汇编实战 本章主要介绍打开一个led,具体采用51汇编 1.打开IDE 选择STC89C52RC 后缀是.asm 2.设置编辑器 3.设置输出 4. 原理图 5.编写代码 …...

每天一点python——day62

为了方便复制,我在下面附带了一个python文件。 C:\Users\Admin>python Python 3.9.13 (main, Aug 25 2022, 23:51:50) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32Warning: This Python interpreter is in a conda environment, but the environmen…...

基于SSM的智慧作业试题管理系统(有报告)。Javaee项目。

演示视频: 基于SSM的智慧作业试题管理系统(有报告)。Javaee项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构,通过Spring Sprin…...

ESP32 未来能够取代 STM32吗?

今日话题,ESP32 未来能够取代 STM32吗?ESP32和STM32各自有其特点和优势,能否取代彼此取决于具体应用和需求。STM32的流行除了性价比外,还有其强大的开发环境,例如Cubemx能够快速生成代码,使得上手STM32的速…...

Java连接Redis并操作Redis中的常见数据类型

目录 一. Java连接Redis 1. 导入依赖 2. 建立连接 二. Java操作Redis的常见数据类型存储 1. Redis字符串(String) 2. Redis哈希(Hash) 3. Redis列表(List) 4. Redis集合(Set) 一. Java连接Redis 1. 导入依赖 pom依赖…...

Python 基于分位数-正态分布转换的评分算法

在实验的时候遇到一个比较实际的问题,就是怎样对数据进行评分。比如我想根据样本的正确率进行打分,有两种方法,一种是将准确率排序,然后根据序号进行打分,这样可以排除极端数据的影响,但是准确率之间的差距…...

如何修改CentOS登录时默认目录

查了一下,有说改/etc/passwd文件的,有说改.bashrc文件的,也有说改.bash_profile,修改的方法都不一样。 我要改的是root登录时的目录,最后修改了/root/.bash_profile文件,只要加一行cd 路径就可以。 这个文…...

JavaFX Scene Builder Gluon 控件详解

在 JavaFX Scene Builder 工具中,Gluon 是一个扩展库,提供了一些额外的控件和功能,用于创建更丰富和现代化的用户界面。本文将详细介绍 Gluon 中的各个控件及其作用。 AppBar(应用栏) AppBar 是一个用于显示应用程序…...

Vue路由(router-link)——高亮、动态传参

一、声明式导航-导航链接 1.需求 实现导航高亮效果 如果使用a标签进行跳转的话,需要给当前跳转的导航加样式,同时要移除上一个a标签的样式,太麻烦!!! 2.解决方案 vue-router 提供了一个全局组件 router…...

Java中将List转换为Map

在Java 8中,Stream API和Collectors类提供了一种方便的方式来处理集合数据。其中,将List转换为Map是一个常见的操作。下面我们将介绍如何使用Stream API和Collectors类将List转换为Map。 首先,假设我们有一个User类,包含id和name两…...

进程控制2——进程等待

在上一小节中我们介绍了进程的创建(fork)与退出(main函数的return与exit函数) 并且要有一个意识,进程退出的时候只有三种情况: 1.进程退出,结果正确 2.进程退出,结果不正确 3.运行异…...

k8s service

文章目录 Service 基础概念Service 类型:Service 的工作流程:东西流量,南北流量NodePortLoadBalancer Service 基础概念 在 Kubernetes(K8s)中,Service 是一个抽象的概念,表示一个应用程序的逻…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

线程同步:确保多线程程序的安全与高效!

全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...

uniapp中使用aixos 报错

问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

2025季度云服务器排行榜

在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

Linux-进程间的通信

1、IPC: Inter Process Communication(进程间通信): 由于每个进程在操作系统中有独立的地址空间,它们不能像线程那样直接访问彼此的内存,所以必须通过某种方式进行通信。 常见的 IPC 方式包括&#…...

goreplay

1.github地址 https://github.com/buger/goreplay 2.简单介绍 GoReplay 是一个开源的网络监控工具,可以记录用户的实时流量并将其用于镜像、负载测试、监控和详细分析。 3.出现背景 随着应用程序的增长,测试它所需的工作量也会呈指数级增长。GoRepl…...

2025-05-08-deepseek本地化部署

title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek:小白也能轻松搞定! 如何给本地部署的 DeepSeek 投喂数据,让他更懂你 [实验目的]:理解系统架构与原…...