基本上来说,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已经废除了这个读取的步骤,在此我作为记录,以后再有需求就不会忘记
评论记录:
回复评论: