STM32 USB CDC VPC

这篇具有很好参考价值的文章主要介绍了STM32 USB CDC VPC。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

STM32 USB CDC VPC

关键字

STM32,STM32CubeMX,HAL库,USB,虚拟串口,串口不定长接收

1.简介

通过使用stm32cubemx,实现USB CDC虚拟串口,并与硬件串口进行数据传输,实现了硬件串口数据的不定长接收,以及USB虚拟串口超过64字节的数据接收,最终实现了一个简单的USB转串口功能。

使用USB的CDC类来虚拟出一个串口与电脑进行通信,可以省去硬件转换电路,同时由于通信使用USB,速度比硬件串口快。ST针对使用CDC虚拟串口有非常完备的代码支持,几乎是到手即用,本文简单介绍一下如何快速使用USB CDC虚拟串口。

2.使用CubeMX生成工程

本次使用的芯片为STM32F407VET6

首先配置好时钟,debug接口,然后使能测试用的硬件串口,使能串口的接收和发送dma,全部默认即可。

usb cdc,STM32,stm32,单片机,嵌入式硬件注意一定要打开串口的全局中断,否则串口收发会出问题。

usb cdc,STM32,stm32,单片机,嵌入式硬件然后开启USB

usb cdc,STM32,stm32,单片机,嵌入式硬件USB分为全速和高速两种模式,一般芯片只支持全速模式,高速模式需要外接PHY芯片才能实现,全速模式的最高理论速度是12Mbit/s,高速模式的最高理论速度是480Mbit/s,这里只使用了全速模式。

然后打开中间件的选项,选择USB驱动,在USB类选项里面选择CDC类,虚拟串口。

usb cdc,STM32,stm32,单片机,嵌入式硬件此时直接生成工程运行,连接电脑就可以检测到虚拟出来的串口(我使用的是win11,旧版本的系统可能需要装驱动才能检测到)。

usb cdc,STM32,stm32,单片机,嵌入式硬件

这个串口就是USB虚拟出来的串口。

3.代码分析

简单使用虚拟串口需要更改的只有这一个文件

usb cdc,STM32,stm32,单片机,嵌入式硬件

主要只涉及这4个函数

usb cdc,STM32,stm32,单片机,嵌入式硬件

int8_t CDC_Control_FS(uint8_t cmd, uint8_t *pbuf, uint16_t length)​是主机进行一些控制的回调函数

int8_t CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)​是完成一个USB包接收的回调函数

uint8_t CDC_Transmit_FS(uint8_t *Buf, uint16_t Len)​是进行USB发送的函数

static int8_t CDC_TransmitCplt_FS(uint8_t *Buf, uint32_t *Len, uint8_t epnum)​是USB发送完成的回调函数

需要注意的是CDC_Receive_FS​是一个回调函数,从机接收USB数据不需要启动,在USB初始化之后就会自动接收,一个包接收完成就会自动调用这个函数,因此需要在这个函数中实现对接收到的数据的处理。

3.1 USB接收代码

进行USB数据的接收需要改写CDC_Receive_FS​函数。

定义的全局变量

uint8_t Rx_Buffer[Rx_Buffer_Len];
__IO uint8_t DataReceive_Flag;
uint32_t SinglePackLength;
uint8_t *p_TempBuf;
uint8_t Num_Packet;
uint32_t Rx_Data_Len;

文章来源地址https://www.toymoban.com/news/detail-616885.html

/**
 * @brief  Data received over USB OUT endpoint are sent over CDC interface
 *         through this function.
 *
 *         @note
 *         This function will issue a NAK packet on any OUT packet received on
 *         USB endpoint until exiting this function. If you exit this function
 *         before transfer is complete on CDC interface (ie. using DMA controller)
 *         it will result in receiving more data while previous ones are still
 *         not sent.
 *
 * @param  Buf: Buffer of data to be received
 * @param  Len: Number of data received (in bytes)
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t CDC_Receive_FS(uint8_t *Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);
  p_TempBuf = Buf;
  SinglePackLength = *Len;
  if (SinglePackLength == CDC_DATA_FS_MAX_PACKET_SIZE)
  {
    memcpy((Rx_Buffer + (CDC_DATA_FS_MAX_PACKET_SIZE * Num_Packet)), Buf, SinglePackLength);
    Rx_Data_Len = (CDC_DATA_FS_MAX_PACKET_SIZE * Num_Packet) + SinglePackLength;
    Num_Packet++;
  }
  else
  {
    memcpy((Rx_Buffer + (CDC_DATA_FS_MAX_PACKET_SIZE * Num_Packet)), Buf, SinglePackLength);
    Rx_Data_Len = (CDC_DATA_FS_MAX_PACKET_SIZE * Num_Packet) + SinglePackLength;
    DataReceive_Flag = 1;
    Num_Packet = 0;
  }

  return (USBD_OK);
  /* USER CODE END 6 */
}

进行USB接收数据的处理需要了解到一个USB通信的特性,全速USB一个包最大只能有64字节数据,而高速USB一个包可以有512字节。可以从usbd_cdc.h​文件中查看到定义。

usb cdc,STM32,stm32,单片机,嵌入式硬件在接收到一个数据包之后就会调用一下CDC_Receive_FS​函数,如果主机发送过来的数据没有超过64字节,那么一切正常,可以在回调函数中获得数据的缓存位置以及数据长度,但是如果主机发送的数据长度大于64字节,就会分成多个包发送过来,而回调函数还是一个包调用一次,如果每次回调都认为传输结束,就会丢掉后面的数据,所以必须进行处理。

如果接收到的包长度正好是64字节,那么认为后面还会有包传输过来,不产生接收完成标志,同时将接收到的数据复制到数据缓存,如果接收到的数据包长度不是64,那么认为数据接收完成,产生完成标志,复制数据到缓存区。主程序判断接收完成后,进行下一步的处理。

上述的操作基本可以完成数据的接收,但是有一个问题,如果主机发送的数据刚刚好就是64字节,那么就会一直等待下一个不满64字节的包,无法产生接收完成标志。解决这个问题可以加一个接收延时判断,例如,超过1ms没有数据包接收就认为数据接收结束了,在每次满包的状态进入到回调函数中就重新设置定时器的值,重新开始计时,在计时器中断中进行接收完成标志设定,这样在超时之后就可以实现数据的处理。上述思路提供一个解决办法,但是我并没有实现,单独为了整64字节进行这样的操作有些浪费资源,如果用途不是如此特殊,完全可以在主机发数据时注意一下,不发整64字节倍数的数据就好了。

我实现的小demo是简单的USB转串口,因此在判断接收数据完成之后,主程序利用DMA讲接收到的数据通过串口发送出去即可。

3.2 串口接收不定长数据

在已知通信协议的情况下,每次串口接收的数据长度是已知的,给发送的数据报增加特定的包头包尾进行数据包识别就可以实现串口数据的接收。但是做USB转串口的话,需要接收的数据长度是未知的,需要进行一些特殊处理。这里选择了一种办法是利用串口的空闲中断进行数据的接收。

在接收空闲时,可以产生空闲中断,从而可以判断数据接收完毕。

usb cdc,STM32,stm32,单片机,嵌入式硬件

开启空闲中断接收HAL库没有像正常接收一样有个函数,HAL库只提供了宏定义的方式。

通过下面的定义开启空闲中断。


/** @brief  Enable the specified UART interrupt.
  * @param  __HANDLE__ specifies the UART Handle.
  *         UART Handle selects the USARTx or UARTy peripheral
  *         (USART,UART availability and x,y values depending on device).
  * @param  __INTERRUPT__ specifies the UART interrupt source to enable.
  *          This parameter can be one of the following values:
  *            @arg UART_IT_CTS:  CTS change interrupt
  *            @arg UART_IT_LBD:  LIN Break detection interrupt
  *            @arg UART_IT_TXE:  Transmit Data Register empty interrupt
  *            @arg UART_IT_TC:   Transmission complete interrupt
  *            @arg UART_IT_RXNE: Receive Data register not empty interrupt
  *            @arg UART_IT_IDLE: Idle line detection interrupt
  *            @arg UART_IT_PE:   Parity Error interrupt
  *            @arg UART_IT_ERR:  Error interrupt(Frame error, noise error, overrun error)
  * @retval None
  */
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)   ((((__INTERRUPT__) >> 28U) == UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) & UART_IT_MASK)): \
                                                           (((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) & UART_IT_MASK)): \
                                                           ((__HANDLE__)->Instance->CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))

通过下面的方式清除中断标志位

/** @brief  Clears the UART IDLE pending flag.
  * @param  __HANDLE__ specifies the UART Handle.
  *         UART Handle selects the USARTx or UARTy peripheral
  *         (USART,UART availability and x,y values depending on device).
  * @retval None
  */
#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_PEFLAG(__HANDLE__)

实现过程首先在主函数开启串口接收和空闲中断。

	HAL_UART_Receive_DMA(&huart1,uart_data,max_num); 
	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);

之后在中断服务函数中增加对空闲中断的判断。


/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);
		Usart1_Rec_Cnt = max_num - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
		HAL_UART_DMAStop(&huart1);
		HAL_UART_Receive_DMA(&huart1,uart_data,max_num);
		RECE_OK = 1;
	}
  /* USER CODE END USART1_IRQn 1 */
}

如果进入空闲中断,清除标志位,计算一下接收数据的长度,产生接收完成标志,重新开启串口接收。DMA接收的缓冲区设置可以大一些,这样在一般情况下就不会进行到串口DMA接收完成。因为只是一个小demo,没有考虑持续性大量接收数据的情况,只考虑几百字节一次的传输。如果是持续性的大量输出传输,可能还需要考虑缓冲区的设置等处理方式才可以。

在主程序中,判断串口接收标志,然后通过CDC_Transmit_FS​函数将数据通过USB发送出去。

3.3通过主机USB设置串口通信参数

在USB虚拟串口的通信中,主机和STM32是通过USB协议进行数据传输的,因此不需要设置波特率等数据即可进行通信,电脑的串口助手无需设置波特率即可正常通信,但是要实现USB转串口的功能,就必须将电脑串口助手设置的波特率信息同步到硬件串口上,此时需要利用CDC_Control_FS​函数。

/**
 * @brief  Manage the CDC class requests
 * @param  cmd: Command code
 * @param  pbuf: Buffer containing command data (request parameters)
 * @param  length: Number of data to be sent (in bytes)
 * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
 */
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t *pbuf, uint16_t length)
{
  /* USER CODE BEGIN 5 */
  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;

    /*******************************************************************************/
    /* Line Coding Structure                                                       */
    /*-----------------------------------------------------------------------------*/
    /* Offset | Field       | Size | Value  | Description                          */
    /* 0      | dwDTERate   |   4  | Number |Data terminal rate, in bits per second*/
    /* 4      | bCharFormat |   1  | Number | Stop bits                            */
    /*                                        0 - 1 Stop bit                       */
    /*                                        1 - 1.5 Stop bits                    */
    /*                                        2 - 2 Stop bits                      */
    /* 5      | bParityType |  1   | Number | Parity                               */
    /*                                        0 - None                             */
    /*                                        1 - Odd                              */
    /*                                        2 - Even                             */
    /*                                        3 - Mark                             */
    /*                                        4 - Space                            */
    /* 6      | bDataBits  |   1   | Number Data bits (5, 6, 7, 8 or 16).          */
    /*******************************************************************************/
  case CDC_SET_LINE_CODING:
        VCP_Parameters.bitrate = (uint32_t)(pbuf[0] | (pbuf[1] << 8) | (pbuf[2] << 16) | (pbuf[3] << 24));
      VCP_Parameters.format = pbuf[4];
      VCP_Parameters.paritytype = pbuf[5];
      VCP_Parameters.datatype = pbuf[6];
      huart1.Init.BaudRate = VCP_Parameters.bitrate;
      HAL_UART_Init(&huart1);

    break;

  case CDC_GET_LINE_CODING:

    break;

  case CDC_SET_CONTROL_LINE_STATE:

    break;

  case CDC_SEND_BREAK:

    break;

  default:
    break;
  }

  return (USBD_OK);
  /* USER CODE END 5 */
}

在电脑设置串口参数时会调用这个函数,根据cmd判断主机发送的命令,CDC_SET_LINE_CODING就是主机在进行通信参数的设置,上面的注释已经写明了pbuf​中的数据结构,前4个字节是波特率,然后是停止位,奇偶校验位和数据位数。获取到这个信息即可设置硬件串口的通信参数,这里只设置了波特率。

4.实验

左侧连接硬件串口,右侧连接USB虚拟串口

usb cdc,STM32,stm32,单片机,嵌入式硬件

首先验证通过串口助手设置通信参数。

usb cdc,STM32,stm32,单片机,嵌入式硬件

验证USB发送

usb cdc,STM32,stm32,单片机,嵌入式硬件

可以观察右侧发送的字节数和左侧接收的字节数,没有丢包。

再验证串口发送

usb cdc,STM32,stm32,单片机,嵌入式硬件

左右对比没有丢包,此时验证USB转串口小demo基本实现。

5.总结和疑问

USB通信非常复杂,如需深入了解还需要多多学习。

此外,在我将这个例程移植到H750上时,却工作不正常,整个代码的原理完全一样,在测试硬件串口接收时,只要进行了USB的初始化,没有其他操作,串口就无法进行正常的接收,不进行USB的初始化,串口就一切正常,目前还没有找到原因。

工程已上传
‍https://download.csdn.net/download/Master_0_/87890600


参考链接

https://shequ.stmicroelectronics.cn/thread-638862-1-1.html

https://blog.csdn.net/Naisu_kun/article/details/118192032

https://359303267.github.io/STM32_UART_TxRx_noDMA

https://blog.csdn.net/qq_33160790/article/details/78668950


到了这里,关于STM32 USB CDC VPC的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32与USB3300共同实现USB OTG HS的CDC串口通信速度测试

      STM32和上位机传统通信方式就是串口,IIC,SPI等。IIC和SPI一般不常用,串口是用的最多的通信方式。然而串口一般用于输出调试信息这种对传输速度没要求的场景,那种大容量数据快速传输的场景,串口显得捉襟见肘。STM32自带USB FS,然而既然都使用USB了,为什么不用US

    2024年02月07日
    浏览(45)
  • 第10课【STM32 USB通讯协议实战】HID键盘+CDC虚拟串口组合设备

    文章中的部分概念可参考第9课【USB协议】USB总线 接口 端点 管道 数据包 枚举 STM32_USB-FS-Device_Lib V4.1.0 USB协议中为了提供对多样设备的支持,定义了许多外部设备子类,常见的包括: 人机交互类设备HID(Human Interface Device) 通信类设备CDC(Communicate Device Class) 大容量存储设备

    2024年02月04日
    浏览(66)
  • 手把手教你使用USB的CDC+MSC复合设备(基于stm32f407)

      最近对usb有点兴趣,感觉挺好玩的,于是买了本圈圈大神的经典著作-圈圈教你玩USB,里面使用51单片机+usb芯片对usb的基本知识潺潺道来,做了十个左右的常用案例实验,很有趣,建议大家看看。   趁热打铁,拿身边的开发板来练练手,探索一下复合设备的好玩方便的

    2024年02月13日
    浏览(67)
  • stm32 USB复合设备 cubeMX库一键生成 多路CDC串口 HID鼠标键盘 Composite Device

    最近有个需求,需要同时用usb键盘鼠标和虚拟串口等,因为平时没怎么研究过usb协议,所以自己写复合设备一直没有成功,然后正巧在github上看到了一个stm32的一个usb复合设备库,可以快速配置usb组合设备,并且支持超级多路串口 Gihub地址 https://github.com/alambe94/I-CUBE-USBD-Compo

    2024年02月09日
    浏览(68)
  • 野火指南者(STM32F103VET6)应用:实现USB虚拟串口(CDC_VPC)

    MCU:STM32F103VET6 开发环境:STM32CubeMX+MDK5   实现USB的虚拟串口不需要去理解USB的底层驱动,只需要STM32CubeMX去配置生成工程即可。在野火的指南者中,是没有这一类的视频和示例的,博主使用这款开发板实现USB虚拟串口。 首先需要打开STM32CubeMX工具。输入开发板MCU对应型号,找

    2024年02月08日
    浏览(44)
  • STM32F407ZGT6单片机连接ST_LINK和USB转TTL的接线方法+舵机接线方法

    目录 1.STM32F407ZG单片机连接ST_LINK 2.STM32F407ZG单片机连接USB转TTL(用于串口通信) 3  舵机 单片机             ST_LINK      9--------------------6      7--------------------2      20------------------3/4      1--------------------7/8  这里附上具体的接线图片: 单片机      USB转TTL TX————

    2024年02月02日
    浏览(69)
  • STM32单片机(一)STM32简介

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月10日
    浏览(60)
  • STM32单片机(二)STM32环境搭建

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月10日
    浏览(63)
  • STM32单片机开发-01 STM32介绍

    通过野火开发板学习单片机 从内核上分有Cortex-M0、M3、M4 和M7 F1 代表了基础型,基于Cortex-M3 内核,主频为72MHZ F4 代表了高性能,基于Cortex-M4 内核,主频180M。 数据手册:用于芯片选型和设计原理图 参考手册:用于编程时查阅 Icode总线 – 该总线讲M3内核的指令总线与闪存指令

    2024年01月21日
    浏览(63)
  • 【STM32】STM32单片机结构及部件原理

    STM32是目前比较常见并且多功能的单片机,要想学习STM32,首先要去了解它的基本构成部分以及各部分的原理。 单片机型号:正点原子STM32F103ZET6 目录 STM32内部结构总览图: 2.内部结构解析         1.内核 :STM32F103ZET6采用的是 ARM Cortex-M3 处理器,内核可以理解为单片机 处

    2023年04月08日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包