第五篇 USB设备枚举过程(2)
二、复位总线
主机第一次正确接收到设备描述符,就会复位一次总线,接下来发送设置地址的标准请求。
1. 协议分析仪数据
这次复位是正常复位。
如果主机第一次获取设备描述符之后,一直复位(异常复位),很大情况下是因为获取设备描述符的过程有错(返回的数据或者数据长度都有错)!比如:在一次验证FPGA的过程中,设备栈刚移植结束,跑起Demo之后,发现获取设备描述符后,Host一直复位总线,如下:
将数据展开,仔细分析后发现有错:返回的长度和数据内容都有错。
应该是18字节的设备描述符,而这里返回的是64字节的错误数据。说明底层设备栈能解析指令,但是在回复设备描述符处理上有误。
三、设置地址阶段
主机在收到第一个数据包(设备描述符数据包),确认无误,下发一个0长度的确认数据包之后,复位总线,接下来就进入设置地址阶段。也就是说,接下来下发的是一个设置地址请求。
1. SET_ADDRESS请求
设置地址请求也是一个USB标准设备请求。这是一个分配地址的过程(主机给设备分配一个地址)。既然是标准请求,那么它也是有8个字节的数据。指定的地址包含在wValue字段中。主机从地址为0的设备获取设备描述符,一旦第一次成功获取到设备描述符之后,主机就会立刻发送设置地址的请求,减少设备使用公共地址0的时间(每个设备插入,都先被复位,默认的地址为0,也就是0地址是所有的USB设备的初始化地址,即可以理解为公共地址)。
(1) 设置地址请求的结构
设置地址的请求结构如下:
bmRequestType |
bRequest |
wValue(2Byte) |
wIndex(2Byte) |
wLength(2Byte) |
数据过程 |
0x0000 0000 0x00 |
SET_ADDRESS 0x05 |
设备地址 |
0x0000 |
0x0000 |
没有 |
说明:
1. 设置地址的请求过程,是没有数据的,所以,数据长度就是0。
2. 索引也用不着(请求字符串描述符,索引才用得多),所以也为0。
(2) 实例
主机下发的数据包为 00 05 1D 00 00 00 00 00
数据方向:主机--->设备 设置地址请求 地址数据为0x001D=29
协议分析仪中的地址数据
分析:
wValue域的值是0x001D。转成十进制就是29,也就是主机分配给设备的地址是29。USB数据传输使用的是小端模式,低字节在前,所以,在协议分析中的数据就是:1D 00。
8个字节的标准请求数据是这样传输的:
左 右 <---------------------------------------------------------------------------------> 低字节 高字节 <---------------------------------------------------------------------------------> 前 后 <---------------------------------------------------------------------------------> 先发送 后发送 |
1)设备解析到地址数据之后,并没有能立刻使用。当设备成功读取到地址数据后,就进入状态过程(数据传输原本使用的是DATA0数据包,现在要切换到DATA1,这就是数据包的切换过程,这个过程也是芯片自动完成的),等待主机读取0长度的状态数据包(确认数据包)。书记成功读取到状态数据包之后,使用握手包ACK回应。至此,一次请求完毕,过程有点复杂,但是能确保地址数据已经传送给设备了(控制传输方式,过程很复杂,但是能保证数据的可靠性)。可结合协议分析仪里面,第2个事务来看。主机ACK响应设备之后,设备就启用新的地址了。接下来的一系列数据通信,使用的不在是0地址,而是新分配的地址。
2)这个过程有两个事务(两次数据传输)。经ACK回应的数据,对用户是可见的【程序能捕获到这部分数据,并进行分析】。
下面代码分析的过程仅供参考。
2. 代码分析
设置地址也是一个标准请求,代码流程和获得设备描述符是一样的。
2.1 端点有数据,则回调usb_handle_control_transfer() 打印:Transfer: ep=0x00, status=0x00 打印:Data Direction: Host---->Device
2.2 有标准请求,则回调usb_handle_standard_request() 打印: usb_handle_standard_request is called first
2.3 具体的请求,则调用usb_handle_std_device_req() 打印: REQ_SET_ADDRESS, addr=0x11
2.4 标准SETUP数据包,各个域的打印usb_print_setup() 打印: SETUP_Packet_Dat: bmRequestType=0x00 bRequest=0x05 wValue=0x0011 wIndex=0x0000 wLength=0 Byte
2.5 返回0长度的状态数据包用到两个函数:usb_data_to_host() 和 usb_dc_ep_write() 打印:usb_data_to_host: usb_data_to_host 打印:usb_dc_ep_write : send_0_bytes_to_Host!
|
串口打印跟踪如下:
四、再次获取设备描述符
设备设置好地址之后,又收到了主机获取设备描述符的请求,并且这次的请求,使用的是新的地址,也就说明地址设置成功了(这次的获取设备描述符请求,是为了确认地址是否已经设置成功)。如果上一阶段,设备这边没有成功设置地址,也就是说,地址还是0,那么主机的这次请求肯定得不到回应,主机会检测到超时!进而复位总线,又回到第一阶段……,如此反复。
也就是说,某次请求,如果检测到超时(设备没给主机回应它想要的数据),那么主机就会进行复位,把复位信号作为依据,就可以知道某次请求是否异常(某次事务是否异常)。这里需要注意的是,第一次,也就是主机获得设备描述符后,复位总线,这次的复位是正常的复位,不属于异常复位。
1. 协议分析仪捕获到的数据:
这个过程,能说明两点:
(1)主机请求设备设置某个指定的地址,已经成功请求,并且设备已经启用了新的地址。
(2)接下来设备能使用新的地址和主机进行通信了。
下面代码分析的过程仅供参考。
2. 代码分析
和第一阶段的,请求设备描述符是一样的。设备返回18字节的设备描述符就可以了。下面是串口跟踪打印: