第五十八章 USB虚拟串口(Slave)实验
本章,我们将向大家介绍如何利用USB在开发板实现一个USB虚拟串口,通过USB与电脑数据数据交互。
本章分为如下几个部分:
58.1 USB虚拟串口简介
58.2 硬件设计
58.3 程序设计
58.4 下载验证
58.1 USB虚拟串口简介
USB虚拟串口,简称VCP,是Virtual COM Port的简写,它是利用USB的CDC类来实现的一种通信接口。
我们可以利用STM32自带的USB功能,来实现一个USB虚拟串口,从而通过USB,实现电脑与STM32的数据互传。上位机无需编写专门的USB程序,只需要一个串口调试助手即可调试,非常实用。
本实验,我们直接移植官方的USB VCP例程,官方例程路径:A盘→8,STM32参考资料→1,STM32CubeH5固件包→STM32Cube_FW_H5_V1.0.0àProjects→NUCLEO-H503RB→Applications→USBX→Ux_Device_CDC_ACM。该例程采用USB CDC类来实现,利用STM32的USB接口,实现一个USB转串口的功能。
58.2 硬件设计
1 . 例程功能
本实验利用STM32自带的USB功能,连接电脑USB,虚拟出一个USB串口,实现电脑和开发板的数据通信。本例程功能完全同实验5(串口通信实验),只不过串口变成了STM32的USB虚拟串口。当USB连接电脑(USB线插入USB_SLAVE接口),开发板将通过USB和电脑建立连接,并虚拟出一个串口(注意:需要先安装:光盘\6,软件资料\1,软件\ 5,其他软件.zip\5,其他软件\STM32 USB虚拟串口驱动\VCP_V1.4.0_Setup.exe这个驱动软件,虚拟串口驱动我们还可以在论坛上下载,链接是: http://www.openedv.com/thread-284178-1-1.html )。
LED0闪烁,提示程序运行。
2. 硬件资源
1)LED灯
LED0 - PB4
2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
4)USB_SLAVE接口(D-/D+连接在PA11/PA12上)
58.3 程序设计
58.3.1 程序流程图

图58.3.1.1 USB虚拟串口(Slave)实验程序流程图
58.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。本实验,在实验5 串口通信实验基础上,添加USB相关代码到工程中,最终得到如图58.3.2.1所示的工程:

图58.3.2.1 USB虚拟串口工程分组
1. USB驱动代码
app_usbx_device.c代码,和上一章基本一样,具体请查看源码,这里就不贴出来了。
ux_device_descriptors.c代码,同上一个实验不一样,上一个实验描述符是USB Audio设备,本实验变成了USB虚拟串口了(CDC),所以直接用ST官方的就行。
ux_device_cdc_acm.c/.h代码,是重点要修改的,首先介绍ux_device_cdc_acm.h文件的相关宏定义,具体如下:
#define USBX_DEVICE_CDC_ACM_WRITE_BUFFER_SIZE 200/* CDC ACM写传输数据缓冲区大小 */ #define USBX_DEVICE_CDC_ACM_READ_BUFFER_SIZE 200 /* CDC ACM读传输数据缓冲区大小 */
这两个宏定义分别定义USB串口发送和接收缓冲区的最大字节数,这里都设置为200。
下面查看一下ux_device_cdc_acm.c文件,其定义如下:
/**
* @brief CDC写传输回调函数
* @param cdc_acm: CDC类实例
* @param status: 写传输状态
* @param length: 写传输数据长度
* @retval 状态
*/
UINT ux_device_cdc_acm_write_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT
*cdc_acm, UINT status, ULONG length)
{
UX_PARAMETER_NOT_USED(cdc_acm);
UX_PARAMETER_NOT_USED(status);
UX_PARAMETER_NOT_USED(length);
g_ux_device_cdc_acm_write_sta = 0;
return UX_SUCCESS;
}
/**
* @brief CDC读传输回调函数
* @param cdc_acm: CDC类实例
* @param status: 读传输状态
* @param data_pointer: 读传输数据
* @param length: 读传输数据长度
* @retval 状态
*/
UINT ux_device_cdc_acm_read_callback(struct UX_SLAVE_CLASS_CDC_ACM_STRUCT
*cdc_acm, UINT status, UCHAR *data_pointer, ULONG length)
{
UX_PARAMETER_NOT_USED(cdc_acm);
UX_PARAMETER_NOT_USED(status);
g_ux_device_cdc_acm_rx_buffer = (uint8_t *)data_pointer;
g_ux_device_cdc_acm_rx_len = length;
/* 重新开始CDC ACM读传输 */
ux_device_class_cdc_acm_read_run(g_cdc_acm, (UCHAR
*)&g_ux_device_cdc_acm_read_buffer, USBX_DEVICE_CDC_ACM_READ_BUFFER_SIZE,
(ULONG *)&g_ux_device_cdc_acm_read_actual_length);
return UX_SUCCESS;
}
/**
* @brief 激活CDC类
* @param cdc_acm: CDC类实例
* @retval 无
*/
VOID ux_device_cdc_acm_instance_activate(VOID *cdc_acm)
{
UX_SLAVE_CLASS_CDC_ACM_CALLBACK_PARAMETER parameter;
g_cdc_acm = (UX_SLAVE_CLASS_CDC_ACM *)cdc_acm;
/* 开启CDC ACM传输 */
parameter.ux_device_class_cdc_acm_parameter_write_callback =
ux_device_cdc_acm_write_callback;
parameter.ux_device_class_cdc_acm_parameter_read_callback =
ux_device_cdc_acm_read_callback;
ux_device_class_cdc_acm_ioctl(g_cdc_acm,
UX_SLAVE_CLASS_CDC_ACM_IOCTL_TRANSMISSION_START, (VOID *)¶meter);
/* 开始CDC ACM读传输 */
ux_device_class_cdc_acm_read_run(g_cdc_acm, (UCHAR
*)&g_ux_device_cdc_acm_read_buffer, USBX_DEVICE_CDC_ACM_READ_BUFFER_SIZE,
(ULONG *)&g_ux_device_cdc_acm_read_actual_length);
}
/**
* @brief 停用CDC类
* @param cdc_acm: CDC类实例
* @retval 无
*/
VOID ux_device_cdc_acm_instance_deactivate(VOID *cdc_acm)
{
UX_PARAMETER_NOT_USED(cdc_acm);
g_cdc_acm = NULL;
}
/**
* @brief CDC类请求处理
* @param cdc_acm: CDC类实例
* @retval 无
*/
VOID ux_device_cdc_acm_parameter_change(VOID *cdc_acm)
{
UX_SLAVE_CLASS_CDC_ACM_LINE_CODING_PARAMETER line_coding = {0};
line_coding.ux_slave_class_cdc_acm_parameter_baudrate =
((UX_SLAVE_CLASS_CDC_ACM *)cdc_acm)->ux_slave_class_cdc_acm_baudrate;
line_coding.ux_slave_class_cdc_acm_parameter_stop_bit =
((UX_SLAVE_CLASS_CDC_ACM *)cdc_acm)->ux_slave_class_cdc_acm_stop_bit;
line_coding.ux_slave_class_cdc_acm_parameter_parity =
((UX_SLAVE_CLASS_CDC_ACM *)cdc_acm)->ux_slave_class_cdc_acm_parity;
line_coding.ux_slave_class_cdc_acm_parameter_data_bit =
((UX_SLAVE_CLASS_CDC_ACM *)cdc_acm)->ux_slave_class_cdc_acm_data_bit;
ux_device_class_cdc_acm_ioctl((UX_SLAVE_CLASS_CDC_ACM *)cdc_acm,
UX_SLAVE_CLASS_CDC_ACM_IOCTL_SET_LINE_CODING, (VOID *)&line_coding);
}
/**
* @brief CDC发送数据
* @param data: 数据
* @param len: 数据长度
* @retval 无
*/
void ux_device_cdc_acm_transmit(uint8_t *data, uint32_t len)
{
if ((g_cdc_acm != NULL))
{
g_ux_device_cdc_acm_write_sta = 1;
ux_device_class_cdc_acm_write_with_callback(g_cdc_acm, (UCHAR *)data,
(ULONG)len);
/* 等待发送完毕 */
while (g_ux_device_cdc_acm_write_sta == 1);
}
}
/**
* @brief CDC printf函数
* @param fmt: printf参数
* @retval 无
*/
void ux_device_cdc_acm_printf(char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsprintf((char *)g_ux_device_cdc_acm_write_buffer, fmt, ap);
va_end(ap);
ux_device_cdc_acm_transmit(g_ux_device_cdc_acm_write_buffer,
strlen((const char *)g_ux_device_cdc_acm_write_buffer));
}
2. main.c代码
下面是main.c的代码,具体如下:
int main(void)
{
uint16_t t = 0;
uint8_t res;
uint8_t key;
HAL_Init(); /* HAL库初始化 */
HAL_ICACHE_Enable(); /* 使能指令缓存 */
sys_stm32_clock_init(250, 4, 2, 2); /* 配置时钟,250MHz */
delay_init(250); /* 初始化延时 */
usart_init(115200); /* 初始化串口 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
lcd_init(); /* 初始化LCD */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "USB VCP TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
/* USB设备初始化 */
res = app_usbx_device_init();
while (res != 0)
{
lcd_show_string(30, 110, 200, 16, 16, "USB Device Init Failed!", RED);
delay_ms(500);
lcd_show_string(30, 110, 200, 16, 16, "Please Check! ", RED);
delay_ms(500);
}
/* 每1毫秒运行一次独立模式下的USBX系统任务 */
btim_timx_int_init(1000 - 1, 250 - 1);
while (1)
{
if (g_ux_device_cdc_acm_rx_len != 0)
{
ux_device_cdc_acm_printf("\r\n您发送的消息为:\r\n");
ux_device_cdc_acm_transmit(g_ux_device_cdc_acm_rx_buffer,
g_ux_device_cdc_acm_rx_len);
ux_device_cdc_acm_printf("\r\n\r\n");
g_ux_device_cdc_acm_rx_buffer = NULL;
g_ux_device_cdc_acm_rx_len = 0;
}
else
{
if ((t % 1000) == 0)
{
ux_device_cdc_acm_printf("\r\n正点原子 STM32H5开发板 USB虚拟串口实验
\r\n");
ux_device_cdc_acm_printf("正点原子@ALIENTEK\r\n\r\n");
}
if ((t % 200) == 0)
{
ux_device_cdc_acm_printf("请输入数据,以回车键结束\r\n");
}
if ((t % 20) == 0)
{
LED0_TOGGLE();
}
t++;
}
delay_ms(10);
}
}
该部分代码功能,先进行系统和外设初始化,然后每1毫秒运行一次独立模式下的USBX系统任务。本实验实现的是USB虚拟串口的功能,在死循环里面判断,如果接收到数据了,就会将收到的数据原原本本的返回给电脑端串口调试助手。如果没有接收到数据,则会每隔一段时间往上位机发送实验信息和提示信息。
58.4 下载验证
本例程的测试,需要在电脑上先安装ST提供的USB虚拟串口驱动软件,该软件(V1.5.0版)下载地址: http://www.openedv.com/thread-284178-1-1.html ,下载完以后,根据自己电脑的系统,选择合适的驱动安装即可。
将程序下载到开发板后(注意:USB数据线,要插在USB_SLAVE口!而不是USB_UART端口!),我们打开设备管理器(我用的是WIN10),在端口(COM和LPT)里面可以发现多出了一个COM42的设备,这就是USB虚拟的串口设备端口,如图58.4.1所示:

图58.4.1 通过设备管理器查看USB虚拟的串口设备端口
如图58.4.1,STM32通过USB虚拟的串口,被电脑识别了,端口号为:COM42(可变),字符串名字为:STMicroelectronics Virtual COM Port(COM42)。此时,LED0在闪烁,提示程序运行。开发板的LCD显示内容,如图58.4.2所示:

图58.4.2 USB虚拟串口连接成功
然后我们打开XCOM,选择COM42(需根据自己的电脑识别到的串口号选择),并打开串口(注意:波特率可以随意设置),就可以进行测试了,如图58.4.3所示:

图58.4.3 STM32虚拟串口通信测试
可以看到,我们的串口调试助手,收到了来自STM32开发板的数据,同时,按发送按钮(串口助手必须勾选:发送新行),也可以收到电脑发送给STM32的数据(原样返回),说明我们的实验是成功的。
至此,USB虚拟串口实验就完成了,通过本实验,我们就可以利用STM32的USB,直接和电脑进行数据互传了,具有广泛的应用前景。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3107662/ ,如需转载,请注明出处,否则将追究法律责任。