你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】LAT1496 基于 USBD库CDC Standalone例程中的一个Bug解析

[复制链接]
攻城狮Melo 发布时间:2026-3-2 13:38

LAT1496 基于 USBD 库 CDC Standalone 例程中的一个 Bug 解析

详细点击:LAT1496 基于 USBD库CDC Standalone例程中的一个Bug解析

关键字:STM32U5,USB,ST_USBD_Library

1. 引言

当前越来越多的 STM32 芯片支持 USB-PD,目前新提供的 STM32 的 USB 例程中会把 USB-PD 和 USB 合在一起。如果有客户只需要 USB,那就需要剥离 USB-PD。另外,STM32U5 的 Cube 库中只提供了基于 USBX 的例程,如果客户要使用基于 USB_USBD_Library 则需要到 GitHub 上去下载。

2. 问题

2.1. 问题详情

客户在开发其产品过程中,使用了 STM32U575ZIT6Q。因为客户对 USBX 不熟悉,所以希望 ST 能够提供基于 ST_USBD_Library 的 CDC_Standalone 的例程,以实现虚拟串口功能。

建议客户到 GitHub 上去下载:https://github.com/STMicroelectronics/stm32u5classic-coremw-apps

客户下载后发现里边提供的 CDC_Standalone 的例程是带有 USB-PD 的,客户对 USB-PD 不熟悉,希望能够提供给他们不带 USB-PD 的,所以就需要从这个例程中对 USB-PD 进行剥离。

在对 USB-PD 进行剥离后,并在 usb_device.c 中的 MX_USB_Device_Init () 函数中添加 USBD_Start () 函数启动 USB。

按理说,进展到这一步,USB 就应该可以工作的。事实上,发现在 NUCLEO-U575ZI-Q 板子上测试时,当通过 USB 连接到 PC 后,就会发现枚举成功了,PC 可以正常识别到虚拟串口。但是,看起来无法工作,在线调试时发现其进入了 Error_Handler (),也就是说,在什么地方出错了,工作不了。

2.2. 问题分析

查了很多遍,怎么都看不出 USB 的配置哪里有问题,看起来都还好,而且是从原例程剥离时并没有动 USB 的代码,觉得有点不可思议。

后来实在没办法,只好挨个在进入 Error_Handler 的入口处设断点,最后终于捕捉到进入 Error_Handler 的地方了,为在 CDC_Itf_Init () 函数中以中断方式启动 TIM3 的地方出问题了。

static int8_t CDC_Itf_Init(void)
{
  /* USART configured as follow: -Word Length=8 Bits - Stop Bit = One Stop bit - Parity = No parity -BaudRate=115200 baud - Hardware flow control disabled (RTS and CTS signals)*/
  UartHandle.Instance = USARTx;
  UartHandle.Init.BaudRate=115200;
  UartHandle.Init.WordLength = UART_WORDLENGTH_8B;
  UartHandle.Init.StopBits = UART_STOPBITS_1;
  UartHandle.Init.Parity=UART_PARITY_NONE;
  UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  UartHandle.Init.Mode = UART_MODE_TX_RX;
  UartHandle.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&UartHandle)!= HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }
  /* Any data received will be stored in "UserTxBuffer"buffer*/
  if (HAL_UART_Receive_IT(&UartHandle, (uint8_t *) UserTxBuffer, 1) != HAL_OK)
  {
    /* Transfer error in reception process */
    Error_Handler();
  }
  /* Configur th TIM Base generation */
  TIM_Config();
  /* Start the TIM Base generation in interrupt mode */
  if (HAL_TIM_Base_Start_IT(&TimHandle)!= HAL_OK)
  {
    /*Starting Error */
    Error_Handler();
  }
  /* Set Applitin ffers */
  USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBuffer, 0);
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, UserRxBuffer);
  return(USBD_OK);
}

觉得很奇怪,这只是以中断方式启动 TIM3 而已,为什么会出错?

通过在线调试,查看上面的 TIM_Config () 函数时,发现了问题。TIM_Config () 函数内容如下:

static void TIM_Config(void)
{
  /* Set TIMx instance*/
  TimHandle.Instance = TIMx;
  /* Compute the prescaler value to have TIMx counter clock equal to 10000 Hz*/
  uwPrescalerValue =(uint32_t)(SystemCoreClock /(2* 10000))-1;
  /* Initialize TIM3 peripheral as follows: + Period = (CDC_POLLING_INTERVAL *10000) -1 + Prescaler=((APB1 frequency /1000000)-1)+ ClockDivision=0+Counter direction=Up */
  TimHandle.Init.Period = (CDC_POLLING_INTERVAL * 1000) - 1;
  TimHandle.Init.Prescaler = uwPrescalerValue;
  TimHandle.Init.ClockDivision=0;
  TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
  if (HAL_TIM_Base_Init(&TimHandle)!= HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }
  /* Start the TIM Base generation in interrupt mode */
  if (HAL_TIM_Base_Start_IT(&TimHandle)!= HAL_OK)
  {
    /* Starting Error */
    Error_Handler();
  }
}

可以发现这个例程中对 TIM3 以中断方式启动了两次,这会导致运行异常。

再来看一下 HAL_TIM_Base_Start_IT () 函数,从其逻辑可以看到,如果连续两次调用,第一次调用时,TIM3 的状态机会变成 HAL_TIM_STATE_BUSY,第二次再调用时就会因为状态机不是 HAL_TIM_STATE_READY 而返回 HAL_ERROR,回到 CDC_Itf_Init () 函数就会跳入 Error_Handler () 了,从而导致运行出现问题。所以,连续调用两次 HAL_TIM_Base_Start_IT () 函数是不合理的。

HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
  uint32_t tmpsmcr;
  /* Check the parameters*/
  assert_param(IS_TIM_INSTANCE(htim->Instance));
  /*Check the TIM state*/
  if (htim->State!=HAL_TIM_STATE_READY)
  {
    return HAL_ERROR;
  }
  htim->State =HAL_TIM_STATE_BUSY; /* Set the TIM state*/
  /*Enable the TIM Update interrupt*/
  HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
  /* Enable the Peripheral,except in trigger mode where enable is automatically done with trigger */
  if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
  {
    tmpsmcr=htim->Instance->SMCR &TIM_SMCR_SMS;
    if (!IS_TIM_SLAVENODE_TRIGGER_ENABLED(tmpsmcr))
    {
      _HAL_TIM_ENABLE(htim);
    }
  }
  else
  {
    _HAL_TIM_ENABLE(htim);
  }
  return HAL_OK; /* Return function status*/
}

2.3. 解决方法

问题找到了后,解决方法也很简单,就是把 TIM_Config () 函数中的调用 HAL_TIM_Base_Start_IT () 的语句去掉,避免重复调用即可,修改后的 TIM_Config () 函数如下:

static void TIM_Config(void)
{
  /* Set TIMx instance*/
  TimHandle.Instance = TIMx;
  /* Compute the prescaler value to have TIMx counter clock equal to 10000 Hz*/
  uwPrescalerValue =(uint32_t)(SystemCoreClock /(2*10000))-1;
  /* Initialize TIM3 peripheral as follows: + Period = (CDC_POLLING_INTERVAL *10000)- 1 + Prescaler=((APB1 frequency /1000000)-1)+ ClockDivision =0+ Counter direction=Up */
  TimHandle.Init.Period = (CDC_POLLING_INTERVAL * 1000) - 1;
  TimHandle.Init.Prescaler = uwPrescalerValue;
  TimHandle.Init.ClockDivision =0;
  TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
  if (HAL_TIM_Base_Init(&TimHandle)!= HAL_OK)
  {
    /* Initialization Error */
    Error_Handler();
  }
}

3. 原代码为什么能运行?

细心的开发者可能会有疑问:原代码中存在这个 Bug,为什么原代码能正常运行?

主要原因是在进行 USB-PD 剥离后,编译时编译器提示 Error_Handler () 重复声明了 —— 在 IAR 中只是警告,在 Keil 中则直接报错误。

原代码中位于 usbd_cdc_if.c 中的 Error_Handler () 函数如下:

/**
  @brief This function is executed in case of error occurrence.
  @param None
  @retval None
*/
static void Error_Handler(void)
{
  /* Add your own code here */
}

可以看到,该 Error_Handler () 中没有做任何处理,直接返回,所以原代码中即使两次调用 HAL_TIM_Base_Start_IT () 导致进入该函数,问题也不会被发现,看似运行正常。

而在 main.c 中还有一个 Error_Handler () 函数,内容如下:

/**
  @brief This function is executed in case of error occurrence.
  @retval None
*/
void Error_Handler(void)
{
  BSP_LED_On(LED1);
  while(1)
  {
  }
}

正常来说,运行到 Error_Handler () 应该通过 while (1) 暂停程序,排查问题。因此在编译出现重复声明问题时,对 usbd_cdc_if.c 中的 Error_Handler () 进行了修改,声明为 weak,使得程序出错时,会直接运行 main.c 中的 Error_Handler ()。

修改后 usbd_cdc_if.c 中的 Error_Handler () 函数如下:

/**
* @brief This function is executed in case of error occurrence.
  @param None
  @retval None
*/
__weak void Error_Handler(void)
{
  //static void Error_Handler(void)
  /*Add your own code here*/
}

所以,剥离 USB-PD 后测试程序卡死的原因是:连续两次调用 HAL_TIM_Base_Start_IT () 导致程序进入 main.c 的 Error_Handler (),并卡在 while (1) 中。

4. 小结

使用 GitHub 上的https://github.com/STMicroelectronics/stm32u5-classic-coremwapps 软件包,进行基于 USB_USBD_Library 的 STM32U5 的 CDC_Standalone 开发时,需要注意到此 Bug。如果在开发过程中,程序进入 Error_Handler (),可以排查是否是该重复调用 TIM 启动函数的问题未修改。

收藏 评论0 发布时间:2026-3-2 13:38

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版