STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC

STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC


本博客完整代码下载地址:https://download.csdn.net/download/qq153471503/18160432?spm=1001.2014.3001.5501

或关注以下公众号,回复关键字CDCMSC获取下载链接!
在这里插入图片描述


目录

  • STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC
      • 第一步:基础工程生成
      • 第二步:USB设备描述符的修改
      • 第三步:修改PMA端点分布
      • 第四步:编写我们自己的组合设备配置
      • 第五步:MX_DEVICE_USB_Init修改
      • 遇到的问题


工程试验环境

  • STM32F103RC
  • STM32CUBEIDE1.5.1

1、本教程默认你已经会使用STM32CUBEMX生成CDC代码和MSC代码,这两个工程的生成很简单,网络上的教程一搜遍地是。

2、USB组合设备的移植修改需要具备一定的USB知识储备,如果没有强烈建议看一下我的另一篇博客:STM32 USB相关知识扫盲


首先说一下STM32的USB库的初始化操作,MX_USB_DEVICE_Init函数中使用USBD_RegisterClass函数注册绑定了实际的端口初始化控制等操作,如果是CDC那么注册的就是USBD_CDC这个结构,如果是MSC那么就是注册的USB_MSC这个结构,所以我们的组合设备思路就是用哪个的时候,就将这个结构切换成对应的操作结构。


第一步:基础工程生成

首先先用STM32CUBEMX生成CDC的工程和MSC的工程,并测试通过没有问题后,我这里使用CDC工程为基础工程进行修改成USB组合设备。

基本步骤如下:

  1. USB设备描述符修改成组合设备类型
  2. 修改PMA端点分布
  3. 修改USB配置描述符并添加MSC的配置描述
  4. 修改初始化函数接口,改写成我们自己的组合设备初始化操作函数
  5. 修改MX_USB_DEVICE_Init函数,注册成我们自己的组合设备

下面进行分布修改。

第二步:USB设备描述符的修改

这一步很简单的,就是修改usbd_desc.c中的设备描述符数组USBD_FS_DeviceDesc,将设备类型改为组合设备类型:
STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC_第1张图片

第三步:修改PMA端点分布

首先修改一下CDC所用到的端点地址,CDC的输入输出端点不动,将命令端点成0X83:

在改一下MSC的输入输出端点,改成0X82和0X02:
STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC_第2张图片
然后进入usbd_conf.c文件中,找到USBD_LL_Init函数,修改PMA端点初始化:
STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC_第3张图片

这里的修改非常关键,为什么addr要从0X38开始呢?

因为我们目前用到了:

  • 默认的两个端点(0X80、0X00)
  • CDC的三个端点(0X81、0X01、0X83)
  • MSC的两个端点(0X82、0X02)

共7个端点,每个端点占用8字节,总共56字节,十六进制就是0X38!

好多人修改自己的USB应用时,就是卡在了这里!如果起始地址修改不对,那么USB端点缓冲就会覆盖PMA头部的端点描述!

有关PMA的详细描述及使用,请看文档头部的《STM32 USB相关知识扫盲》链接文章!


第四步:编写我们自己的组合设备配置

这里我为了方便修改,新建了一个usbd_composite.c文件,用于编写组合设备驱动,文件内容如下:

#include "usbd_def.h"
#include "usbd_msc.h"
#include "usbd_cdc.h"
#include "usbd_storage_if.h"
#include "usbd_cdc_if.h"


#define USB_MC_CONFIG_DESC_SIZ 106


extern USBD_HandleTypeDef hUsbDeviceFS;


extern uint8_t USBD_MSC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx);
extern uint8_t USBD_MSC_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx);
extern uint8_t  USBD_MSC_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req);
extern uint8_t USBD_MSC_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum);
extern uint8_t USBD_MSC_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum);


extern uint8_t  USBD_CDC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx);
extern uint8_t  USBD_CDC_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx);
extern uint8_t  USBD_CDC_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req);
extern uint8_t  USBD_CDC_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum);
extern uint8_t  USBD_CDC_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum);
extern uint8_t  USBD_CDC_EP0_RxReady(USBD_HandleTypeDef *pdev);


static uint8_t  USBD_Composite_Init (USBD_HandleTypeDef *pdev, uint8_t cfgidx);
static uint8_t  USBD_Composite_DeInit (USBD_HandleTypeDef *pdev, uint8_t cfgidx);
static uint8_t  USBD_Composite_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req);
static uint8_t  USBD_Composite_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum);
static uint8_t  USBD_Composite_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum);
static uint8_t  *USBD_Composite_GetHSCfgDesc (uint16_t *length);
static uint8_t  *USBD_Composite_GetFSCfgDesc (uint16_t *length);
static uint8_t  *USBD_Composite_GetOtherSpeedCfgDesc (uint16_t *length);
static uint8_t  *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length);
static uint8_t USBD_Composite_RxReady (USBD_HandleTypeDef *pdev);
static void USBD_Composite_Switch_MSC(USBD_HandleTypeDef *pdev);
static void USBD_Composite_Switch_CDC(USBD_HandleTypeDef *pdev);


USBD_ClassTypeDef  USBD_Composite_CDC_MSC =
{
  USBD_Composite_Init,
  USBD_Composite_DeInit,
  USBD_Composite_Setup,
  NULL, /*EP0_TxSent*/
  USBD_Composite_RxReady, /*EP0_RxReady*/
  USBD_Composite_DataIn,
  USBD_Composite_DataOut,
  NULL, /*SOF */
  NULL,
  NULL,
  USBD_Composite_GetHSCfgDesc,
  USBD_Composite_GetFSCfgDesc,
  USBD_Composite_GetOtherSpeedCfgDesc,
  USBD_Composite_GetDeviceQualifierDescriptor,
};


/*   All Descriptors (Configuration, Interface, Endpoint, Class, Vendor */
__ALIGN_BEGIN uint8_t USBD_Composite_CfgDesc[USB_MC_CONFIG_DESC_SIZ] __ALIGN_END =
{
  /*Configuration Descriptor*/
  0x09,   /* bLength: Configuration Descriptor size */
  USB_DESC_TYPE_CONFIGURATION,      /* bDescriptorType: Configuration */
  USB_MC_CONFIG_DESC_SIZ,           /* wTotalLength:no of returned bytes */
  0x00,
  0x03,   /* bNumInterfaces: 3 interface */ /* +++lakun:CDC用了两个接口,MSC用了一个接口,所以是3 */
  0x01,   /* bConfigurationValue: Configuration value */
  0x00,   /* iConfiguration: Index of string descriptor describing the configuration */
  0xC0,   /* bmAttributes: self powered */
  0x32,   /* MaxPower 0 mA */

  /*---------------------------------------------------------------------------*/

  //
  // +++lakun: IAD(Interface Association Descriptor),用于指示CDC
  //
  0X08,  // bLength: Interface Descriptor size,固定值
  0X0B,  // bDescriptorType: IAD,固定值
  0X00,  // bFirstInterface,第一个接口的起始序号,从0开始
  0X02,  // bInterfaceCount,本IAD下的接口数量
  0X02,  // bFunctionClass: CDC,表明该IAD是一个CDC类型的设备
  0X02,  // bFunctionSubClass:子类型,默认即可
  0X01,  // bFunctionProtocol:控制协议,默认即可
  0X00,  // iFunction

  /*Interface Descriptor */
  0x09,   /* bLength: Interface Descriptor size */
  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: Interface */
  /* Interface descriptor type */
  0x00,   /* bInterfaceNumber: Number of Interface */                   /* +++lakun:接口编号  */
  0x00,   /* bAlternateSetting: Alternate setting */
  0x01,   /* bNumEndpoints: One endpoints used */
  0x02,   /* bInterfaceClass: Communication Interface Class */          /* +++lakun:表明这是一个通信接口 */
  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 */           /* +++lakun:这里是用来指示CDC通信接口的编号的 */
  0x01,   /* bSlaveInterface0: Data Class Interface */                    /* +++lakun:这里是用来指示CDC数据接口的编号的 */

  /*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_FS_BINTERVAL,                           /* bInterval: */
  /*---------------------------------------------------------------------------*/

  /*Data class interface descriptor*/
  0x09,   /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_INTERFACE,  /* bDescriptorType: */
  0x01,   /* bInterfaceNumber: Number of Interface */                     /* +++lakun:CDC数据接口的编号为1 */
  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_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  HIBYTE(CDC_DATA_FS_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_FS_MAX_PACKET_SIZE),  /* wMaxPacketSize: */
  HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
  0x00,                               /* bInterval: ignore for Bulk transfer */

  //
  // +++lakun: IAD(Interface Association Descriptor)
  //
  0X08,  // bLength: Interface Descriptor size,固定值
  0X0B,  // bDescriptorType: IAD,固定值
  0X02,  // bFirstInterface,接口的起始序号(第0、1编号的接口用于CDC1,所以从2开始)
  0X01,  // bInterfaceCount,本IAD下的接口数量
  0X08,  // bFunctionClass: MSC,表明该IAD是一个MSC类型的设备
  0X06,  // bFunctionSubClass:子类型,默认即可
  0X50,  // bFunctionProtocol:控制协议,默认即可
  0X05,  // iFunction

  /********************  Mass Storage interface ********************/
  0x09,   /* bLength: Interface Descriptor size */
  0x04,   /* bDescriptorType: */
  0x02,   /* bInterfaceNumber: Number of Interface */  /* +++lakun:第0和1编号的用给了CDC,所以MSC接口的编号从2开始 */
  0x00,   /* bAlternateSetting: Alternate setting */
  0x02,   /* bNumEndpoints*/
  0x08,   /* bInterfaceClass: MSC Class */
  0x06,   /* bInterfaceSubClass : SCSI transparent*/
  0x50,   /* nInterfaceProtocol */
  0x05,          /* iInterface: */
  /********************  Mass Storage Endpoints ********************/
  0x07,   /*Endpoint descriptor length = 7*/
  0x05,   /*Endpoint descriptor type */
  MSC_EPIN_ADDR,   /*Endpoint address (IN, address 1) */
  0x02,   /*Bulk endpoint type */
  LOBYTE(MSC_MAX_FS_PACKET),
  HIBYTE(MSC_MAX_FS_PACKET),
  0x00,   /*Polling interval in milliseconds */

  0x07,   /*Endpoint descriptor length = 7 */
  0x05,   /*Endpoint descriptor type */
  MSC_EPOUT_ADDR,   /*Endpoint address (OUT, address 1) */
  0x02,   /*Bulk endpoint type */
  LOBYTE(MSC_MAX_FS_PACKET),
  HIBYTE(MSC_MAX_FS_PACKET),
  0x00     /*Polling interval in milliseconds*/
} ;


/* USB Standard Device Descriptor */
uint8_t USBD_Composite_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] =
{
  USB_LEN_DEV_QUALIFIER_DESC,
  USB_DESC_TYPE_DEVICE_QUALIFIER,
  0x00,
  0x02,
  0x00,
  0x00,
  0x00,
  0X40,
  0x01,
  0x00,
};


// 这个函数是修改的USBD_MSC_Init
static void USBD_Composite_MSC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
	USBD_Composite_Switch_MSC(pdev);

	/* Open EP OUT */
	USBD_LL_OpenEP(pdev, MSC_EPOUT_ADDR, USBD_EP_TYPE_BULK, MSC_MAX_FS_PACKET);
	pdev->ep_out[MSC_EPOUT_ADDR & 0xFU].is_used = 1U;

	/* Open EP IN */
	USBD_LL_OpenEP(pdev, MSC_EPIN_ADDR, USBD_EP_TYPE_BULK, MSC_MAX_FS_PACKET);
	pdev->ep_in[MSC_EPIN_ADDR & 0xFU].is_used = 1U;

	/* Init the BOT  layer */
	MSC_BOT_Init(pdev);
}

// 这个函数是修改的USBD_CDC_Init
static void USBD_Composite_CDC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
	USBD_CDC_HandleTypeDef   *hcdc = NULL;

	USBD_Composite_Switch_CDC(pdev);

	/* Open EP IN */
	USBD_LL_OpenEP(pdev, CDC_IN_EP, USBD_EP_TYPE_BULK, CDC_DATA_FS_IN_PACKET_SIZE);
	pdev->ep_in[CDC_IN_EP & 0xFU].is_used = 1U;

	/* Open EP OUT */
	USBD_LL_OpenEP(pdev, CDC_OUT_EP, USBD_EP_TYPE_BULK,CDC_DATA_FS_OUT_PACKET_SIZE);
	pdev->ep_out[CDC_OUT_EP & 0xFU].is_used = 1U;

	/* Open Command IN EP */
	USBD_LL_OpenEP(pdev, CDC_CMD_EP, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
	pdev->ep_in[CDC_CMD_EP & 0xFU].is_used = 1U;

	hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;

	/* Init  physical Interface components */
	((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Init();

	/* Init Xfer states */
	hcdc->TxState = 0U;
	hcdc->RxState = 0U;

	/* Prepare Out endpoint to receive next packet */
	USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer, CDC_DATA_FS_OUT_PACKET_SIZE);
}

uint8_t USBD_Composite_Init (USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
	USBD_Composite_MSC_Init(pdev, cfgidx);  // 初始化MSC
	USBD_Composite_CDC_Init(pdev, cfgidx);  // 初始化CDC
	return USBD_OK;
}

uint8_t  USBD_Composite_DeInit (USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
	return USBD_OK;
}

uint8_t  USBD_Composite_Setup (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
{
	switch(req->wIndex)  // wIndex是接口编号,在配置描述符里面我们第一个IAD是CDC使用的是编号0和1两个接口,第二个IAD是MSC使用的是编号2的接口
	{

	//
	// 第一个IAD
	//
	case 0:  // CDC的命令接口编号为0
	case 1:  // CDC的数据接口编号为1
		USBD_Composite_Switch_CDC(pdev);
		USBD_CDC_Setup(pdev, req);
		break;

	//
	// 第二个IAD
	//
	case 2:  // MSC只有一个接口,编号为2
		USBD_Composite_Switch_MSC(pdev);
		USBD_MSC_Setup(pdev, req);
		break;

	//
	// 第三个IAD(如果有,在这里初始化)
	//
	case 3:
		break;
	default:break;
	}
	return USBD_OK;
}

uint8_t  USBD_Composite_DataIn (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
	switch(epnum | 0X80)
	{
	case MSC_EPIN_ADDR:
	    USBD_Composite_Switch_MSC(pdev);
	    USBD_MSC_DataIn(pdev, epnum);
		break;
	case CDC_IN_EP:
	    USBD_Composite_Switch_CDC(pdev);
	    USBD_CDC_DataIn(pdev, epnum);
		break;
	default:break;
	}

	return USBD_OK;
}

uint8_t  USBD_Composite_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum)
{
	switch(epnum)
	{
	case MSC_EPOUT_ADDR:
		USBD_Composite_Switch_MSC(pdev);
		USBD_MSC_DataOut(pdev, epnum);
		break;
	case CDC_OUT_EP:
		USBD_Composite_Switch_CDC(pdev);
		USBD_CDC_DataOut(pdev, epnum);
		break;
	default:break;
	}
	return USBD_OK;
}

uint8_t  *USBD_Composite_GetHSCfgDesc (uint16_t *length)
{
  *length = sizeof (USBD_Composite_CfgDesc);
  return USBD_Composite_CfgDesc;
}

uint8_t  *USBD_Composite_GetFSCfgDesc (uint16_t *length)
{
  *length = sizeof (USBD_Composite_CfgDesc);
  return USBD_Composite_CfgDesc;
}

uint8_t  *USBD_Composite_GetOtherSpeedCfgDesc (uint16_t *length)
{
  *length = sizeof (USBD_Composite_CfgDesc);
  return USBD_Composite_CfgDesc;
}

uint8_t  *USBD_Composite_GetDeviceQualifierDescriptor (uint16_t *length)
{
  *length = sizeof (USBD_Composite_DeviceQualifierDesc);
  return USBD_Composite_DeviceQualifierDesc;
}

static uint8_t  USBD_Composite_RxReady (USBD_HandleTypeDef *pdev)
{
	uint8_t ret = 0;
	switch(pdev->request.wIndex)  // wIndex是接口编号,这里我们通过接口编号确定CDC还是其他设备的EP0接收
	{
		case 0:  // CDC的命令接口编号是0
		case 1:  // CDC的数据接口编号是1
			USBD_Composite_Switch_CDC(pdev);
			ret = USBD_CDC_EP0_RxReady(pdev);
			break;
		// 如果有其他设备还用到了EP0接收,在这里加入
		case 2:
			break;
		default:break;
	}
    return ret;
}

static void USBD_Composite_Switch_MSC(USBD_HandleTypeDef *pdev)
{
	static USBD_MSC_BOT_HandleTypeDef USBD_MSC_Handle;
	USBD_MSC_RegisterStorage(pdev, &USBD_Storage_Interface_fops_FS);
	pdev->pClassData = (void *)&USBD_MSC_Handle;
}

static void USBD_Composite_Switch_CDC(USBD_HandleTypeDef *pdev)
{
	static USBD_CDC_HandleTypeDef USBD_CDC_Handle;
	USBD_CDC_RegisterInterface(pdev, &USBD_Interface_fops_FS);
	pdev->pClassData = (void *)&USBD_CDC_Handle;
}

其中有关设备描述符的修改,我都用+++lakun进行了标注,并写上注释,方便查看。


第五步:MX_DEVICE_USB_Init修改

上面我们已经完成了统一接口的编写,现在就可以修改MX_DEVICE_USB_Init函数了,屏蔽掉接口注册函数USBD_CDC_RegisterInterface,然后将类注册换成我们自己的组合设备,如下图:
STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC_第4张图片

到这里就已经改完了,运行程序之后设备管理器会出现:一个组合设备、一个虚拟的串口、一个USB大容量存储设备,如下图:
STM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC_第5张图片

这样就是成功了!

!!如果修改过后发现设备管理器的MSC或者CDC显示感叹号,那么在回头检查PMA的配置以及组合设备描述符!!


遇到的问题

我修改完毕之后,将板子接入电脑,虚拟出来的串口没问题,但是就是死活认不出U盘,后来找到问题的原因是因为我先使用的CDC串口这个单独的工程,我的win10已经默认枚举成了CDC,所以认不出U盘。

解决办法:

  1. 卸载识别出来的串口,重新拔插
  2. 在程序里修改一下PIDSTM32CubeMX | 基于STM32使用HAL库实现USB组合设备CDC+MSC_第6张图片
    我是用的第二种办法解决的!

ends…

你可能感兴趣的:(单片机,STM32,USB组合设备,MSC+CDC,HAL)