上篇笔记,大家了解了一些枚举相关的概念,本篇笔记将详述 CDC 设备枚举过程,让大家对整体的枚举过程有个概念。
为了更好到理解并分析接下来的通信流程,鱼鹰首先介绍 标准请求和 描述符。
上篇笔记了解了标准请求和描述符是怎么回事,但还不够,还需要更细节的东西。
首先从标准请求的8字说起:
第一字节:位图请求信息。
D7 代表了接下来传输的数据是从主机到从机,还是从机到主机的。我们知道枚举过程使用控制传输,有三个阶段,如果有数据阶段,那么这个阶段的DATA数据是由主机发出还是由从机发出,就看这个位的值了。
当然了,因为每一次事务都有令牌包存在,所以IN令牌后的数据一定是由从机发出的,但是标准请求的这个位可以让从机做好发送数据或者接收数据的准备。
D6~D5 :代表了请求类型。
请求类型代表本次请求属于什么类型的请求。目前有三类,标准、类、厂商。标准请求主要有:
除了标准请求,还有类请求,比如 CDC 类,用到了三种类请求:
SET_LINE_CODING(0x20) 设置串口波特率、起始位、停止位、流控等信息
GET_LINE_CODING(0x21) 获取串口波特率、起始位、停止位、流控等信息
SET_CONTROL_LINE_STATE(0x22)用于设置串口的状态
厂商请求一般不会用于标准设备,CDC 类就没有用到(如果需要的话,应该也是能发出的)。
D4~D0 :代表了请求类型。
因为请求的内容可能是面向设备,也可能面向接口、端点,所以这个域确定了本次请求面向的对象,这样设备可以根据请求的对象作相应的措施。
第 二 字节: bRe q u est :请求代码,即上面的几种请求代码,每个请求都会有请求代码,代表了具体请求。
第 三四 字节: wValue :这个双字节主要根据bReuest来确定含义,比如如果是获取描述符(GET_DESCRIPTOR),而描述符有很多种,比如 设备描述符、 字符串描述符、 配置描述符,那么到底主机要获取什么描述符?就看这个双字的高字节了。如果 高 字节为 1,代表获取设备描述符,高字节为 2,代表获取配置描述符。
总之这个值的具体含义需要根据请求代码来确定,而每一种请求代码都会明确规定wValue具体含义。
第 五六 字节: wIndex :这个值和上面一样,也是需要请求代码来确定含义的。比如在获取产品序列号字符串时,这个值代表了语言 ID,告诉从机需要返回什么哪种字符串,当值为0x0409时代表英语。
第 七八 字节: wLenth :这个值代表接下来主机会发送或者需要接收字节长度。
一般来说,主机会根据需要在接下来的数据阶段获取或发送指定长度数据,主机发送的数据因为是由主机控制的,所以可以很容易确定这个值,但是因为主机并不清楚从机到底有多少数据会返回,所以这个值可能会比实际的更大。
比如第一次获取设备描述符时,因为主机不清楚这个描述符多长,一般会比实际的描述符长度更大,所以如果从机没有足够的数据返回,那么只要返回从机能返回的最大数据即可;
但是如果主机请求返回的数据比从机实际的数据 短,那么从机就按照主机的要求来就行,不必把自己所有的数据返回。
以上就是标准请求的内容。设备返回的描述符通用格式比较简单:
第一字节:描述符总长度(包括本字节)
第二字节:描述符类型(对应标准请求 wValue 的高字节)
其余字节就代表了这个描述符的具体内容了,每种描述的具体内格式都不相同,需要根据实际的描述符确定,比如:
bcdUSB 代表 USB 版本号,比如 0x0110,代表 USB 1.1 版本(bcd编码,即写成十六进制时的版本号),这样主机就知道这个设备只支持全速 12 Mbit/s 那么关于关于高速的请求qualifier(wValue 高字节为6)就不用发送到该设备了,因为发送给设备也肯定会被回复 STALL,那么主机就没必要浪费这个带宽了。
但是如果你这里写成 0x0200,那么这个设备可能是全速的,也可能是高速的,那么主机就会发送请求来询问是否支持高速,如果设备不支持,回复的描述符设置为0即可。
接下来的三个字节根据设备属于什么类别来确定,比如 CDC 类,这三个值分别为 0x02 ,0x00,0x00。
bMaxPacketSize0 确定了端点0的数据包大小,主机可以据此知道设备的传输能力,进而控制传输数据包的大小,不然主机一次发送的数据包太大,那么从机可能无法正确接收。
idVendor 由 USB-IF 分配,这个值确定了这个设备属于哪个厂家的产品。比如 0xC251,代表了KEIL,只要主机看到了这个代号,就知道这个设备由哪个厂家生产的了,因为这个在USB-IF中挂了号,大家都可以从网上查到。
和必须购买的 idVendor 不同却类似的是,iProduct 是由厂家自己定义的,可以根据这个来确定这个设备属于哪个产品。
这个USB设备更新到哪一个版本了?通过bcdDevice 即可确定,也是bcd 编码。
iManufacture 代表厂商的字符串序号,一般都是 1,这样当主机需要获取厂商的字符串,只要在wValue 的 低字节为设置为 1,那么从机就知道该发送什么字符串给主机了。
iProject 代表产品字符串序号,一般为2。
iSerialNumber 代表产品序列字符串序号,一般为3。
为什么从 1 开始编号,而不是从 0 开始呢,这是因为如果设备没有这个字符串的话,可以设置该值为 0,这样主机就知道没有这个字符串,也就不会主动获取这个字符串。
当然了,即使你告诉了主机有这个字符串存在,主机也是按照需求来获取的,不一定会把所有的字符串描述符都获取回来。
iNumConfigurations 代表了设备有多少种配置,前面说过,设备可能会在不同时刻的功能表现不一样,那么可以通过该值确定这个设备有多少种配置,一般而言这个值是1,即只有一种配置。毕竟 复合设备可以同时满足多功能的要求,没必要使用多种配置来达到多种功能的要求。
以上就是设备描述符的具体含义,其他描述符比如配置描述符、接口描述符、端点描述符等就自行看鱼鹰给的资料理解即可,只要找到对应描述符的格式说明,分析代码中的描述符数据也不是那么难的事情。
接下来鱼鹰介绍枚举总体流程。
主机在对设备复位后,首先会请求获取 设备描述符。这个描述符一般为18个字节,但是主机一开始并不知道这个描述符多大(虽然一般是18,但万一不是呢),所以一般主机会以更大的请求长度来获取,而从机根据实际长度18字节返回即可。
现在我们从多个维度看看这次交互的数据情况:
从传输事务的角度看:
从包的角度看:
从DATA内容看:
主机发送数据: 80 06 00 01 00 00 40 00
从机回复数据:
从D+、D-数据线电平变化的角度:
主机发送(建立阶段):
从机回复(数据阶段):
状态阶段:
现在把整个枚举过程大概图解一遍(其他请求交互的具体情况请看鱼鹰提供的资料):
数据流截取(鱼鹰提供的《CDC设备完整数据通信.txt》):