STM32L151单片机USB虚拟串口传输速度深度解析与实战记录

UART串口常用波特率最高115200的情况下,理论传输速度极限是11KB/s,实际使用差不多在5KB/s左右,使用USB虚拟串口除去不需要设置波特率这个优点以外,主要还是希望提高传输速度,缩短下载数据花费的时间。然而在简单配置虚拟串口替代串口后,发现传输速度没有明显变化,所以实验一下虚拟串口的传输速度的主要影响因素。

首先查看一下CubeMX生成的usb相关源码

复位(每次USB插上时,触发USB外设复位):


void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd)
#endif /* USE_HAL_PCD_REGISTER_CALLBACKS */
{ 
  USBD_SpeedTypeDef speed = USBD_SPEED_FULL;

  if ( hpcd->Init.speed != PCD_SPEED_FULL)
  {
    Error_Handler();
  }
    /* Set Speed. */
  USBD_LL_SetSpeed((USBD_HandleTypeDef*)hpcd->pData, speed);

  /* Reset Device. */
  USBD_LL_Reset((USBD_HandleTypeDef*)hpcd->pData);
}

其中配置了传输速率,这个速率可以在CubeMx里修改,所以代码就不用动了

由于我研究的是发送,所以接收就不看了,直接看虚拟串口发送:

uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
  uint8_t result = USBD_OK;
  /* USER CODE BEGIN 7 */
  USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
  if (hcdc->TxState != 0){
    return USBD_BUSY;
  }
  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
  result = USBD_CDC_TransmitPacket(&hUsbDeviceFS);
  /* USER CODE END 7 */
  return result;
}

基本逻辑是判断TxState(发送状态标志)看发送是否处于进行中,如果目前没有发送任务,才给USB外设传入发送任务,源码里我没有找到完成任务后清除这个标志的内容,不知道有没有大佬懂,这里暂且跳过。

从这个逻辑中可以看出来一点,就是USB发送是非阻塞的,USB执行发送任务和核心的计算可以同步进行,因此做一个测试,在循环中输出有规律的字符,由于循环输出字符的运算速度极快,发现输出的字符有大量丢失,说明确实是非阻塞的。

接下来就是测速。

从HAL库里提取代码,重写一个发送函数:


void USBD_Send(uint8_t* Buf, uint16_t Len)
{
	extern USBD_HandleTypeDef hUsbDeviceFS;
	USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;

	while(hcdc->TxState != 0)    //USB繁忙时,阻塞等待

	USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
      /* Tx Transfer in progress */
    hcdc->TxState = 1U;
      /* Update the packet total length */
    hUsbDeviceFS.ep_in[CDC_IN_EP & 0xFU].total_length = hcdc->TxLength;
      /* Transmit next packet */
    USBD_LL_Transmit(&hUsbDeviceFS, CDC_IN_EP, hcdc->TxBuffer,(uint16_t)hcdc->TxLength);
	//udelay(Len*20);//20
}

 然后是发送一个512字节的缓存区,并利用SysTick测试发送耗时

extern USBD_HandleTypeDef hUsbDeviceFS;
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
uint8_t pf_buf[512];
memset(pf_buf, 0x30, 512);
//int i = 0;
uint32_t check_time;
check_time = SysTick->VAL;
//while(i<1024){
	USBD_Send(pf_buf, 512);
//	i++;
//}
while(hcdc->TxState != 0);
ALL_printf("\r\n%d\r\n",check_time-SysTick->VAL);

得到输出的计数为20000~22000,本次我使用的SysTick频率为2097kHz即发送一包512字节数据耗时约10ms,每秒100包,传输速度最高50KB/s

去掉 // 后测试循环发送1024包,,结果发现前几包速度正常,后面出现明显卡顿,最终发送1024包耗时约40秒,速度只有12KB/s左右。经过反复实验,我怀疑是多次阻塞等待造成的影响,于是我在发送后加上延时:


void udelay(uint16_t us)
{
	uint32_t wait_time = 0UL;
	uint32_t check_time = 0UL;
	uint32_t start_time = 0UL;
	uint32_t delay_time = 0UL;

	start_time = SysTick->VAL;
	delay_time = SystemCoreClock;
	delay_time = delay_time / 1000000UL;
	delay_time = delay_time * us;

	while (wait_time < delay_time) {
		check_time = SysTick->VAL;
		if (check_time > start_time) {
			wait_time += SysTick->LOAD - check_time + start_time;
		} else {
			wait_time += start_time - check_time;
		}
		start_time = check_time;
	}
}

void USBD_Send(uint8_t* Buf, uint16_t Len)
{
	extern USBD_HandleTypeDef hUsbDeviceFS;
	USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;

	while(hcdc->TxState != 0) udelay(Len*5);

	USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
      /* Tx Transfer in progress */
    hcdc->TxState = 1U;
      /* Update the packet total length */
    hUsbDeviceFS.ep_in[CDC_IN_EP & 0xFU].total_length = hcdc->TxLength;
      /* Transmit next packet */
    USBD_LL_Transmit(&hUsbDeviceFS, CDC_IN_EP, hcdc->TxBuffer,(uint16_t)hcdc->TxLength);
	
    udelay(Len*20);//延时发送长度x20us,512*20约10ms
}

发送512字节*1024包的耗时成功缩短到10~11s,接近50KB/s的传输速度。奇怪的是,TxState是存在于RAM内的变量,我无法理解为什么反复访问这个变量会影响到USB发送的速度,而且在前几包发送时很明显并没有受到影响。

综上,考虑到USB是非阻塞传输,想达到最大速度,如果下一包发送数据准备的时间小于上一包传输的时间,最好添加一个延时补足时间差,以求达到最大速度,如过一包数据准备的时间大于传输时间(例如用vsnprintf进行字符串生成),则需要尽可能缩短数据准备耗时。

如过还有其他方法及观点欢迎讨论补充。

——————

24-06-25 更新:

        在新板上实验发现,另一设计的pcb(MCU为L151CC)能达到最高500KB的传输速度,同时为移植方便,单独编写了一个USB应用层驱动,代码如下:

.h文件:

#ifndef __USBPORT_H
#define __USBPORT_H

#include "main.h"
extern uint16_t BYTE_us;
extern uint32_t wait_cnt;

uint8_t USER_USB_Init(void);
uint8_t USBD_Send(uint8_t* Buf, uint16_t Len);

#endif

.c文件:

#include "USBport.h"

#include "usb_device.h"
#include "usbd_core.h"
#include "usbd_cdc.h"
#include "usbd_cdc_if.h"

#include "delay.h"
#include "serial.h"
#include "commands.h"
#include "transfer.h"


extern uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
extern USBD_HandleTypeDef hUsbDeviceFS;
extern USBD_CDC_ItfTypeDef USBD_Interface_fops_FS;

static uint16_t Last_len = 10;
 uint16_t BYTE_us = 2;
 uint32_t wait_cnt;
static int8_t USB_Receive_FS(uint8_t* Buf, uint32_t *Len);

uint8_t USER_USB_Init(void)
{
	USBD_Interface_fops_FS.Receive = USB_Receive_FS;
	//USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBufferFS);//如果要修改接收缓存区,调用这个函数
	USBD_CDC_ReceivePacket(&hUsbDeviceFS);
	return 0;
}

//极限1M
uint8_t USBD_Send(uint8_t* Buf, uint16_t Len)
{
	if(Len==0) return 0;
    USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceFS.pClassData;
	
	wait_cnt = 0;
    do{
        udelay(Last_len*BYTE_us);
		if(wait_cnt >> 14) return 1;
		wait_cnt ++;
	}while(hcdc->TxState != 0);
	if(wait_cnt > 2) BYTE_us <<= 1;
	else if(wait_cnt < 2) BYTE_us = (BYTE_us>>1)|0x1;
	
    USBD_CDC_SetTxBuffer(&hUsbDeviceFS, Buf, Len);
      /* Tx Transfer in progress */
    hcdc->TxState = 1U;
      /* Update the packet total length */
    hUsbDeviceFS.ep_in[CDC_IN_EP & 0xFU].total_length = hcdc->TxLength;
      /* Transmit next packet */
    USBD_LL_Transmit(&hUsbDeviceFS, CDC_IN_EP, hcdc->TxBuffer,(uint16_t)hcdc->TxLength);
	
	Last_len = Len;
	return 0;
}

static int8_t USB_Receive_FS(uint8_t* Buf, uint32_t *Len)//接收回调函数
{
	USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
	USBD_CDC_ReceivePacket(&hUsbDeviceFS);
	
	//****此处自行插入接收后处理 例如:
	memcpy(get_rxbuf(EPORT_USB), UserRxBufferFS, *Len);
	set_rxlen(EPORT_USB, Len);
	
	return (USBD_OK);
}

作者:乌龙三

物联沃分享整理
物联沃-IOTWORD物联网 » STM32L151单片机USB虚拟串口传输速度深度解析与实战记录

发表回复