ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H562开发指南 V1.0 》第五十八章 USB虚拟串口(Slave)实验

《STM32H562开发指南 V1.0 》第五十八章 USB虚拟串口(Slave)实验

原创 嵌入式/内核开发 作者:正点原子 时间:2026-01-12 10:34:29 0 删除 编辑

第五十八章 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 *)&parameter);
    
    /* 开始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/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764178