文章中的部分概念可参考第9课【USB协议】USB总线 接口 端点 管道 数据包 枚举 STM32_USB-FS-Device_Lib V4.1.0
USB协议中为了提供对多样设备的支持,定义了许多外部设备子类,常见的包括:
USB支持的设备众多,实现方法上会有很多不同点。但从实现思路上可以重点关注两个点:
指仅能让计算机识别出其USB设备身份,没有具体指明其功能的USB设备
识别USB设备的硬件前提
USB集线器识别USB设备是否接入依赖的是USB总线上D+/D-的电平变化。在USB集线器上,D+/D-信号线上都会接一个15kΩ的下拉电阻,而对应的USB设备上都会在D+/D-接上一个1.5kΩ上拉电阻(高速全速设备上拉电阻在D+,低速设备的上拉电阻在D-),当集线器检测到空闲的USB接口D+/D-信号线的电平突然被拉高时,就知道有USB设备接入了,进而进入美剧流程
const uint8_t Undefined_USB_DeviceDescriptor[18] =
{
0x12, // bLength:描述符长度,固定为0x12
0x01, // bDescriptorType:描述符类型,固定为0x01,表示设备描述符
0x00, 0x02, // bcdUSB:USB协议版本号,这里是2.0
0x00, // bDeviceClass:设备类别,0表示未定义
0x00, // bDeviceSubClass:设备子类别,0表示未定义
0x00, // bDeviceProtocol:设备协议,0表示未定义
0x40, // bMaxPacketSize:最大数据包大小,这里是64字节
0x83, 0x04, // idVendor:USB设备厂商ID,这里是0x0483
0x20, 0x57, // idProduct:USB设备产品ID,这里是0x5720
0x00, 0x01, // bcdDevice:设备版本号,这里是1.0
0x01, // iManufacturer:USB设备制造商字符串描述符的索引值,这里是1
0x02, // iProduct:USB设备产品字符串描述符的索引值,这里是2
0x03, // iSerialNumber:USB设备序列号字符串描述符的索引值,这里是3
0x01 // bNumConfigurations:USB设备支持的配置数量,这里是1
}
const uint8_t Undefined_USB_ConfigDescriptor[32] =
{
/************** 配置描述符 ****************/
0x09 // bLength: 描述符长度,固定为9
0x02 // bDescriptorType: 描述符类型,表示该描述符的类型为Configuration Descriptor,固定为2
0x20
0x00 // wTotalLength: 该配置描述符及其所包含的所有描述符的总长度,单位为字节
0x01 // bNumInterfaces: 该配置所包含的接口数量
0x01 // bConfigurationValue: 该配置的值,用于选择设备的配置
0x00 // iConfiguration: 该配置的字符串描述符的索引,如果不存在则为0
0xA0 // bmAttributes: 该配置的属性,包括供电方式和远程唤醒功能等
0x32 // bMaxPower: 该配置所需的最大电流,单位为2mA
/************** 接口描述符 ****************/
0x09 // bLength: 描述符长度,固定为9
0x04 // bDescriptorType: 描述符类型,表示该描述符的类型为Interface Descriptor,固定为4
0x00 // bInterfaceNumber: 该接口的编号
0x00 // bAlternateSetting: 备用设置的编号,用于支持多种设置
0x02 // bNumEndpoints: 该接口所包含的端点数量
0x00 // bInterfaceClass: 该接口的类别,0表示未定义
0x00 // bInterfaceSubClass: 该接口的子类别,0表示未定义
0x00 // bInterfaceProtocol: 该接口的协议,0表示未定义
0x00 // iInterface: 该接口的字符串描述符的索引,如果不存在则为0
/************** 端点描述符 ****************/
0x07 // bLength: 描述符长度,固定为7
0x05 // bDescriptorType: 描述符类型,表示该描述符的类型为Endpoint Descriptor,固定为5
0x81 // bEndpointAddress: 该端点的地址,包括端点方向和端点编号,表示为IN端点1
0x02 // bmAttributes: 该端点的属性,包括传输类型和数据类型等,表示为Bulk传输类型
0x40
0x00 // wMaxPacketSize: 该端点所支持的最大数据包大小,单位为字节
0x00 // bInterval: 该端点所需的轮询间隔,单位为毫秒
/************** 端点描述符 ****************/
0x07 // bLength: 描述符长度,固定为7
0x05 // bDescriptorType: 描述符类型,表示该描述符的类型为Endpoint Descriptor,固定为5
0x02 // bEndpointAddress: 该端点的地址,包括端点方向和端点编号,表示为OUT端点2
0x02 // bmAttributes: 该端点的属性,包括传输类型和数据类型等,表示为Bulk传输类型
0x40
0x00 // wMaxPacketSize: 该端点所支持的最大数据包大小,单位为字节
0x00 // bInterval: 该端点所需的轮询间隔,单位为毫秒
};
设备描述符定义了一个未指定功能的USB设备,支持的USB协议为USB2.0,单次传输最大数据包传输大小为64字节,配置描述符描述了未定义USB设备的接口,接口对应功能也是未定义的,所以也不能通过端点传输数据
以上设备描述符中有三个重要的值,用于指定USB设备身份:
对于常用的设备类型,它们有以下组合方式:
设备类别 | bDeviceClass | bDeviceSubClass(多选一) | bDeviceProtocol(多选一) |
---|---|---|---|
未定义 | 0x00 | 0x00 | 0x00 |
人机交互类设备 HID | 0x03 | 未定义:0x00 启动接口子类:0x01 鼠标接口子类:0x02 摇杆接口子类:0x03 游戏手柄接口子类:0x04 键盘接口子类:0x05 … |
未定义:0x00 键盘协议:0x01 鼠标协议:0x02 摇杆协议:0x03 游戏手柄协议:0x04 … |
通信类设备 CDC | 0x02 | 未定义:0x00 ACM子类:0x01 ECM子类:0x02 NCM子类:0x03 自定义子类:0xFE |
未定义:0x00 V.25ter协议:0x01 AT命令协议:0x02 PPP协议:0x03 Ethernet Emulation协议:0x04 ATM Emulation协议:0x05 供应商自定义协议: 0xFF |
大容量存储设备 MSC | 0x08 | 未定义:0x00 公开SCSI命令集子类:0x01 ATAPI命令集子类:0x02 USB命令集子类:0x03 自定义子类:0xFE |
未定义:0x00 USB批量传输协议:0x01 公开SCSI协议:0x50 UAS传输协议:0x62 自定义协议:0x80~0xFF |
组合设备 | 0xEF | 通用子类:0x01 IAD多接口子类:0x02 供应商自定义子类:0x0F |
未定义:0x00 IAD多接口协议:0x01 供应商自定义协议:0xFF |
要配置设备专用描述符和从机端点,首先要了解配置描述符的组成。常见的配置描述符结构如下:
接口关联描述符
Interface Associate Descriptor接口关联描述符。在USB部分设备中,一般实现某个设备功能只会要求使用一个接口,但也可能会要求使用两个或以上的接口,这时候就需要将使用到的多个接口,通过IAD描述符关联起来,以便于主机识别这些关联接口之间的关系。一般来说IAD描述符出现在配置描述符之后,接口描述符之前,IAD描述符之后会紧跟着相关联的接口描述符
根据上述的特点,设备专用的描述符只要插入了其上层描述符之后就可以了,如果有多个,按照包含顺序紧挨着实现。一般来说因为接口描述符和设备的功能有紧密的联系,所以设备专用的描述符位置都和接口描述符并列,或者包含于接口描述符之中
端点描述符的配置较为简单,一般要:
缓冲区及缓冲区描述表
缓冲区是在STM32 USB标准库开发中的概念。缓冲区实际大小为512Bytes,位于STM32的RAM中,地址为0x40006000~0x400063FF(大小为1KByte,由于对于内核是通过32位的读取方式读取缓冲的,而USB外设是通过16位的方式读取缓冲区。内核读取到的四个字节只有两个是有意义的,另一半由于没有对应的实际存储器,会读取不到东西),用于存放缓冲区描述表,同时还用作端点收发数据Buffer。缓冲区描述表是缓冲区中的一个区域,记录缓冲区端点相关寄存器信息。缓冲区中除了描述表的以外空间可以分配给端点,作为收发数据Buffer,Buffer之间不能重叠,一个Buffer的最大为64Bytes
缓冲区描述表头和端点分配
缓冲区描述表头应该位于缓冲区的最前面,记录每个端点相关的四个寄存器信息,包括缓冲地址寄存器,发送数据字节寄存器,接收缓冲地址寄存器,接收数据字节寄存器,每个寄存器占用两个字节,所以每增加一个端点会让表头增大八个字节。一般来说紧挨着表头是端点0Buffer地址,会定义在距离表头64Bytes的位置,因为单个USB设备可用端点为8个,所以所有的寄存器信息大小为8*8=64Bytes。之后的表头地址就根据各个端点Buffer需求的大小定义即可,可参考以下示例:
// 端点数量
#define EP_NUM (5)
// 缓冲区描述表定义
// 缓冲区描述表头
#define BTABLE_ADDRESS (0x00)
// 端点0发送/接收Buffer首地址,大小都为64Bytes
#define ENDP0_RXADDR (0x40)
#define ENDP0_TXADDR (0x80)
// 端点1发送/接收Buffer首地址,大小都为64Bytes
#define ENDP1_TXADDR (0xC0)
#define ENDP1_RXADDR (0x100)
// 端点2发送/接收Buffer首地址,大小都为64Bytes
#define ENDP2_TXADDR (0x140)
#define ENDP2_RXADDR (0x180)
// 端点3发送Buffer首地址,大小都为64Bytes
#define ENDP3_TXADDR (0x1C0)
USB设备类特定请求属于USB设备标准请求的拓展,其数据内容类似于标准请求,都有请求码,索引,值和长度。USB设备标准请求是所有USB设备都支持的,它可以实现最基础的USB设备配置和控制功能。而USB设备类特定请求指的是针对特定设备的请求,例如HID设备的GET_REPORT/SET_REPORT请求,CDC设备的GET_LINE_CODING/SET_LINE_COFING请求。具体到每种设备,其对类特定请求的定义和实现都不同,这类请求一般由开发者来实现
HID设备是USB协议中定义的一种广泛运用于计算机,游戏机,工业设备等电子设备中的输入设备子类,包括鼠标,键盘,摇杆,游戏手柄等。符合HID规范的设备不仅可以通过USB,还可以通过蓝牙,无线电的方式同主机相连接,通过特殊的HID报文同主机进行数据传输
HID设备有以下优点:
USB HID协议中额外规定了HID设备/主机之间的通信格式和通信协议。所以相对于标准USB设备,HID设备实现上有以下的特点:
const uint8_t HID_Keyboard_DeviceDescriptor[18] =
{
0x12, // bLength: 描述符长度,固定为0x12
0x01, // bDescriptorType: 描述符类型,固定为0x01,表示设备描述符
0x00, 0x02, // bcdUSB: USB协议版本号,这里是2.0
0x03, // bDeviceClass: 设备类别,3表示HID设备
0x01, // bDeviceSubClass: 设备子类别,1表示启动接口子类
0x01, // bDeviceProtocol: 设备协议,1表示键盘协议
0x40, // bMaxPacketSize0: 最大数据包大小,这里是64字节
0x83, 0x04, // idVendor: USB设备厂商ID,这里是0x0483
0x20, 0x57, // idProduct: USB设备产品ID,这里是0x5720
0x00, 0x01, // bcdDevice: 设备版本号,这里是1.0
0x01, // iManufacturer: USB设备制造商字符串描述符的索引值,这里是1
0x02, // iProduct: USB设备产品字符串描述符的索引值,这里是2
0x03, // iSerialNumber: USB设备序列号字符串描述符的索引值,这里是3
0x01 // bNumConfigurations: USB设备支持的配置数量,这里是1
}
const uint8_t HID_Keyboard_ConfigDescriptor[41] =
{
/************** 配置描述符 ****************/
0x09 // bLength: 描述符长度,固定为9
0x02 // bDescriptorType: 描述符类型,表示该描述符的类型为Configuration Descriptor,固定为2
0x29
0x00 // wTotalLength: 该配置描述符及其所包含的所有描述符的总长度,单位为字节
0x01 // bNumInterfaces: 该配置所包含的接口数量
0x01 // bConfigurationValue: 该配置的值,用于选择设备的配置
0x00 // iConfiguration: 该配置的字符串描述符的索引,如果不存在则为0
0xA0 // bmAttributes: 该配置的属性,包括供电方式和远程唤醒功能等
0x32 // bMaxPower: 该配置所需的最大电流,单位为2mA
/************** 接口描述符 ****************/
0x09 // bLength: 描述符长度,固定为9
0x04 // bDescriptorType: 描述符类型,表示该描述符的类型为Interface Descriptor,固定为4
0x00 // bInterfaceNumber: 该接口的编号
0x00 // bAlternateSetting: 备用设置的编号,用于支持多种设置
0x02 // bNumEndpoints: 该接口所包含的端点数量
0x03 // bInterfaceClass: 该接口的类别,表示为Human Interface Device
0x01 // bInterfaceSubClass: 该接口的子类别,表示为启动接口子类
0x01 // bInterfaceProtocol: 该接口的协议,表示为键盘协议
0x00 // iInterface: 该接口的字符串描述符的索引,如果不存在则为0
/************** HID主描述符 ****************/
0x09 // bLength: 描述符长度,固定为9
0x21 // bDescriptorType: 描述符类型,表示该描述符的类型为HID Descriptor,固定为33
0x10, 0x01, // bcdHID:HID规范版本(1.1)
0x00, // bCountryCode:国家代码(未指定)
0x01, // bNumDescriptors:支持的描述符类型数
0x22, // bDescriptorType:描述符类型(报告描述符)
0x3F, 0x00, // wDescriptorLength:描述符长度
/************** 端点描述符 ****************/
0x07 // bLength: 描述符长度,固定为7
0x05 // bDescriptorType: 描述符类型,表示该描述符的类型为Endpoint Descriptor,固定为5
0x01 // bEndpointAddress: 该端点的地址,包括端点方向和端点编号,表示为OUT端点1
0x03 // bmAttributes: 该端点的属性,包括传输类型和数据类型等,表示为Interrup传输类型
0x40
0x00 // wMaxPacketSize: 该端点所支持的最大数据包大小64字节
0x0A // bInterval: 该端点所需的轮询间隔10ms
/************** 端点描述符 ****************/
0x07 // bLength: 描述符长度,固定为7
0x05 // bDescriptorType: 描述符类型,表示该描述符的类型为Endpoint Descriptor,固定为5
0x81 // bEndpointAddress: 该端点的地址,包括端点方向和端点编号,表示为IN端点1
0x03 // bmAttributes: 该端点的属性,包括传输类型和数据类型等,表示为Interrup传输类型
0x40
0x00 // wMaxPacketSize: 该端点所支持的最大数据包大小64字节
0x0A // bInterval: 该端点所需的轮询间隔10ms
};
以上描述符实现了一个HID键盘设备,其描述符分别指出:
前面提到HID设备通过特殊的HID报文同主机进行数据传输。这里的报文,指的是可变长度,指定格式的特殊数据,其中:
HID报文描述符中最基本的单元是条目(Item),条目又分为长条目和短条目,长条目不经常使用,所以此处仅讨论短条目
短条目由前缀+可选的数据组成,其中:
可选数据不是独立存在的,需要通过条目的前缀去解析可选数据,到官方提供的HID Usage Table中查询到对应的意义
实际的代码中,通常将短条目分为前缀和可选数据,分别用两位十六进制数表示,其形式为:
// 前缀 数据
0x81, 0x06 // 表示主条目的Input
其形式类似于键值对,前缀为键,数据为值
通过将相关的条目进行组合,能得到有层次结构的HID报文描述符。基本的报文描述符结构如下:
通过对一个HID键盘的基础报文描述符的分析,可以很好的对以上所有的HID描述符相关概念进行收尾
const uint8_t CustomHID_ReportDescriptor[63] ={
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xE0, // Usage Minimum (224)
0x29, 0xE7, // Usage Maximum (231)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute) ; Modifier byte
0x75, 0x08, // Report Size (8)
0x95, 0x01, // Report Count (1)
0x81, 0x01, // Input (Constant) ; Reserved byte
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (1)
0x29, 0x05, // Usage Maximum (5)
0x75, 0x01, // Report Size (1)
0x95, 0x05, // Report Count (5)
0x91, 0x02, // Output (Data, Variable, Absolute) ; LED report
0x75, 0x03, // Report Size (3)
0x95, 0x01, // Report Count (1)
0x91, 0x01, // Output (Constant) ; LED report padding
0x05, 0x07, // Usage Page (Key Codes)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x65, // Usage Maximum (101)
0x75, 0x08, // Report Size (1)
0x95, 0x06, // Report Count (48)
0x81, 0x00, // Input (Data, Array) ; Key array (6 keys)
0xC0 // End Collection
};
可以看出HID报文描述符在C语言中,是通过结构体实现的。其中每一行都是一个短条目,短条目由前缀+1字节数据构成(可以参考上面说到的条目表现形式)。最外层的集合告知计算机,要将此报文作为键盘数据进行解析。最内层的字段描述了报文数据的结构,报文数据大小为9个字节,共分为3部分:
阅读报文描述符的技巧
由于报文由条目组成,结构上有一定的特殊性,按照正常的顺序浏览可能会比较乱,所以阅读前先将报文合理分段,每段按照从后往前阅读,会比较好理解
Modifier Keys:结合Modifier Keys段报文描述符代码进行单独解析
0x05, 0x07, // Usage Page (Key Codes) 【表明字段的用途是表示键码】
0x19, 0xE0, // Usage Minimum (224)
0x29, 0xE7, // Usage Maximum (231) 【表示键码的值范围从224~231】
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1) 【表示单个按键可存在的逻辑状态,为0释放/1按下】
0x75, 0x01, // Report Size (1) 【表示报文大小,单位比特】
0x95, 0x08, // Report Count (8) 【表示报文数量】
0x81, 0x02, // Input (Data, Variable, Absolute) ; Modifier byte 【表示该段报文的数据方向,数据类型】
【分界线】
0x95, 0x01, // Report Count (1) 【表示报文大小,单位比特】
0x75, 0x08, // Report Size (8) 【表示报文数量】
0x81, 0x01, // Input (Constant) ; Reserved byte 【表示报文的数据方向,数据类型】
代码可以通过【分界线】分为两部分来看:
前半部分:这部分定义了报文数据类型为输入计算机的绝对值变量,用途为键码,报文数据数量为8,单个报文数据大小为1bit,所以总的数据量为8x1bit=8bits=1Byte。而八个比特位能表示八个按键,键码范围从224~231,对应如下图的八个特殊按键(参考官方HID Usage Table)
八个比特位分别按照高位对大键码,低位位对小键码的关系一一对应,每个比特位的0/1装袋分别表示按键被释放/按下。例如0b0000 0001对应就是LeftControl键按下
后半部分:这部分定义了报文数据类型为输入计算机的常量,没有指明实际用途,主要起到了填充数据的作用,保持整体数据按照8bits对齐,报文数据数量为8,单个报文数据大小为1bit,所以后半部分的数据量为8x1bit=8bits=1Byte
整体字段描述的数据的大小为2Bytes,描述的数据内容为Modified Keys
LED:这部分字段定义了报文数据类型为输出计算机的绝对值变量,用途为LED灯,报文数据数量为48,单个报文数据大小为1bit,所以总的数据量为5x1bit=5bits。五个比特位能表示五个LED灯,灯代表的含义范围从1~5,对应如下图的五个LED(参考官方HID Usage Table):
LED由计算机控制亮灭。这部分字段还额外带了一个 3bits的填充数据。
整体字段描述的数据的大小为1Bytes,描述的数据内容为LED
Normal Keys:这部分字段定义了报文数据类型为输入计算机的绝对值变量,用途为键码,报文数据数量为48,单个报文数据大小为1bit,所以总的数据量为48x1bit=48bits=6Byte。而六个字节能表示四十八个按键,键码范围从0~101,对应如下图的102个特殊按键(参考官方HID Usage Table)
整体字段描述的数据的大小为6Bytes,描述的数据内容为Normal Keys
报文数据大小为2+1+6=9Bytes,报文内容如下
Byte 9 | Byte 8 | Byte 7 | Byte 6 | Byte 5 | Byte 4 | Byte 3 | Byte 2 | Byte 1 | Byte 0 |
---|---|---|---|---|---|---|---|---|---|
- | - | - | - | - | - | Normal Keys | LED+Padding | Padding | Modif Keys |
报文数据本身由HID键盘生成并放置到对应IN端点,等待计算机按照端点描述符中定义的中断时间间隔内来获取。计算机取到报文数据后,自带的HID驱动会按照HID键盘报文描述符解析此报文,得到键盘释放/按下的消息。最终按键消息会转发给操作系统底层,由操作系统执行对应响应操作
HID报文描述符可能是所有USB描述符中最复杂的,抽象的概念相当多,理解起来也需要一定的时间。但是复杂的定义带来的却是报文易用性和灵活性,它几乎可以描述任何形式的数据,而且长度都由开发者来定义,形式也较为简洁清晰
CDC设备USB协议中定义的一种通信设备(通常指电信通信设备和中速网络通信设备)子类,包括调制解调器,网络适配器,手机,PDA等。通信设备领域,使用到很多种无线/有线通信协议以及各式的数据传输方式,CDC协议提供了对这些设备的协议和数据传输支持,例如常见的COM口UART协议,以太网口IP协议等。通过CDC协议可以将USB设备虚拟成通信设备,通过指定的传输协议和数据传输方式同USB设备进行通信
CDC设备有以下优点:
USB CDC协议中同样额外规定了CDC设备/主机之间的通信格式和通信协议,以CDC-ACM设备为例,相对于标准USB设备,它实现上有以下的特点:
CDC设备的接口组成
不同于常见的单接口设备,CDC设备由多个接口类组成,分别是【CDC控制接口类】和【CDC数据接口类】(可选)。所以在较新的操作系统中需要用额外的IAD描述符将两个接口关联起来。其中:
- CDC控制接口类:负责完成通信设备的配置和管理,包含了CDC控制接口,头部功能描述符,呼叫管理功能描述符,抽象控制模型描述符,联合功能描述符。各个描述符的作用分别为:
【CDC控制接口】:用于设备管理/电话管理(可选),设备管理一般涉及请求(request)和通知(notification),请求一般由端点0进行处理,通知需要额外配置其他端点进行处理
【头部功能描述符】:用于表示CDC功能描述符的开始,紧跟其后的是CDC设备的其他功能描述符
【呼叫管理功能描述符】:负责电话,AT指令相关的功能
【抽象控制模型描述符】:PSTN(Public Switched Telephone Net)下定义的抽象控制模型。用于描述使用AT指令,或AT.25Ver指令的调制解调设备
【联合功能描述符】:用于告知计算机,将哪几个接口联合起来,用来表示CDC功能。类似于IAD接口关联描述符- CDC数据接口类:负责进行数据的传输,包含了数据接口,数据调制解调器接口(可选),后者用于模拟调制解调设备的控制信号
接口关联描述符和联合功能描述符的关系
在USB协议的初期,没有规划好多接口设备的支持,基本上都是一个设备只使用一个接口。后来出现了CDC设备这样需要使用多接口的设备,于是便专门出了联合功能描述符用于应对。等到后期USB协议逐渐完善,推出了接口关联描述符这样的用于多个接口关联的描述符。两者使用起来并不会冲突,在旧版操作系统中,会忽略接口关联描述符,识别联合功能描述符;在新版操作系统中会忽略联合功能描述符,仅识别接口关联描述符
const uint8_t VirtualComPort_DeviceDescriptor[18] =
{
0x12, // bLength: 描述符长度,固定为0x12
0x01, // bDescriptorType: 描述符类型,固定为0x01,表示设备描述符
0x00, 0x02, // bcdUSB: USB协议版本号,这里是2.0
0x02, // bDeviceClass: 设备类别,2表示CDC设备
0x01, // bDeviceSubClass: 设备子类别,1表示使用抽象控制模型
0x01, // bDeviceProtocol: 设备协议,1表示使用AT指令交互
0x40, // bMaxPacketSize0: 最大数据包大小,这里是64字节
0x83, 0x04, // idVendor: USB设备厂商ID,这里是0x0483
0x20, 0x57, // idProduct: USB设备产品ID,这里是0x5720
0x00, 0x01, // bcdDevice: 设备版本号,这里是1.0
0x01, // iManufacturer: USB设备制造商字符串描述符的索引值,这里是1
0x02, // iProduct: USB设备产品字符串描述符的索引值,这里是2
0x03, // iSerialNumber: USB设备序列号字符串描述符的索引值,这里是3
0x01 // bNumConfigurations: USB设备支持的配置数量,这里是1
}
const uint8_t VirtualComPort_ConfigDescriptor[41] =
{
/************** 配置描述符 ****************/
0x09 // bLength: 描述符长度,固定为9
0x02 // bDescriptorType: 描述符类型,表示该描述符的类型为Configuration Descriptor,固定为2
0x29
0x00 // wTotalLength: 该配置描述符及其所包含的所有描述符的总长度,单位为字节
0x01 // bNumInterfaces: 该配置所包含的接口数量
0x01 // bConfigurationValue: 该配置的值,用于选择设备的配置
0x00 // iConfiguration: 该配置的字符串描述符的索引,如果不存在则为0
0xA0 // bmAttributes: 该配置的属性,包括供电方式和远程唤醒功能等
0x32 // bMaxPower: 该配置所需的最大电流,单位为2mA
/************** 接口关联描述符 ****************/
/* 09 */
0x08, // bLength:描述符长度,固定为8
0x0B, // bDescriptorType:描述符类型,表示该描述符的类型为接口关联描述符,固定为b
0x00, // bFirstInterface:第一个接口描述符编号 0
0x02, // bInterfaceCount:关联接口描述符总数 2
0x02, // bFunctionClass:功能类 CDC
0x02, // bFunctionSubClass:功能子类
0x01, // bFunctionProtocol:功能协议
0x00, // iFunction: 字符串描述符编号
/************** 控制接口描述符 ****************/
/* 17 */
0x09, // bLength:描述符长度,固定为9
0x04, // bDescriptorType:描述符类型,表示该描述符的类型为接口描述符,固定为4
0x00, // bInterfaceNumber:接口描述符编号
0x00, // bAlternateSetting:替换设置
0x01, // bNumEndpoints 包含端点数
0x02, // bInterfaceClass: 接口类型 CDC
0x02, // bInterfaceSubClass:接口子类 抽象控制模型
0x01, // nInterfaceProtocol:接口协议 Common AT commands V.250
2, // iInterface: 字符串描述符编号
/************** 头部功能描述符 ****************/
/* 26 */
0x05, // bLength:描述符长度,固定为5
0x24, // bDescriptorType:描述符类型 CS_INTERFACE
0x00, // bDescriptorSubtype:描述符子类 头部功能描述符
0x10, // bcdCD:CDC协议版本1.10
0x01,
/************** 呼叫功能描述符 ****************/
/* 31 */
0x05, // bFunctionLength:描述符长度,固定为5
0x24, // bDescriptorType:描述符类型 CS_INTERFACE
0x01, // bDescriptorSubtype:描述符子类 头部功能描述符
0x00, // bmCapabilities: D0+D1 */
0x01, // bDataInterface: 数据描述符接口号 1 */
/************** 抽象控制模型描述符 ****************/
/* 36 */
0x04, // bFunctionLength:描述符长度,固定为4
0x24, // bDescriptorType:描述符类型 CS_INTERFACE
0x02, // bDescriptorSubtype:描述符子类 头部功能描述符
0x02, // bmCapabilities */
/************** 联合功能描述符 ****************/
/* 40 */
0x05, // bFunctionLength:描述符长度,固定为5
0x24, // bDescriptorType:描述符类型 CS_INTERFACE
0x06, // bDescriptorSubtype:描述符子类 联合功能描述符
0x00, // bMasterInterface:主接口描述符描述符
0x01, // bSlaveInterface:附属接口描述符描述符
/************** 控制端点描述符 ****************/
/* 45 */
0x07, // bLength:描述符长度,固定为7
0x05, // bDescriptorType:描述符类型 端点描述符
0x83, // bEndpointAddress:IN端点3
0x03, // bmAttributes:端点类型 中断
0x40 // wMaxPacketSize:最大数据包容量 64字节
0x00,
0x02, // bInterval:数据获取间隔 2ms
/************** 数据接口描述符 ****************/
/* 52 */
0x09, // bLength:描述符长度,固定为9
0x04, // bDescriptorType:描述符类型,表示该描述符的类型为接口描述符,固定为4
0x02, // bInterfaceNumber:接口描述符编号
0x00, // bAlternateSetting:替换设置
0x02, // bNumEndpoints 包含端点数
0x0A, // bInterfaceClass: 接口类型 CDC数据接口
0x00, // bInterfaceSubClass:接口子类 未定义
0x00, // nInterfaceProtocol:接口协议 未定义
0, // iInterface: 字符串描述符编号
/************** 数据端点描述符 ****************/
/* 61 */
0x07, // bLength:描述符长度,固定为7
0x05, // bDescriptorType:描述符类型 端点描述符
0x02, // bEndpointAddress:OUT端点2
0x02, // bmAttributes:端点类型 批量
0x40 // wMaxPacketSize:最大数据包容量 64字节
0x00,
0x00, // bInterval:数据获取间隔 忽略
/* 68 */
0x07, // bLength:描述符长度,固定为7
0x05, // bDescriptorType:描述符类型 端点描述符
0x82, // bEndpointAddress:IN端点2
0x02, // bmAttributes:端点类型 批量
0x40 // wMaxPacketSize:最大数据包容量 64字节
0x00,
0x00, // bInterval:数据获取间隔 忽略
/* 75 */
};
以上描述符实现了一个CDC虚拟串口设备,其描述符分别指出:
相对于HID设备通过特殊的HID报文进行数据传输,CDC-ACM设备直接通过端点发送/接收数据,无需进行其他转换和解析。但是需要在CDC-ACM设备端实现CDC-ACM类的设备类特定请求SetLineCoding和GetLineCoding,这两个请求主要实现了设置和获取虚拟串口的串行通信参数,包括波特率,数据传输位数,奇偶校验位,停止位,具体实现如下:
typedef struct
{
uint32_t bitrate;
uint8_t format;
uint8_t paritytype;
uint8_t datatype;
}LINE_CODING;
LINE_CODING linecoding =
{
115200, // 波特率
0x00, // 停止位 1
0x00, // 奇偶校验位 无
0x08 // 数据位数 8
};
uint8_t *Virtual_Com_Port_GetLineCoding(uint16_t Length)
{
if (Length == 0)
{
pInformation->Ctrl_Info.Usb_wLength = sizeof(linecoding);
return NULL;
}
return(uint8_t *)&linecoding;
}
// 函数等待实现
uint8_t *Virtual_Com_Port_SetLineCoding(uint16_t Length)
{
if (Length == 0)
{
pInformation->Ctrl_Info.Usb_wLength = sizeof(linecoding);
return NULL;
}
return(uint8_t *)&linecoding;
}
在一个USB设备上实现多个功能,例如HID键盘+CDC虚拟串口。有两种方法:
通过以上对HID设备和CDC设备的解析。通过STM32去实现USB组合设备的思路就相当清晰了:
USB组合设备代码已上传Github:Sinuxtm32