OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 76|回复: 0

《STM32H7R7开发指南 V1.1 》第七十章 USB虚拟串口(Slave)实验

[复制链接]

1361

主题

1377

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5812
金钱
5812
注册时间
2019-5-8
在线时间
1597 小时
发表于 昨天 09:28 | 显示全部楼层 |阅读模式
第七十章 USB虚拟串口(Slave)实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

本章,我们将向大家介绍如何利用USB在开发板实现一个USB虚拟串口,通过USB与电脑数据数据交互。
本章分为如下几个小节:
70.1 USB虚拟串口简介
70.2 硬件设计
70.3 程序设计
70.4 下载验证


70.1 USB虚拟串口简介
USB虚拟串口,简称VCP,是Virtual COM Port的简写,它是利用USB的CDC类来实现的一种通信接口。
我们可以利用STM32自带的USB功能,来实现一个USB虚拟串口,从而通过USB,实现电脑与STM32的数据互传。上位机无需编写专门的USB程序,只需要一个串口调试助手即可调试,非常实用。
同上一章一样,我们直接移植官方的USB VCP例程,官方例程路径:8,STM32参考资料→1,STM32CubeH7RS固件包→STM32Cube_FW_H7RS_V1.0.0→Projects  STM32H7S78-DK→Applications→USB_Host→CDC_Standalone,该例程采用USB CDC类来实现,利用STM32的USB接口,实现一个USB转串口的功能。


70.2 硬件设计

1. 例程功能
本实验利用STM32自带的USB功能,连接电脑USB,虚拟出一个USB串口,实现电脑和开发板的数据通信。本例程功能完全同实验4(串口通信实验),只不过串口变成了STM32的USB虚拟串口。当USB连接电脑(USB线插入USB_SLAVE接口),开发板将通过USB和电脑建立连接,并虚拟出一个串口(注意:需要先安装:光盘\6,软件资料\1,软件\STM32 USB虚拟串口驱动\VCP_V1.5.0_Setup.exe这个驱动软件,虚拟串口驱动我们还可以在论坛上下载,链接是:http://www.openedv.com/thread-284178-1-1.html)。
LED0闪烁,提示程序运行。USB和电脑连接成功后,LED1常亮。

2. 硬件资源
1)LED灯
       LED0 :LED0 – PD14
       LED1 :LED1 – PC0
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)USB_SLAVE接口(D-/D+连接在PM5/PM6上)


70.3 程序设计

70.3.1 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
本实验,在上一个实验的基础上,把不需要的文件从工程中移除,并对照官方VCP例子,将相关文件拷贝到USB文件夹下。然后,添加USB相关代码到工程中,由于官方提供的USB_APP文件不太好用,我们正点原子团队直接编写了一套USB_APP代码,读者可以直接拷贝我们例程中的USB_APP文件夹,即:实验59 USB虚拟串口(Slave)实验MiddlewaresUSBUSB_APP。
最终得到如图70.3.2.1所示的工程:


第七十章 USB虚拟串口1356.png
图70.3.2.1 USB虚拟串口工程分组

注意:因为USB驱动库需要用到内存管理,因此我们保留了相关代码(malloc.c)。

1. USB驱动代码
可以看到,USB部分代码,同上一个实验的在结构上是一模一样的,只是.c文件稍微有些变化。同样,我们移植需要修改以下这几个文件的代码。
usbd_conf.c代码,和上一个实验一样,不需要修改,可以直接使用上一个实验的代码。
usbd_desc.c代码,同上一个实验不一样,上一个实验描述符是USB Audio设备,本实验变成了USB虚拟串口了(CDC),所以直接用ST官方的就行。
  1. usbd_cdc.c代码,这个文件是复制自官方VCP例子中,在约561行处需要修改,增加条件判断不为空时才执行,否则会出现卡死。[/size]
  2.     if (((USBD_CDC_ItfTypeDef *)pdev->pUserData)->TransmitCplt != NULL)
  3.     {
  4.       ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->TransmitCplt(hcdc->TxBuffer,
  5.                                                       &hcdc->TxLength, epnum);
  6.     }
复制代码
usbd_cdc_if.c代码,是重点要修改的,直接复制使用即可。首先我们介绍下usbd_cdc_if.h文件的相关宏定义,具体如下:
  1. /* 缓冲区大小定义 */
  2. #define USBD_CDC_RX_BUF_SIZE        200     
  3. #define USBD_CDC_PRINTF_BUF_SIZE    200      
  4. #define USBD_CDC_TX_TIMROUT         10      
复制代码
USBD_CDC_RX_BUF_SIZE宏定义是用于定义USB串口接收缓冲区最大字节数,这里设置为200。USBD_CDC_TX_TIMROUT宏定义是用于定义USB发送数据轮询周期,这里设置为10即可。
下面重点介绍usbd_cdc_if.c文件,首先是一些结构体变量、数组和变量的定义,具体如下:

  1. /* USB虚拟串口相关配置参数 */
  2. USBD_CDC_LineCodingTypeDef linecoding =
  3. {
  4.     115200,     /* 波特率 */
  5.     0x00,       /* 停止位,默认1位 */
  6.     0x00,       /* 校验位,默认无 */
  7.     0x08        /* 数据位,默认8位 */
  8. };

  9. /* usb_printf发送缓冲区, 用于vsprintf */
  10. static uint8_t g_usbd_cdc_printf_buffer[USBD_CDC_PRINTF_BUF_SIZE];

  11. /* USB接收的数据缓冲区,最大USART_REC_LEN个字节,用于USBD_CDC_SetRxBuffer函数 */
  12. uint8_t g_usbd_cdc_rx_buffer[USBD_CDC_RX_BUF_SIZE];

  13. uint16_t g_usbd_cdc_rx_sta=0;  /* 接收状态标记 */

  14. /* 虚拟串口配置函数(供USB内核调用) */
  15. USBD_CDC_ItfTypeDef USBD_CDC_fops =
  16. {
  17.     CDC_Init,
  18.     CDC_DeInit,
  19.     CDC_Control,
  20. CDC_Receive,
  21. CDC_TransmitCplt
  22. };
复制代码
首先是定义一个USBD_CDC_LineCodingTypeDef结构体类型的变量linecoding,并赋值。波特率为115200,停止位和校验位都为0,数据位,默认8位。
g_usbd_cdc_printf_buffer是发送缓冲区,大小由USBD_CDC_PRINTF_BUF_SIZE宏来定义,数组是uint8_t类型,所以数字大小为200字节。
g_usbd_cdc_rx_buffer则是USB接收的数据缓冲区,用于USBD_CDC_BUF_SIZE函数,大小也是200字节。
g_usbd_cdc_rx_sta变量用于表示接收状态,位15表示接收完成标志,位14表示接收到0x0d,位13~位0表示接收到的有效字节数目。
最后定义一个USBD_CDC_ItfTypeDef结构体类型的变量USBD_CDC_fops,供USB内核调用,并把四个函数的首地址赋值给其成员。下面会介绍到这几个函数,以及一些其它的函数。
首先是初始化CDC函数,其定义如下:

  1. /**
  2. * @brief   初始化CDC设备
  3. * [url=home.php?mod=space&uid=271674]@param[/url]   无
  4. * @retval  操作结果
  5. * @arg     USBD_OK: 成功
  6. * @arg     USBD_FAIL: 失败
  7. */
  8. static int8_t CDC_Init(void)
  9. {
  10.     USBD_CDC_SetRxBuffer(&g_usbd_handle, g_usbd_cdc_rx_buffer);
  11.    
  12.     return USBD_OK;
  13. }
复制代码
CDC_Init用于初始化VCP,在初始化的时候由USB内核调用,这里我们调用函数:USBD_CDC_SetRxBuffer,设置USB接收数据缓冲区。USB虚拟串口收到的数据,会先缓存在这个buf里面。
下面介绍的是复位CDC函数,其定义如下:

  1. /**
  2. * @brief   反初始化CDC设备
  3. * @param   无
  4. * @retval  操作结果
  5. * @arg     USBD_OK: 成功
  6. * @arg     USBD_FAIL: 失败
  7. */
  8. static int8_t CDC_DeInit(void)
  9. {
  10.     return USBD_OK;
  11. }
复制代码
CDC_DeInit用于复位VCP,我们用不到,所以直接返回USBD_OK即可。
下面介绍的是控制CDC的设置函数,其定义如下:

  1. /**
  2. * @brief   管理CDC请求
  3. * @param   cmd: 命令
  4. * @param   pbuf: 数据
  5. * @param   length: 数据长度
  6. * @retval  操作结果
  7. * @arg     USBD_OK: 成功
  8. * @arg     USBD_FAIL: 失败
  9. */
  10. static int8_t CDC_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length)
  11. {
  12.     UNUSED(length);
  13.    
  14.     switch (cmd)
  15.     {
  16.         case CDC_SEND_ENCAPSULATED_COMMAND:
  17.         case CDC_GET_ENCAPSULATED_RESPONSE:
  18.         case CDC_SET_COMM_FEATURE:
  19.         case CDC_GET_COMM_FEATURE:
  20.         case CDC_CLEAR_COMM_FEATURE:
  21.         case CDC_SET_CONTROL_LINE_STATE:
  22.         case CDC_SEND_BREAK:
  23.         default:
  24.         {
  25.             break;
  26.         }
  27.         case CDC_SET_LINE_CODING:
  28.         {
  29.             linecoding.bitrate = (uint32_t)(pbuf[0] | (pbuf[1] << 8) |
  30. (pbuf[2] << 16) | (pbuf[3] << 24));
  31.             linecoding.format = pbuf[4];
  32.             linecoding.paritytype = pbuf[5];
  33.             linecoding.datatype = pbuf[6];
  34.             break;
  35.         }
  36.         case CDC_GET_LINE_CODING:
  37.         {
  38.             pbuf[0] = (uint8_t)(linecoding.bitrate);
  39.             pbuf[1] = (uint8_t)(linecoding.bitrate >> 8);
  40.             pbuf[2] = (uint8_t)(linecoding.bitrate >> 16);
  41.             pbuf[3] = (uint8_t)(linecoding.bitrate >> 24);
  42.             pbuf[4] = linecoding.format;
  43.             pbuf[5] = linecoding.paritytype;
  44.             pbuf[6] = linecoding.datatype;
  45.             break;
  46.         }
  47.     }
  48.    
  49.     return USBD_OK;
  50. }
复制代码
CDC_Control用于控制VCP的相关参数,根据cmd的不同,执行不同的操作,这里主要用到CDC_SET_LINE_CODING命令,该命令用于设置VCP的相关参数,比如波特率、数据类型(位数)、校验类型(奇偶校验)等,保存在linecoding结构体里面,在需要的时候,应用程序可以读取linecoding结构体里面的参数,以获得当前VCP的相关信息。
下面介绍的是CDC数据接收函数和处理从USB虚拟串口接收到的数据函数,它们的定义如下:

  1. /**
  2. * @brief   CDC接收
  3. * @param   Buf: 数据
  4. * @param   Len: 数据长度
  5. * @retval  操作结果
  6. * @arg     USBD_OK: 成功
  7. * @arg     USBD_FAIL: 失败
  8. */
  9. static int8_t CDC_Receive(uint8_t *Buf, uint32_t *Len)
  10. {
  11.     uint32_t i;
  12.    
  13.     USBD_CDC_ReceivePacket(&g_usbd_handle);
  14.     for (i=0; i<*Len; i++)
  15.     {
  16.         if((g_usbd_cdc_rx_sta & (1 << 15)) != (1 << 15))
  17.         {
  18.             if((g_usbd_cdc_rx_sta & (1 << 14)) != (1 << 14))
  19.             {
  20.                 if(Buf[i] == 0x0D)
  21.                 {
  22.                     g_usbd_cdc_rx_sta |= (1 << 14);
  23.                 }
  24.                 else
  25.                 {
  26.                     if(++g_usbd_cdc_rx_sta > (USBD_CDC_RX_BUF_SIZE - 1))
  27.                     {
  28.                         g_usbd_cdc_rx_sta = 0;
  29.                     }
  30.                 }
  31.             }
  32.             else
  33.             {
  34.                 if(Buf[i] != 0x0A)
  35.                 {
  36.                     g_usbd_cdc_rx_sta = 0;
  37.                 }
  38.                 else
  39.                 {
  40.                     g_usbd_cdc_rx_sta |= (1 << 15);
  41.                 }
  42.             }
  43.         }
  44.     }
  45.    
  46.     return USBD_OK;
  47. }

  48. /**
  49. * @brief   CDC接收
  50. * @param   Buf: 数据
  51. * @param   Len: 数据长度
  52. * @retval  操作结果
  53. * @arg     USBD_OK: 成功
  54. * @arg     USBD_FAIL: 失败
  55. */
  56. static int8_t CDC_Receive(uint8_t *Buf, uint32_t *Len)
  57. {
  58.     uint32_t i;
  59.    
  60.     USBD_CDC_ReceivePacket(&g_usbd_handle);
  61.     for (i=0; i<*Len; i++)
  62.     {
  63.         if((g_usbd_cdc_rx_sta & (1 << 15)) != (1 << 15))
  64.         {
  65.             if((g_usbd_cdc_rx_sta & (1 << 14)) != (1 << 14))
  66.             {
  67.                 if(Buf[i] == 0x0D)
  68.                 {
  69.                     g_usbd_cdc_rx_sta |= (1 << 14);
  70.                 }
  71.                 else
  72.                 {
  73.                     if(++g_usbd_cdc_rx_sta > (USBD_CDC_RX_BUF_SIZE - 1))
  74.                     {
  75.                         g_usbd_cdc_rx_sta = 0;
  76.                     }
  77.                 }
  78.             }
  79.             else
  80.             {
  81.                 if(Buf[i] != 0x0A)
  82.                 {
  83.                     g_usbd_cdc_rx_sta = 0;
  84.                 }
  85.                 else
  86.                 {
  87.                     g_usbd_cdc_rx_sta |= (1 << 15);
  88.                 }
  89.             }
  90.         }
  91.     }
  92.    
  93.     return USBD_OK;
  94. }
复制代码
CDC_Receive函数用于VCP数据接收,当STM32的USB接收到电脑端串口发送过来的数据时,由USB内核程序调用CDC_Receive,然后在该函数里面再调用USBD_CDC_ReceivePacket函数,实现VCP的数据接收,只需要在该函数里面,将接收到的数据,保存起来即可,接收的原理和(实验5 串口通信实验)完全一样。
下面介绍的是通过USB发送数据函数,其定义如下:

  1. /**
  2. * @brief   CDC发送完成
  3. * @param   Buf: 数据
  4. * @param   Len: 数据长度
  5. * @param   epnum: 端点编号
  6. * @retval  操作结果
  7. * @arg     USBD_OK: 成功
  8. * @arg     USBD_FAIL: 失败
  9. */
  10. static int8_t CDC_TransmitCplt(uint8_t *Buf, uint32_t *Len, uint8_t epnum)
  11. {
  12.     UNUSED(Buf);
  13.     UNUSED(Len);
  14.     UNUSED(epnum);
  15.    
  16.     g_usbd_cdc_tx_done = 1;
  17.    
  18.     return USBD_OK;
  19. }
复制代码
CDC_TransmitCplt用于发送Len个字节的数据给VCP,由VCP通过USB传输给电脑,实现VCP的数据发送。
下面介绍的是通过USB格式化输出函数,其定义如下:

  1. void usb_printf(char *fmt, ...)
  2. {
  3.     va_list ap;
  4.    
  5.     va_start(ap, fmt);
  6.     vsprintf((char *)g_usbd_cdc_printf_buffer, fmt, ap);
  7.     va_end(ap);
  8. usbd_cdc_transmit(g_usbd_cdc_printf_buffer, strlen((const char
  9. *)g_usbd_cdc_printf_buffer));
  10. }
复制代码
usb_printf用于实现和普通串口一样的printf操作,该函数将数据格式化输出到USB VCP,功能完全同printf,方便大家使用。
USB VCP相关代码,就给大家介绍到这里,详细的介绍,请大家参考:UM1734(STM32Cube USB device library).pdf这个文档。

2. main.c代码
下面是main.c的程序,具体如下:

  1. /* USBD句柄 */
  2. /* USBD句柄 */
  3. USBD_HandleTypeDef g_usbd_handle = {0};

  4. int main(void)
  5. {
  6.     uint16_t len;
  7.     uint16_t times = 0;
  8.     uint8_t conn_sta;
  9.    
  10.     sys_mpu_config();                   /* 配置MPU */
  11.     sys_cache_enable();                 /* 使能Cache */
  12.     HAL_Init();                         /* 初始化HAL库 */
  13.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  14.     delay_init(600);                    /* 初始化延时 */
  15.     usart_init(115200);                 /* 初始化串口 */
  16.     led_init();                         /* 初始化LED */
  17.     hyperram_init();                    /* 初始化HyperRAM */
  18.     lcd_init();                         /* 初始化LCD */
  19.     my_mem_init(SRAMIN);                /* 初始化AXI-SRAM1~4内存池 */
  20.     my_mem_init(SRAMEX);                /* 初始化XSPI2 HyperRAM内存池 */
  21.     my_mem_init(SRAM12);                /* 初始化AHB-SRAM1~2内存池 */
  22.     my_mem_init(SRAMDTCM);              /* 初始化DTCM内存池 */
  23.     my_mem_init(SRAMITCM);              /* 初始化ITCM内存池 */
  24.    
  25.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
  26.     lcd_show_string(30, 70, 200, 16, 16, "USB VCP TEST", RED);
  27.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
  28.    
  29.     USBD_Init(&g_usbd_handle, &CDC_Desc, DEVICE_HS);
  30.     USBD_RegisterClass(&g_usbd_handle, USBD_CDC_CLASS);
  31.     USBD_CDC_RegisterInterface(&g_usbd_handle, &USBD_CDC_fops);
  32.     USBD_Start(&g_usbd_handle);
  33.     delay_ms(1000);
  34.    
  35.     while (1)
  36.     {
  37.         if (conn_sta != g_usb_conn_state)
  38.         {
  39.             conn_sta = g_usb_conn_state;
  40.             
  41.             if (g_usb_conn_state != 0)
  42.             {
  43.                 lcd_show_string(30, 190, 200, 16, 16, "USB Connected    ", RED);
  44.             }
  45.             else
  46.             {
  47.                 lcd_show_string(30, 190, 200, 16, 16, "USB Disconnected ", RED);
  48.             }
  49.         }
  50.         
  51.         if ((g_usbd_cdc_rx_sta & (1 << 15)) == (1 << 15))
  52.         {
  53.             len = g_usbd_cdc_rx_sta & 0x3FFF;
  54.             usb_printf("\r\n您发送的消息为:\r\n");
  55.             usbd_cdc_transmit(g_usbd_cdc_rx_buffer, len);
  56.             usb_printf("\r\n\r\n");
  57.             g_usbd_cdc_rx_sta = 0;
  58.         }
  59.         else
  60.         {
  61.             if ((times % 5000) == 0)
  62.             {
  63.                 usb_printf("\r\n正点原子 H7R7开发板 USB虚拟串口(Slave)实验\r\n");
  64.                 usb_printf("正点原子@ALIENTEK\r\n\r\n\r\n");
  65.             }
  66.             if ((times % 200) == 0)
  67.             {
  68.                 usb_printf("请输入数据,以回车键结束\r\n");
  69.             }
  70.             if ((times % 30) == 0)
  71.             {
  72.                 LED0_TOGGLE();
  73.             }
  74.             times++;
  75.             delay_ms(10);
  76.         }
  77.     }
  78. }
复制代码
此部分代码比较简单,首先通过USBD_Init等函数初始化USB,不过本章实现的是USB虚拟串口的功能。然后在死循环里面轮询USB状态并检查是否接收到数据,如果接收到了数据,则通过usbd_cdc_transmit将数据通过VCP原原本本的返回给电脑端串口调试助手。

70.4 下载验证
本例程的测试,需要在电脑上先安装ST提供的USB虚拟串口驱动软件,该软件(V1.5.0版)下载地址:http://www.openedv.com/thread-284178-1-1.html,下载完以后,根据自己电脑的系统,选择合适的驱动安装即可。
将程序下载到开发板后(注意:USB数据线,要插在USB_SLAVE口!而不是USB_UART端口!),我们打开设备管理器(我用的是WIN10),在端口(COM和LPT)里面可以发现多出了一个COM4的设备,这就是USB虚拟的串口设备端口,如图70.4.1所示:


第七十章 USB虚拟串口11814.png
图70.4.1 通过设备管理器查看USB虚拟的串口设备端口

如图70.4.1,STM32通过USB虚拟的串口,被电脑识别了,端口号为:COM4(可变),字符串名字为:STMicroelectronics Virtual COM Port(COM4)。此时, LED0在闪烁,提示程序运行。开发板的LCD显示USB Connected,如图70.4.2所示:

第七十章 USB虚拟串口11996.png
图70.4.2 USB虚拟串口连接成功

然后我们打开XCOM,选择合适的COM(需根据自己的电脑识别到的串口号选择),并打开串口(注意:波特率可以随意设置),就可以进行测试了,如图70.4.3所示:

第七十章 USB虚拟串口12098.png
图70.4.3 STM32虚拟串口通信测试

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

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-7-7 09:44

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表