USB2.0枚举流程(以鼠标为例)——从零开始学习USB2.0协议(四)

1 总线枚举流程介绍

USB 2.0枚举的重要性在于它实现了即插即用功能,让主机自动识别、配置和管理设备,确保设备无需用户干预就能正常工作。

以下是 USB 2.0 枚举过程的概括步骤:

当一个设备连接到一个供电的端口时,将会有下列过程:

  1. 设备连接与电源稳定
    • 设备连接到供电的USB端口,HUB检测到端口电压变化,通过其中断端点向主机报告连接事件。
    • 主机发送Get_Port_Status请求给HUB以获取更多连接细节。
    • HUB通过检测总线空闲时差分线的电压来判断设备速度(低速、全速或高速)。此检测在复位操作之前完成。
  2. 端口复位与设备速度确认
    • 主机等待至少100ms,确保连接稳定和设备电源稳定。
    • 主机发送Set_Port_Feature请求,要求HUB对设备端口进行复位。HUB将数据线(D+和D-)驱动为低电平,持续至少10ms。
    • 复位期间,HUB不会影响其他端口。
    • 对于高速设备,初始以全速运行。如果HUB支持高速,会进行高速检测(通过检查设备是否在复位过程中发出Chirp信号)。如果设备支持高速,则切换到高速模式;否则保持全速。
  3. 设备默认状态与通信建立
    • 复位完成后,设备进入默认状态,使用默认地址0,端点0进行通信。此时设备可从总线获取最大100mA电流。
    • 主机通过反复发送Get_Port_Status请求,确认复位完成。
  4. 初始设备描述符获取
    • 主机向默认地址0发送Get_Descriptor请求,获取设备描述符。设备描述符中包含了端点0的最大包长度(位于第8字节)等重要信息。
    • 首次控制传输完成后,主机会要求HUB再次对设备进行复位,使设备进入确定状态。
  5. 地址分配
    • 主机发送Set_Address请求,为设备分配一个唯一的地址。设备使用新地址,进入地址状态。
  6. 详细设备信息获取
    • 主机使用新地址再次发送Get_Descriptor请求,获取设备描述符(包括设备类型、VID、PID、配置个数等)。
    • 主机获取配置描述符(通常为9字节),然后根据配置描述符中的配置集合总长度,获取完整的配置集合(包括配置描述符、接口描述符、端点描述符等)。
    • 根据需要,主机可能还会获取字符串描述符。
  7. 驱动匹配与加载
    • 主机解析描述符,根据设备信息选择合适的驱动程序。对于复合设备,通常需要匹配接口。
    • 主机将设备添加到USB总线设备列表,USB总线遍历驱动列表,调用match函数进行匹配。匹配成功后,绑定驱动。
  8. 设备配置
    • 设备驱动根据设备信息,发送Set_Configuration请求,选择设备的某个配置作为工作配置。
    • 设备使能所选配置的接口,进入配置状态。

2 模拟鼠标的枚举实现

2.1 鼠标外设程序

程序是基于公司开发的USB IP接口,在FPGA开发板上实现了模拟鼠标的功能,通过:

1、向串口中发送指令字符

2、控制USB向windows主机发送命令,移动鼠标光标。

2.1.1 任务分析

基于对前面对USB2.0协议的理解,可以来做一个鼠标的demo,下面可以来简单规划下整个任务。

  1. 目标

模拟鼠标上下左右移动和左右键,用向uart输入wsad字符来代替光标移动,输入c空格代替左右按键。

  1. 实现

为了实现主要在于:

1、构建描述符,解析主机设备请求,按照流程完成枚举

2、hid鼠标事件上报

2.1.2 描述符构建

鼠标本身属于HID设备的子类,和大多数的USB设备一样,HID设备也有USB设备的一些标准描述符,如设备描述符、配置描述符、接口描述符、端点找述符

但HID设备也有一些特殊描述符,如HID描述符报告描述符物理描述符

这里简单说明下,后面可能会针对HID设备进行详细介绍。

说明:

  • HID设备的设备类型不是在设备类型中定义,而是在接口描述符中定义。设备描述符中的bDeviceClass和bDeviceSubClass字段不用于标识属于HID类的设备。而是在接口描述符中使用bInterfaceClass和bInterfaceSubClass字段。
  • 接口描述符中的bInterfaceSubClass仅用于区分是否支持boot启动,bInterfaceProtocol也只在bInterfaceSubClass有效时用于区分boot起动的设备类型。
  • HID设备在非boot模式下,设备的类型是由报告描述符来定义的。一个报告描述符可以包含多个应用(调备)类型。
  • 物理描述符physical descriptor是可选的
  • 报告描述符report descriptor是必须的
  • 报告描述符的个数和各个报告描述符的长度在HID描述符中定义。
  • 报告描述符的获取是通过发向接口的标准请求实现的。

就上面的描述来看,要实现对USB2.0鼠标的注册,就要完成设备、配置、接口、HID、报告等描述符的。

我们可以根据HID对描述符的要求来构建下。

  1. 设备描述符
c 复制代码
const unsigned char DevDes[18]=
{
    0x12, /* bLength */
    0x01, /* bDescriptorType : device_descriptor */
    0x10,
    0x01, /* bcdUSB usb2.0 = 0x0200 usb1.1 = 0x0110 usb3.11 = 0x0311 */
    0x00, /* bDeviceClass */
    0x00, /* bDeviceSubClass */
    0x00, /* bDeviceProtocol */
    0x40, /* bMaxPacketSize */
    0x66,
    0x66, /* idVendor : 2 Bytes */
    0x88,
    0x88, /* idProduct : 2 Bytes */
    0x01,
    0x00, /* bcdDevice rel. 1.00 */
    0x01, /* Index of manufacturer string */
    0x02, /* Index of product string */
    0x00, /* Index of serial number string */
    0x01  /* bNumConfigurations */
};
  1. 配置描述符

这里将配置描述符、接口描述符、类特殊描述符、端点描述符一同放到配置描述符。

是因为正常的主机获取配置描述符过程:

1、第一次先获取9字节长度的配置描述符,然后根据配置描述符中配置集合的长度,再次获取配置描述符集合(不同主机程序可能有些差异)。

2、第二次获取的时候,设备会将配置描述符、接口描述符、类特殊描述符、端点描述符等一并返回。

后面抓包会看到该流程。

c 复制代码
const unsigned char ConDes[9 + 9 + 9 + 7]=
{
    // Configuation Descriptor
    0x09, // bLength
    0x02, // bDescpriptorType
    sizeof(ConDes) & 0xFF,
    (sizeof(ConDes)) >> 8 & 0xFF, // wTotalLength
    0x01, // bNumInterfaces
    0x01, // bConfigurationValue
    0x00, // iConfiguration
    0x80, // bmAttributes
    0x32, // bMaxPower : 100mA

    // Interface Descriptor
    0x09, // bLength
    0x04, // bDescriptorType
    0x00, // bInterfaceNumber
    0x00, // bAlternateSetting
    0x01, // bNumEndpoints(not include Ep0)
    0x03, // bInterfaceClass (Hid)
    0x01, // bInterfaceSubClass
    0x02, // bInterfaceProtocol : mouse
    0x00, // iInterface
    
    // HID descriptor
    0x09, // bLength
    0x21, // bDescriptorType
    0x11,
    0x01, // bcdHID : 0x0111
    0x00, // bContryCode
    0x01, // bNumDescriptor
    0x22, // bDescriptorType
    sizeof(ReportDescriptor) & 0xFF,
    (sizeof(ReportDescriptor) >> 8) & 0xFF, // wDescriptorLength
    
    // Endpoint Descriptor
    0x07, // bLength
    0x05, // bDescriptorType
    0x81, // Ep1 : In
    0x03, // bmAttributes : Interrupt
    0x04,
    0x00, // wMaxPackeSize : 0x0004
    0xA  // bInterval : 10ms
};
  1. 字符描述符
c 复制代码
/* langDes */
const unsigned char LangDes[]=
{
    0x04, // bLength
    0x03,	// bDescriptorType
    0x09,
    0x04 // wLANGID(0x0409:english)
};

/* Manuf */
const unsigned char Manuf_Des[]=
{
    0x12,
    0x03,
    0x68, 0x00, //hezaizai
    0x65, 0x00,
    0x7A, 0x00,
    0x61, 0x00,
    0x69, 0x00,
    0x7A, 0x00,
    0x61, 0x00,
    0x69, 0x00,
};
/* product Des */
const unsigned char Prod_Des[]=
{
    0x32,
    0x03,
    0x44, 0x00, // Demo USB2.0 optical Mouse
    0x65, 0x00,
    0x6D, 0x00,
    0x6F, 0x00,
    0x20, 0x00,
    0x55, 0x00,
    0x53, 0x00,
    0x42, 0x00,
    0x32, 0x00,
    0x2E, 0x00,
    0x30, 0x00,
    0x20, 0x00,
    0x6F, 0x00,
    0x70, 0x00,
    0x74, 0x00,
    0x69, 0x00,
    0x63, 0x00,
    0x61, 0x00,
    0x6C, 0x00,
    0x20, 0x00,
    0x4D, 0x00,
    0x6F, 0x00,
    0x75, 0x00,
    0x73, 0x00,
    0x65, 0x00,
};
/* product ser */
const unsigned char SerDes[18] =
{
    0x12,
    0x03,
    0x32, 0x00, // 20251024
    0x30, 0x00,
    0x32, 0x00,
    0x35, 0x00,
    0x31, 0x00,
    0x30, 0x00,
    0x32, 0x00,
    0x34, 0x00,
};

2.1.3 设备请求解析

  1. 设备请求格式封装

按照设备请求格式,代码封装如下:

c 复制代码
typedef	union _REQUEST_PACK {
	unsigned char buffer[8];
	struct {
		unsigned char bmReuestType;
		unsigned char bRequest;
		uint16_t wValue;
		uint16_t wIndx;
		uint16_t wLength;
	} r;
} mREQUEST_PACKET, *mpREQUEST_PACKET;

2、根据设备请求格式解析

c 复制代码
void handle_usb_request(mREQUEST_PACKET USB_request)
{
    setUpStage = 1;

    if (USB_request.r.bmReuestType & 0x80) {
        printf("dir: GET\n");
    } else {
        printf("dir: SET\n");
    }
    switch ((request.r.bmReuestType >> 5) & 0x3) {
        case 0:
            printf("std request->");
            handle_std_request(USB_request); //handle standard request
            break;
        case 1:
            printf("class request->");
            break;
        case 2:
            printf("manufacture request->");
            break;
        case 3:
            printf("reserve req!\r\n");
            break;
    }
}

// only handle GET_DESCRIPTOR and SET_ADDRESS req
void handle_std_request(mREQUEST_PACKET USB_request)
{
    switch (USB_request.r.bRequest) {
        case GET_STATUS:
            printf("GET_STATUS->");
            break;
        case CLEAR_FEATURE:
            printf("CLEAR_FEATURE->");
            break;
        case SET_FEATURE:
            printf("SET_FEATURE->");
            break;
        case SET_ADDRESS:
            printf("SET_ADDRESS:\r\n");
            USB_SetDevAddress(USB_request.r.wValue);
            break;
        case GET_DESCRIPTOR:
            printf("GET_DESCRIPTOR->");
            handle_get_desc_request(USB_request);
            break;
        case SET_DESCRIPTOR:
            printf("SET_DESCRIPTOR->");
            break;
        case GET_CONFIGURATION:
            printf("GET_CONFIGURATION->");
            break;
        case SET_CONFIGURATION:
            printf("SET_CONFIGURATION->");
            break;
        case GET_INTERFACE:
            printf("GET_INTERFACE->");
            break;
        case SET_INTERFACE:
            printf("SET_INTERFACE->");
            break;
        case SYNCH_FRAME:
            printf("SYNCH_FRAME->");
            break;
    }
}
  1. 根据设备请求类型处理描述符
c 复制代码
void handle_get_desc_request(mREQUEST_PACKET USB_request)
{
    int len;
    unsigned char req_type = USB_request.r.wValue >> 8;
    unsigned char req_index = USB_request.r.wValue & 0xF;

    switch (req_type) {
        case 1:
            printf("device desc id(%d)\n",req_index);
            VarSetupDescr = DevDes;
            mVarSetupLength = MIN(sizeof(DevDes), SetupMaxLength);
            break;
        case 2:
            printf("cfg desc id(%d)\n",req_index);
            VarSetupDescr = ConDes;
            mVarSetupLength = MIN(sizeof(ConDes), SetupMaxLength);
            break;
        case 3:
            printf("string desc:");
            if ((USB_request.r.wValue & 0xff) == 0) {
                printf("LangDes\n");
                VarSetupDescr = LangDes;
                mVarSetupLength = MIN(sizeof(LangDes), SetupMaxLength);
            } else if ((USB_request.r.wValue & 0xff) == 1) {
                printf("Manuf_Des id(%d)\n",req_index);
                VarSetupDescr = Manuf_Des;
                mVarSetupLength = MIN(sizeof(Manuf_Des), SetupMaxLength);
            } else if ((USB_request.r.wValue & 0xff) == 2) {
                printf("Prod_Des id(%d)\n",req_index);
                VarSetupDescr = Prod_Des;
                mVarSetupLength = MIN(sizeof(Prod_Des), SetupMaxLength);
            } else if ((USB_request.r.wValue & 0xff) == 3) {
                printf("SerDes id(%d)\n",req_index);
                VarSetupDescr = SerDes;
                mVarSetupLength = MIN(sizeof(SerDes), SetupMaxLength);
            } else {
                printf("other\n");
            }
            break;
        case 4:
            printf("interface desc id(%d)\n",req_index);
            break;
        case 5:
            printf("endpoint desc id(%d)\n",req_index);
            break;
        case 6:
            printf("qualifier desc id(%d)\n",req_index);
            VarSetupDescr = DeviceQualifierDesc;
            mVarSetupLength = MIN(sizeof(DeviceQualifierDesc), SetupMaxLength);
            break;
        case 0x22:
            printf("hid desc\n");
            VarSetupDescr = ReportDescriptor;
            mVarSetupLength = MIN(sizeof(ReportDescriptor), SetupMaxLength);
            break;
        default:
            printf("other desc\n");
    }
    UsbEp0Up();
}
  1. uart 模拟鼠标行为
c 复制代码
void USART0_IRQHandler(void)
{
    uint8_t USART0_receive_ch;

    if (USART_GetITStatus(USART0, USART_STATUS_RXIP) == SET) {
        USART0_receive_ch = USART_ReceiveData(USART0);
        
        // 鼠标移动指令
        if (USART0_receive_ch == 'a') {
            buf_mouse[1] = -10;  // X位移:左移
            buf_mouse[2] = 0;    // Y位移:不变
            printf("left\n");
            need_release = 0;   // 移动事件不需要释放,所以清除need_release(防止点击事件未完成)
        } else if (USART0_receive_ch == 'd') {
            buf_mouse[1] = 10;   // X位移:右移
            buf_mouse[2] = 0;    // Y位移:不变
            printf("right\n");
            need_release = 0;
        } else if (USART0_receive_ch == 'w') {
            buf_mouse[1] = 0;    // X位移:不变
            buf_mouse[2] = -10;  // Y位移:上移
            printf("up\n");
            need_release = 0;
        } else if (USART0_receive_ch == 's') {
            buf_mouse[1] = 0;    // X位移:不变
            buf_mouse[2] = 10;   // Y位移:下移
            printf("down\n");
            need_release = 0;
        }
        // 鼠标按键指令
        else if (USART0_receive_ch == 'c') {
            // 左键点击:设置按钮状态bit0为1
            buf_mouse[0] = 0x01;  // 按钮状态:左键按下
            buf_mouse[1] = 0;     // X位移:不变
            buf_mouse[2] = 0;     // Y位移:不变
            printf("left click\n");
            need_release = 1;    // 标记需要发送释放报告
        } else if (USART0_receive_ch == ' ') {
            // 右键点击:设置按钮状态bit1为1
            buf_mouse[0] = 0x02;  // 按钮状态:右键按下
            buf_mouse[1] = 0;     // X位移:不变
            buf_mouse[2] = 0;     // Y位移:不变
            printf("right click\n");
            need_release = 1;
        } else if (USART0_receive_ch == 'x') {
            // 中键点击:设置按钮状态bit2为1
            buf_mouse[0] = 0x04;  // 按钮状态:中键按下
            buf_mouse[1] = 0;     // X位移:不变
            buf_mouse[2] = 0;     // Y位移:不变
            printf("middle click\n");
            need_release = 1;
        } else if (USART0_receive_ch == 'r') {
            // 释放所有按键
            buf_mouse[0] = 0x00;  // 按钮状态:所有键释放
            buf_mouse[1] = 0;     // X位移:不变
            buf_mouse[2] = 0;     // Y位移:不变
            printf("release all buttons\n");
            need_release = 0;     // 不需要再释放
        }

        mouse_data_ready = 1;
    }
}

2.1.4 最终注册情况

  1. 打开windows设备管理器,可以看到注册成功的鼠标设备

    2、window 端通过USB Device Tree查看注册信息如下
bash 复制代码
        +++++++++++++++++ Device Information ++++++++++++++++++
Device Description       : HID-compliant mouse
Device Path 1            : \\?\HID#VID_6666&PID_8888#7&ee10cea&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030} (GUID_DEVINTERFACE_HID)
Device Path 2            : \\?\HID#VID_6666&PID_8888#7&ee10cea&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd} (GUID_DEVINTERFACE_MOUSE)
Kernel Name              : \Device\0000015b
Device ID                : HID\VID_6666&PID_8888\7&EE10CEA&0&0000
Hardware IDs             : HID\VID_6666&PID_8888&REV_0001 HID\VID_6666&PID_8888 HID\VID_6666&UP:0001_U:0002 HID_DEVICE_SYSTEM_MOUSE HID_DEVICE_UP:0001_U:0002 HID_DEVICE
Driver KeyName           : {4d36e96f-e325-11ce-bfc1-08002be10318}\0002 (GUID_DEVCLASS_MOUSE)
Driver                   : \SystemRoot\System32\drivers\mouhid.sys (Version: 10.0.19041.1  Date: 2019-12-07  Company: Microsoft Corporation)
Driver Inf               : C:\Windows\inf\msmouse.inf
Legacy BusType           : PNPBus
Class                    : Mouse
Class GUID               : {4d36e96f-e325-11ce-bfc1-08002be10318} (GUID_DEVCLASS_MOUSE)
Service                  : mouhid
Enumerator               : HID
Location Info            : -
Address                  : 1
Manufacturer Info        : Microsoft
Capabilities             : 0xA0 (SilentInstall, SurpriseRemovalOK)
Status                   : 0x0180200A (DN_DRIVER_LOADED, DN_STARTED, DN_DISABLEABLE, DN_NT_ENUMERATOR, DN_NT_DRIVER)
First Install Date       : 2025-09-17 11:18:39
Last Arrival Date        : 2025-10-09 17:53:44
EnhancedPowerMgmtEnabled : 0
Power State              : D0 (supported: D0, D3, wake from D0)
+++++++++++++ Mouse Information ++++++++++++++
Input Data Queue Length  : 2
Mouse Identifier         : 256
Number of Buttons        : 3
Sample Rate              : 0
++++++++++++++ HID Information +++++++++++++++
Manufacturer             : hezaizai
Product                  : Demo USB2.0 optical Mouse
UsagePage                : 0x01 (Generic Desktop Controls)
Usage                    : 0x02 (Mouse)

Note:

USB Device Tree下载地址: https://www.uwe-sieber.de/files/UsbTreeView_Win32.zip

3、视频效果演示

uart-mouse test

3 枚举流程详细说明

当前市面上已有多种便捷的USB抓包分析工具,同时也可使用Wireshark等软件工具进行USB报文捕获与分析,用于排查连接过程中的问题。

除上述方法外,我们还可通过示波器或逻辑分析仪对USB通信进行底层信号采集与分析。本项目将采用逻辑分析仪对模拟鼠标设备的完整枚举流程进行抓包,并深入解析报文内容。

需要说明的是,实际枚举过程可能与其他技术文档描述存在差异,这主要源于主机端枚举策略的动态调整。为深入验证设备兼容性,后续计划还将开发定制化主机端测试程序,并移植常用USB协议栈以完善功能。

通过这种硬件级的报文分析方式,能够更深入地理解USB通信机制,为后续设备开发与问题排查提供有力支持。

另外,逻辑分析仪截图,前面章节比较详细,后面有一些删减(因为很多都差不多)。

3.0 总线复位

  1. device上电后,拉高dp(d+)总线(高速/全速上拉接在dp,低速接在dm)
  2. hub检测到电压变化并将信息反馈给host
  3. host之后会发送一个Get_Port_Status
  4. hub会将设备速度类型回复给host
  5. 在device拉高100ms稳定后,host会发出Set_Port_Feature请求让hub 复位其管理的端口
  6. hub通过驱动数据线到复位状态(D+和 D-全为低电平),并持续至少 10ms。当然,hub 不会把这样的复位信号发送给其他已有设备连接的端口,所以其他连在该 hub 上的设备看不到复位信号

另外USB热插拔的实现机制也正是基于此。

3.1 Get DevDesc控制传输

3.1.1 SETUP事务

  1. host->device SETUP令牌包
  2. host->device Data0数据包
    80 06 00 01 00 00 40 00(小端格式)
bash 复制代码
0x80,        // bmRequestType: Dir: D2H, Type: Standard, Recipient: Device
0x06,        // bRequest (Get Descriptor)
0x00,        // wValue[0:7]  Desc Index: 0
0x01,        // wValue[8:15] Desc Type: (Device)
0x00, 0x00,  // wIndex Language ID: 0x00
0x40, 0x00,  // wLength = 64

该报文表示,主机请求设备返回一个描述符(0x06),描述符类型为设备描述符(0x01),长度为64字节(0x40)

  1. device->host ACK握手包

3.1.2 IN事务

  1. host->device IN令牌包
  2. device->host Data1数据包
bash 复制代码
0x12,        // bLength
0x01,        // bDescriptorType (Device)
0x10, 0x01,  // bcdUSB 1.10
0x00,        // bDeviceClass (Use class information in the Interface Descriptors)
0x00,        // bDeviceSubClass 
0x00,        // bDeviceProtocol 
0x40,        // bMaxPacketSize0 64
0x66, 0x66,  // idVendor 0x6666
0x88, 0x88,  // idProduct 0x8888
0x01, 0x00,  // bcdDevice 0.01
0x01,        // iManufacturer (String Index)
0x02,        // iProduct (String Index)
0x00,        // iSerialNumber (String Index)
0x01,        // bNumConfigurations 1
  1. host->device ACK握手包

3.1.3 OUT事务

  1. host->device OUT令牌包
  2. host->device Data1数据包
  3. device->host ACK握手包

3.1.4 总结

这里会发现在host获取device描述符的过程分为三个阶段:

  1. 建立阶段
    主机发送一个8字节的请求包(Setup Packet)。这个包包含了请求类型(GET_DESCRIPTOR)、请求值、索引以及最重要的 wLength(40)
  2. 数据阶段
    根据请求,设备向主机回复数据(18字节描述符)
  3. 状态阶段
    主机发送一个空数据包,表示数据传输完成

建立阶段一定是DATA0 数据包,之后如果有数据阶段,将进行翻转,变成 DATA1,并且在每次正确数据传输后都会进行一次翻转,这个机制用于保证数据被正确接收,而不是发送方发送的重复数据包(如果对方没有正确接收数据,DATAx不会翻转)。

在状态阶段,一律使用 DATA 1进行回复,状态阶段的数据包中的数据为空,也就是说不携带任何数据。

当完成了以上几个阶段,一次控制传输才算完成。

3.2 总线复位

当完成第一次的控制传输后,系统会要求 hub 对设备进行再一次的复位操作。再次复位的目的是使设备进入一个确定的状态。

3.3 Set_Address控制传输

3.3.1 SETUP事务

  1. host->device SETUP令牌包
  2. host->device Data0数据包
bash 复制代码
0x00,        // bmRequestType: Dir: H2D, Type: Standard, Recipient: Device
0x05,        // bRequest (Set Address)
0x02, 0x00,  // wValue Device Addr: 2
0x00, 0x00,  // wIndex = 0x00
0x00, 0x00,  // wLength = 0
  1. device->host ACK握手包

3.3.2 IN事务

  1. host->device IN令牌包
  2. device->host Data1数据包
  3. host->device ACK握手包

Set_Address控制传输的信息通过SETUP令牌包以及传输完了,就不用额外的数据传输,只有建立阶段和状态阶段,而不需要数据阶段了。

device会将收到的addr配置到对应的寄存器,后续host也会将通过该addr来和device进行数据交互

3.4 Get DevDesc控制传输

再次获取device的描述信息,收发包同3.1一样。

不同的是这次发出读取18bytes数据SETUP令牌包(第一次请求读取64bytes的描述信息)

3.5 Get CfgDesc控制传输

这里是第一次获取device的配置信息,主要是获取配置描述符长度,后续会再发一次,获取完整配置描述符信息。

我用的windows主机第一次请求长度是256bytes,其他主机也有9bytes;但这里不关键,不影响配置描述符长度的获取,也不影响后续配置描述符的获取。

3.5.1 SETUP事务

  1. host->device SETUP令牌包
    可以看到此时的Addr = 2
  2. host->device Data0数据包
    0x80, // bmRequestType: Dir: D2H, Type: Standard, Recipient: Device
    0x06, // bRequest (Get Descriptor)
    0x00, // wValue[0:7] Desc Index: 0
    0x02, // wValue[8:15] Desc Type: (Configuration)
    0x00, 0x00, // wIndex Language ID: 0x00
    0xFF, 0x00, // wLength = 255
  3. device->host ACK握手包

3.5.2 IN事务

  1. host->device IN令牌包
  2. device->host Data1数据包
bash 复制代码
0x09,        // bLength: 描述符长度=9字节
0x02,        // bDescriptorType: 配置描述符类型(0x02)
0x22, 0x00,  // wTotalLength: 配置信息总长度=34字节(0x0022)
0x01,        // bNumInterfaces: 接口数量=1
0x01,        // bConfigurationValue: 配置值=1(用于SET_CONFIGURATION)
0x00,        // iConfiguration: 配置字符串索引=0(无字符串)
0x80,        // bmAttributes: 属性
             //   - 位7: 1=总线供电
             //   - 位6: 0=不支持远程唤醒
             //   - 位5-0: 保留
0x32,        // bMaxPower: 最大功耗=100mA(0x32=50, 单位2mA→100mA)

0x09,        // bLength: 描述符长度=9字节
0x04,        // bDescriptorType: 接口描述符类型(0x04)
0x00,        // bInterfaceNumber: 接口编号=0
0x00,        // bAlternateSetting: 备用设置=0
0x01,        // bNumEndpoints: 端点数量=1(除了默认控制端点0)
0x03,        // bInterfaceClass: 接口类=0x03(HID类)
0x01,        // bInterfaceSubClass: 接口子类=0x01(启动接口)
0x02,        // bInterfaceProtocol: 接口协议=0x02(鼠标)
0x00,        // iInterface: 接口字符串索引=0(无字符串)

0x09,        // bLength: 描述符长度=9字节
0x21,        // bDescriptorType: HID描述符类型(0x21)
0x11, 0x01,  // bcdHID: HID规范版本=1.11
0x00,        // bCountryCode: 国家代码=0(不支持本地化)
0x01,        // bNumDescriptors: 下级描述符数量=1
0x22,        // bDescriptorType[0]: 报告描述符类型(0x22)
0x34, 0x00,  // wDescriptorLength[0]: 报告描述符长度=52字节(0x0034)

0x07,        // bLength: 描述符长度=7字节
0x05,        // bDescriptorType: 端点描述符类型(0x05)
0x81,        // bEndpointAddress: 端点地址
                //   - 位7: 1=IN方向(设备到主机)
                //   - 位3-0: 0001=端点1
0x03,        // bmAttributes: 端点属性
             //   - 位1-0: 11=中断传输
0x04, 0x00,  // wMaxPacketSize: 最大包大小=4字节
0x0A,        // bInterval: 轮询间隔=10ms
  1. host->devece ACK握手包

3.5.3 OUT事务

  1. host->device OUT令牌包
  2. host->device Data1数据包
  3. device->host ACK握手包

本次的控制传输同样分为建立阶段,数据阶段和状态阶段

3.6 Get StrDesc控制传输

在host获取字符描述符前,一般会先获取下字符支持的语言,方便主机根据字符编码来解析显示。

这里获取的是厂商信息。

3.6.1 SETUP事务

bash 复制代码
0x80,        // bmRequestType: Dir: D2H, Type: Standard, Recipient: Device
0x06,        // bRequest (Get Descriptor)
0x00,        // wValue[0:7]  Desc Index: 0
0x03,        // wValue[8:15] Desc Type: (String)
0x00, 0x00,  // wIndex Language ID: 0x00
0xFF, 0x00,  // wLength = 255

3.6.2 IN事务

bash 复制代码
0x04          //字符长度
0x03          //字符串描述符
0x09,0x04  //支持语言为英语

3.6.3 OUT事务

3.6.4 SETUP事务

bash 复制代码
0x80,        // bmRequestType: Dir: D2H, Type: Standard, Recipient: Device
0x06,        // bRequest (Get Descriptor)
0x02,        // wValue[0:7]  Desc Index: 2
0x03,        // wValue[8:15] Desc Type: (String)
0x09, 0x04,  // wIndex Language ID: 0x0409
0xFF, 0x00,  // wLength = 255

3.6.5 IN事务

3.6.6 OUT事务

3.7 Get DevDesc控制传输

再次获取设备描述符,同3.4节。

3.8 Get CfgDesc控制传输

这里是第二次获取device的配置描述符,和第一次(3.5节)不一样支持在于,该次设备请求的数据长度为第一次上报的34bytes

3.9 SET Configure

用于​​激活​​设备的一个指定配置,使device从"寻址状态"进入"配置状态",device可以启用相应的端点(端点可以开始接收/发送报告)

3.9.1 SETUP事务

bash 复制代码
0x00,        // bmRequestType: Dir: H2D, Type: Standard, Recipient: Device
0x09,        // bRequest (Set Config)
0x01, 0x00,  // wValue Config Num: 1
0x00, 0x00,  // wIndex = 0x00
0x00, 0x00,  // wLength = 0

3.9.2 IN事务

3.10 SET IDLE

属于 HID类特定请求,当输入数据没有变化时,不要频繁报告,只有在数据发生变化时,或者达到指定的空闲时间后才发送报告

3.10.1 SETUP事务

bash 复制代码
0x21,        // bmRequestType: Dir: H2D, Type: Class, Recipient: Interface
0x0A,        // bRequest
0x00, 0x00,  // wValue[0:15] = 0x00
0x00, 0x00,  // wIndex = 0x00
0x00, 0x00,  // wLength = 0

3.11 Get HID Report控制传输

3.11.1 SETUP事务

bash 复制代码
0x81,        // bmRequestType: Dir: D2H, Type: Standard, Recipient: Interface
0x06,        // bRequest (Get Descriptor)
0x00,        // wValue[0:7]  Desc Index: 0
0x22,        // wValue[8:15] Desc Type: (HID Report)
0x00, 0x00,  // wIndex Language ID: 0x00
0x74, 0x00,  // wLength = 116

3.11.2 IN事务

bash 复制代码
0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x02,        // Usage (Mouse)
0xA1, 0x01,        // Collection (Application)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x05, 0x09,        //     Usage Page (Button)
0x19, 0x01,        //     Usage Minimum (0x01)
0x29, 0x03,        //     Usage Maximum (0x03)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x75, 0x01,        //     Report Size (1)
0x95, 0x03,        //     Report Count (3)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x75, 0x05,        //     Report Size (5)
0x95, 0x01,        //     Report Count (1)
0x81, 0x03,        //     Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x09, 0x38,        //     Usage (Wheel)
0x15, 0x81,        //     Logical Minimum (-127)
0x25, 0x7F,        //     Logical Maximum (127)
0x75, 0x08,        //     Report Size (8)
0x95, 0x03,        //     Report Count (3)
0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0xC0,              // End Collection

3.11.6 OUT事务

3.12 Get StrDesc控制传输

后续可能还会获取其他的字符描述符,包括:厂商信息、设备信息等。

篇幅原因,就不再这里累述了,可以参考3.6。

4 中断传输

鼠标、键盘等人机交互设备(HID)的特点是​​数据量小​​,但要求​​主机能及时响应​​。中断传输正是为这种场景设计的:主机保证会以固定的时间间隔去主动询问(轮询)设备是否有数据要上报。

通过端点描述符可以获取到信息:允许鼠标设备以​​最高100Hz的频率​​,通过​​端点1​​向主机发送​​最多4字节​​的移动和按键数据。

下面简单抓取下执行上下左右时的波形

可以看出此时上报的ADDR=2,EP=1。





参考

HID 简介
HID规范
HID Usage Tables

相关推荐
TeleostNaCl4 小时前
一种使用 PowerToys 的键盘管理器工具编辑惠普暗影精灵11 的 OMEN 自定义按键的方法
windows·经验分享·计算机外设·1024程序员节
码力引擎4 小时前
【零基础学MySQL】第一章:MySQL介绍与安装
数据库·mysql·1024程序员节
体育分享_大眼4 小时前
体育数据传输:HTTP API与WebSocket的核心差异
1024程序员节
Python大数据分析@4 小时前
Python哪个Excel库最好用?
1024程序员节
Despacito0o4 小时前
Keil MDK-ARM 5.42a 完整安装指南(2025.4.19最新版)
arm开发·stm32·单片机·嵌入式硬件·物联网·51单片机·嵌入式实时数据库
倔强吧!青铜4 小时前
嵌入式八股文总结(ARM篇)
单片机·嵌入式硬件
溜追4 小时前
OEC-Turbo刷群晖&Armbian流程记录
linux·经验分享·嵌入式硬件
Zain Lau4 小时前
HongKongの1024
1024程序员节