6、USB枚举

6 USB枚举

USB的概念实在是太多了,只挑了一些重要的作为笔记还有这么多概念,实在是心态爆炸.
USB枚举,每个USB设备上电都会经历的一段过程,其实就是USB的初始化,只有初始化完成了,其他USB主机才能知道你到底是做啥的,至于怎么去做,做的好不好那就是枚举成功之后的事情了.

6.1 枚举流程

枚举流程在USB2.0协议规范里面的9.1.2小节有提到过,如下所示。

When a USB device is attached to or removed from the USB, the host uses a process known as bus enumeration to identify and manage the device state changes necessary. When a USB device is attached to a powered port, the following actions are taken:

  1. The hub to which the USB device is now attached informs the host of the event via a reply on its status change pipe (refer to Section11.12.3 for more information). At this point, the USB device is in the Powered state and the port to which it is attached is disabled.
  2. The host determines the exact nature of the change by querying the hub.
  3. Now that the host knows the port to which the new device has been attached, the host then waits for at least 100 ms to allow completion of an insertion process and for power at the device to become stable. The host then issues a port enable and reset command to that port. Refer to Section 7.1.7.5 for sequence of events and timings of connection through device reset.
  4. The hub performs the required reset processing for that port (see Section 11.5.1.5). When the reset signal is released, the port has been enabled. The USB device is now in the Default state and can draw no more than 100 mA from VBUS. All of its registers and state have been reset and it answers to the default address.
  5. The host assigns a unique address to the USB device, moving the device to the Address state.
  6. Before the USB device receives a unique address, its Default Control Pipe is still accessible via the default address. The host reads the device descriptor to determine what actual maximum data payload size this USB device’s default pipe can use.
  7. The host reads the configuration information from the device by reading each configuration zero to n-1, where n is the number of configurations. This process may take several milliseconds to complete.
  8. Based on the configuration information and how the USB device will be used, the host assigns a configuration value to the device. The device is now in the Configured state and all of the endpoints in this configuration have taken on their described characteristics. The USB device may now draw the amount of VBUS power described in its descriptor for the selected configuration. From the device’s point of view, it is now ready for use.

When the USB device is removed, the hub again sends a notification to the host. Detaching a device disables the port to which it had been attached. Upon receiving the detach notification, the host will update its local topological information.

并不是很好理解,而且里面不仅仅包括软件,还有硬件, 这里借用博客大佬的一段描述来形容.

  1. 设备上电

用户把USB设备插入USB端口(主机下的根hub或主机下行端口上的hub端口)或给系统启动时设备上电。此时,USB设备处于加电状态,它所连接的端口是无效的。

  1. 检测电压变化,报告主机

USB设备上电后,一直监测USB设备接口电平变化HUB检测到有电压变化,将利用自己的中断端点将信息反馈给主控制器有设备连接。
这一步是HUB给主机报告有设备连接。(如下图,空闲状态,接上设备HUB总线电平会变化)

  1. 主机了解连接设备

每个hub利用它自己的中断端点向主机报告它的各个端口的状态(对于这个过程,设备是看不到的,也不必关心),报告的内容只是hub端口的设备连接/断开的事件。如果有连接/断开事件发生,那么host会发送一个
Get_Port_Status请求(request)给hub以了解此次状态改变的确切含义。Get_Port_Status等请求属于所有hub都要求支持的hub类标准请求(standard hub-class requests)。

  1. Hub检测所插入的设备是高速还是低速

hub通过检测USB总线空闲(Idle)时差分线的高低电压来判断所连接设备的速度类型,当host发来Get_Port_Status请求时,hub就可以将此设备的速度类型信息回复给host。USB
2.0规范要求速度检测要先于复位(Reset)操作。

根据是D+还是D-被拉高来判断到底是什么设备(全速/低速)插入端口(全速、高速设备的区分在前面的文章中有描述)

  1. hub复位设备

主机一旦得知新设备已连上以后,它至少等待100ms以使得插入操作的完成以及设备电源稳定工作。然后主机控制器就向hub发出一个 Set_Port_Feature请求让hub复位其管理的端口(刚才设备插上的端口)。hub通过驱动数据线到复位状态(D+和D-全为低电平 ),并持续至少10ms。当然,hub不会把这样的复位信号发送给其他已有设备连接的端口,所以其他连在该hub上的设备自然看不到复位信号,不受影响。(如下图,hub的每个接口是单独检测的)

  1. Host检测所连接的全速设备是否是支持高速模式

因为根据USB 2.0协议,高速(High Speed)设备在初始时是默认全速(Full Speed )状态运行,所以对于一个支持USB 2.0的高速hub,当它发现它的端口连接的是一个全速设备时,会进行高速检测,看看目前这个设备是否还支持高速传输,如果是,那就切到高速信号模式,否则就一直在全速状态下工作。
同样的,从设备的角度来看,如果是一个高速设备,在刚连接bub或上电时只能用全速信号模式运行(根据USB 2.0协议,高速设备必须向下兼容USB 1.1的全速模式)。随后hub会进行高速检测,之后这个设备才会切换到高速模式下工作。假如所连接的hub不支持USB 2.0,即不是高速hub,不能进行高速检测,设备将一直以全速工作。

  1. Hub建立设备和主机之间的信息通道

主机不停地向hub发送Get_Port_Status请求,以查询设备是否复位成功。Hub返回的报告信息中有专门的一位用来标志设备的复位状态。
当hub撤销了复位信号,设备就处于默认/空闲状态(Default state),准备接收主机发来的请求。设备和主机之间的通信通过控制传输,默认地址0,端点号0进行。此时,设备能从总线上得到的最大电流是100mA。(所有的USB设备在总线复位后其地址都为0,这样主机就可以跟那些刚刚插入的设备通过地址0通信。)

  1. 主机发送Get_Descriptor请求获取默认管道的最大包长度

默认管道(Default
Pipe)在设备一端来看就是端点0。主机此时发送的请求是默认地址0,端点0,虽然所有未分配地址的设备都是通过地址0来获取主机发来的请求,但由于枚举过程不是多个设备并行处理,而是一次枚举一个设备的方式进行,所以不会发生多个设备同时响应主机发来的请求。
设备描述符的第8字节代表设备端点0的最大包大小。虽然说设备所返回的设备描述符(Device
Descriptor)长度只有18字节,但系统也不在乎,此时,描述符的长度信息对它来说是最重要的,其他的瞄一眼就过了。当完成第一次的控制传输后,也就是完成控制传输的状态阶段,系统会要求hub对设备进行再一次的复位操作(USB规范里面可没这要求)。再次复位的目的是使设备进入一个确定的状态。

  1. 主机给设备分配一个地址

主机控制器通过Set_Address请求向设备分配一个唯一的地址。在完成这次传输之后,设备进入地址状态(Address state),之后就启用新地址继续与主机通信。这个地址对于设备来说是终生制的,设备在,地址在;设备消失(被拔出,复位,系统重启),地址被收回。同一个设备当再次被枚举后得到的地址不一定是上次那个了。

  1. 主机获取设备的信息

主机发送
Get_Descriptor请求到新地址读取设备描述符,这次主机发送Get_Descriptor请求可算是诚心,它会认真解析设备描述符的内容。设备描述符内信息包括端点0的最大包长度,设备所支持的配置(Configuration)个数,设备类型,VID(Vendor
ID,由USB-IF分配), PID(Product ID,由厂商自己定制)等信息。
之后主机发送Get_Descriptor请求,读取配置描述符(Configuration
Descriptor),字符串等,逐一了解设备更详细的信息。事实上,对于配置描述符的标准请求中,有时wLength一项会大于实际配置描述符的长度(9字节),比如255。这样的效果便是:主机发送了一个Get_Descriptor_Configuration
的请求,设备会把接口描述符,端点描述符等后续描述符一并回给主机,主机则根据描述符头部的标志判断送上来的具体是何种描述符。
接下来,主机就会获取配置描述符。配置描述符总共为9字节。主机在获取到配置描述符后,根据里面的配置集合总长度,再获取配置集合。配置集合包括配置描述符,接口描述符,端点描符等等。
如果有字符串描述符的话,还要获取字符串描述符。另外HID设备还有HID描述符等。

  1. 主机给设备挂载驱动(复合设备除外)

主机通过解析描述符后对设备有了足够的了解,会选择一个最合适的驱动给设备。 然后tell the
world(announce_device)说明设备已经找到了,最后调用设备模型提供的接口device_add将设备添加到 usb
总线的设备列表里,然后 usb总线会遍历驱动列表里的每个驱动,调用自己的 match(usb_device_match)
函数看它们和你的设备或接口是否匹配,匹配的话调用device_bind_driver函数,现在就将控制权交到设备驱动了。
对于复合设备,通常应该是不同的接口(Interface)配置给不同的驱动,因此,需要等到当设备被配置并把接口使能后才可以把驱动挂载上去。

  1. 设备驱动选择一个配置

驱动程序根据前面设备回复的信息,发送Set_Configuration请求来正式确定选择设备的哪个配置(Configuration)作为工作配置(对于大多数设备来说,一般只有一个配置被定义)。至此,设备处于配置状态(Configured),当然,设备也应该使能它的各个接口(Interface)。
对于复合设备,主机会在这个时候根据设备接口信息,给它们挂载驱动。

USB协议定义了设备的6种状态,仅在枚举过程中,设备就经历了4个状态的迁移:上电状态(Powered),默认状态(Default),地址状态(Address)和配置状态(Configured)(其他两种是连接状态(Attached)和挂起状态(Suspend),前面文章中有描述)。

上面这几段话是全盘照搬的从零开始学USB(十七、USB的枚举),其实如果学习USB概念的话,跟着这位大佬的教程基本上对于USB的一些通用概念会有一个很全面的了解了.

6.2 例子

下面这个是CDC的一个设备的枚举过程.里面涉及到CDC类的一些特殊请求.但是不影响.
在这里插入图片描述

6.2.1 获取设备描述符

结合最开始的控制传输可以知道,一共是三个阶段,如下.
在这里插入图片描述

  • Setup阶段

    • 发送一个SETUP包,告诉设备我要发送数据了.
    • 发送了 80 06 00 01 00 00 40 00 这8个数据
      • 80:这是一个主机发给设备(bit0-bit4)的一个标准(bit5-bit6)请求命令,请求的结果是要求设备给Host返回(bit7 = 1)。
      • 06:这是一个GET_DESCRIPTOR,即获取描述符的请求。
      • 0100:高字节表示描述符类型,01表示设备,02表示配置;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。
      • 0000:为0的时候不需要关心,如果不为0,则表示为Langurage ID.
      • 0040:表示返回的数据长度不能超过0X40.
    • 设备接收到数据后,回复了一个ACK包
  • DATA阶段

    • 发送了一个IN包,告诉设备我可以接收数据了
    • 根据上个阶段接收到的数据回复对应的数据,回传数据如下
      __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
      {
        0x12,                       /*bLength */
        USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
        0x00,                       /*bcdUSB */
        0x02,
        0x02,                       /*bDeviceClass*/
        0x02,                       /*bDeviceSubClass*/
        0x00,                       /*bDeviceProtocol*/
        USB_MAX_EP0_SIZE,           /*bMaxPacketSize*/
        LOBYTE(USBD_VID),           /*idVendor*/
        HIBYTE(USBD_VID),           /*idVendor*/
        LOBYTE(USBD_PID_FS),        /*idProduct*/
        HIBYTE(USBD_PID_FS),        /*idProduct*/
        0x00,                       /*bcdDevice rel. 2.00*/
        0x02,
        USBD_IDX_MFC_STR,           /*Index of manufacturer  string*/
        USBD_IDX_PRODUCT_STR,       /*Index of product string*/
        USBD_IDX_SERIAL_STR,        /*Index of serial number string*/
        USBD_MAX_NUM_CONFIGURATION  /*bNumConfigurations*/
      };
      
    • 主机接收到后,回复了一个ACK包
  • STATUS阶段

    • 因为数据阶段是读取数据,所以这个阶段是相反的OUT包
    • 主机发送了一个0长度的DATA1包
    • 设备收到信息后回复ACK包
6.2.2 复位

在这里插入图片描述
可以清楚的看到在第一次描述符之后,总线上会有一次复位状态.

6.2.3 设置地址

在这里插入图片描述

  • Setup阶段
    • 发送一个SETUP包,告诉设备我要发送数据了.
    • 发送了 00 05 19 00 00 00 00 00 这8个数据
      • 00:这是一个主机发给设备(bit0-bit4)的一个标准(bit5-bit6)请求命令,请求的结果是要求设备接收Host的消息(bit7 = 0)。
      • 05:这是一个SET_ADDRESS,即设置地址。
      • 0019:对照标准设备请求表可知,该数值即为主机为设备分配的地址.
    • 设备接收到回复了一个ACK包
  • STATUS阶段
    • 因为数据阶段是空的,所以这个阶段是规定的IN包
    • 回复了一个0长度的DATA1包
    • 主机收到后回复了一个ACK,此流程结束
6.2.4 继续获取设备描述符

在这里插入图片描述可以看到通过上述两个流程后,设备的地址已经更改为0X19(25)了.
这个阶段的数据和第一次进行控制传输的操作几乎一摸一样(区别在于六七字节的返回长度),不在说了.

6.2.5 读取完整描述及配置描述

这里获取的就是USB设备的配置描述符了.同样是控制传输.
在这里插入图片描述

  • Setup阶段

    • 发送一个SETUP包,告诉设备我要发送数据了.
    • 发送了 80 06 00 02 00 00 FF 00 这8个数据
      • 80 :这是一个主机发给设备(bit0-bit4)的一个标准(bit5-bit6)请求命令,请求的结果是要求设备给Host返回(bit7 = 1)。
      • 06 :这是一个GET_DESCRIPTOR,即获取描述符的请求。
      • 0200:高字节表示描述符类型,01表示设备,02表示配置;低字节表示索引。比如设备有多个配置,那需要读取不同配置的时候就通过低字节。
      • 0000:为0的时候不需要关心,如果不为0,则表示为Langurage ID.
      • 00FF:表示返回的数据长度不能超过0XFF.
    • 设备接收到数据后,回复了一个ACK包
  • DATA阶段

    • 发送了一个IN包,告诉设备我可以接收数据了
    • 根据上个阶段接收到的数据回复对应的数据,回传数据如下
      /* USB CDC device Configuration Descriptor */
      __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END =
      {
        /*Configuration Descriptor*/
        0x09,   /* bLength: Configuration Descriptor size */
        USB_DESC_TYPE_CONFIGURATION,      /* bDescriptorType: Configuration */
        USB_CDC_CONFIG_DESC_SIZ,                /* wTotalLength:no of returned bytes */
        0x00,
        0x02,   /* bNumInterfaces: 2 interface */
        0x01,   /* bConfigurationValue: Configuration value */
        0x00,   /* iConfiguration: Index of string descriptor describing the configuration */
        0xC0,   /* bmAttributes: self powered */
        0x32,   /* MaxPower 0 mA */
      
        /*---------------------------------------------------------------------------*/
      
        /*Interface Descriptor */
        0x09,   /* bLength: Interface Descriptor size */
        USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */
        /* Interface descriptor type */
        0x00,   /* bInterfaceNumber: Number of Interface */
        0x00,   /* bAlternateSetting: Alternate setting */
        0x01,   /* bNumEndpoints: One endpoints used */
        0x02,   /* bInterfaceClass: Communication Interface Class */
        0x02,   /* bInterfaceSubClass: Abstract Control Model */
        0x01,   /* bInterfaceProtocol: Common AT commands */
        0x00,   /* iInterface: */
      
        /*Header Functional Descriptor*/
        0x05,   /* bLength: Endpoint Descriptor size */
        0x24,   /* bDescriptorType: CS_INTERFACE */
        0x00,   /* bDescriptorSubtype: Header Func Desc */
        0x10,   /* bcdCDC: spec release number */
        0x01,
      
        /*Call Management Functional Descriptor*/
        0x05,   /* bFunctionLength */
        0x24,   /* bDescriptorType: CS_INTERFACE */
        0x01,   /* bDescriptorSubtype: Call Management Func Desc */
        0x00,   /* bmCapabilities: D0+D1 */
        0x01,   /* bDataInterface: 1 */
      
        /*ACM Functional Descriptor*/
        0x04,   /* bFunctionLength */
        0x24,   /* bDescriptorType: CS_INTERFACE */
        0x02,   /* bDescriptorSubtype: Abstract Control Management desc */
        0x02,   /* bmCapabilities */
      
        /*Union Functional Descriptor*/
        0x05,   /* bFunctionLength */
        0x24,   /* bDescriptorType: CS_INTERFACE */
        0x06,   /* bDescriptorSubtype: Union func desc */
        0x00,   /* bMasterInterface: Communication class interface */
        0x01,   /* bSlaveInterface0: Data Class Interface */
      
        /*Endpoint 2 Descriptor*/
        0x07,                           /* bLength: Endpoint Descriptor size */
        USB_DESC_TYPE_ENDPOINT,   /* bDescriptorType: Endpoint */
        CDC_CMD_EP,                     /* bEndpointAddress */
        0x03,                           /* bmAttributes: Interrupt */
        LOBYTE(CDC_CMD_PACKET_SIZE),     /* wMaxPacketSize: */
        HIBYTE(CDC_CMD_PACKET_SIZE),
        CDC_HS_BINTERVAL,                           /* bInterval: */
        /*---------------------------------------------------------------------------*/
      
        /*Data class interface descriptor*/
        0x09,   /* bLength: Endpoint Descriptor size */
        USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */
        0x01,   /* bInterfaceNumber: Number of Interface */
        0x00,   /* bAlternateSetting: Alternate setting */
        0x02,   /* bNumEndpoints: Two endpoints used */
        0x0A,   /* bInterfaceClass: CDC */
        0x00,   /* bInterfaceSubClass: */
        0x00,   /* bInterfaceProtocol: */
        0x00,   /* iInterface: */
      
        /*Endpoint OUT Descriptor*/
        0x07,   /* bLength: Endpoint Descriptor size */
        USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
        CDC_OUT_EP,                        /* bEndpointAddress */
        0x02,                              /* bmAttributes: Bulk */
        LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
        HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
        0x00,                              /* bInterval: ignore for Bulk transfer */
      
        /*Endpoint IN Descriptor*/
        0x07,   /* bLength: Endpoint Descriptor size */
        USB_DESC_TYPE_ENDPOINT,      /* bDescriptorType: Endpoint */
        CDC_IN_EP,                         /* bEndpointAddress */
        0x02,                              /* bmAttributes: Bulk */
        LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
        HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE),
        0x00                               /* bInterval: ignore for Bulk transfer */
      } ;
      
    • 主机接收到后,回复了一个ACK包
  • STATUS阶段

    • 因为数据阶段是读取数据,所以这个阶段是相反的OUT包
    • 主机发送了一个0长度的DATA1包
    • 设备收到信息后回复ACK包

这一次回传的内容,包含配置描述符和接口描述符,端点描述符等.

6.2.6 字符串描述符

有的设备并没有字符串描述符,这步可以忽略掉,在32做的CDC设备里面是由三个字符串描述符的,下面一一列举出来.

6.6.6.1 serial number string

在这里插入图片描述在这里插入图片描述
有所差异的是wIndex是0X0409,代表的是美式英语,具体的所有选项可以参考这里.
可以很清晰的看到是在请求字符串描述符,且索引为3.在STM32的USB库里面定义如下
在这里插入图片描述所以在这里请求的是USBD_IDX_SERIAL_STR,所以返回值是

uint8_t * USBD_FS_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
  UNUSED(speed);
  *length = USB_SIZ_STRING_SERIAL;

  /* Update the serial number string descriptor with the data from the unique
   * ID */
  Get_SerialNum();
  /* USER CODE BEGIN USBD_FS_SerialStrDescriptor */
  
  /* USER CODE END USBD_FS_SerialStrDescriptor */
  return (uint8_t *) USBD_StringSerial;
}

最终结果如下,USB库默认的SERIAL_NUMBER是32芯片内的设备唯一ID(每个芯片都不一样).

在这里插入图片描述

6.2.6.2 LangID string

在这里插入图片描述在这里插入图片描述
这里可以很清晰的看到和上条指令唯二的不同点是

  • wIndex为0
  • wValue的低八位为0,索引值为0
    参考STM32里面的定义,可知获取的Lang ID string.
    所以返回值是04 03 09 04,如下所示.
#define USBD_LANGID_STRING     1033
__ALIGN_BEGIN uint8_t USBD_LangIDDesc[USB_LEN_LANGID_STR_DESC] __ALIGN_END =
{
     USB_LEN_LANGID_STR_DESC,
     USB_DESC_TYPE_STRING,
     LOBYTE(USBD_LANGID_STRING),
     HIBYTE(USBD_LANGID_STRING)
};
uint8_t * USBD_FS_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
  UNUSED(speed);
  *length = sizeof(USBD_LangIDDesc);
  return USBD_LangIDDesc;
}

在这里插入图片描述

6.2.6.3 product string

在这里插入图片描述请求含义如下所示.
在这里插入图片描述返回值如下所示.

#define USBD_PRODUCT_STRING_FS     "STM32 Virtual ComPort"
uint8_t * USBD_FS_ProductStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
  if(speed == 0)
  {
    USBD_GetString((uint8_t *)USBD_PRODUCT_STRING_FS, USBD_StrDesc, length);
  }
  else
  {
    USBD_GetString((uint8_t *)USBD_PRODUCT_STRING_FS, USBD_StrDesc, length);
  }
  return USBD_StrDesc;
}
6.2.7 读取完整设备描述及配置描述

在这里插入图片描述

  1. 读取设备描述符
  2. 读取配置描述符(和6.2.5不同,这次只读取了配置描述符的9个字节)
  3. 读取完整的配置描述符(同6.2.5)
  4. 同第三步.
6.2.6 设置配置描述符

在这里插入图片描述具体含义如下
在这里插入图片描述
至此,USB的枚举就算完成了,剩下的就是CDC特殊类的一些操作了.

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值