STM32 USB如何配置多个CDC设备---5个CDC设备
2022-07-15 来源:csdn
1. 背景
由于项需求,在STM32F072中需实现5个CDC设备,折腾了很久分享出来,希望能帮助别人少踩一些坑.USB2.0全速,该款单片机支持8个输出和8个输入端点
2.USB CDC设备
2.1 它是什么?需要使用到什么?
它是USB设备类型的一种,如U盘插入电脑时,电脑会知道其是U盘,是一个存储设备,那么它就属于USB MCU存储设备。USB协议中对设备进行了分类.它在stm32上叫虚拟串口设备,当将其链接电脑后,可以把它当成串口使用,如使用windows的串口工具打开它,设置波特率等等,这是我的理解。
它需要两个接口,三个端点
InterFace 0:第一个接口,其包括一个端点,和Functional_Destriptcrs,该端点为控制接口(输入型端点(IN)),Functional_Destriptcrs则是一些描述符。先不管这个
InterFace 1:第二接口,其包括两个端点,endpoint In 和endpoint out端点,IN为stm32数据输出端点,OUT为stm32数据输入端点,随便提下,在STM32 HAL库中,USB的IN,OUT是相对于主机而言的,如:上诉的IN是指主机的输入,主机的输入(IN),对于设备来说就是OUT,数据的输出端点,IN OUT都是相对于主机而言
由此可见,正常情况下,一个CDC需要三个端点,其中两个IN端点,一个OUT端点
3.配置过程
3.1 配置流程
在STM32CubeIDE的帮助下,生成一个CDC的代码,这里没什么坑,点几下就成功了,注意的是可能需要将Heap设置大一点
修改设备描述符
分配端点和修改配置描述符
分配PMA端点缓存地址
修改HAL库关于CDC部分函数接口的参数,以适配多CDC设备的情况
3.2 使用STM32CubeIDE生成一个CDC程序的代码
根据下面的文件操作即可,文件会介绍USBCDC的知识,在PDF的最后会说怎么使用cubeIDE配置,很简单没什么坑(注意的是可能需要将Heap设置大一点) USB CDC类入门培训.pdf
3.3 分配端点和修改配置描述符
此时问题来了,CDC要求3个端点,其中2个输入1个输出,而stm32F072只支持8个输入端点,8个输出端点,2x5>8,正常情况下是不够的,这里就需要将IN端口中的控制端口省略,也就是第一幅图中的Interface 0中的endpoint IN端点,不分配有效端点给它,在本款单片机中IN端点的有效范围(0x80-0x87),OUT(0x00-0x08)
#define CDC_IN_EP 0x81U /* CDC*/
#define CDC_OUT_EP 0x01U
#define CDC_CMD_EP 0x88U //无效端点
#define CDC_IN_EP1 0x82U /* CDC1*/
#define CDC_OUT_EP1 0x02U
#define CDC_CMD_EP1 0x89U //无效端点
#define CDC_IN_EP2 0x83U /* CDC2*/
#define CDC_OUT_EP2 0x03U
#define CDC_CMD_EP2 0x8AU //无效端点
#define CDC_IN_EP3 0x84U /* CDC3*/
#define CDC_OUT_EP3 0x04U
#define CDC_CMD_EP3 0x8CU //无效端点
#define CDC_IN_EP4 0x85U /* CDC4*/
#define CDC_OUT_EP4 0x05U
#define CDC_CMD_EP4 0x8BU //无效端点
修改配置描述符号,这里面的内容就很多了,里面涉及到USB方方面面的属性和参数,其中USB_CDC_CONFIG_DESC_SIZ是该结构体的大小,记得修改,或者直接改为最大255。
配置描述符的结构如下:
配置描述符
{
配置描述符总概括(相当于预览:9字节)
IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC
{
接口描述符1
{
其他描述符(ACM等)
端点描述符(控制端点)
}
接口描述符2
{
端点描述符(IN)
端点描述符(OUT)
}
}
IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC1
{
接口描述符1
{
其他描述符(ACM等)
端点描述符(控制端点)
}
接口描述符2
{
端点描述符(IN)
端点描述符(OUT)
}
}
IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC2
{
接口描述符1
{
其他描述符(ACM等)
端点描述符(控制端点)
}
接口描述符2
{
端点描述符(IN)
端点描述符(OUT)
}
}
IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC3
{
接口描述符1
{
其他描述符(ACM等)
端点描述符(控制端点)
}
接口描述符2
{
端点描述符(IN)
端点描述符(OUT)
}
}
IAD描述符(标识接下来的接口属于一个设备,一个设备就一个IAD描述符)CDC4
{
接口描述符1
{
其他描述符(ACM等)
端点描述符(控制端点)
}
接口描述符2
{
端点描述符(IN)
端点描述符(OUT)
}
}
}
配置描述符如下,下面有些字段需要修改的,都用casojie标记了,CTRL+F搜索casojie就可以方便显示;
__ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[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,
0x0A, /* bNumInterfaces: 10 interface *///总接口大小,5个CDC*2=10=0x0A casojie
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
0xC0, /* bmAttributes: self powered */
0x32, /* MaxPower 0 mA */
/*------------------------CDC0--------------------------------------------------8+9+5+5+7+9+7+7-*/
// // // IAD Interface Association Descriptor //IAD描述符需要添加 casojie
// 0x08, // bLength: Interface Descriptor size //标识IAD描述符的长度,基本都是8,casojie
// 0x0B, // bDescriptorType: IAD //不用管
// 0x00, // bFirstInterface //第一个接口的序号
// 0x02, // bInterfaceCount //本IDA的接口数量
// 0x02, // bFunctionClass: CDC //表明该IAD是一个CDC设备
// 0x02, // bFunctionSubClass //不用管
// 0x01, // bFunctionProtocol //控制协议等其他我也不懂,默认就行
// 0x02, // iFunction
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x00, /* bInterfaceNumber: Number of Interface */ //接口编号,从0开始 casojie
0x00, /* bAlternateSetting: Alternate setting */
0x01, /* bNumEndpoints: One endpoints used */ //接口内有多少个端点被使用 1个 casojie
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 */
0x0F, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x00, /* bMasterInterface: Communication class interface */ //联合描述符,与IAD功能类似,标识哪两个接口是属于一个设备的casojie
0x01, /* bSlaveInterface0: Data Class Interface */ //与上面一样,上面是0,这里是1,标识0号,1号接口是属于一个CDC设备的 casojie
/*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),
0x10, /* bInterval: */
/*---------------------------------------------------------------------------*/
/*Data class interface descriptor*/ //第二个接口描述符 casojie
0x09, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
0x01, /* bInterfaceNumber: Number of Interface */ //接口的序号,上一个接口是0,那这里就是1 casojie
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints: Two endpoints used */ //标识此接口有两个端点(IN)(OUT) casojie
0x0A, /* bInterfaceClass: CDC */
0x00, /* bInterfaceSubClass: */
0x00, /* bInterfaceProtocol: */
0x00, /* iInterface: */
/*Endpoint OUT Descriptor*/ //OUT端点
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*/ //IN端点
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 */
/*---------------------------------------------------------------------------*/
// IAD Interface Association Descriptor //这里就是CDC1了,重复CDC0的配置过程,注意有些字段的序号 casojie
// 0x08, // bLength: Interface Descriptor size
// 0x0B, // bDescriptorType: IAD
// 0x02, // bFirstInterface //太多了,写不下去了,意思和第一个IDA一样的
// 0x02, // bInterfaceCount
// 0x02, // bFunctionClass: CDC
// 0x02, // bFunctionSubClass
// 0x01, // bFunctionProtocol
// 0x02, // iFunction
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x02, /* 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 */
0x0F, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x02, /* bMasterInterface: Communication class interface */
0x03, /* bSlaveInterface0: Data Class Interface */
// /*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC_CMD_EP1, /* bEndpointAddress */
0x03, /* bmAttributes: Interrupt */
LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_CMD_PACKET_SIZE),
0x10, /* bInterval: */
/*---------------------------------------------------------------------------*/
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
0x03, /* 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_EP1, /* 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_EP1, /* 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 */
/*---------------------------------------------------------------------------*/
// IAD Interface Association Descriptor
// 0x08, // bLength: Interface Descriptor size
// 0x0B, // bDescriptorType: IAD
// 0x04, // bFirstInterface
// 0x02, // bInterfaceCount
// 0x02, // bFunctionClass: CDC
// 0x02, // bFunctionSubClass
// 0x01, // bFunctionProtocol
// 0x02, // iFunction
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x04, /* 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 */
0x0F, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x04, /* bMasterInterface: Communication class interface */
0x05, /* bSlaveInterface0: Data Class Interface */
// /*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC_CMD_EP2, /* bEndpointAddress */
0x03, /* bmAttributes: Interrupt */
LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_CMD_PACKET_SIZE),
0x10, /* bInterval: */
/*---------------------------------------------------------------------------*/
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
0x05, /* 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_EP2, /* 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_EP2, /* 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 */
/*---------------------------------------------------------------------------*/
// IAD Interface Association Descriptor
// 0x08, // bLength: Interface Descriptor size
// 0x0B, // bDescriptorType: IAD
// 0x06, // bFirstInterface
// 0x02, // bInterfaceCount
// 0x02, // bFunctionClass: CDC
// 0x02, // bFunctionSubClass
// 0x01, // bFunctionProtocol
// 0x02, // iFunction
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x06, /* 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 */
0x0F, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x06, /* bMasterInterface: Communication class interface */
0x07, /* bSlaveInterface0: Data Class Interface */
/*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC_CMD_EP3, /* bEndpointAddress */
0x03, /* bmAttributes: Interrupt */
LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_CMD_PACKET_SIZE),
0x10, /* bInterval: */
/*---------------------------------------------------------------------------*/
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
0x07, /* 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_EP3, /* 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_EP3, /* 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 */
// /*---------------------------------------------------------------------------*/
// // IAD Interface Association Descriptor
// // 0x08, // bLength: Interface Descriptor size
// // 0x0B, // bDescriptorType: IAD
// // 0x08, // bFirstInterface
// // 0x02, // bInterfaceCount
// // 0x02, // bFunctionClass: CDC
// // 0x02, // bFunctionSubClass
// // 0x01, // bFunctionProtocol
// // 0x02, // iFunction
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x08, /* 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 */
0x0F, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x08, /* bMasterInterface: Communication class interface */
0x09, /* bSlaveInterface0: Data Class Interface */
/*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC_CMD_EP4, /* bEndpointAddress */
0x03, /* bmAttributes: Interrupt */
LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_CMD_PACKET_SIZE),
0x10, /* bInterval: */
/*---------------------------------------------------------------------------*/
/*
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
0x09, /* 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_EP4, /* 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_EP4, /* 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 */
}
为什么上面有些描述符被注释掉了?
配置5个CDC如果描述符都写上了,这个结构体就超过了255个字节大小,超过了USB2.0最大配置描述符长度
在我的项目中,IAD和联合描述符(/Union Functional Descriptor/)功能一样,我这里注释IAD描述符,不会影响USB,但在windows平台下,它认的是IAD描述符,这个时候就可以注释联合描述符,打开IAD描述符,总之,省略一些非必要描述,减少长度
这里的坑很多很多,需要很耐心,配错一个字段,千奇百怪的问题就来了
3.4 配置PMA---USB硬件缓存
这里又有坑了,我把我知道的都说出
3.4.1 PMA是啥?
此部分感谢一个大佬的帖子,理解来源与它
大佬的PMA
这个PMA的作用就是USB设备模块用来实现MCU与主机进行数据通信的一个专门的数据缓冲区,我们称之为USB硬件缓冲区。说得具体点就是USB模块把来自主机的数据接收进来后先放到PMA,然后再被拷贝到用户数据缓存区;或者MCU要发送到主机的数据,先从用户数据缓存区拷贝进PMA,再通过USB模块负责发送给主机。
3.4.2 5个CDC的PMA怎么配置?
啥也不说,先上代码
u_int16_t ep_addR=0x70; //起始分配地址: 这里是大坑 casojie
u_int8_t ep_addr_size=64;
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size; //此端点是用于USB枚举和基本的通信使用,默认就有,且不可省去
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;
/* USER CODE END EndPoint_Configuration */
//CDC0 每个端点都分配了64B
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;
//CDC1 //+5
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP1, PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size; //192 258
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP1 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size; //272 2A8
//CDC2
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP2 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size; //0x270
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP2 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;//0x2C0
//CDC3
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP3, PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size; //192 258 0x1E0
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP3 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;
//CDC4
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP4, PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP4 , PCD_SNG_BUF, ep_addR);ep_addR+=ep_addr_size;
PMA图(我理解的)
在PMA的开头部分,是用于记录信息的,这些信息就是:端点缓存区的起始地址:说这简单点,里面写了端点0的缓存区的起始地址是X,端点1的缓冲区起始地址是Y等等
由此可见,当使用的端点数量比较多时,信息记录区索要的空间就比较大,反之,就小,需要动态调整
由大佬的帖子可知,记录一个端点的起始地址信息需要8字节,那么我们五个CDC,则5x2(IN,OUT)=10,再加上0x80 ,0x00端点,一共12个.则12*8=76字节
因此,端点(0x00)分配的地址需要往后偏移,默认的不行,如这次需要76字节的大小,换成16进制,0x4C,我设置了0x70>0x4C,默认的太小了,会导致部分信息记录区被端点0的数据覆盖,而导致部分端点无法使用.
4.修改USB初始化代码,新增对新端点的初始化
static uint8_t USBD_CDC_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
uint8_t ret = 0U;
USBD_CDC_HandleTypeDef *hcdc;
if (pdev->dev_speed == USBD_SPEED_HIGH)
{
/* Open EP IN */
USBD_LL_OpenEP(pdev, CDC_IN_EP, USBD_EP_TYPE_BULK,
CDC_DATA_HS_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_HS_OUT_PACKET_SIZE);
pdev->ep_out[CDC_OUT_EP & 0xFU].is_used = 1U;
}
else
{
/* 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 EP IN */
USBD_LL_OpenEP(pdev, CDC_IN_EP1, USBD_EP_TYPE_BULK,
CDC_DATA_FS_IN_PACKET_SIZE);
pdev->ep_in[CDC_IN_EP1 & 0xFU].is_used = 1U;
/* Open EP OUT */
USBD_LL_OpenEP(pdev, CDC_OUT_EP1, USBD_EP_TYPE_BULK,
CDC_DATA_FS_OUT_PACKET_SIZE);
pdev->ep_out[CDC_OUT_EP1 & 0xFU].is_used = 1U;
//--------------------------------------------------------------------
/* Open EP IN */
USBD_LL_OpenEP(pdev, CDC_IN_EP2, USBD_EP_TYPE_BULK,
CDC_DATA_FS_IN_PACKET_SIZE);
pdev->ep_in[CDC_IN_EP2 & 0xFU].is_used = 1U;
/* Open EP OUT */
USBD_LL_OpenEP(pdev, CDC_OUT_EP2, USBD_EP_TYPE_BULK,
CDC_DATA_FS_OUT_PACKET_SIZE);
pdev->ep_out[CDC_OUT_EP2 & 0xFU].is_used = 1U;
//-------------------------5---------------------
/* Open EP IN */
USBD_LL_OpenEP(pdev, CDC_IN_EP3, USBD_EP_TYPE_BULK,
CDC_DATA_FS_IN_PACKET_SIZE);
pdev->ep_in[CDC_IN_EP3 & 0xFU].is_used = 1U;
/* Open EP OUT */
USBD_LL_OpenEP(pdev, CDC_OUT_EP3, USBD_EP_TYPE_BULK,
CDC_DATA_FS_OUT_PACKET_SIZE);
pdev->ep_out[CDC_OUT_EP3 & 0xFU].is_used = 1U;
//-------------------------5---------------------
/* Open EP IN */
USBD_LL_OpenEP(pdev, CDC_IN_EP4, USBD_EP_TYPE_BULK,
CDC_DATA_FS_IN_PACKET_SIZE);
pdev->ep_in[CDC_IN_EP4 & 0xFU].is_used = 1U;
/* Open EP OUT */
USBD_LL_OpenEP(pdev, CDC_OUT_EP4, USBD_EP_TYPE_BULK,
CDC_DATA_FS_OUT_PACKET_SIZE);
pdev->ep_out[CDC_OUT_EP4 & 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;
// USBD_LL_OpenEP(pdev, CDC_CMD_EP1, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
// pdev->ep_in[CDC_CMD_EP1 & 0xFU].is_used = 1U;
// USBD_LL_OpenEP(pdev, CDC_CMD_EP2, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
// pdev->ep_in[CDC_CMD_EP2 & 0xFU].is_used = 1U;
// USBD_LL_OpenEP(pdev, CDC_CMD_EP3, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
// pdev->ep_in[CDC_CMD_EP3 & 0xFU].is_used = 1U;
pdev->pClassData = USBD_malloc(sizeof(USBD_CDC_HandleTypeDef));
if (pdev->pClassData == NULL)
{
ret = 1U;
}
else
{
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;
if (pdev->dev_speed == USBD_SPEED_HIGH)
{
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,
CDC_DATA_HS_OUT_PACKET_SIZE);
}
else
{
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC_OUT_EP1, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC_OUT_EP2, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC_OUT_EP3, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC_OUT_EP4, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
USBD_LL_PrepareReceive(pdev, CDC_OUT_EP5, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
}
}
return ret;
}
5.修改HAL默认USB CDC相关接口函数
现在是多CDC的状况了,但默认的HAL库的函数均只是针对一个CDC的情况,我们需要将端口的参数引出来
USBD_CDC_DataOut:USB接收函数回调,修改提供端口参数
CDC_Receive_FS(uint8_t *Buf, uint32_t *Len):USB CDC接收函数
CDC_Transmit_FS(uint8_t* Buf, uint16_t Len):USB CDC发送函数
USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)
USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev)
先说说这几个函数的关系:CDC_Transmit_FS---调用--->USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev)
USBD_CDC_DataOut(数据接收回调)---调用--->CDC_Receive_FS
CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)修改为CDC_Receive_FS(uint8_t* Buf, uint32_t *Len,uint8_t epnum): 此后所有OUT端点来的数据都将进入此函数,可通过参数epnum区分
static uint8_t USBD_CDC_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum)
{
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;
/* Get the received data length */
hcdc->RxLength = USBD_LL_GetRxDataSize(pdev, epnum);
/* USB data will be immediately processed, this allow next USB traffic being
NAKed till the end of the application Xfer */
// printf('USBD_CDC_DataOut.1294:Len:%d :EP:%02Xn',hcdc->RxLength,epnum);
if (pdev->pClassData != NULL)
{
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength,epnum);//此处修改,加入端点,相关的函数声明也跟这这样改
return USBD_OK;
}
else
{
return USBD_FAIL;
}
}
CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)该为CDC_Transmit_FS(uint8_t* Buf, uint16_t Len,uint8_t epnum):只是增加一个参数,函数体不用动
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len,uint8_t epnum)
{
uint8_t result = USBD_OK;
/* USER CODE BEGIN 7 */
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS_CDC.pClassData;
if (hcdc->TxState != 0){
return USBD_BUSY;
}
USBD_CDC_SetTxBuffer(&hUsbDeviceFS_CDC, Buf, Len);
result = USBD_CDC_TransmitPacket(&hUsbDeviceFS_CDC,epnum); //将epnum的端点传到这个函数,这个函数也需要改
/* USER CODE END 7 */
return result;
}
USBD_CDC_TransmitPacket的修改
uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev,uint8_t epnum)
{
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;
if (pdev->pClassData != NULL)
{
if (hcdc->TxState == 0U)
{
/* Tx Transfer in progress */
hcdc->TxState = 1U;
/* Update the packet total length */
pdev->ep_in[epnum & 0xFU].total_length = hcdc->TxLength;//将原本的CDC_IN_EP更改为我们传入的端点
/* Transmit next packet */
USBD_LL_Transmit(pdev, epnum, hcdc->TxBuffer,
(uint16_t)hcdc->TxLength);//此处也是
return USBD_OK;
}
else
{
return USBD_BUSY;
}
}
else
{
return USBD_FAIL;
}
}
该函数的作用:当端口接收一包数据后,需要执行此函数来接收下一包数据,简单的说:告诉主机我可以接收下一包数据了
uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev,uint8_t epnum)
{
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *) pdev->pClassData;
/* Suspend or Resume USB Out process */
if (pdev->pClassData != NULL)
{
if (pdev->dev_speed == USBD_SPEED_HIGH)
{
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev,
CDC_OUT_EP,
hcdc->RxBuffer,
CDC_DATA_HS_OUT_PACKET_SIZE);
}
else
{
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev,
epnum,
hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
}
return USBD_OK;
}
else
{
return USBD_FAIL;
}
}
对以后而言,我们就使用CDC_Receive_FS(uint8_t* Buf, uint32_t *Len,uint8_t epnum)和CDC_Transmit_FS(uint8_t* Buf, uint16_t Len,uint8_t epnum) 向CDCx发送数据,就填写CDCx的IN端口 * 接收CDCx数据时,CDC_Receive_FS函数会传入该数据来源(OUT)端口,就可知道哪个CDCx了
6.后续
此时就完成了,不过不排除有些深层的问题。
如:当五个CDC同时在Read时,其中一个close,有可能会导致其他四个都无法再Read出数据,需要重新Open,但在电脑的深度depin系统上并没有这个问题,在项目中的板子上就有,希望大佬们提供下线索。感激不尽