正点原子STM32连载第61章:USB虚拟串口实验摘要

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第六十一章 USB虚拟串口实验

本章,我们将向大家介绍如何利用USB FS 在正点原子战舰STM32F1开发板实现一个USB虚拟串口,通过USB与电脑数据数据交互。本章分为如下几个部分:
61.1 USB虚拟串口简介
61.2 硬件设计
61.3 软件设计
61.4 下载验证

61.1 USB虚拟串口简介

USB虚拟串口,简称VCP,是Virtual COM Port的简写,它是利用USB的CDC类来实现的一种通信接口。CDC(Communication Device Class)类是 USB2.0 标准下的一个设备类,定义了通信相关设备的抽象集合。ST官方文档《USB CDC 类入门培训》提供了关于USB CDC的详细知识,同样的,我们把资料放到“光盘资料(A盘)→8,STM32参考资料→2,STM32 USB 学习资料”中了。
我们可以利用STM32自带的USB功能,来实现一个USB虚拟串口,从而通过USB,实现电脑与STM32的数据互传。上位机无需编写专门的USB程序,只需要一个串口调试助手即可调试,非常实用。
61.2硬件设计

  1. 例程功能
    本章实验功能简介:本实验利用STM32自带的USB功能,连接电脑USB,虚拟出一个USB串口,实现电脑和开发板的数据通信。本例程功能完全同实验 串口通信实验,只不过串口变成了STM32的USB虚拟串口。当USB连接电脑(USB线插入USB_SLAVE接口),开发板将通过USB和电脑建立连接,并虚拟出一个串口(注意:虚拟串口需要先安装驱动,虚拟串口驱动在:A盘6,软件资料1,软件STM32 USB虚拟串口驱动en.stsw-stm32102.zip),USB和电脑连接成功后,DS1常亮。
    在找到虚拟串口后,即可打开串口调试助手,实现同串口实验类似的功能,即:STM32通过USB虚拟串口和上位机对话,STM32在收到上位机发过来的字符串(以回车换行结束)后,原样返回给上位机。下载后,DS0闪烁,提示程序在运行,同时每隔一定时间,通过USB虚拟串口输出一段信息到电脑。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    LED1 – PE5
    2)KEY0按键
    3)TFTLCD 模块
    4)USB SLAVE接口
    6)串口1
    这几个外设原理图我们在之前的章节已经介绍过了,这里就不重复介绍了,不清楚的话可以查看本文之前章节的描述或对光盘资料提供的开发板原理图。这里再次提醒大家,P10的连接,要通过跳线帽连接PA11和D-以及PA12和D+。
    61.3 程序设计
    同上一章一样,我们直接移植官方的USB VCP例程,官方例程路径:8,STM32参考资料→1,STM32F1xx固件库→STM32Cube_FW_F1_V1.8.0→Projects→STM3210E_EVAL→Applications→USB_Device→CDC_Standalone,该例程采用USB CDC类来实现,利用STM32的USB接口,实现一个USB转串口的功能。我们先打开该例程的MDK工程(MDK-ARM文件夹下),查看一下其工程结构:

图61.3.1 ST官方例程的结构
从工程结构不难找出我们需要的USB功能代码,并且这些文件是加了只读属性的,我们移植后需要把只读属性去掉才能进行我们需要的修改。我们复制之前的“内存管理实验”工程,重命名为“USB虚拟串口实验”,因为我们并不是所有例程都使用USB库驱动,故我们把USB作为一个第三方组件放到我们的“Middlewares”文件夹下,我们在该文件夹下新建一个USB文件目录,把USB相关的全部放到USB文件夹下使这部分驱动完全独立,这样可以方便我们以后事移植到其它项目中。
首先是usbd_core.c、usbd_ioreq.c、usbd_ctlreq.c这三个文件,我们查看它们所在的路径发现它们都位于“STM32_USB_Device_Library”文件夹下,所以我们可以直接把该文件夹复制到我们的“USB虚拟串口/Middlewares/USB”文件夹下,后面再考虑精简工程。复制后USB文件夹下就有“STM32_USB_Device_Library /Class”和“STM32_USB_Device_Library /Core”两个目录了。
接着同样的方法,找到usbd_cdc.c文件,文件位于“STM32_USB_Device_Library/Class”文件夹下,上一步我们已经把整个文件夹复制到我们的USB目录下了,所以这步不需要再操作。
接下来的USB应用程序usbd_cdc_interface.c、 usbd_desc.c、usbd_conf.c三个文件,源文件和头文件分别位于USB_Device\CDC_Standalone\Src和\USB_Device\CDC_Standalone\Inc下,我们在USB文件夹下新建一个“USB_APP”文件夹,把它们连同头文件都放到该文件夹下。复制后的USP_APP目录结构如图61.3.2所示。

图61.3.2 USB APP文件夹下的文件
我们需要添加的文件已经准备好了,接下来需要把这些文件添加到我们移植的MDK工程中。为了使USB的驱动更加独立,我们按原来的定义,在MDK中新建Middlewares/USB_CORE、Middlewares/USB_CLASS、Middlewares/USB_APP三个分组,把上面的文件的只读属性去掉后添加到我们的工程中,然后把相关的HAL库的驱动加到Drivers/STM32F1xx_HAL_Driver目录下,添加后的工程目录如图61.3.3所示。

图61.3.3 在MDK中添加需要的代码
为了保持USB驱动部分更少的改动,我们添加原有USB库的头文件的引用路径,结果如图61.3.4所示。

图61.3.4 在MDK中添加USB引用的头文件的路径
这时我们直接编译会报错,因为我们没有引用ST开发板的BSP文件,这时我们还需要修改相关源码以匹配我们的底层的驱动,这部分与我们开发板硬件相关,我们在程序设计的部分再讲解。
61.3.1 程序流程图

图61.3.1.1 USB虚拟串口程序流程图
我们按流程图编写的初始化顺序,在STM32注册USB内核,最后通过USB的中断和回调函数得到USB的操作状态和操作结果,主程序通过查询设定的标记变量的状态值后,在LCD上显示对应的USB操作状态。最后通过USB和电脑实现数据交互。
61.3.2 usbd_cdc_interface驱动
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码,USB虚拟串口的驱动主要包括两个文件:usbd_cdc_interface.c和usbd_cdc_interface.h。
usbd_cdc_interface.c/.h需要适配我们的硬件信息。本例中,我们需要把USB操作与我们的串口操作对应起来。为了实现我们流程图设想的功能,我们将对原例程中的这部分代码进行修改,在此之前,我们先大致了解一下需要用到的USB设备类的一些参数和操作接口。下面介绍对应接口时我们给出的是修改后的源码,详细的修改点大家可以参考对照光盘的源码即可。

  1. USBD_CDC_fops结构体
    USBD_CDC_fops结构体定义了CDC通讯设备的操作,它是USBD_CDC类下封装的一个结构体,用于USBD_CDC类与应用层的分离,我们通过对这个结构体进行实例化即可完成对CDC类的应用层的功能定义,它会在注册后被USB内核调用,我们需要为这些USB操作实现跟我们硬件相关的底层代码,它们会在USB枚举过程中被调用。
    /* 虚拟串口配置函数(供USB内核调用) */
USBD_CDC_ItfTypeDef USBD_CDC_fops =
{
    CDC_Itf_Init,
    CDC_Itf_DeInit,
    CDC_Itf_Control,
    CDC_Itf_Receive
};

操作描述:
CDC_Itf_Init用于初始化VCP,在初始化的时候由USB内核调用,这里我们调用函数:USBD_CDC_SetRxBuffer,设置USB接收数据缓冲区。USB虚拟串口收到的数据,会先缓存在这个buf里面,ST原来的例程同时设置了写缓冲,我们为了方便单独编写了写发送函数。
CDC_Itf_DeInit用于复位VCP,我们用不到,所以直接返回USBD_OK即可。
CDC_Itf_Control用于控制VCP的相关参数,根据cmd的不同,执行不同的操作,这里主要用到CDC_SET_LINE_CODING命令,该命令用于设置VCP的相关参数,比如波特率、数据类型(位数)、校验类型(奇偶校验)等,保存在linecoding结构体里面,在需要的时候,应用程序可以读取LineCoding结构体里面的参数,以获得当前VCP的相关信息。
CDC_Itf_Receive和VCP_DataRx,这两个函数一起,用于VCP数据接收,当STM32的USB接收到电脑端串口发送过来的数据时,由USB内核程序调用CDC_Itf_Receive,然后在该函数里面再调用VCP_DataRx函数,实现VCP的数据接收,只需要在该函数里面,将接收到的数据,保存起来即可,接收的原理同串口通信实验完全一样。
2. CDC_Itf_Init函数
CDC_Itf_Init用于初始化VCP,在初始化的时候由USB内核调用,这里我们可以编写实际的串口应用,使虚拟串口与物理串口对应上,但为了简化测试,我们没有加上这个操作,这里只为CDC设备通讯分配接收缓存:
static int8_t CDC_Itf_Init(void)
函数描述:
初始存储CDC通讯设备,这里主要包括跟开发板底层驱动相关的及CDC设备类相关的收发缓存的分配,所以在这个函数里面会调用USBD_CDC_SetRxBuffer,设置USB接收数据缓冲区。USB虚拟串口收到的数据,会先缓存在这个buf里面。

/**
 * @brief       初始化 CDC
 * @param       无
 * @retval      USB状态
 * @arg         USBD_OK(0)   , 正常;
 * @arg         USBD_BUSY(1) , 忙;
 * @arg         USBD_FAIL(2) , 失败;
 */
static int8_t CDC_Itf_Init(void)
{
    USBD_CDC_SetRxBuffer(&USBD_Device, g_usb_rx_buffer);
    return USBD_OK;
}

函数返回值:
返回硬件初始化结果:USBD_OK(0):成功,其它:错误或忙。
3. CDC_Itf_Control函数
CDC_Itf_Control用于CDC设备的控制,主机可以通过这些接口配置CDC设备:
static int8_t CDC_Itf_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length)
函数描述:
控制接口主要用来做设备管理和电话管理(可选),设备管理涉及到请求(request)和通知(notification),端点0一般用做请求,一般用来控制和配置设备的运行状态,而非0端点(0x82)一般用作异步事件通知,设备端通过此端点向主机端发送设备内部的一些事件,比如串口状态变化事件,电话状态改变等等
USB请求在USB器件库文档中分为三类:标准请求、特定类请求、特定厂商请求。其中标准请求由默认的控制端点0接收处理,另外两类请求由回调函数传递到特定类代码进行处理,这里的CDC_Itf_Control()函数即为回调函数最终实现者,其处理CDC类的特定请求,这些请求可以在PSTN120协议文档以及CDC120协议文档中找到。
函数形参:
形参1 cmd为控制命令,PSTN定义了三种模型:DLM(Direct Line Mode),ACM(Abstract Control Model)和TCM(Telephone Control Model),STM32的CDC设备采用的是抽象控制模型(ACM:Abstract Control Model),对这此控制指令的格式进行了定义,这些请求可以在PSTN120协议文档以及CDC120协议文档中找到,但能否实现还需要主机支持。ST文档《USB CDC类入门培训》也有对这些类进行描述,这里我们就不列举了,大家看代码实现即可。
形参2 pbuf用于需要传递数据的控制命令时使用。
形参3 length表示控制命令的传递过来的数据按字节计的数量。

/**
 * @brief       控制 CDC 的设置
 * @param       cmd     : 控制命令
 * @param       buf     : 命令数据缓冲区/参数保存缓冲区
 * @param       length  : 数据长度
 * @retval      USB状态
 * @arg         USBD_OK(0)   , 正常;
 * @arg         USBD_BUSY(1) , 忙;
 * @arg         USBD_FAIL(2) , 失败;
 */
static int8_t CDC_Itf_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length)
{
    switch (cmd)
    {
        case CDC_SEND_ENCAPSULATED_COMMAND:
            break;
        case CDC_GET_ENCAPSULATED_RESPONSE:
            break;
        case CDC_SET_COMM_FEATURE:
            break;
        case CDC_GET_COMM_FEATURE:
            break;
        case CDC_CLEAR_COMM_FEATURE:
            break;
        case CDC_SET_LINE_CODING:
            LineCoding.bitrate = (uint32_t) (pbuf[0] | (pbuf[1] << 8) |
                                             (pbuf[2] << 16) | (pbuf[3] << 24));
            LineCoding.format = pbuf[4];
            LineCoding.paritytype = pbuf[5];
            LineCoding.datatype = pbuf[6];
            /* 打印配置参数 */
            printf("linecoding.format:%d\r\n", LineCoding.format);
            printf("linecoding.paritytype:%d\r\n", LineCoding.paritytype);
            printf("linecoding.datatype:%d\r\n", LineCoding.datatype);
            printf("linecoding.bitrate:%d\r\n", LineCoding.bitrate);
            break;
        case CDC_GET_LINE_CODING:
            pbuf[0] = (uint8_t) (LineCoding.bitrate);
            pbuf[1] = (uint8_t) (LineCoding.bitrate >> 8);
            pbuf[2] = (uint8_t) (LineCoding.bitrate >> 16);
            pbuf[3] = (uint8_t) (LineCoding.bitrate >> 24);
            pbuf[4] = LineCoding.format;
            pbuf[5] = LineCoding.paritytype;
            pbuf[6] = LineCoding.datatype;
            break;
        case CDC_SET_CONTROL_LINE_STATE:
            break;
        case CDC_SEND_BREAK:
            break;
        default:
            break;
    }
    return USBD_OK;
}

函数返回值:
返回操作结果:0(USBD_OK): 成功, 其它 : 错误或忙等状态。
4. CDC_Itf_Receive函数
CDC_Itf_Receive用于CDC数据接收处理,我们虚拟串口的数据接收通过这个接口实现。
static int8_t CDC_Itf_Receive(uint8_t *buf, uint32_t *len)
函数描述:
初始化指定编号的磁盘,磁盘所指定的存储区。
函数形参:
形参1 buf是接收数据缓冲,我们收到的数据存放在这个缓冲中。
形参2为按字节计从虚拟串口接收到的数据。

/**
 * @brief       CDC 数据接收函数
 * @param       buf     : 接收数据缓冲区
 * @param       len     : 接收到的数据长度
 * @retval      USB状态
 * @arg         USBD_OK(0)   , 正常;
 * @arg         USBD_BUSY(1) , 忙;
 * @arg         USBD_FAIL(2) , 失败;
 */
static int8_t CDC_Itf_Receive(uint8_t *buf, uint32_t *len)
{
    USBD_CDC_ReceivePacket(&USBD_Device);
    cdc_vcp_data_rx(buf, *len);
    return USBD_OK;
}

上面的cdc_vcp_data_rx(buf, *len)函数为我们编写的虚拟串口缓冲数据处理接收函数,写法与我们的串口实验相同,以全局g_usb_usart_rx_sta为接收标记和数据计数器,以“\r\n”为结束符,大家参考光盘中的代码即可。
函数返回值:
返回操作结果:0(USBD_OK): 成功, 其它 : 错误或忙等状态。
5. cdc_vcp_data_tx函数
cdc_vcp_data_tx实现USB对物理设备的读操作,其声明如下:
void cdc_vcp_data_tx(uint8_t *data, uint32_t Len)
函数描述:
用于STM32向虚拟串口发送数据。
函数形参:
形参1 data为接收的数据指针。
形参2 Len同为为按字节计的数据长度。
代码实现如下:

/**
 * @brief       通过 USB 发送数据
 * @param       buf     : 要发送的数据缓冲区
 * @param       len     : 数据长度
 * @retval      无
 */
void cdc_vcp_data_tx(uint8_t *data, uint32_t Len)
{
    USBD_CDC_SetTxBuffer(&USBD_Device, data, Len);
    USBD_CDC_TransmitPacket(&USBD_Device);
    delay_ms(CDC_POLLING_INTERVAL);
}

为了方便实现打参数格式化和测试打印,我们编写了usb_printf这个接口用于实际的写测试,这是一个变参函数,与printf的功能类似, 本质上还是调用 cdc_vcp_data_tx接口,主要还是调用了USBD下的CDC类的发送操作接口,大家参考我们的源代码即可。
函数返回值:
无。
usbd_cdc的代码就讲到这里,usbd_conf.c的配置和USB读卡器的章节一致,我们就不重复了。
2. main.c代码
在main.c就比较简单了,按照我们的流程图的思路编写即可,我们初始化探按键,LCD和LED、SRAM等外设辅助程序显示,同样地在while循环之前调用我们之前编写的usbd_port_config接口以保证每次复位后USB都能重连成功。大家也可以尝试其它的方法。
最后,我们编写的main函数如下:

USBD_HandleTypeDef USBD_Device;             	/* USB Device处理结构体 */
extern volatile uint8_t g_device_state;     /* USB连接 情况 */

int main(void)
{
    uint16_t len;
    uint16_t times = 0;
    uint8_t usbstatus = 0;
    HAL_Init();                         		/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9); 	/* 设置时钟, 72Mhz */
    delay_init(72);                     		/* 延时初始化 */
    usart_init(115200);                 		/* 串口初始化为115200 */
    led_init();                         		/* 初始化LED */
    lcd_init();                         		/* 初始化LCD */
    sram_init();                        		/* SRAM初始化 */
    my_mem_init(SRAMIN);                		/* 初始化内部SRAM内存池 */
    my_mem_init(SRAMEX);                		/* 初始化外部SRAM内存池 */
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "USB Virtual USART TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
/* 提示USB开始连接 */
lcd_show_string(30, 110, 200, 16, 16, "USB Connecting...", RED); 
    usbd_port_config(0);    /* USB先断开 */
    delay_ms(500);
    usbd_port_config(1);    /* USB再次连接 */
    delay_ms(500);
    USBD_Init(&USBD_Device, &VCP_Desc, 0);
    USBD_RegisterClass(&USBD_Device, USBD_CDC_CLASS);
    USBD_CDC_RegisterInterface(&USBD_Device, &USBD_CDC_fops);
    USBD_Start(&USBD_Device);
    while (1)
    {
        if (usbstatus != g_device_state)   		/* USB连接状态发生了改变 */
        {
            usbstatus = g_device_state; 			/* 记录新的状态 */
            if (usbstatus == 1)
{/* 提示USB连接成功 */
lcd_show_string(30, 110, 200, 16, 16, "USB Connected    ", RED);
             LED1(0);    							/* 绿灯亮 */
            }
            else
            {/* 提示USB断开 */
lcd_show_string(30, 110, 200, 16, 16, "USB disConnected ", RED); 
              LED1(1);    							/* 绿灯灭 */
            }
        }
        if (g_usb_usart_rx_sta & 0x8000)
        {
            len = g_usb_usart_rx_sta & 0x3FFF;  	/* 得到此次接收到的数据长度 */
            usb_printf("\r\n您发送的消息长度为:%d\r\n\r\n", len);
            cdc_vcp_data_tx(g_usb_usart_rx_buffer, len);;
            usb_printf("\r\n\r\n");				/* 插入换行 */
            g_usb_usart_rx_sta = 0;
        }
        else
        {
            times++;
            if (times % 5000 == 0)
            {
                usb_printf("\r\nSTM32开发板USB虚拟串口实验\r\n");
                usb_printf("正点原子@ALIENTEK\r\n\r\n");
            }
            if (times % 200 == 0)usb_printf("请输入数据,以回车键结束\r\n");
            if (times % 30 == 0)
            {
                LED0_TOGGLE();  /* 闪烁LED,提示系统正在运行 */
            }
            delay_ms(10);
        }
    }
}

61.4 下载验证
本例程的测试,需要在电脑上先安装ST提供的USB虚拟串口驱动软件,该软件可以在STM32论坛搜索“STM32 Virtual COM Port Driver”获取最新的虚拟串口版本。我们光盘资料提供了V1.5版本的压缩包,位于A盘6,软件资料1,软件STM32 USB虚拟串口驱动en.stsw-stm32102.zip,我们解压这个文件可以看到有多个版本的软件可选,如图61.4.1所示:

图61.4.1选择适合自己电脑的软件版本安装
由于我们电脑是系统是Win10 64位的系统,所以我们安装win7以后的版本,这里我们选择VCP_V1.5.0_Setup_W8_x64_64bits.exe这个版本,安装过程比较简单,我们填写一些简单的信息,一直点下一步安装到默认路径即可,安装完成后界面如图61.4.2所示:

图61.4.2 STM32 VCOM驱动安装完成
安装完驱动后我们就可以开始实验了。在代码编译成功之后,我们下载代码到STM32开发板上,然后将USB数据线,插入USB_SLAVE口(此时要检查P9排针是处的PA11、PA12是否与USB_D+、USB_D-正确连接),通过USB_Slave连接电脑和开发板,此时电脑会提示找到新硬件,并自动安装驱动。不过,如果自动安装不成功(有惊叹号),如图63.4.3所示:

图61.4.3自动安装驱动失败
此时,我们可手动选择驱动(以WIN7为例),进行安装,在如图61.4.3所示的条目上面,右键→更新驱动程序软件→浏览计算机以查找驱动程序软件→浏览,选择STM32虚拟串口的驱动的路径为:C:\ProgramFiles(x86)\STMicroelectronics\Software \Virtual comport driver,上述的文件夹下同样有对应对应的WIN7和WIN8版本,如果是WIN7以后的windows版本我们选择WIN8就可以了。然后点击下一步,即可完成安装。安装完成后,可以看到设备管理器里面多出了一个STM32的虚拟串口,这时我们就可以开始正常测试了,效果如图63.4.4。

图63.4.4 发现STM32USB虚拟串口
如图63.4.4,STM32通过USB虚拟的串口,被电脑识别了,端口号为:COM7(不同电脑有差异),字符串名字为:STMicroelectronics Virtual COM Port(固定)。此时,开发板的DS1常亮,同时,开发板的LCD显示USBConnected,如图63.4.5所示:

图63.4.5 USB虚拟串口连接成功
正确安装好虚拟串口驱动后,我们打开XCOM进行测试,选择电脑识别到的虚拟串口号,我们这里是COM7(需根据自己的电脑识别到的串口号选择),并打开串口(注意:波特率可以随意设置),我们例程的代码以接收到的数据中的回车换行为接收结束符,故测试时串口助手必须勾选:发送新行,STM32才会把收到的数据回发给USB口,测试结果如图63.4.6所示:

图63.4.6 STM32虚拟串口通信测试
可以看到,我们的串口调试助手,收到了来自STM32开发板的数据,同时,按发送按钮(串口助手必须勾选:发送新行),也可以收到电脑发送给STM32的数据(原样返回),说明我们的实验是成功的。实验现象同串口通讯实验完全一样。
至此,USB虚拟串口实验就完成了,通过本实验,我们就可以利用STM32的USB,直接和电脑进行数据互传了,具有广泛的应用前景。

物联沃分享整理
物联沃-IOTWORD物联网 » 正点原子STM32连载第61章:USB虚拟串口实验摘要

发表评论