[学习资料]

USB主机是如何检测到设备的插入的呢?

[复制链接]
282|15
手机看帖
扫描二维码
随时随地手机跟帖
phosphate|  楼主 | 2019-11-8 14:51 | 显示全部楼层 |阅读模式
USB设备的插入检测机制
      首先,在USB集线器的每个下游端口的D+和D-上,分别接了一个15K欧姆的下拉电阻到地。这样,在集线器的端口悬空时,就被这两个下拉电阻拉到了低电平。而在USB设备端,在D+或者D-上接了1.5K欧姆上拉电阻.对于全速和高速设备,上拉电阻是接在D+上;而低速设备则是上拉电阻接在D-上。这样,当设备插入到集线器时,由1.5K的上拉电阻和15K的下拉电阻分压,结果就将差分数据线中的一条拉高了。集线器检测到这个状态后,它就报告给USB主控制器(或者通过它上一层的集线器报告给USB主控制器),这样就检测到设备的插入了。USB高速设备先是被识别为全速设备,然后通过HOST和DEVICE两者之间的确认,再切换到高速模式的。在高速模式下,是电流传输模式,这时将D+上的上拉电阻断开。  

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:52 | 显示全部楼层
   一个简单的实验:只用一个上拉电阻接在USB的+5V和D+或者D-上,WINDOWS也会提示发现新硬件,但是无法找到驱动程序。这时去设备管理器里面看,有显示未知USB设备,并且其VID和PID为0。根据这个,我们可以简单的判断设备是否枚举成功。如下图所示,分别是枚举不成功和枚举成功的图。

109115dc5108b93e54.png
38265dc51091e2b2a.png

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:52 | 显示全部楼层
USB设备的枚举过程:

     USB主机在检测到USB设备插入后,就要对设备进行枚举了。为什么要枚举呢?枚举就是从设备读取一些信息,知道设备是什么样的设备,如何进行通信,这样主机就可以根据这些信息来加载合适的驱动程序。调试USB设备,很重要的一点就是USB的枚举过程,只要枚举成功了,那么就已经成功大半了。

       在说枚举之前,先大概说一下USB的一种传输模式——控制传输。这种传输在USB中是非常重要的,它要保证数据的正确性,在设备的枚举过程中都是使用控制传输。控制传输分为三个过程:①建立过程。②可选的数据过程。③状态过程。建立(Setup)过程都是由USB主机发起,它开始于一个Setup令牌包,后面紧跟一个DATA0包。如果是控制输入传输,那么数据过程就是输入数据;如果是控制输出传输,那么数据过程是输出数据。如果在设置过程中,指定了数据长度为0,则没有数据过程。数据过程之后是状态过程。状态过程刚好与数据过程的数据传输方向相反:如果是控制输入传输,则状态过程是一个输出数据包;如果是控制输出传输,则状态过程是一个输入数据包。状态阶段用来确认所有的数据都已经正确传输。

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:52 | 显示全部楼层
枚举的详细过程。

       首先,USB主机检测到USB设备插入后,就会先对设备复位。设备复位后,USB主机就会对地址为0的设备发送获取设备描述符的标准请求。所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。主机在建立阶段发出获取设备描述符的输入请求,设备收到该请求后,在数据过程将设备描述符返回给主机。主机在成功获取到一个数据包的设备描述符后并且确认没有什么错误后(注意:有些USB设备的端点0大小不足18字节(但至少具有8字节),而标准的设备描述有18字节,在这种情况下,USB设备只能暂时按最大包将部分设备描述符返回,而主机在成功获取到前面一部分描述符后,就不会再请求剩下的设备描述符部分,而是进入设置地址阶段),就会返回一个0长度的状态数据包给设备。

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:53 | 显示全部楼层
然后主机再对设备复位一下,接下来就会进入到设置地址阶段。这时USB主机发出一个设置地址的请求(建立过程,设置地址无数据过程),地址包含在建立包中,具体的地址USB主机会负责管理,它会分配一个唯一的地址给新的设备。USB设备在收到地址后,返回0长度的状态包,主机收到0长度的状态包之后,会返回一个ACK给设备。设备在收到这个ACK之后,就可以启用新的地址了。这样设备就分配到了一个唯一的设备地址,以后主机就通过它来进行访问该设备。然后主机再次获取设备描述符,这次跟第一次可能有点不一样,这次需要获取完全部的18个字节的设备描述符。当然,如果你的端点0缓冲大于18字节的话,那就跟第一次的情形一样了。

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:53 | 显示全部楼层
接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。如果有字符串描述符的话,还要获取字符串描述符。另外HID设备还有HID描述符等。使用BUSHOUND以及通过串口返回信息,很容易看到具体的过程。总之是主机请求什么,你的程序就响应什么.

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:53 | 显示全部楼层
USB是个通用的总线,端口都是统一的。但是USB设备却各种各样,例如USB鼠标,USB键盘,U盘等等,那么USB主机是如何识别出不同的设备的呢?这就要依赖于描述符了。USB的描述符主要有设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符,HID描述符,报告描述符等等.

     一个USB设备有一个设备描述符,设备描述符里面决定了该设备有多少种配置,每种配置描述符对应着配置描述符;而在配置描述符中又定义了该配置里面有多少个接口,每个接口有对应的接口描述符;在接口描述符里面又定义了该接口有多少个端点,每个端点对应一个端点描述符;端点描述符定义了端点的大小,类型等等。由此我们可以看出,USB的描述符之间的关系是一层一层的,最上一层是设备描述符,下面是配置描述符,再下面是接口描述符,再下面是端点描述符。在获取描述符时,先获取设备描述符,然后再获取配置描述符,根据配置描述符中的配置集合长度,一次将配置描述符、接口描述符、端点描述符一起一次读回。其中可能还会有获取设备序列号,厂商字符串,产品字符串等。

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:53 | 显示全部楼层
每种描述符都有自己独立的编号,如下:

#defineDEVICE_DESCRIPTOR 0x01 //设备描述符
#defineCONFIGURATION_DESCRIPTOR 0x02 //配置描述符
#defineSTRING_DESCRIPTOR 0x03 //字符串描述符
#defineINTERFACE_DESCRIPTOR 0x04 //接口描述符
#defineENDPOINT_DESCRIPTOR 0x05 //端点描述符

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:54 | 显示全部楼层
下面分别详细介绍一下各描述符。
1.设备描述符
//定义标准的设备描述符结构
typedefstruct_DEVICE_DCESCRIPTOR_STRUCT
{
BYTEblength; //设备描述符的字节数大小
BYTEbDescriptorType; //设备描述符类型编号
WORDbcdUSB; //USB版本号
BYTEbDeviceClass; //USB分配的设备类代码
BYTEbDeviceSubClass; //USB分配的子类代码
BYTEbDeviceProtocol; //USB分配的设备协议代码
BYTEbMaxPacketSize0; //端点0的最大包大小
WORDidVendor; //厂商编号
WORDidProduct; //产品编号
WORDbcdDevice; //设备出厂编号
BYTEiManufacturer; //设备厂商字符串的索引
BYTEiProduct; //描述产品字符串的索引
BYTEiSerialNumber; //描述设备序列号字符串的索引
BYTEbNumConfigurations; //可能的配置数量
}
DEVICE_DESCRIPTOR_STRUCT,*pDEVICE_DESCRIPTOR_STRUCT;

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:54 | 显示全部楼层
//实际的设备描述符示例
codeDEVICE_DESCRIPTOR_STRUCTdevice_descriptor= //设备描述符
{
sizeof(DEVICE_DESCRIPTOR_STRUCT), //设备描述符的字节数大小,这里是18字节
DEVICE_DESCRIPTOR, //设备描述符类型编号,设备描述符是01
0x1001, //USB版本号,这里是USB01.10,即USB1.1。由于51是大端模式,所以高低字节交换
0x00, //USB分配的设备类代码,0表示类型在接口描述符中定义
0x00, //USB分配的子类代码,上面一项为0时,本项也要设置为0
0x00, //USB分配的设备协议代码,上面一项为0时,本项也要设置为0
0x10, //端点0的最大包大小,这里为16字节
0x7104, //厂商编号,这个是需要跟USB组织申请的ID号,表示厂商代号。
0xf0ff, //该产品的编号,跟厂商编号一起配合使用,让主机注册该设备并加载相应的驱动程序
0x0100, //设备出厂编号
0x01, //设备厂商字符串的索引,在获取字符串描述符时,使用该索引号来识别不同的字符串
0x02, //描述产品字符串的索引,同上
0x03, //描述设备序列号字符串的索引,同上
0x01 //可能的配置数为1,即该设备只有一个配置
};

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:55 | 显示全部楼层
2.配置描述符
//定义标准的配置描述符结构
typedefstruct_CONFIGURATION_DESCRIPTOR_STRUCT
{
BYTEbLength; //配置描述符的字节数大小
BYTEbDescriptorType; //配置描述符类型编号
WORDwTotalLength; //此配置返回的所有数据大小
BYTEbNumInterfaces; //此配置所支持的接口数量
BYTEbConfigurationValue; //Set_Configuration命令所需要的参数值
BYTEiConfiguration; //描述该配置的字符串的索引值
BYTEbmAttributes; //供电模式的选择
BYTEMaxPower; //设备从总线提取的最大电流
}
CONFIGURATION_DESCRIPTOR_STRUCT,*pCONFIGURATION_DESCRIPTOR_STRUCT;

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:55 | 显示全部楼层
3.接口描述符
//定义标准的接口描述符结构
typedefstruct_INTERFACE_DESCRIPTOR_STRUCT
{
BYTEbLength; //接口描述符的字节数大小
BYTEbDescriptorType; //接口描述符的类型编号
BYTEbInterfaceNumber; //该接口的编号
BYTEbAlternateSetting; //备用的接口描述符编号
BYTEbNumEndpoints; //该接口使用的端点数,不包括端点0
BYTEbInterfaceClass; //接口类型
BYTEbInterfaceSubClass; //接口子类型
BYTEbInterfaceProtocol; //接口遵循的协议
BYTEiInterface; //描述该接口的字符串索引值
}
INTERFACE_DESCRIPTOR_STRUCT,*pINTERFACE_DESCRIPTOR_STRUCT;

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:55 | 显示全部楼层
4.端点描述符

//定义标准的端点描述符结构
typedefstruct_ENDPOINT_DESCRIPTOR_STRUCT
{
BYTEbLegth; //端点描述符字节数大小
BYTEbDescriptorType; //端点描述符类型编号
BYTEbEndpointAddress; //端点地址及输入输出属性
BYTEbmAttributes; //端点的传输类型属性
WORDwMaxPacketSize; //端点收、发的最大包大小
BYTEbInterval; //主机查询端点的时间间隔
}
ENDPOINT_DESCRIPTOR_STRUCT,*pENDPOINT_DESCRIPTOR_STRUCT;

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:56 | 显示全部楼层
下面是一个配置描述符集合的定义
typedefstruct_CON_INT_ENDP_DESCRIPTOR_STRUCT
{
CONFIGURATION_DESCRIPTOR_STRUCTconfiguration_descriptor;
INTERFACE_DESCRIPTOR_STRUCT interface_descritor;
ENDPOINT_DESCRIPTOR_STRUCT endpoint_descriptor[ENDPOINT_NUMBER];
}CON_INT_ENDP_DESCRIPTOR_STRUCT;

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:57 | 显示全部楼层
配置描述符集合的示例

codeCON_INT_ENDP_DESCRIPTOR_STRUCTcon_int_endp_descriptor= //配置描述符集合
{
//configuration_descriptor //配置描述符
{
sizeof(CONFIGURATION_DESCRIPTOR_STRUCT), //配置描述符的字节数大小,这里为9
CONFIGURATION_DESCRIPTOR, //配置描述符类型编号,配置描述符为2
(sizeof(CONFIGURATION_DESCRIPTOR_STRUCT)+
sizeof(INTERFACE_DESCRIPTOR_STRUCT)+
sizeof(ENDPOINT_DESCRIPTOR_STRUCT)*ENDPOINT_NUMBER)*256+
(sizeof(CONFIGURATION_DESCRIPTOR_STRUCT)+
sizeof(INTERFACE_DESCRIPTOR_STRUCT)+
sizeof(ENDPOINT_DESCRIPTOR_STRUCT)*ENDPOINT_NUMBER)/256, //配置描述符集合的总大小
0x01, //只包含一个接口
0x01, //该配置的编号
0x00, //iConfiguration字段
0x80, //采用总线供电,不支持远程唤醒
0xC8 //从总线获取最大电流400mA
},
//interface_descritor //接口描述符
{

sizeof(INTERFACE_DESCRIPTOR_STRUCT), //接口描述符的字节数大小,这里为9
INTERFACE_DESCRIPTOR, //接口描述符类型编号,接口描述符为3
0x00, //接口编号为4
0x00, //该接口描述符的编号为0
ENDPOINT_NUMBER, //非0端点数量为2,只使用端点主端点输入和输出
0x08, //定义为USB大容量存储设备
0x06, //使用的子类,为简化块命令
0x50, //使用的协议,这里使用单批量传输协议
0x00 //接口描述符字符串索引,为0,表示没有字符串
},
//endpoint_descriptor[]
{
{ //主端点输入描述
sizeof(ENDPOINT_DESCRIPTOR_STRUCT), //端点描述符的字节数大小,这里为7
ENDPOINT_DESCRIPTOR, //端点描述符类型编号,端点描述符为5
MAIN_POINT_IN, //端点号,主输入端点
ENDPOINT_TYPE_BULK, //使用的传输类型,批量传输
0x4000, //该端点支持的最大包尺寸,64字节
0x00 //中断扫描时间,对批量传输无效
},
{ //主端点输出描述
sizeof(ENDPOINT_DESCRIPTOR_STRUCT), //端点描述符的字节数大小,这里为7
ENDPOINT_DESCRIPTOR, //端点描述符类型编号,端点描述符为5
MAIN_POINT_OUT, //端点号,主输出端点
ENDPOINT_TYPE_BULK, //使用的传输类型,批量传输
0x4000, //该端点支持的最大包尺寸,64字节
0x00 //中断扫描时间,对批量传输无效
}

使用特权

评论回复
phosphate|  楼主 | 2019-11-8 14:57 | 显示全部楼层
其中关于端点的类型定义如下
//定义的端点类型
#defineENDPOINT_TYPE_CONTROL 0x00 //控制传输
#defineENDPOINT_TYPE_ISOCHRONOUS 0x01 //同步传输
#defineENDPOINT_TYPE_BULK 0x02 //批量传输
#defineENDPOINT_TYPE_INTERRUPT 0x03 //中断传输
端点号的定义如下
#defineMAIN_POINT_OUT 0x02 //2号输出端点
#defineMAIN_POINT_IN 0x82 //2号输入端点

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

32

主题

393

帖子

1

粉丝