基本上来说,STM32 在CubeMX生成的不同class的设备,都是支持windows免驱的,唯独在DFU模式的情况下,需要手动安装st的驱动才能实现功能,那么有什么办法能够在DFU模式下免驱呢,答案就是WinUSB。

废话不多说,我们用最简单明了的方式来实现此功能,上代码!

目前我们选用的都是Microsoft OS 2.0 描述符规范,因为1.0的描述符规范已经逐渐被微软抛弃了,在这里都没有什么存在的意义,1.0是通过请求0xEE的描述符来进行识别,到2.0是通过BOS的请求来获取完整的内容。

通过CubeMX生成基础程序

我这边选择的芯片是STM32F103,当然,其他有USB功能的STM芯片都是适用于这个功能的,我想既然打算开发WinUSB,这部分的内容读者都应该轻车熟路,所以我在这里省略,直接进入正题

修改代码

1.修改设备描述符

一般来说F4的设备需要使能USBD_LPM_ENABLED这个功能,最重要的一点,需要把bcdUSB的版本改为0x0210,这样windows才会试图请求BOS描述符,如下所示:

__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
  0x12,                       /*bLength */
  USB_DESC_TYPE_DEVICE,       /*bDescriptorType*/
  0x10,                       /*bcdUSB */ 修改此值
  0x02,
  0x00,                       /*bDeviceClass*/
  0x00,                       /*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*/
};

2.修改二进制描述符

将程序自动生成的二进制描述符改为如下所示,其中USB_REQ_GET_OS_FEATURE_DESCRIPTOR为请求的VendorCode,我们在下面会有提及

__ALIGN_BEGIN uint8_t USBD_FS_BOSDesc[33] __ALIGN_END =
{
  ///
  /// WCID20 BOS descriptor
  ///
  0x05,                                             /* bLength */
  USB_DESC_TYPE_BOS,                                /* bDescriptorType */
  0x21, 0x00,                                       /* wTotalLength */
  0x01,                                             /* bNumDeviceCaps */
  ///
  /// WCID20 device capability descriptor
  ///
  0x1c,                                             /* bLength */
  0x10,                                             /* bDescriptorType */
  0x05,                                             /* bDevCapabilityType */
  0x00,                                             /* bReserved */
  0xdf, 0x60, 0xdd, 0xd8, 0x89, 0x45, 0xc7, 0x4c,   /* bPlatformCapabilityUUID_16 */
  0x9c, 0xd2, 0x65, 0x9d, 0x9e, 0x64, 0x8a, 0x9f,   /* bPlatformCapabilityUUID_16 */
  0x00, 0x00, 0x03, 0x06,                           /* dwWindowsVersion */
  LOBYTE(WINUSB20_WCID_DESC_SET_SIZE), HIBYTE(WINUSB20_WCID_DESC_SET_SIZE),/* wDescriptorSetTotalLength */
  USB_REQ_GET_OS_FEATURE_DESCRIPTOR,                                 /* bVendorCode */
  0x00,    
};

3.增加WCID描述符

这是基础的单配置的最简单的WCID描述符,用于识别设备可用的window版本等信息

__ALIGN_BEGIN const uint8_t WINUSB20_WCIDDescriptorSet[WINUSB20_WCID_DESC_SET_SIZE] __ALIGN_END = {
  ///
  /// WCID20 descriptor set descriptor
  ///
  0x0a, 0x00,                                       /* wLength */
  0x00, 0x00,                                       /* wDescriptorType */
  0x00, 0x00, 0x03, 0x06,                           /* dwWindowsVersion */
  0xa2, 0x00,                                       /* wDescriptorSetTotalLength */ 
  ///
  /// WCID20 compatible ID descriptor
  ///
  0x14, 0x00,                                       /* wLength */
  0x03, 0x00,                                       /* wDescriptorType */
  /* WINUSB */
  'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00,         /* cCID_8 */
  /*  */
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   /* cSubCID_8 */
  ///
  /// WCID20 registry property descriptor
  ///
  0x84, 0x00,                                       /* wLength */
  0x04, 0x00,                                       /* wDescriptorType */
  0x07, 0x00,                                       /* wPropertyDataType */
  0x2a, 0x00,                                       /* wPropertyNameLength */
  /* DeviceInterfaceGUIDs */
  'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00,       /* wcPropertyName_21 */
  'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00,       /* wcPropertyName_21 */
  't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00,       /* wcPropertyName_21 */
  'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00,       /* wcPropertyName_21 */
  'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00,       /* wcPropertyName_21 */
  0x00, 0x00,                                       /* wcPropertyName_21 */
  0x50, 0x00,                                       /* wPropertyDataLength */
  /* {36FC9E60-C465-11CF-8056-444553540000} */
  '{', 0x00, '3', 0x00, '6', 0x00, 'F', 0x00,       /* wcPropertyData_40 */
  'C', 0x00, '9', 0x00, 'E', 0x00, '6', 0x00,       /* wcPropertyData_40 */
  '0', 0x00, '-', 0x00, 'C', 0x00, '4', 0x00,       /* wcPropertyData_40 */
  '6', 0x00, '5', 0x00, '-', 0x00, '1', 0x00,       /* wcPropertyData_40 */
  '1', 0x00, 'C', 0x00, 'F', 0x00, '-', 0x00,       /* wcPropertyData_40 */
  '8', 0x00, '0', 0x00, '5', 0x00, '6', 0x00,       /* wcPropertyData_40 */
  '-', 0x00, '4', 0x00, '4', 0x00, '4', 0x00,       /* wcPropertyData_40 */
  '5', 0x00, '5', 0x00, '3', 0x00, '5', 0x00,       /* wcPropertyData_40 */
  '4', 0x00, '0', 0x00, '0', 0x00, '0', 0x00,       /* wcPropertyData_40 */
  '0', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00      /* wcPropertyData_40 */
};

以上都是在usbd_desc.h中添加的数据,下接下来还有2个文件需要修改

4.增加描述符的获取接口

打开usbd_def.h

先增加两个宏定义

#define  USB_REQ_GET_STATUS                             0x00U
#define  USB_REQ_CLEAR_FEATURE                          0x01U
#define  USB_REQ_SET_FEATURE                            0x03U
#define  USB_REQ_SET_ADDRESS                            0x05U
#define  USB_REQ_GET_DESCRIPTOR                         0x06U
#define  USB_REQ_SET_DESCRIPTOR                         0x07U
#define  USB_REQ_GET_CONFIGURATION                      0x08U
#define  USB_REQ_SET_CONFIGURATION                      0x09U
#define  USB_REQ_GET_INTERFACE                          0x0AU
#define  USB_REQ_SET_INTERFACE                          0x0BU
#define  USB_REQ_SYNCH_FRAME                            0x0CU
#define	 USB_REQ_GET_OS_FEATURE_DESCRIPTOR              0x20U //新增的
#define  MS_OS_20_DESCRIPTOR_INDEX                      0x07U //新增的

再增加两个接口的函数指针

typedef struct
{
  uint8_t  *(*GetDeviceDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
  uint8_t  *(*GetLangIDStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
  uint8_t  *(*GetManufacturerStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
  uint8_t  *(*GetProductStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
  uint8_t  *(*GetSerialStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
  uint8_t  *(*GetConfigurationStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
  uint8_t  *(*GetInterfaceStrDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
#if (USBD_LPM_ENABLED == 1U)
  uint8_t  *(*GetBOSDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length);
#if (USBD_WINUSB_ENABLED == 1U)
  uint8_t  *(*GetWCIDDescriptor)(USBD_SpeedTypeDef speed, uint16_t *length); //新增的
#endif
#endif
} USBD_DescriptorsTypeDef;

5.实现描述符的获取函数

首先在usbd_desc.c中声明函数

uint8_t * USBD_FS_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t * USBD_FS_LangIDStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t * USBD_FS_ManufacturerStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t * USBD_FS_ProductStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t * USBD_FS_SerialStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t * USBD_FS_ConfigStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
uint8_t * USBD_FS_InterfaceStrDescriptor(USBD_SpeedTypeDef speed, uint16_t *length);
#if (USBD_LPM_ENABLED == 1U)
uint8_t * USBD_FS_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *length); //新增
#if (USBD_WINUSB_ENABLED == 1U)
uint8_t * USBD_FS_WCIDDescriptor(USBD_SpeedTypeDef speed, uint16_t *length); //新增
#endif
#endif

在结构体中增加函数调用

USBD_DescriptorsTypeDef FS_Desc =
{
  USBD_FS_DeviceDescriptor
, USBD_FS_LangIDStrDescriptor
, USBD_FS_ManufacturerStrDescriptor
, USBD_FS_ProductStrDescriptor
, USBD_FS_SerialStrDescriptor
, USBD_FS_ConfigStrDescriptor
, USBD_FS_InterfaceStrDescriptor
#if (USBD_LPM_ENABLED == 1U)
, USBD_FS_BOSDescriptor //新增
#if (USBD_WINUSB_ENABLED == 1U)
, USBD_FS_WCIDDescriptor //新增
#endif
#endif
};

最后,增加函数实现

#if (USBD_LPM_ENABLED == 1U)
uint8_t * USBD_FS_BOSDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) 
{
  UNUSED(speed);
  *length = sizeof(USBD_FS_BOSDesc);
  return (uint8_t*)USBD_FS_BOSDesc;
}
#if (USBD_WINUSB_ENABLED == 1U)
uint8_t * USBD_FS_WCIDDescriptor(USBD_SpeedTypeDef speed, uint16_t *length)
{
  UNUSED(speed);
  *length = sizeof(WINUSB20_WCIDDescriptorSet);
  return (uint8_t*)WINUSB20_WCIDDescriptorSet;
}
#endif
#endif

6.增加Vendor接口的实现

打开usbd_ctlreq.c文件,在文件中新增函数声明

static void USBD_GetDescriptor(USBD_HandleTypeDef *pdev,
                               USBD_SetupReqTypedef *req);

static void USBD_SetAddress(USBD_HandleTypeDef *pdev,
                            USBD_SetupReqTypedef *req);

static void USBD_SetConfig(USBD_HandleTypeDef *pdev,
                           USBD_SetupReqTypedef *req);

static void USBD_GetConfig(USBD_HandleTypeDef *pdev,
                           USBD_SetupReqTypedef *req);

static void USBD_GetStatus(USBD_HandleTypeDef *pdev,
                           USBD_SetupReqTypedef *req);

static void USBD_SetFeature(USBD_HandleTypeDef *pdev,
                            USBD_SetupReqTypedef *req);

static void USBD_ClrFeature(USBD_HandleTypeDef *pdev,
                            USBD_SetupReqTypedef *req);
#if (USBD_LPM_ENABLED == 1)
static void USBD_GetVendor(USBD_HandleTypeDef *pdev,
                            USBD_SetupReqTypedef *req); //新增的
#endif
static uint8_t USBD_GetLen(uint8_t *buf);

在文件中新增函数实现

#if (USBD_LPM_ENABLED == 1)
static void USBD_GetVendor(USBD_HandleTypeDef *pdev,
                            USBD_SetupReqTypedef *req)
{
  uint16_t len = 0U;
  uint8_t *pbuf = NULL;

  switch (req->wIndex) 
  {
	  case MS_OS_20_DESCRIPTOR_INDEX: //MS OS 2.0 的7号请求
      if (pdev->pDesc->GetWCIDDescriptor != NULL)
      {
        pbuf = pdev->pDesc->GetWCIDDescriptor(pdev->dev_speed, &len);
      }
      else
      {
        USBD_CtlError(pdev, req);
      }
    break;
  }

  if((len != 0)&& (req->wLength != 0))
  {
    len = MIN(len , req->wLength);
    
    USBD_CtlSendData (pdev, 
                      pbuf,
                      len);
  }  
}
#endif

最后,修改USBD_StdDevReq函数的前部分为

USBD_StatusTypeDef  USBD_StdDevReq(USBD_HandleTypeDef *pdev,
                                   USBD_SetupReqTypedef *req)
{
  USBD_StatusTypeDef ret = USBD_OK;

  switch (req->bmRequest & USB_REQ_TYPE_MASK)
  {
    case USB_REQ_TYPE_VENDOR:
#if (USBD_LPM_ENABLED == 1)    
      USBD_GetVendor(pdev, req);
      break;    
#endif      
    case USB_REQ_TYPE_CLASS:
      pdev->pClass->Setup(pdev, req);
      break;

修改USBD_StdItfReq函数的前部分为

USBD_StatusTypeDef  USBD_StdItfReq(USBD_HandleTypeDef *pdev,
                                   USBD_SetupReqTypedef  *req)
{
  USBD_StatusTypeDef ret = USBD_OK;

  switch (req->bmRequest & USB_REQ_TYPE_MASK)
  {
    case USB_REQ_TYPE_VENDOR:
#if (USBD_LPM_ENABLED == 1)   
      USBD_GetVendor(pdev, req);  
      break;
#endif      
    case USB_REQ_TYPE_CLASS:
    case USB_REQ_TYPE_STANDARD:

修改USBD_StdEPReq的前部分为

USBD_StatusTypeDef  USBD_StdEPReq(USBD_HandleTypeDef *pdev,
                                  USBD_SetupReqTypedef  *req)
{
  USBD_EndpointTypeDef *pep;
  uint8_t   ep_addr;
  USBD_StatusTypeDef ret = USBD_OK;
  ep_addr  = LOBYTE(req->wIndex);

  switch (req->bmRequest & USB_REQ_TYPE_MASK)
  {
    case USB_REQ_TYPE_VENDOR:
#if (USBD_LPM_ENABLED == 1) 
      USBD_GetVendor(pdev, req);
      break;
#endif      
    case USB_REQ_TYPE_CLASS:
      pdev->pClass->Setup(pdev, req);
      break;

这个目的是获取vendor的支持

最后

到此,程序应该可以被识别成winUSB设备了,如图所示

网上有很多教程混淆了1.0和2.0的概念,导致大家可能遇到0xEE请求无法收到的情况,这主要是目前使用的2.0已经废除了这个读取的步骤,在此我作为记录,以后再有需求就不会忘记

参考链接

STM32F407实现USB BULK传输+WINUSB免驱

Microsoft OS 2.0 描述符规范

物联沃分享整理
物联沃-IOTWORD物联网 » STM32与WinUSB的适配指南

发表评论