Fork me on GitHub
0%

USB基础知识

术语表

术语 英文全称 含义
USB Universal Serial Bus 通用串行总线
CDC Communications Device Class 通信设备类,用于实现串行通信功能,如虚拟串口
HID Human Interface Device Class 人机接口设备类,用于输入设备,如键盘、鼠标等
MSC Mass Storage Class 大容量存储设备类,用于存储设备,如外部硬盘、SD 卡读卡器等
VID Vendor ID 供应商 ID,标识 USB 设备制造商
PID Product ID 产品 ID,标识 USB 设备的具体型号
UDC USB Device Controller USB 设备控制器(Device端)
HDC Host Device Controller USB主机设备控制器(Host端)

USB描述符

USB 描述符是 USB 设备用于自我描述的标准结构。它们为设备提供了设备的各种信息,以便主机可以正确识别、配置和使用设备。一个典型的USB设备描述符链结构可能如下图

1
2
3
4
5
6
7
8
9
10
11
12
13
Device Descriptor
|
+-- Configuration Descriptor
|
+-- Interface Descriptor (Interface 0)
| |
| +-- Endpoint Descriptor (Endpoint 1)
| +-- Endpoint Descriptor (Endpoint 2)
|
+-- Interface Descriptor (Interface 1)
|
+-- Endpoint Descriptor (Endpoint 3)
+-- Endpoint Descriptor (Endpoint 4)

设备描述符(Device Descriptor)

设备描述符包含有关 USB 设备的基本信息,如供应商 ID(VID)、产品 ID(PID)、设备类别等。它是USB主机枚举USB设备申请的第1个描述符,每个设备有且仅有一个设备描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct usb_device_descriptor {
uint8_t bLength; // 描述符全部长度(固定18)
uint8_t bDescriptorType; // 描述符类型 (设备描述符固定 0x01)
uint16_t bcdUSB; // USB 规范版本
uint8_t bDeviceClass; // 设备类(0、1-FEH根据Desc Usage描述定义、FFH)
uint8_t bDeviceSubClass; // 设备子类(根据bDeviceClass字段USB-IF分配)
uint8_t bDeviceProtocol; // 设备协议
uint8_t bMaxPacketSize0; // 控制端点0的最大包大小(8、16、32、64合法)
uint16_t idVendor; // 供应商ID (VID)
uint16_t idProduct; // 产品ID (PID)
uint16_t bcdDevice; // 设备版本
uint8_t iManufacturer; // 制造商字符串描述符的索引
uint8_t iProduct; // 产品字符串描述符的索引
uint8_t iSerialNumber; // 序列号字符串描述符的索引
uint8_t bNumConfigurations; // 配置数量
};

配置描述符(Configuration Descriptor)

配置描述符包含关于设备电源需求和配置的详细信息。一个USB设备至少有一个或者多个配置,但通常只有一个处于活动状态,每一种配置都对应一个配置描述符的集合,配置描述符集合主要由标准配置描述符、接口描述符、端点描述符、(类描述符HID、UVC、CDC等类功能描述符),报告描述符和物理描述符单独返回给主机

1
2
3
4
5
6
7
8
9
10
11
12
struct usb_config_descriptor {
uint8_t bLength; // 描述符长度(固定9字节)
uint8_t bDescriptorType; // 描述符类型(配置描述符固定0x02)
uint16_t wTotalLength; // 该配置的总长度
uint8_t bNumInterfaces; // 此配置支持的接口数量
uint8_t bConfigurationValue; // 用作SetConfiguration()请求的参数值,用于选择哪一个配置
uint8_t iConfiguration; // 配置字符串描述符的索引
uint8_t bmAttributes; // 配置特性
uint8_t bMaxPower; // 设备最大电流需求
};

// 先获得9字节,进而通过wTotalLength字段获取全部的描述符

接口描述符(Interface Descriptor)

接口描述符描述了一个配置中的单个接口。每个接口可以有多个端点。接口描述符 不能单独返回给主机,必须跟在配置描述符后面返回

1
2
3
4
5
6
7
8
9
10
11
struct usb_interface_descriptor {
uint8_t bLength; // 描述符长度 (固定9字节)
uint8_t bDescriptorType; // 描述符类型 (接口描述符固定0x04)
uint8_t bInterfaceNumber; // 接口编号
uint8_t bAlternateSetting; // 备用设置号(一般很少用)
uint8_t bNumEndpoints; // 端点数量(这个端点数量不包含端点0)
uint8_t bInterfaceClass; // 接口类(由USB-IF分配,通常usb作UVC、HID等功能都是这个字段进行定义)
uint8_t bInterfaceSubClass; // 接口子类(由USB-IF分配)
uint8_t bInterfaceProtocol; // 接口协议
uint8_t iInterface; // 接口字符串描述符的索引
};

端点描述符(Endpoint Descriptor)

端点描述符包含主机要确定每个端点的带宽要求所需要的信息。每个接口可以有多个端点描述符。

1
2
3
4
5
6
7
8
struct usb_endpoint_descriptor {
uint8_t bLength; // 描述符长度(固定7字节)
uint8_t bDescriptorType; // 描述符类型 (端点描述符固定0x05)
uint8_t bEndpointAddress; // 端点地址(每个比特特定含义)
uint8_t bmAttributes; // 端点属性(每个比特特定含义)
uint16_t wMaxPacketSize; // 当前配置下此端点能够接受或发送的最大数据包的大小(控制端点512,其它所有端点类型1024)
uint8_t bInterval; // 轮询间隔,主机多久和设备通讯一次
};

USB传输的对象:端点(endpoint)

  • 我们说”读U盘”、“写U盘”,可以细化为:把数据写到U盘的端点1,从U盘的端点2里读出数据
  • 除了端点0外,每一个端点只支持一个方向的数据传输;
  • 端点0用于控制传输,既能输出也能输入;
  • 每一个端点都有传输类型,传输方向;
  • 端点是USB设备通信的基本单位,所有通信都是从端点发起的

字符串描述符(String Descriptor)

字符串描述符包含设备、制造商和产品等的人类可读信息。这些信息可以通过索引在其他描述符中引用

1
2
3
4
5
struct usb_string_descriptor {
uint8_t bLength; // 描述符长度
uint8_t bDescriptorType; // 描述符类型 (USB_DESCRIPTOR_TYPE_STRING)
uint16_t wString[]; // Unicode 编码的字符串
};

枚举

USB 枚举(Enumeration)是指主机在检测到一个新的 USB 设备连接后,识别、配置并准备与该设备进行通信的过程。一旦确认设备类型,主机就可以根据这些信息来加载合适的驱动程序。

USB协议定义了设备的6中状态,仅在而枚举过程中就经历了4个状态的迁移:上电状态、默认状态、地址状态、配置状态(另外两种是连接状态和挂起状态)

  1. 上电过程

    用户把USB设备插入USB端口(hub),此时USB设备处于加电状态,它所连接的端口是无效的

  2. 设备类型检测

    Hub端会根据数据线(D+ 和 D-)上的电压变化,两个数据线上上哪一个被拉高,从而识别出设备是低速、还是非低速设备(高速/全速)。

  3. Host了解连接的设备

    Hub利用自己的中断端点向Host报告它的端口状态(对于这个过程,设备是看不到的,也不关心),如果有连接断开事件,Host向Hub发送一个Get_Port_Status请求了解更多。

    此时Hub将检测到的速度类型信息返回给Host

  4. Hub复位设备

    连接成功之后至少等待100ms确保电源稳定,主机控制器向Hub发出Set_Port_Freature请求让hub复位刚才连接成功的端口,持续最少10ms

  5. 确认是高速设备还是全速设备

    不论是Hub还是设备,起始状态都将以全速模式运行,进一步的进行高速检测,如果确认对方支持高速,再切换到高速模式

    发送复位信号后,设备会发送 chirp 信号。主机识别 chirp 信号,确定设备为高速设备。

  6. Hub建立设备和主机之间的信道

    主机不停发送Get_Port_Status请求,确认设备是否复位成功。复位成功,对应的USB设备地址变成0,主机就可以通过控制传输,与默认地址0,端口号0的设备进行通信

  7. 获取设备描述符

    主机通过发送 GET_DESCRIPTOR 请求读取设备的部分设备描述符,主机读取设备描述符的第 8 个字节,以获取设备的最大包大小(bMaxPacketSize0)

    此时系统为了设备进入一个确定的状态,会要求hub对设备再一次进行复位操作(但协议中没有该规定)

  8. 设备地址分配

    主机控制器发送 SET_ADDRESS 请求,分配一个唯一的地址(1-127)。设备在确认收到该请求后,进入地址状态,使用分配的地址进行后续通信。一旦设备掉电消失、重新进行枚举,地址也将重新分配。

  9. 再次获取设备描述符

    主机再次发送 GET_DESCRIPTOR获取设备所有的详细信息(其它字段的解析)

  10. 获取配置描述符及其它描述符

  11. 驱动匹配及挂载

    这个阶段主机已经拿到了设备的全部信息,将匹配对应的驱动。主机首先announce_device说明设备已经找到;调用设备驱动模型提供的接口device_add,将设备添加到usb总线的设备列表里;usb总线将遍历驱动列表里的驱动,调用usb_device_match将进行匹配;匹配成功调用device_bind_driver函数,现在设备控制权交到设备驱动上

    对于复合设备,通常是不同的接口配置给不同的驱动,因此需要等待设备被配置并把接口使能之后才能挂载驱动

  12. 设置设备配置

    主机选择一个配置,发送 SET_CONFIGURATION 请求配置设备,设备进入配置状态,准备与主机进行正常的数据通信。

复合设备

复合设备与组合设备

  • USB Composite Device(复合设备)

    一个USB设备芯片实现了多个USB设备功能,其是通过USB接口描述符来实现不同的设备功能。软件工程师复合的USB设备

    • 只有一个Function,只有一套PID VID
  • USBCompound Device(组合设备)

    从外观或者包装上来看的这个USB设备,如果其USB接口是其一个USB集线器的上游端口,这类设备被定义为USB组合设备。硬件工程师组合的USB设备

    • 每个设备有独立的设备描述符,分配独立的USB总线地址
    • 内嵌多个Function,每个设备的PID VID一般不相同
    • 每个设备有独立的配置描述符,并且都需要独立在USB总线上传输

结构体及函数

Composite Device Framework

类型 名称 功能
struct usb_function 描述配置的一个功能
struct usb_configuration 代表一个gadget配置
struct usb_composite_driver 将配置分组到一个gadget中(每个驱动有且只有一个)
struct usb_composite_dev 代表一个复合 USB gadget(驱动不需要关心)
struct usb_function 代表一种usb的功能(CDC、MSG…)
需要使用不同功能的alloc函数获取实例
function int usb_add_function(
struct usb_configuration* config,
struct usb_function * function);
将函数添加到配置中,每个function调用其的 bind进行资源分配
复合驱动多个function需要调用多次
function int usb_add_config (
struct usb_composite_dev * cdev,
struct usb_configuration * config,
int (*bind) (struct usb_configuration *`));
将配置添加到设备上(每个驱动有且只有一个)
(第三个参数回调的实现就需要调用usb_add_function)
function int usb_composite_probe (
struct usb_composite_driver * driver);
注册复合驱动程序

FAQ

  1. usb主机如何区分设备时高速设备、全速设备还是低速设备?

    主机端数据线D+和D-都有一个阻值在14.25k到24.8k的下拉电阻,而在设备端,D+(全速、高速)和D-(低速)上有一个1.5k的上拉电阻。

    • 差分电平检测。低速设备:D- 上拉电阻将电平拉高;全速/高速设备:D+ 上拉电阻将电平拉高。

    • 高速信号检测。hub检测到有设备插入/上电时,向主机通报,主机发送Set_Port_Feature请求让hub复位新插入的设备。设备复位操作是hub通过驱动数据线到复位状态SE0(Single-ended 0,即D+和D-全为低电平),并持续至少10ms。

      高速设备初始是以一个全速设备的身份出现的。USB2.0的hub把它当作一个全速设备,之后,hub和设备通过一系列握手信号确认双方的身份。在这里对速度的检测是双向的,比如高速的hub需要检测所挂上来的设备是高速、全速还是低速,高速的设备需要检测所连上的hub是USB2.0的还是1.x的,如果是前者,就进行一系列动作切到高速模式工作,如果是后者,就以全速模式工作。

参考资料