• 正文
  • 相关推荐
申请入驻 产业图谱

STM32 CDC 类 USB 设备接收64字节以上数据实操指南:分包拼接 + 超时优化方案

8小时前
201
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

CDC 类 USB 设备(虚拟串口)默认支持 BULK 传输,全速模式下单包最大 64 字节,接收超过 64 字节的数据需处理 USB 分包逻辑 —— 主机自动将大数据拆分为多个 64 字节整包 + 最后一个非整包(或整包),设备通过接收回调拼接分包、添加超时判断,即可实现无丢包接收。本文基于 STM32 HAL 库,拆解核心原理与完整实操步骤。

资料获取:如何优化STM32N6 MCU的低功耗模式

1. 核心原理:USB 分包传输机制

  • USB BULK 传输规则:单次传输超过 64 字节时,主机会拆分为多个 Transaction(事务),每个事务传输 1 个数据包(最大 64 字节);
  • 分包特征:最后一个数据包长度<64 字节(非整包),或刚好为 64 字节(整包),设备需以此判断传输是否结束;
  • 接收逻辑:在 USB 接收回调函数中,持续拼接各分包数据,直到检测到非整包或超时,标记传输完成。

2. 实操步骤:基于 HAL 库的完整实现

以 STM32F429(HS USB 工作在 FS 模式)为例,核心是配置接收缓冲区、实现回调拼接逻辑、添加超时区分传输批次。

2.1 前置配置(STM32CubeMX)

  • 配置 USB 为 CDC 类设备,启用 BULK IN/OUT 端点,端点最大包长设为 64 字节;
  • 生成 HAL 库工程,确保 USB 中断使能(默认生成)。

2.2 关键变量定义(全局 / 全局外部声明)

main.cusbd_cdc_if.c中定义接收相关变量,用于缓存数据、计数分包:

#define MAX_PACK_SIZE 64    // BULK端点单包最大长度
#define RX_BUFFER_LEN 512   // 接收缓冲区大小(按需调整,需大于最大接收数据量)
uint8_t Rx_Buffer[RX_BUFFER_LEN];  // 总接收缓冲区
uint32_t Num_Rx_Data = 0;         // 已接收数据总长度
uint8_t Num_Packet = 0;           // 已接收分包个数
uint32_t Wait_RX_Dly = 0;         // 超时延时计数器(区分传输批次)
uint8_t Flag_DataReceived = 0;     // 数据接收完成标志

2.3 接收回调函数实现(核心逻辑)

usbd_cdc_if.cCDC_Receive_HS()回调函数中,处理分包拼接与传输结束判断:

static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t* Len)
{
  // 1. 重新设置接收缓冲区(确保下次能正常接收下一包)
  USBD_CDC_SetRxBuffer(&hUsbDeviceHS, Buf);
  USBD_CDC_ReceivePacket(&hUsbDeviceHS);

  uint32_t SinglePackLen = *Len;  // 当前分包长度

  // 2. 初始化接收(新传输批次)
  if (Num_Packet == 0)
  {
    memset(Rx_Buffer, 0, RX_BUFFER_LEN);  // 清空缓冲区
    Num_Rx_Data = 0;
  }

  // 3. 拼接当前分包到总缓冲区
  if (SinglePackLen == MAX_PACK_SIZE)
  {
    // 整包:继续等待下一包,设置超时(避免不同传输批次数据混淆)
    memcpy(&Rx_Buffer[Num_Packet * MAX_PACK_SIZE], Buf, MAX_PACK_SIZE);
    Num_Rx_Data += MAX_PACK_SIZE;
    Num_Packet++;
    Wait_RX_Dly = 5;  // 超时时间(单位:ms,可按需调整)
  }
  else
  {
    // 非整包:视为传输结束,标记接收完成
    memcpy(&Rx_Buffer[Num_Packet * MAX_PACK_SIZE], Buf, SinglePackLen);
    Num_Rx_Data += SinglePackLen;
    Num_Packet++;
    Flag_DataReceived = 1;  // 通知主循环处理数据
    Num_Packet = 0;         // 重置分包计数,准备下一次传输
  }

  return USBD_OK;
}

2.4 超时处理:解决连续整包传输批次区分问题

当主机连续发送 64 字节整包(如 256 字节 = 4 个 64 字节包),设备无法通过 “非整包” 判断传输结束,需添加超时机制:

  • Systick中断中处理超时倒计时(HAL 库默认 1ms 中断):
    void SysTick_Handler(void)
    {
      HAL_IncTick();
      // 超时计数器递减,为0时视为传输结束(仅针对连续整包场景)
      if (Wait_RX_Dly > 0)
      {
        Wait_RX_Dly--;
        if (Wait_RX_Dly == 0)
        {
          Flag_DataReceived = 1;
          Num_Packet = 0;
        }
      }
    }
    

2.5 主循环数据处理

接收完成后,在主循环中读取Rx_Buffer数据(如回显给主机):

while (1)
{
  if (Flag_DataReceived)
  {
    // 回显接收的数据给主机
    CDC_Transmit_HS(Rx_Buffer, Num_Rx_Data);
    Flag_DataReceived = 0;  // 重置接收标志
    Num_Rx_Data = 0;        // 重置数据长度
  }
  /* 其他业务逻辑 */
}

3. 验证结果:多场景测试有效

测试数据量 分包情况 接收结果
5 字节 1 个非整包 数据完整,Num_Rx_Data=5
64 字节 1 个整包 超时后标记完成,Num_Rx_Data=64
305 字节 4 个整包(64×4=256)+1 个 49 字节包 拼接后 Num_Rx_Data=305,无丢包
256 字节 4 个整包 超时后标记完成,Num_Rx_Data=256

4. 避坑关键要点

  1. 缓冲区大小:RX_BUFFER_LEN需大于最大接收数据量,避免缓冲区溢出;
  2. 超时时间:Wait_RX_Dly建议设 3~5ms,过短可能截断数据,过长影响响应速度;
  3. 端点配置:确保 USB BULK OUT 端点最大包长设为 64 字节(CubeMX 默认配置);
  4. 回调重置:每次回调需调用USBD_CDC_SetRxBufferUSBD_CDC_ReceivePacket,否则无法接收下一包;
  5. 数据清空:新传输批次开始时(Num_Packet==0)需清空接收缓冲区,避免旧数据干扰。

CDC 类 USB 设备接收 64 字节以上数据的核心是 “分包拼接 + 超时区分”:利用 USB 分包传输特性,在接收回调中拼接整包数据,通过超时判断连续整包的传输结束,即可实现无丢包接收。该方案基于 HAL 库,适配 STM32 全系列支持 CDC 类 USB 的型号,通用性强。

相关推荐