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