第一阶段:实现USB-单CDC功能。
这个容易实现,我是使用CubeMx直接配置生成的。
1.在中勾选使用USB Device,
2.在中选择USB_DEVICE,选择Class For FS IP为Communication Device Class,
选择CDC模式可以生成一个可以直接使用CDC功能的工程模板。
Parameter Setting和Device Descriptor可以保持默认设置,不需要修改。
3.在时钟配置中将USB的时钟配置为48M,一定要是48M。
4.最后需要修改的是Heap Size, 将Heap Size大小修改为0x1000,Stack Size可以不修改。
完成上述配置后直接选择自己习惯的IDE生成工程就行了。
5.将生成的工程编译后,下载到Stm32f103c8t6中,重新拔插USB就能看到设备管理器中出现了一个串口。
因为在生成工程中我们并没有做任何修改,所以此时串口上并不能看到任何数据变化。
我主要演示一下从MCU向电脑端串口发送数据。
只需要修改main.c文件中的两个地方
/* USER CODE BEGIN Includes */
#include "usbd_cdc_if.h"
/* USER CODE END Includes */
以及在main()函数的while中添加
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
CDC_Transmit_FS((uint8_t *)"Hallow Word!\n", strlen("Hellow Word!\n"));
HAL_Delay(100);
}
/* USER CODE END 3 */
编译后下载,打开串口后能看到串口一直打印Hellow Word!
单CDC串口的发送测试就算是完成了,接收我没有测试,准备在双CDC串口是测试。
只使用单CDC串口可以节省出一个USB TO TTL,虽然占用了大量的内存。接下来就是双CDC的实验了。
第二阶段:实现USB-双CDC功能
在学习的过程中我参考了三篇文章和一篇文档:
通过这三篇文章和一篇文档的讲解,我也算是粗略的明白了怎么配置多CDC。
总结一下修改过程可以按照以下几步来完成:
- 修改设备描述符
- 修改端点
- 修改配置描述符、修改IAD、修改接口描述符、修改端点描述符
- 分配PMA端点缓存地址
- 修改初始化、去初始化函数
一、修改设备描述符
在“USB描述符详细讲解“这篇文章中,可以看到设备描述符的结构
对应在工程里找到usbd_desc.c文件,这里面可以找到设备描述符定义数组。此处需要修改设备类、子类和设备协议代码。由原本的0x02,0x02,0x00改为0xEF,0x02,0x01。
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END =
{
0x12, /*bLength */
USB_DESC_TYPE_DEVICE, /*bDescriptorType*/
0x00, /*bcdUSB */
0x02,
0xEF, /*bDeviceClass*/
0x02, /*bDeviceSubClass*/
0x01, /*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*/
};
此处修改完成后还需要修改设备的VID和PID,同样在这个文件的开头,在#define中定义了USBD_VID和USBD_PID_FS。
#define USBD_VID 0x3EB
#define USBD_LANGID_STRING 1033
#define USBD_MANUFACTURER_STRING "STMicroelectronics"
#define USBD_PID_FS 0x6135
#define USBD_PRODUCT_STRING_FS "STM32 Virtual ComPort"
#define USBD_CONFIGURATION_STRING_FS "CDC Config"
#define USBD_INTERFACE_STRING_FS "CDC Interface"
修改完这里后这个文件就已经不用再修改了,设备描述符也修改完成了,但是在USBD_CDC.c文件中还有一个设备描述符数组,这个设备描述符不知道有没有起作用,可能没有用处,因为不用修改它也能正常配置。它的结构和正常的设备描述符相似,但只有10字节,我没有找到关于它的解读。
/* USB Standard Device Descriptor */
__ALIGN_BEGIN static uint8_t USBD_CDC_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] __ALIGN_END =
{
USB_LEN_DEV_QUALIFIER_DESC,
USB_DESC_TYPE_DEVICE_QUALIFIER,
0x00,
0x02,
0x00,
0x00,
0x00,
0x40,
0x01,
0x00,
};
二、修改端点
在修改端点前我们需要先了解STM32F103C8T6有多少个端点,在“STM32 USB如何配置多个CDC设备---5个CDC设备”中提到STM32有8个输入端点(0x80-0x87)和8个输出端点(0x00-0x08),总共可用端点16个。
在知道有多少个端点可用后还需要知道CDC功能需要的端点组成。
通过上面的图片可以看出一个CDC功能包含了两个接口以及一个IAD,接口0用来传输COM信息,有1个输入端点,接口1用来传输数据,有一个输入、一个输出共两个端点,两个接口共需要3个端点。
而这次的实验目的是创建两个CDC,共需要4个输入端点,2个输出端点。端点的定义在USBD_CDC.h文件中,我将它修改如以下,
#define CDC1_IN_EP 0x81U /* EP1 for data IN */
#define CDC1_OUT_EP 0x01U /* EP1 for data OUT */
#define CDC1_CMD_EP 0x82U /* EP2 for CDC commands */
#define CDC2_IN_EP 0x83U /* EP3 for data IN */
#define CDC2_OUT_EP 0x02U /* EP3 for data OUT */
#define CDC2_CMD_EP 0x84U /* EP4 for CDC commands */
从上面的端点分配可以看出我是从0x81和0x01开始分配的,至于为什么不从0x80和0x00开始,会在分配PMA端点缓存地址中解释。
修改完端点号后,我们顺便找到 #define USB_CDC_CONFIG_DESC_SIZ ,将它的值修改为255。
三、修改配置描述符、修改IAD、修改接口描述符、修改端点描述符
在看这一节内容前建议先到USB描述符详细讲解 处了解各个描述符的结构,这里我先贴上已经修改后的描述符数组。
__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,
0x04, /* bNumInterfaces: 2*2 interface *///修改为0x04
0x01, /* bConfigurationValue: Configuration value */
0x00, /* iConfiguration: Index of string descriptor describing the configuration */
0xC0, /* bmAttributes: self powered */
0x32, /* MaxPower 0 mA */
/*---------------------------------------------------------------------------*/
/*----------------------------------------CDC1--------------------------------*/
//串口1的接口关联描述符
/*IAD*/
0x08, /* bLength: Interface Association Descriptor size *///IAD长度
0x0b, /* bDescriptorType: Interface Association Descriptor*///IAD描述符编号固定值0x0b
0x00, /* bFirstlnterface: *///第一个接口号:0
0x02, /* bInterfaceCount: *///此IAD有几个接口:2
0x02, /* bFunctionClass: *///IAD代表设备类别:CDC-0x02
0x02, /* bFunctionSubClass: *///设备子类 0x02
0x00, /* bFunctionProtocol: *///协议 0x00
0x00, /* iFunction: */
/*---------------------------------------------------------------------------*/
//控制接口0描述符
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x00, /* 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 */
0x02, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x00, /* bMasterInterface: Communication class interface */
0x01, /* bSlaveInterface0: Data Class Interface */
//控制接口0输入端点描述符
/*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC1_CMD_EP, /* bEndpointAddress *///修改为CDC1_CMD_EP
0x03, /* bmAttributes: Interrupt */
LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_CMD_PACKET_SIZE),
CDC_FS_BINTERVAL, /* bInterval: */
/*---------------------------------------------------------------------------*/
//数据接口1接口描述符
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
0x01, /* bInterfaceNumber: Number of Interface */
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints: Two endpoints used */
0x0A, /* bInterfaceClass: CDC */
0x00, /* bInterfaceSubClass: */
0x00, /* bInterfaceProtocol: */
0x00, /* iInterface: */
//数据接口1输出端点描述符
/*Endpoint OUT Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC1_OUT_EP, /* bEndpointAddress *///修改为CDC1_OUT_EP
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
0x00, /* bInterval: ignore for Bulk transfer */
//数据接口1输入端点描述符
/*Endpoint IN Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC1_IN_EP, /* bEndpointAddress *///修改为CDC1_IN_EP
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
0x00, /* bInterval: ignore for Bulk transfer */
/*---------------------------------------------------------------------------*/
/*----------------------------------------CDC2--------------------------------*/
//串口2的接口关联描述符
/*IAD*/
0x08, /* bLength: Interface Association Descriptor size *///IAD长度
0x0b, /* bDescriptorType: Interface Association Descriptor*///IAD描述符编号固定值0x0b
0x02, /* bFirstlnterface: *///第一个接口号:2
0x02, /* bInterfaceCount: *///此IAD有几个接口:2
0x02, /* bFunctionClass: *///IAD代表设备类别:CDC-0x02
0x02, /* bFunctionSubClass: *///设备子类 0x02
0x00, /* bFunctionProtocol: *///协议 0x00
0x00, /* iFunction: */
/*---------------------------------------------------------------------------*/
//控制接口2接口描述符
/*Interface Descriptor */
0x09, /* bLength: Interface Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */
/* Interface descriptor type */
0x02, /* bInterfaceNumber: Number of Interface *///修改为0x02
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 */
0x02, /* bmCapabilities */
/*Union Functional Descriptor*/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x00, /* bMasterInterface: Communication class interface */
0x01, /* bSlaveInterface0: Data Class Interface */
//控制接口2输入端点描述符
/*Endpoint 2 Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC2_CMD_EP, /* bEndpointAddress *///修改为CDC2_CMD_EP
0x03, /* bmAttributes: Interrupt */
LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_CMD_PACKET_SIZE),
CDC_FS_BINTERVAL, /* bInterval: */
/*---------------------------------------------------------------------------*/
//数据接口3接口描述符
/*Data class interface descriptor*/
0x09, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */
0x03, /* bInterfaceNumber: Number of Interface *///修改为0x03
0x00, /* bAlternateSetting: Alternate setting */
0x02, /* bNumEndpoints: Two endpoints used */
0x0A, /* bInterfaceClass: CDC */
0x00, /* bInterfaceSubClass: */
0x00, /* bInterfaceProtocol: */
0x00, /* iInterface: */
//数据接口3输出端点描述符
/*Endpoint OUT Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC2_OUT_EP, /* bEndpointAddress *///修改为CDC2_OUT_EP
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
0x00, /* bInterval: ignore for Bulk transfer */
//数据接口3输入端点描述符
/*Endpoint IN Descriptor*/
0x07, /* bLength: Endpoint Descriptor size */
USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */
CDC2_IN_EP, /* bEndpointAddress *///修改为CDC2_IN_EP
0x02, /* bmAttributes: Bulk */
LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */
HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE),
0x00, /* bInterval: ignore for Bulk transfer */
} ;
在以上这个数组中,数组的长度标识 USB_CDC_CONFIG_DESC_SIZ 我们在前面已经把它改为了255,改成255的原因是当我们修改了这个数组后,添加了CDC2的描述符,原本由程序生成的0x64不够装载这么多得数据。
其实在看过 USB描述符详细讲解 和 USB CDC类入门培训.pdf 之后我们已经能够看明白这个描述数组中的各个描述符的意义。
我们来看配置描述符的结构
从这个结构分析在我的代码中修改了 BYTE bNumlnterfaces ,我将它的值从0x02修改为0x04,在前面我们提到一个CDC使用两个接口,而我们现在要使用两个CDC,这很显然2*2=4个接口。
我们还可以修改最后两个数据 bmAtributes 和MaxPower ,在 USB描述符详细讲解 中提到了这两个数据的定义:bmAttributes : 供电模式选择.Bit4-0保留,D7:总线供电,D6:自供电,D5:远程唤醒 MaxPower : 总线供电的USB设备的最大消耗电流.以2mA为单位,我们可以根据实际情况作出修改。
对于IAD接口关联描述符每个数据的分析,我已经在程序的注释中给出来了。
0x08, /* bLength: Interface Association Descriptor size *///IAD长度
0x0b, /* bDescriptorType: Interface Association Descriptor*///IAD描述符编号固定值0x0b
0x00, /* bFirstlnterface: *///第一个接口号:0
0x02, /* bInterfaceCount: *///此IAD有几个接口:2
0x02, /* bFunctionClass: *///IAD代表设备类别:CDC-0x02
0x02, /* bFunctionSubClass: *///设备子类 0x02
0x00, /* bFunctionProtocol: *///协议 0x00
0x00, /* iFunction: */
关于IAD分析可以去这里看看 ,我没有看得很明白,也找不到一个专门介绍IAD的文档。
再来看接口描述符的结构
在这个描述符中,值得注意的是 bInterfaceNuber 、 bNumEndpoints 、bInterfaceClass 、bInterfaceSubClass 和 bInterfaceProtocol。
bInterfaceNuber 接口的编号没什么可说的,但是是需要修改的, bNumEndpoints 该接口的端点数,这里的端点数是变化的,在上面的程序中可以分析出,在接口为控制接口时端点数为1,在接口为数据接口时端点数为2。这也符合CDC设备类配置描述符的机构。bInterfaceClass 、bInterfaceSubClass 和 bInterfaceProtocol 这三个数据也是根据接口的类型变化的。
对于端点描述符,它的结构如下
typedef struct _USB_ENDPOINT_DESCRIPTOR_
{
BYTE bLength, //端点描述符的字节数大小
BYTE bDescriptorType, //端点描述符的类型编号
BYTE bEndpointAddress, //端点地址
BYTE bmAttributes, // 端点属性
WORD wMaxPacketSize, //本端点接收或发送的最大信息包大小
BYTE bInterval //轮训数据传送端点的时间间隔
}USB_ENDPOINT_DESCRIPTOR;
对于端点描述符内的数据,我只修改了端点地址,暂时也只需要修改端点地址。
四、分配PMA端点缓存地址
对于PMA端点缓存地址的设置,这里推荐看大佬的PMA解读,大佬的PMA解读讲的很详细。
而 STM32 USB如何配置多个CDC设备---5个CDC设备 这篇文章中也为我们解释了他的理解,以及起始地址的计算。
这里提示上图用红线删除的是我在分析后,认为这是错误的理解。
在看完上面两位大佬的PMA讲解后,我们知道STM32的USB端点有8组输入输出双向端点,每一个双向端点占用8字节,总共只会占用8*8字节=64字节,即我们只需要给PMA初始地址设为0x40,就能满足PMA起始地址最大需求。然而为了节省空间,通过我的端点分配情况,我们可以看出,使用两个CDC后,我们使用了双向端点0-4,即我们最小的起始地址为5*8=40=0x28。同时我们也可以推断出为什么cubemx生成的代码CDC_IN_EP的起始地址是0xCO,0x40+0x40+0x40=0xC0。对于PMA的设置按照下面这个函数配置就行了,这个函数位于usbd_conf.c文件中。
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
/* Init USB Ip. */
/* Link the driver to the stack. */
hpcd_USB_FS.pData = pdev;
pdev->pData = &hpcd_USB_FS;
hpcd_USB_FS.Instance = USB;
hpcd_USB_FS.Init.dev_endpoints = 8;
hpcd_USB_FS.Init.speed = PCD_SPEED_FULL;
hpcd_USB_FS.Init.low_power_enable = DISABLE;
hpcd_USB_FS.Init.lpm_enable = DISABLE;
hpcd_USB_FS.Init.battery_charging_enable = DISABLE;
if (HAL_PCD_Init(&hpcd_USB_FS) != HAL_OK)
{
Error_Handler( );
}
#if (USE_HAL_PCD_REGISTER_CALLBACKS == 1U)
/* Register USB PCD CallBacks */
HAL_PCD_RegisterCallback(&hpcd_USB_FS, HAL_PCD_SOF_CB_ID, PCD_SOFCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_FS, HAL_PCD_SETUPSTAGE_CB_ID, PCD_SetupStageCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_FS, HAL_PCD_RESET_CB_ID, PCD_ResetCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_FS, HAL_PCD_SUSPEND_CB_ID, PCD_SuspendCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_FS, HAL_PCD_RESUME_CB_ID, PCD_ResumeCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_FS, HAL_PCD_CONNECT_CB_ID, PCD_ConnectCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_FS, HAL_PCD_DISCONNECT_CB_ID, PCD_DisconnectCallback);
HAL_PCD_RegisterDataOutStageCallback(&hpcd_USB_FS, PCD_DataOutStageCallback);
HAL_PCD_RegisterDataInStageCallback(&hpcd_USB_FS, PCD_DataInStageCallback);
HAL_PCD_RegisterIsoOutIncpltCallback(&hpcd_USB_FS, PCD_ISOOUTIncompleteCallback);
HAL_PCD_RegisterIsoInIncpltCallback(&hpcd_USB_FS, PCD_ISOINIncompleteCallback);
#endif /* USE_HAL_PCD_REGISTER_CALLBACKS */
/* USER CODE BEGIN EndPoint_Configuration */
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x28);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x68);
/* USER CODE END EndPoint_Configuration */
/* USER CODE BEGIN EndPoint_Configuration_CDC */
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC1_IN_EP , PCD_SNG_BUF, 0xC0);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC1_OUT_EP , PCD_SNG_BUF, 0x110);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC1_CMD_EP , PCD_SNG_BUF, 0x100);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC2_IN_EP , PCD_SNG_BUF, 0x310);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC2_OUT_EP , PCD_SNG_BUF, 0x360);
HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC2_CMD_EP , PCD_SNG_BUF, 0x350);
/* USER CODE END EndPoint_Configuration_CDC */
return USBD_OK;
}
五、修改初始化、去初始化函数
这个直接看程序吧,没啥好写的,改完这两个函数后还需要修改一个地方,在usbd_conf.h文件中将 #define USBD_MAX_NUM_INTERFACES 的值修改为3及以上,这个值决定了我们最大能有几个复合设备。
/**
* @brief USBD_CDC_Init
* Initialize the CDC interface
* @param pdev: device instance
* @param cfgidx: Configuration index
* @retval status
*/
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 EP1 IN */
USBD_LL_OpenEP(pdev, CDC1_IN_EP, USBD_EP_TYPE_BULK,
CDC_DATA_HS_IN_PACKET_SIZE);
pdev->ep_in[CDC1_IN_EP & 0xFU].is_used = 1U;
/* Open EP1 OUT */
USBD_LL_OpenEP(pdev, CDC1_OUT_EP, USBD_EP_TYPE_BULK,
CDC_DATA_HS_OUT_PACKET_SIZE);
pdev->ep_out[CDC1_OUT_EP & 0xFU].is_used = 1U;
/* Open EP2 IN */
USBD_LL_OpenEP(pdev, CDC2_IN_EP, USBD_EP_TYPE_BULK,
CDC_DATA_HS_IN_PACKET_SIZE);
pdev->ep_in[CDC2_IN_EP & 0xFU].is_used = 1U;
/* Open EP2 OUT */
USBD_LL_OpenEP(pdev, CDC2_OUT_EP, USBD_EP_TYPE_BULK,
CDC_DATA_HS_OUT_PACKET_SIZE);
pdev->ep_out[CDC2_OUT_EP & 0xFU].is_used = 1U;
}
else
{
/* Open EP1 IN */
USBD_LL_OpenEP(pdev, CDC1_IN_EP, USBD_EP_TYPE_BULK,
CDC_DATA_FS_IN_PACKET_SIZE);
pdev->ep_in[CDC1_IN_EP & 0xFU].is_used = 1U;
/* Open EP1 OUT */
USBD_LL_OpenEP(pdev, CDC1_OUT_EP, USBD_EP_TYPE_BULK,
CDC_DATA_FS_OUT_PACKET_SIZE);
pdev->ep_out[CDC1_OUT_EP & 0xFU].is_used = 1U;
/* Open EP2 IN */
USBD_LL_OpenEP(pdev, CDC2_IN_EP, USBD_EP_TYPE_BULK,
CDC_DATA_FS_IN_PACKET_SIZE);
pdev->ep_in[CDC2_IN_EP & 0xFU].is_used = 1U;
/* Open EP2 OUT */
USBD_LL_OpenEP(pdev, CDC2_OUT_EP, USBD_EP_TYPE_BULK,
CDC_DATA_FS_OUT_PACKET_SIZE);
pdev->ep_out[CDC2_OUT_EP & 0xFU].is_used = 1U;
}
/* Open Command IN EP1 */
USBD_LL_OpenEP(pdev, CDC1_CMD_EP, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
pdev->ep_in[CDC1_CMD_EP & 0xFU].is_used = 1U;
/* Open Command IN EP2 */
USBD_LL_OpenEP(pdev, CDC2_CMD_EP, USBD_EP_TYPE_INTR, CDC_CMD_PACKET_SIZE);
pdev->ep_in[CDC1_CMD_EP & 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, CDC1_OUT_EP, hcdc->RxBuffer,
CDC_DATA_HS_OUT_PACKET_SIZE);
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC2_OUT_EP, hcdc->RxBuffer,
CDC_DATA_HS_OUT_PACKET_SIZE);
}
else
{
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC1_OUT_EP, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
/* Prepare Out endpoint to receive next packet */
USBD_LL_PrepareReceive(pdev, CDC2_OUT_EP, hcdc->RxBuffer,
CDC_DATA_FS_OUT_PACKET_SIZE);
}
}
return ret;
}
/**
* @brief USBD_CDC_Init
* DeInitialize the CDC layer
* @param pdev: device instance
* @param cfgidx: Configuration index
* @retval status
*/
static uint8_t USBD_CDC_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx)
{
uint8_t ret = 0U;
/* Close EP IN */
USBD_LL_CloseEP(pdev, CDC1_IN_EP);
pdev->ep_in[CDC1_IN_EP & 0xFU].is_used = 0U;
/* Close EP OUT */
USBD_LL_CloseEP(pdev, CDC1_OUT_EP);
pdev->ep_out[CDC1_OUT_EP & 0xFU].is_used = 0U;
/* Close Command IN EP */
USBD_LL_CloseEP(pdev, CDC1_CMD_EP);
pdev->ep_in[CDC1_CMD_EP & 0xFU].is_used = 0U;
/* Close EP IN */
USBD_LL_CloseEP(pdev, CDC2_IN_EP);
pdev->ep_in[CDC2_IN_EP & 0xFU].is_used = 0U;
/* Close EP OUT */
USBD_LL_CloseEP(pdev, CDC2_OUT_EP);
pdev->ep_out[CDC2_OUT_EP & 0xFU].is_used = 0U;
/* Close Command IN EP */
USBD_LL_CloseEP(pdev, CDC2_CMD_EP);
pdev->ep_in[CDC2_CMD_EP & 0xFU].is_used = 0U;
/* DeInit physical Interface components */
if (pdev->pClassData != NULL)
{
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->DeInit();
USBD_free(pdev->pClassData);
pdev->pClassData = NULL;
}
return ret;
}
完成上面步骤后,我们已经可以编译工程,下载到stm32f103c8t6中去,在设备管理器中可以看到两个串口设备
说明我们已经成功创建出了两个CDC设备。
六、修改接收/发送接口函数
因为我们这里创建了两个CDC串口,CDC的接收和发送函数在usbd_cdc_if.c中被定义,分别是发送接口函数uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);
和接收接口函数static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len);
,CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
这个函数调用usbd_cdc.c中的uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev);
发送处理函数, CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
这个函数调用usbd_cdc.c中的uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev);
接收处理函数,又被usbd_cdc.c中的接收回调函数static uint8_t USBD_CDC_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum);
调用。
用关系表达如下:
USBD_CDC_DataOut
->CDC_Receive_FS
->USBD_CDC_ReceivePacket
CDC_Transmit_FS
->USBD_CDC_TransmitPacket
要实现对不同CDC传来的数据进行区别,我们只需要添加一个端口变量,具体修改如下:
USBD_CDC_DataOut函数
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 */
if (pdev->pClassData != NULL)
{
((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength, epnum);
return USBD_OK;
}
else
{
return USBD_FAIL;
}
}
CDC_Receive_FS函数申明
static int8_t CDC_Receive_FS(uint8_t* pbuf, uint32_t *Len, uint8_t epnum);
usbd_cdc.h中的声明
typedef struct _USBD_CDC_Itf
{
int8_t (* Init)(void);
int8_t (* DeInit)(void);
int8_t (* Control)(uint8_t cmd, uint8_t *pbuf, uint16_t length);
int8_t (* Receive)(uint8_t *Buf, uint32_t *Len, uint8_t epnum);
} USBD_CDC_ItfTypeDef;
添加使用到的参数
uint16_t usart1_now_stack=0;
uint16_t usart1_buffer_stack=0;
uint8_t usart1_buffer_len[8]={0};
uint16_t usart3_now_stack=0;
uint16_t usart3_buffer_stack=0;
uint8_t usart3_buffer_len[8]={0};
并在usbd_cdc_if.h文件中外部引用
extern uint16_t usart1_now_stack;
extern uint16_t usart1_buffer_stack;
extern uint8_t usart1_buffer_len[8];
extern uint16_t usart3_now_stack;
extern uint16_t usart3_buffer_stack;
extern uint8_t usart3_buffer_len[8];
CDC_Receive_FS函数(改写接收到的数据到USART),这个函数是真的把我整崩溃了,各种不进中断,参数传递出错等,在后面我会提到一个非常重要的参数设置
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len, uint8_t epnum)
{
/* USER CODE BEGIN 6 */
switch(epnum)
{
case CDC1_OUT_EP:
/*判断将来扇区是否已经逼近现在扇区 //现在扇区--usart1_now_stack,将来扇区--usart1_buffer_stack*/
while((1 == ((int16_t)usart1_now_stack - (int16_t)usart1_buffer_stack)) || ((usart1_buffer_stack == 7) && (usart1_now_stack == 0)));
/*将收到的数据使用DMA传送到将来扇区*/
while(HAL_BUSY == HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1,(uint32_t)&Buf[0],(uint32_t)&usart1_txBuffer[usart1_buffer_stack*64],*Len));
/*等待DMA传送完成*/
while(HAL_DMA_STATE_BUSY == HAL_DMA_GetState(&hdma_memtomem_dma1_channel1));
/*将收到的数据长度放入扇区对应数组中*/
usart1_buffer_len[usart1_buffer_stack] = *Len;
/*判断将来扇区是否和现在扇区重合 --是 开启串口传送并将将来扇区值加1, --否 将将来扇区值加1*/
if(usart1_now_stack == usart1_buffer_stack)
{
if(usart1_buffer_stack == 7)
{
usart1_buffer_stack = 0;
}
else usart1_buffer_stack += 1;
while(usart1_txOK == 1);
usart1_txOK = 1;
while(HAL_BUSY == HAL_UART_Transmit_DMA(&huart1, &usart1_txBuffer[usart1_now_stack*64], usart1_buffer_len[usart1_now_stack]));
}
else
{
if(usart1_buffer_stack == 7)
{
usart1_buffer_stack = 0;
}
else usart1_buffer_stack += 1;
}
break;
case CDC2_OUT_EP:
/*同case CDC1_OUT_EP:*/
while((1 == ((int16_t)usart3_now_stack - (int16_t)usart3_buffer_stack)) || ((usart3_buffer_stack == 7) && (usart3_now_stack == 0)));
while(HAL_BUSY == HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel6,(uint32_t)&Buf[0],(uint32_t)&usart3_txBuffer[usart3_buffer_stack*64],*Len));
while(HAL_DMA_STATE_BUSY == HAL_DMA_GetState(&hdma_memtomem_dma1_channel6));
usart3_buffer_len[usart3_buffer_stack] = *Len;
if(usart3_now_stack == usart3_buffer_stack)
{
if(usart3_buffer_stack == 7)
{
usart3_buffer_stack = 0;
}
else usart3_buffer_stack += 1;
while(usart3_txOK == 1);
usart3_txOK = 1;
while(HAL_BUSY == HAL_UART_Transmit_DMA(&huart3, &usart3_txBuffer[usart3_now_stack*64], usart3_buffer_len[usart3_now_stack]));
}
else
{
if(usart3_buffer_stack == 7)
{
usart3_buffer_stack = 0;
}
else usart3_buffer_stack += 1;
}
break;
default:
break;
}
/*设置接收到的数据指针*/
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
/*将接收到的数组放入到设定的PMA中, --次函数后将立即开始接收下一个USB传来的数据*/
USBD_CDC_ReceivePacket(&hUsbDeviceFS, epnum);
return (USBD_OK);
/* USER CODE END 6 */ /* USER CODE END 6 */}
usbd_cdc.h中USBD_CDC_ReceivePacket函数申明
uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev, uint8_t epnum);
USBD_CDC_ReceivePacket函数
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,
epnum,
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;
}
}
在usart.c文件中添加接收、发送缓冲
uint8_t usart1_rxBuffer[64] = {0};
uint8_t usart3_rxBuffer[64] = {0};
uint8_t usart1_txBuffer[576] = {0};
uint8_t usart3_txBuffer[576] = {0};
uint8_t usart1_txOK = 0;
uint8_t usart3_txOK = 0;
在usart.h文件中申明外部引用
extern uint8_t usart1_rxBuffer[64];
extern uint8_t usart3_rxBuffer[64];
extern uint8_t usart1_txBuffer[576];
extern uint8_t usart3_txBuffer[576];
extern uint8_t usart1_txOK;
extern uint8_t usart3_txOK;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern DMA_HandleTypeDef hdma_usart3_rx;
extern DMA_HandleTypeDef hdma_usart3_tx;
接下来改写usart发送回调函数,在usart.c中添加下列函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == huart1.Instance)
{
if(usart1_now_stack == 7)
{
usart1_now_stack = 0;
}
else usart1_now_stack += 1;
if(usart1_now_stack != usart1_buffer_stack)
{
while(HAL_BUSY == HAL_UART_Transmit_DMA(&huart1, &usart1_txBuffer[usart1_now_stack*64], usart1_buffer_len[usart1_now_stack]));
}
else if(usart1_now_stack == usart1_buffer_stack)
{
usart1_txOK = 0;
}
}
else
if(huart->Instance == huart3.Instance)
{
if(usart3_now_stack == 7)
{
usart3_now_stack = 0;
}
else usart3_now_stack += 1;
if(usart3_now_stack != usart3_buffer_stack)
{
while(HAL_BUSY == HAL_UART_Transmit_DMA(&huart3, &usart3_txBuffer[usart3_now_stack*64], usart3_buffer_len[usart3_now_stack]));
}
else if(usart3_now_stack == usart3_buffer_stack)
{
usart3_txOK = 0;
}
}
}
由于字数限制,接下来转看第二篇:对接收函数讲解、写usart--cdc函数、写修改配置函数。