首页 > 代码库 > STM32 USB Virtual COM

STM32 USB Virtual COM

STM32 USB Virtual COM USB转串口的功能实现  

 
这次讲的是如何实现USB转串口功能的实现。首先看看工程的布局吧:
STM32 USB Virtual COM USB转串口的功能实现 - ziye334 - ziye334的博客我们主要要介绍的文件的在USB_User这个组文件。从上面的截图可以看到USB_User这个文件由hw_config.c、usb_desc.c、usb_endp.c、usb_istr.c、usb_prop.c、usb_pwr.c几个文件组成。其中usb_istr.c和usb_pwr.c整两个文件不用修改,其他的文件都需要修改。下面接慢慢将来。
首先讲讲hw_config.c这个文件。由于我们用到串口,所以这个文件需要添加串口相关代码。在这个文件的开始就需要定义一下串口的相关变量:

uint8_t USART_Rx_Buffer [USART_RX_DATA_SIZE]; //串口接收缓冲
uint32_t USART_Rx_ptr_in = 0; //这里采用的是一个环形缓冲,串口数据输入起始位置
uint32_t USART_Rx_ptr_out = 0; //环形缓冲的数据结束位置
uint32_t USART_Rx_length = 0; //接收数据的长度
uint8_t USB_Tx_State = 0; //USB发送标志,当串口缓冲有数据没有发送,该位置1

这里开了一个2K的环形缓冲如下图所示:
STM32 USB Virtual COM USB转串口的功能实现 - ziye334 - ziye334的博客其中USART_Rx_ptr_in指向的就是图中read position处,USART_Rx_ptr_out指向write position处, USART_Rx_length就是数据的长度,就是图中橙色的圆弧。当没有数据的时候,USART_Rx_ptr_in=USART_Rx_ptr_out,有数据收到的时候USART_Rx_ptr_in就向后偏移,当数据被读出去的时候USART_Rx_ptr_out也会向后偏移。
这里需要定义一个串口默认配置:波特率为9600,数据长度为8位,停止位为1位,奇校验,没有数据流控制,代码如下:

/*******************************************************************************
* Function Name : USART_Config_Default.
* Description : 串口的默认配置值
* Input : None.
* Return : None.
*******************************************************************************/
void USART_Config_Default(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

/* 使能 UART2 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

/* 配置 USART2 的Tx 引脚类型为推挽式的 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

/* 配置 USART2 的Rx 为输入悬空 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 串口默认配置*/
/* 串口配置值如下:
- 波特率 = 9600 baud
- 数据长度 = 8 Bits
- 1位停止位
- 奇校验
- 不使能硬件流控制
- 接收传输使能
*/
USART_InitStructure.USART_BaudRate = 9600; //波特率9600
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位
USART_InitStructure.USART_Parity = USART_Parity_Odd; //奇校验
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;//没有数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收、发送使能
USART_Init(USART2, &USART_InitStructure); //配置串口
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //使能串口接收中断
USART_Cmd(USART2,ENABLE); //串口使能
}

这里还要定义一个串口配置函数,这个函数会根据linecoding这个结构体来配置串口设置:

/*******************************************************************************
* Function Name : USART_Config.
* Description : 根据line coding 结构体配置串口.
* Input : None.
* Return : 配置状态
TRUE : 配置成功
FALSE: 配置中断
*******************************************************************************/
bool USART_Config(void)
{
/*设置停止位*/
switch (linecoding.format)
{
case 0:
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位
break;
case 1:
USART_InitStructure.USART_StopBits = USART_StopBits_1_5; //1.5为停止位
break;
case 2:
USART_InitStructure.USART_StopBits = USART_StopBits_2; //2位停止位
break;
default :
{
USART_Config_Default(); //默认配置
return (FALSE);
}
}

/* 设置校验位*/
switch (linecoding.paritytype)
{
case 0:
USART_InitStructure.USART_Parity = USART_Parity_No; //没有校验
break;;
case 1:
USART_InitStructure.USART_Parity = USART_Parity_Even; //偶校验
break;
case 2:
USART_InitStructure.USART_Parity = USART_Parity_Odd; //奇校验
break;
default :
{
USART_Config_Default(); //默认配置
return (FALSE);
}
}

/*设置数据位: 8位或9位*/
switch (linecoding.datatype)
{
case 0x07:
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8为数据位,这个选项就校验位必须设置(奇校验/偶校验)
break;
case 0x08:
if (USART_InitStructure.USART_Parity == USART_Parity_No)//没有设置校验位时
{
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//8位数据位
}
else
{
USART_InitStructure.USART_WordLength = USART_WordLength_9b;//9位数据位
}
break;
default :
{
USART_Config_Default(); //默认配置
return (FALSE);
}
}

USART_InitStructure.USART_BaudRate = linecoding.bitrate; //设置波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//设置没有硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //使能接收、发送

USART_Init(USART2, &USART_InitStructure); //初始化串口

return (TRUE);
}

还要定义一个USB_To_USART_Send_Data()函数,将USB接收到的数据从串口发送数据:

/*******************************************************************************
* Function Name : USB_To_USART_Send_Data.
* Description : 将USB接收到的数据到URAT.
* Input : data_buffer: data address.
Nb_bytes: number of bytes to send.
* Return : none.
*******************************************************************************/
void USB_To_USART_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes)
{
uint32_t i;

for (i = 0; i < Nb_bytes; i++) //串口发送数据
{
USART_SendData(USART2, *(data_buffer + i));
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
}

接下去要定义Handle_USBAsynchXfer();函数,这个函数将串口收到的数据通过USB发送给PC机:

/*******************************************************************************
* Function Name : Handle_USBAsynchXfer.
* Description : 给USB发送数据
* Input : None.
* Return : none.
*******************************************************************************/
void Handle_USBAsynchXfer (void)
{
uint16_t USB_Tx_ptr; //USB发送数据位置
uint16_t USB_Tx_length;

if(USB_Tx_State != 1) //USB没有在发送
{
if (USART_Rx_ptr_out == USART_RX_DATA_SIZE)//环形接收缓冲输出位置到了缓冲区的最后
{
USART_Rx_ptr_out = 0; //设置接收缓冲位置为从0开始
}

if(USART_Rx_ptr_out == USART_Rx_ptr_in) //环形接收缓冲输出位置等于输入位置,说明USB数据发送完毕了
{
USB_Tx_State = 0; //设置USB发送标志位为0
return;
}
if(USART_Rx_ptr_out > USART_Rx_ptr_in) //环形接收缓冲输出位置大于输入位置,说明已经串口数如数据已经越过界了
{
USART_Rx_length = USART_RX_DATA_SIZE - USART_Rx_ptr_out;//计算缓冲数据长度
}
else
{
USART_Rx_length= USART_Rx_ptr_in - USART_Rx_ptr_out; //计算串口缓冲的数据长度
}
if (USART_Rx_length > VIRTUAL_COM_PORT_DATA_SIZE)//如果数据的长度大于端点的最大长度
{
USB_Tx_ptr = USART_Rx_ptr_out; //USB发送数据起始位置为串口缓冲输出位置
USB_Tx_length=VIRTUAL_COM_PORT_DATA_SIZE;//USB的发送长度为 端点的最大传输长度

USART_Rx_ptr_out += VIRTUAL_COM_PORT_DATA_SIZE;//移动串口缓冲输出位置
USART_Rx_length -= VIRTUAL_COM_PORT_DATA_SIZE;//计算剩余数据长度
}
else //如果数据的长度小于端点的最大长度
{
USB_Tx_ptr = USART_Rx_ptr_out; //USB发送数据起始位置为串口缓冲输出位置
USB_Tx_length = USART_Rx_length;//USB发送数据长度为 串口缓冲数据的长度

USART_Rx_ptr_out += USART_Rx_length;//移动串口缓冲输出位置
USART_Rx_length = 0; //没有剩余的数据要发送了
}
USB_Tx_State = 1; //设置USB发送状态为正在发送

UserToPMABufferCopy(&USART_Rx_Buffer[USB_Tx_ptr], ENDP1_TXADDR, USB_Tx_length);//从缓冲数据区拷贝数据到端点发送数据
SetEPTxCount(ENDP1, USB_Tx_length); //设置端点1发送计数
SetEPTxValid(ENDP1); //使能端点1
}
}

这里还要定义一个函数用来调整串口接收到的串口环形缓冲的写入读出数据的位子USART_To_USB_Send_Data():

/*******************************************************************************
* Function Name : UART_To_USB_Send_Data.
* Description : 发送串口接收到的数据大USB
* Input : None.
* Return : none.
*******************************************************************************/
void USART_To_USB_Send_Data(void)
{
if (linecoding.datatype == 7) //数据长度为7
{
USART_Rx_Buffer[USART_Rx_ptr_in] = USART_ReceiveData(USART2)&0x7F; //保存接收到的数据
}
else if (linecoding.datatype == 8) //数据长度为8
{
USART_Rx_Buffer[USART_Rx_ptr_in] = USART_ReceiveData(USART2);//保存接收到的数据
}
USART_Rx_ptr_in++; //串口缓冲输入位置递增
/* To avoid buffer overflow */
if(USART_Rx_ptr_in == USART_RX_DATA_SIZE) //如果串口缓冲数据位置到达了缓冲的最后
{
USART_Rx_ptr_in = 0; //重新开始
}
}

接下去要将的是usb_desc.c真个文件。先讲讲设备描述符,设备描述符跟之前讲述的没有多少出入,只是厂商ID要独享为0483,否则上位机无法安装相应的驱动。

/* USB标准设备描述符*/
const uint8_t Virtual_Com_Port_DeviceDescriptor[VIRTUAL_COM_PORT_SIZ_DEVICE_DESC] =
{
0x12, /*bLength:长度,设备描述符的长度为18字节*/
USB_DEVICE_DESCRIPTOR_TYPE, /*bDescriptorType:类型,设备描述符的编号是0x01*/
0x00, /*bcdUSB:所使用的USB版本为2.0*/
0x02,
0x02, /*bDeviceClass:设备所使用的类代码*/
0x00, /*bDeviceSubClass:设备所使用的子类代码*/
0x00, /*bDeviceProtocol:设备所使用的协议*/
0x40, /*bMaxPacketSize:最大包长度为64字节*/
0x83, /*idVendor:厂商ID为0x0483*/
0x04,
0x40, /*idProduct:产品ID为0x5740*/
0x57,
0x00, /*bcdDevice:设备的版本号为2.00*/
0x02,
1, /*iManufacturer:厂商字符串的索引*/
2, /*iProduct:产品字符串的索引*/
3, /*iSerialNumber:设备的序列号字符串索引*/
0x01 /*bNumConfiguration:设备有1种配置*/
}; /* CustomHID设备描述符 */

接下去是配置描述符集合,由于这个USB转串口是通讯设备类(CDC),USB需要实现两个接口:一个是抽象控制模型通信接口,这个接口有一个输入中断输入端点2,这个端点随让定义了,但是查看所有的代码,却没有在任何一处被调用,也就是说该端点虽然声明了但是没有使用,所以我尝试着将端点2描述符删去,却发现竟然无法使用了;另一个接口是抽象控制模型数据接口,这个接口定义了一个批量输入端点1和批量输出端点3,端点1用于想PC机的真是USB发送从USART接收到的数据,端点3用于接收来自PC的数据并通过UART发送。

/* USB配置描述符集合(配置、接口、端点、类、厂商)(Configuration, Interface, Endpoint, Class, Vendor */
const uint8_t Virtual_Com_Port_ConfigDescriptor[VIRTUAL_COM_PORT_SIZ_CONFIG_DESC] =
{
0x09, /*bLength:长度,设备字符串的长度为9字节*/
USB_CONFIGURATION_DESCRIPTOR_TYPE, /*bDescriptorType:类型,配置描述符的类型编号为0x2*/
VIRTUAL_COM_PORT_SIZ_CONFIG_DESC, /*wTotalLength:配置描述符的总长度为41字节*/
0x00,
0x02, /*bNumInterfaces:配置所支持的接口数量1个*/
0x01, /*bConfigurationValue:该配置的值*/
0x00, /*iConfiguration:该配置的字符串的索引值,该值为0表示没有字符串*/
0xC0, /* bmAttributes:设备的一些特性,0xc0表示自供电,不支持远程唤醒
D7:保留必须为1,D6:是否自供电,D5:是否支持远程唤醒,D4~D0:保留设置为0*/
0x32, /*从总线上获得的最大电流为100mA */

/************** 接口描述符****************/
0x09, /*bLength:长度,接口描述符的长度为9字节 */
USB_INTERFACE_DESCRIPTOR_TYPE,/* bDescriptorType:接口描述符的类型为0x4 */
0x00, /*bInterfaceNumber:该接口的编号*/
0x00, /*bAlternateSetting:该接口的备用编号 */
0x01, /*bNumEndpoints:该接口所使用的端点数*/
0x02, /*bInterfaceClass该接口所使用的类为通讯接口类*/
0x02, /*bInterfaceSubClass:该接口所用的子类 :抽象控制模型 */
0x01, /*nInterfaceProtocol :该接口使用的协议 Common AT commands */
0, /*iInterface: 该接口字符串的索引 */

/****************头功能描述符***************/
0x05, /* bLength: 端点描述描述符大小*/
0x24, /* bDescriptorType: CS_INTERFACE */
0x00, /* bDescriptorSubtype: Header Func Desc */
0x10, /* bcdCDC: 版本1.1*/
0x01,

/*************调用管理功能描述符*************/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x01, /* bDescriptorSubtype: Call Management Func Desc */
0x00, /* bmCapabilities: D0+D1 */
0x01, /* bDataInterface: 1 */

/**************ACM 功能描述符****************/
0x04, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x02, /* bDescriptorSubtype: Abstract Control Management desc */
0x02, /* bmCapabilities */

/***************联合功能描述符**************/
0x05, /* bFunctionLength */
0x24, /* bDescriptorType: CS_INTERFACE */
0x06, /* bDescriptorSubtype: Union func desc */
0x00, /* bMasterInterface: Communication class interface */
0x01, /* bSlaveInterface0: Data Class Interface */

/********************输入端点2描述符******************/
0x07, /* bLength: 端点描述符的长度为7字节*/
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端点描述符的类型为0x21*/
0x82, /* bEndpointAddress: 该端点(输入)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/
0x03, /* bmAttributes: 端点的属性为为中断端点.
D0~D1表示传输类型:0(控制传输),1(等时传输),2(批量传输),3(中断传输)
非等时传输端点:D2~D7:保留为0
等时传输端点:
D2~D3表示同步的类型:0(无同步),1(异步),2(适配),3(同步)
D4~D5表示用途:0(数据端点),1(反馈端点),2(暗含反馈的数据端点),3(保留),D6~D7:保留,*/
VIRTUAL_COM_PORT_INT_SIZE, /* wMaxPacketSize: 该端点支持的最大包长度为64字节*/
0x00,
0xFF, /* bInterval */

/**************数据类接口描述符************/
0x09, /* bLength: 接口描述符大小*/
USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType: */
0x01, /* bInterfaceNumber: 接口号*/
0x00, /* bAlternateSetting: 备用接口*/
0x02, /* bNumEndpoints: 使用2个端点*/
0x0A, /* bInterfaceClass: CDC */
0x00, /* bInterfaceSubClass: */
0x00, /* bInterfaceProtocol: */
0x00, /* iInterface: */

/********************输出端点3描述符******************/
0x07, /* 端点描述符的长度为7字节 */
USB_ENDPOINT_DESCRIPTOR_TYPE,/* bDescriptorType: 端点描述符的类型为0x21*/
0x03, /* bEndpointAddress: 该端点(输出)的地址,D7:0(OUT),1(IN),D6~D4:保留,D3~D0:端点号*/
0x02, /* bmAttributes: 端点的属性为仅批量 */
VIRTUAL_COM_PORT_DATA_SIZE, /* wMaxPacketSize: 该端点支持的最大包长度为64字节 */
0x00,
0x00, /* bInterval*/

/*输入端点1描述符*/
0x07, /* bLength: Endpoint Descriptor size */
USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: Endpoint */
0x81, /* bEndpointAddress: (IN1) */
0x02, /* bmAttributes: Bulk */
VIRTUAL_COM_PORT_DATA_SIZE, /* wMaxPacketSize: */
0x00,
0x00 /* bInterval */
};

接下去的语言ID字符串描述符、厂商字符串描述符、产品字符串描述符、序列号字符串描述符灯就不详细介绍了。
下面讲讲usb_endp.c这个文件。这个文件主要定义了数据通讯相关的代码,分别是EP1_IN_Callback()、EP3_OUT_Callback()、SOF_Callback()这三个函数。EP1_IN_Callback()向PC机发送数据,EP3_OUT_Callback()接收从PC机发送过来的数据。这里需要注意的是,我们在这里定义了这两个函数,所以在usb_conf.h中需要将
#define  EP1_IN_Callback   NOP_Process
#define  EP3_OUT_Callback   NOP_Process将这两句话屏蔽掉。至于SOF_Callback()这个函数会在发生SOF中断后被调用,可以看usb_istr.c:

#if (IMR_MSK & ISTR_SOF) //帧首(SOF)中断标志
if (wIstr & ISTR_SOF & wInterrupt_Mask)//读出的中断标志是SOF中断标志,且SOF中断使能了
{
_SetISTR((uint16_t)CLR_SOF);//清除SOF中断标志
bIntPackSOF++; //统计共接收到多少SOF

#ifdef SOF_CALLBACK
SOF_Callback();//当定义了SOF_CALLBACK,则调用SOF_Callback,像钩子函数一样,在发生SOF中断时做点什么
#endif
}
#endif

 
上面的代码可以看到SOF_Callback()这个函数只有在定义了SOF_CALLBACK这个宏定义之后再回被调用,所以我们在usb_conf.h中定义该宏定义:#define SOF_CALLBACK。

/*******************************************************************************
* Function Name : EP1_IN_Callback.
* Description : 端点1输入的回调函数
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void EP1_IN_Callback(void)
{
uint16_t USB_Tx_ptr;
uint16_t USB_Tx_length;

if (USB_Tx_State == 1) //如果该标志位为1,表示要正要发送数据
{
if (USART_Rx_length == 0) //如果没有数据需要发送
{
USB_Tx_State = 0; //USB_Tx_State标志位为0
}
else
{
if (USART_Rx_length > VIRTUAL_COM_PORT_DATA_SIZE)//如果串口接收到的数据大于端点的最大长度
{
USB_Tx_ptr = USART_Rx_ptr_out; //USB发送数据位置等于串口数据的位置
USB_Tx_length = VIRTUAL_COM_PORT_DATA_SIZE;//数据长度为最大数据长度

USART_Rx_ptr_out += VIRTUAL_COM_PORT_DATA_SIZE; //移动串口数据输出位置
USART_Rx_length -= VIRTUAL_COM_PORT_DATA_SIZE; //计算剩余数据长度
}
else
{
USB_Tx_ptr = USART_Rx_ptr_out; //USB发送数据位置等于串口数据的位置
USB_Tx_length = USART_Rx_length;//获取串口缓冲数据

USART_Rx_ptr_out += USART_Rx_length;//移动串口数据传输位置
USART_Rx_length = 0; //这时串口接收到的数据为0
}

UserToPMABufferCopy(&USART_Rx_Buffer[USB_Tx_ptr], ENDP1_TXADDR, USB_Tx_length); //发送数据
SetEPTxCount(ENDP1, USB_Tx_length); //设置端点长度
SetEPTxValid(ENDP1); //使能端点1
}
}
}

 

/*******************************************************************************
* Function Name : EP3_OUT_Callback
* Description : 端点3输出回调
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void EP3_OUT_Callback(void)
{
uint16_t USB_Rx_Cnt;

USB_Rx_Cnt = USB_SIL_Read(EP3_OUT, USB_Rx_Buffer); //获取USB接收到的数据
USB_To_USART_Send_Data(USB_Rx_Buffer, USB_Rx_Cnt); //USB接口到的数据从串口发送
SetEPRxValid(ENDP3); //使能端点3
}


/*******************************************************************************
* Function Name : SOF_Callback
* Description : 数据开始帧回调
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void SOF_Callback(void)
{
static uint32_t FrameCount = 0;

if(bDeviceState == CONFIGURED)
{
if (FrameCount++ == VCOMPORT_IN_FRAME_INTERVAL)
{
FrameCount = 0; //复位帧计数
Handle_USBAsynchXfer(); //给USB发送数据
}
}
}

最后要讲讲最重要的usb_prop.c文件。这个文件首先在Virtual_Com_Port_Reset()这个函数,该函数中要初始化3个端点;接下去重要的是Virtual_Com_Port_Data_Setup(),在建立阶段,USB会发来喜多请求,包括设置产口设置信息等,都在这个更函数中调用,其他的一些函数都更之前的差不多,这里将代码贴出来:

Virtual_Com_Portuint8_t Request = 0;

LINE_CODING linecoding =
{
115200, /* baud rate*/
0x00, /* stop bits-1*/
0x00, /* parity - none*/
0x08 /* no. of bits 8*/
};

/* -------------------------------------------------------------------------- */
/* Structures initializations */
/* -------------------------------------------------------------------------- */

DEVICE Device_Table =
{
EP_NUM, //被使用的端点数
1 //可以使用的端点数
};

DEVICE_PROP Device_Property = //注册一些CustomHID函数
{
Virtual_Com_Port_init, //Virtual_Com_Port的初始化函数
Virtual_Com_Port_Reset, //Virtual_Com_Port的复位函数
Virtual_Com_Port_Status_In, //CustomVirtual_Com_PortHID状态输入函数

Virtual_Com_Port_Status_Out, //CustomHID状态输出函数
Virtual_Com_Port_Data_Setup, //CustomHID的处理有数据阶段的特殊类请求函数
Virtual_Com_Port_NoData_Setup, //CustomHID的处理没有数据阶段的特殊类请求函数
Virtual_Com_Port_Get_Interface_Setting, //CustomHID获取接口及备用接口设置(是否可用)
Virtual_Com_Port_GetDeviceDescriptor, //CustomHID获取设备描述符
Virtual_Com_Port_GetConfigDescriptor, //CustomHID获取配置描述符
Virtual_Com_Port_GetStringDescriptor, //CustomHID获取字符串描述符
0, //当前库未使用
0x40 /*MAX PACKET SIZE*/ //最大的包长度为64字节
};

/*注册USB标准请求的实现函数*/
USER_STANDARD_REQUESTS User_Standard_Requests =
{
Virtual_Com_Port_GetConfiguration, //获取配置请求
Virtual_Com_Port_SetConfiguration, //设置配置请求
Virtual_Com_Port_GetInterface, //获取接口请求
Virtual_Com_Port_SetInterface, //设置接口请求
Virtual_Com_Port_GetStatus, //获取状态请求
Virtual_Com_Port_ClearFeature, //清除属性请求
Virtual_Com_Port_SetEndPointFeature, //设置端点属性请求
Virtual_Com_Port_SetDeviceFeature, //设置设备属性请求
Virtual_Com_Port_SetDeviceAddress //设置设备地址请求
};

/*注册设备描述符信息*/
ONE_DESCRIPTOR Device_Descriptor =
{
(uint8_t*)Virtual_Com_Port_DeviceDescriptor, //注册设备描述符数组
VIRTUAL_COM_PORT_SIZ_DEVICE_DESC //设备描述符的长度
};

/*注册设备描述符信息*/
ONE_DESCRIPTOR Config_Descriptor =
{
(uint8_t*)Virtual_Com_Port_ConfigDescriptor, //注册配置描述符数组
VIRTUAL_COM_PORT_SIZ_CONFIG_DESC //配置描述符的长度
};

/*注册字符串描述符,包括语言ID、厂商、产品、序列号描述符*/
ONE_DESCRIPTOR String_Descriptor[4] =
{
{(uint8_t*)Virtual_Com_Port_StringLangID, VIRTUAL_COM_PORT_SIZ_STRING_LANGID}, //注册语言字符串描述符数组
{(uint8_t*)Virtual_Com_Port_StringVendor, VIRTUAL_COM_PORT_SIZ_STRING_VENDOR}, //注册厂商字符串描述符数组
{(uint8_t*)Virtual_Com_Port_StringProduct, VIRTUAL_COM_PORT_SIZ_STRING_PRODUCT},//注册产品字符串描述符数组
{(uint8_t*)Virtual_Com_Port_StringSerial, VIRTUAL_COM_PORT_SIZ_STRING_SERIAL} //注册序列号字符串描述符数组
};

/* Extern variables ----------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Extern function prototypes ------------------------------------------------*/
/* Private functions ---------------------------------------------------------*/

/*******************************************************************************
* Function Name : CustomHID_init.
* Description : 初始化
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void Virtual_Com_Port_init(void)
{
Get_SerialNum(); //获取序列号

pInformation->Current_Configuration = 0;
PowerOn(); //上电
USB_SIL_Init(); //执行基本的初始化操作,比如说设备IP和端点0的初始化
USART_Config_Default();
bDeviceState = UNCONNECTED; //设置状态为未连接
}

/*******************************************************************************
* Function Name : CustomHID_Reset.
* Description : 复位
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void Virtual_Com_Port_Reset(void)
{
pInformation->Current_Configuration = 0; //设置当前的配置为0,表示没有配置过
pInformation->Current_Interface = 0;

pInformation->Current_Feature = Virtual_Com_Port_ConfigDescriptor[7];//当前的属性,bmAttributes:设备的一些特性,0xc0表示自供电,不支持远程唤醒

SetBTABLE(BTABLE_ADDRESS);

/*初始化端点0 */
SetEPType(ENDP0, EP_CONTROL); //设置端点0为控制端点
SetEPTxStatus(ENDP0, EP_TX_STALL); //设置端点0发送延时
SetEPRxAddr(ENDP0, ENDP0_RXADDR); //设置端点0的接收缓冲区地址
SetEPTxAddr(ENDP0, ENDP0_TXADDR); //设置端点0的发送缓冲区地址
Clear_Status_Out(ENDP0); //清除端点0的状态
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize);//设置端点0的接收的计数
SetEPRxValid(ENDP0); //使能接收状态

/* 初始化端点1*/
SetEPType(ENDP1, EP_BULK); //设置端点1为进批量传输
SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置端点发送地址
SetEPTxStatus(ENDP1, EP_TX_NAK); //设置端点1的发送不响应
SetEPRxStatus(ENDP1, EP_RX_DIS); //设置端点1不接收

/*初始化端点2*/
SetEPType(ENDP2, EP_INTERRUPT); //设置端点2为中断传输
SetEPTxAddr(ENDP2, ENDP2_TXADDR); //设置端点2发送地址
SetEPRxStatus(ENDP2, EP_RX_DIS); //设置不端点2接收状态
SetEPTxStatus(ENDP2, EP_TX_NAK); //设置端点2端点2为接收不响应

/*初始化端点3*/
SetEPType(ENDP3, EP_BULK); //设置端点3为仅批量传输
SetEPRxAddr(ENDP3, ENDP3_RXADDR); //设置端点3接收地址
SetEPRxCount(ENDP3, VIRTUAL_COM_PORT_DATA_SIZE);//设置端点3的计数值
SetEPRxStatus(ENDP3, EP_RX_VALID); //设置端点3接收有效
SetEPTxStatus(ENDP3, EP_TX_DIS); //设置端点3不发送

SetDeviceAddress(0); //设置设备为默认地址

bDeviceState = ATTACHED;
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_SetConfiguration.
* Description : 更新设备配置状态
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void Virtual_Com_Port_SetConfiguration(void)
{
DEVICE_INFO *pInfo = &Device_Info;

if (pInfo->Current_Configuration!=0)
{
/* Device configured */
bDeviceState = CONFIGURED;
}
}
/*******************************************************************************
* Function Name : Virtual_Com_Port_SetDeviceAddress .
* Description : 更新设备的编址状态
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
voidVirtual_Com_Port_SetDeviceAddress(void)
{
bDeviceState = ADDRESSED;
}
/*******************************************************************************
* Function Name : Virtual_Com_Port_Status_In.
* Description : 状态输入函数
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
voidVirtual_Com_Port_Status_In(void)
{
if(Request== SET_LINE_CODING)
{
USART_Config();
Request=0;
}
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_Status_Out
* Description : 状态输出函数
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
voidVirtual_Com_Port_Status_Out(void)
{}

/*******************************************************************************
* Function Name : Virtual_Com_Port_Data_Setup
* Description : 处理有数据阶段的特殊类请求
* Input : Request Nb.
* Output : None.
* Return : USB_UNSUPPORT or USB_SUCCESS.
*******************************************************************************/
RESULT Virtual_Com_Port_Data_Setup(uint8_tRequestNo)
{
uint8_t*(*CopyRoutine)(uint16_t);

CopyRoutine= NULL;

if(RequestNo== GET_LINE_CODING)//获取串口通信信息请求
{
if(Type_Recipient==(CLASS_REQUEST| INTERFACE_RECIPIENT))//类请求,请求的接收者是接口
{
CopyRoutine=Virtual_Com_Port_GetLineCoding;//指针函数指向Virtual_Com_Port_GetLineCoding函数
}
}
elseif(RequestNo== SET_LINE_CODING)//设置串口通讯信息请求
{
if(Type_Recipient==(CLASS_REQUEST | INTERFACE_RECIPIENT))//类请求,请求的接收者是接口
{
CopyRoutine=Virtual_Com_Port_SetLineCoding;//指针函数指向Virtual_Com_Port_SetLineCoding函数
}
Request= SET_LINE_CODING;
}

if(CopyRoutine== NULL)
{
return USB_UNSUPPORT;
}

pInformation->Ctrl_Info.CopyData=CopyRoutine;//注册指针指向的函数
pInformation->Ctrl_Info.Usb_wOffset=0;
(*CopyRoutine)(0);//调用该函数
return USB_SUCCESS;
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_NoData_Setup
* Description : 处理没有数据阶段特殊类请求
* Input : Request Nb.
* Output : None.
* Return : USB_UNSUPPORT or USB_SUCCESS.
*******************************************************************************/
RESULT Virtual_Com_Port_NoData_Setup(uint8_tRequestNo)
{
if(Type_Recipient==(CLASS_REQUEST | INTERFACE_RECIPIENT))//类请求,请求的接收者是接口
{
if(RequestNo== SET_COMM_FEATURE)//设置虚拟串口特性
{
return USB_SUCCESS;
}
elseif(RequestNo== SET_CONTROL_LINE_STATE)//设置控制信息状态
{
return USB_SUCCESS;
}
}

return USB_UNSUPPORT;
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_GetDeviceDescriptor.
* Description : 获取设备描述符
* Input : Length
* Output : None.
* Return : The address of the device descriptor.
*******************************************************************************/
uint8_t*Virtual_Com_Port_GetDeviceDescriptor(uint16_tLength)
{
returnStandard_GetDescriptorData(Length,&Device_Descriptor);
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_GetConfigDescriptor.
* Description : 获取配置描述符
* Input : Length
* Output : None.
* Return : The address of the configuration descriptor.
*******************************************************************************/
uint8_t*Virtual_Com_Port_GetConfigDescriptor(uint16_tLength)
{
returnStandard_GetDescriptorData(Length,&Config_Descriptor);
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_GetStringDescriptor
* Description : 根据索引获取字符描述符
* Input : Length
* Output : None.
* Return : The address of the string descriptors.
*******************************************************************************/
uint8_t*Virtual_Com_Port_GetStringDescriptor(uint16_tLength)
{
uint8_t wValue0 = pInformation->USBwValue0;
if(wValue0 >4)
{
return NULL;
}
else
{
returnStandard_GetDescriptorData(Length,&String_Descriptor[wValue0]);
}
}


/*******************************************************************************
* Function Name : Virtual_Com_Port_Get_Interface_Setting.
* Description : 测试接口及其备用接口是否可用
* Input : - Interface : interface number.
* - AlternateSetting : Alternate Setting number.
* Output : None.
* Return : USB_SUCCESS or USB_UNSUPPORT.
*******************************************************************************/
RESULT Virtual_Com_Port_Get_Interface_Setting(uint8_tInterface,uint8_tAlternateSetting)
{
if(AlternateSetting>0)//备用的编号大于0
{
return USB_UNSUPPORT;
}
elseif(Interface>1)//接口的编号大于1
{
return USB_UNSUPPORT;
}
return USB_SUCCESS;
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_GetLineCoding.
* Description : 发送linecoding结构体到PC机
* Input : Length:长度.
* Output : None.
* Return : Linecoding结构体的基地址
*******************************************************************************/
uint8_t*Virtual_Com_Port_GetLineCoding(uint16_tLength)
{
if(Length==0)
{
pInformation->Ctrl_Info.Usb_wLength=sizeof(linecoding);
return NULL;
}
return(uint8_t*)&linecoding;
}

/*******************************************************************************
* Function Name : Virtual_Com_Port_SetLineCoding.
* Description : 设置linecoding结构体
* Input : Length.
* Output : None.
* Return : Linecoding结构体的基地址.
*******************************************************************************/
uint8_t*Virtual_Com_Port_SetLineCoding(uint16_tLength)
{
if(Length==0)
{
pInformation->Ctrl_Info.Usb_wLength=sizeof(linecoding);
return NULL;
}
return(uint8_t*)&linecoding;
}

对了,差点忘记串口2的中断设置和串口2的中断服务程序了,在hw_config.c的USB_Interrupt()里我们设置串口2的中断优先级:

NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //设置串口2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_Init(&NVIC_InitStructure);

我们还要在stm32f10x.c中编写中断服务程序:

void USART2_IRQHandler(void)
{
if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
/* Send the received data to the PC Host*/
USART_To_USB_Send_Data();
}

/* If overrun condition occurs, clear the ORE flag and recover communication */
if (USART_GetFlagStatus(USART2, USART_FLAG_ORE) != RESET)
{
(void)USART_ReceiveData(USART2);
}
}

如果该工程成功后,就可以在电脑的设备管理器中看到一个新的端口:
STM32 USB Virtual COM USB转串口的功能实现 - ziye334 - ziye334的博客
  我们可以自点击的电脑上最测试,这里需要一个USB转串口线,连接我们的开发板的串口2,这个串口就当做USB转产口的串口端,我们将串口线连接好后,连接电脑,打开串口调试助手,我电脑上显示的是COM4.接着在打开一个串口调试助手窗口,这个串口选择我们之前装好驱动过后的COM10(上图),然后两个串口调试助手就可以互相发送数据了,如果成功,应该能互相通信,否者再根据现象调试代码。
 STM32 USB Virtual COM USB转串口的功能实现 - ziye334 - ziye334的博客

STM32 USB Virtual COM