stmf103基于HAL库的串口+DMA接受、发送实现分析

发布于:2023-01-07 ⋅ 阅读:(797) ⋅ 点赞:(0)

        之前参加比赛使用过USART+DMA的串口通信,最近找工作准备在stm32f103上复习下,结果调了一下午都没成功,最后终于成功通信并通过分析HAL源代码找到问题所在。整个过程主要遇到一下问题:

        1、在串口中断中调用HAL_Delay()函数会卡死

        原因:是因为HAL_Delay()函数是基于sys_clock中断实现,而这个中断优先级不够导致程序在调用出卡死。解决方法可以修改系统时钟的优先级。

        2、在usart1调用DMA发送后必须调用HAL_UART_DMAStop(),不然程序后续无法继续发送

        原因:首先串口配置正常配置,DMA选择普通模式而不是循环模式。此函数源代码如下:

HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
{
  uint32_t dmarequest = 0x00U;
  /* The Lock is not implemented on this API to allow the user application
     to call the HAL UART API under callbacks HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback():
     when calling HAL_DMA_Abort() API the DMA TX/RX Transfer complete interrupt is generated
     and the correspond call back is executed HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback()
     */

  /* Stop UART DMA Tx request if ongoing */
  dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT);
  if ((huart->gState == HAL_UART_STATE_BUSY_TX) && dmarequest)
  {
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);

    /* Abort the UART DMA Tx channel */
    if (huart->hdmatx != NULL)
    {
      HAL_DMA_Abort(huart->hdmatx);
    }
    UART_EndTxTransfer(huart);
  }

  /* Stop UART DMA Rx request if ongoing */
  dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
  if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && dmarequest)
  {
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    /* Abort the UART DMA Rx channel */
    if (huart->hdmarx != NULL)
    {
      HAL_DMA_Abort(huart->hdmarx);
    }
    UART_EndRxTransfer(huart);
  }

  return HAL_OK;
}

        我们发现此函数会两次判断,首先判断是否发送,然后中断发送,并设置一些标志位,然后判断是否还在接受,后续操作同上。其中非常关键一点是UART_EndRxTransfer()和UART_EndTxTransfer()函数,以UART_EndTxTransfer()函数代码为例(如下),其会在最后面设置huart->RxState = HAL_UART_STATE_READY。没有这句话即使一次数据发生完,其状态并不会改变,而在后续调用的HAL_UART_Receive_DMA()函数首先就会判断huart->RxState状态,如果不是HAL_UART_STATE_READY则直接返回错误(具体可以取看HAL_UART_Transmit_DMA()代码)。所以每次发送完后必须调用HAL_UART_DMAStop()函数,不然只会成功发送第一次数据。

        同理,在normal模式下,每调用一次HAL_UART_Receive_DMA()函数也必须调用一次HAL_UART_DMAStop(),不然后续无法成功接受数据。

static void UART_EndRxTransfer(UART_HandleTypeDef *huart)
{
  /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
  CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
  CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

  /* In case of reception waiting for IDLE event, disable also the IDLE IE interrupt source */
  if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
  {
    CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
  }

  /* At end of Rx process, restore huart->RxState to Ready */
  huart->RxState = HAL_UART_STATE_READY;
  huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;
}

              3、第三个问题:使用HAL_UART_Receive_DMA()接受不到数据(DMA还是normal模式),我代码逻辑是初始化调用DMA读取数据,然后在串口UART_FLAG_IDLE中断中读取DMA传输数量再打印到上位机上,结果能正常工作,但是每次DMA数据传输量都为0。

        原因:在我的程序逻辑中是一个TIM2每1s产生一次中断,然后触发标志位发送一个句到上位机;同时串口一旦触发UART_FLAG_IDLE也触发标志位将接受内容发送到上位机。

        根据问题2我每次发送数据后调用HAL_UART_DMAStop(),这个函数作用会同时关闭huart中Tx和Rx两条DMA通道,所以就当我在等待的时候如果TIM2触发发送就会导致我等待数据过程中Rx的DMA通道关闭,即使触发UART_FLAG_IDLE中断数据进来但是DMA并不会工作,接受缓冲区也就没有数据。解决方法我通过设置一个is_stop标志位,只要一调用HAL_UART_DMAStop()触发标志,然后主程序while循环中再次调用HAL_UART_Receive_DMA()函数开启接受DMA通道。

        所以这个问题在于HAL_UART_DMAStop()的调用会关闭usart相关联的DMA,这点很奇怪。

最后还有一点疑问:

        我在串口1中断函数中调用HAL_UART_Receive_DMA()函数和在main()函数中调用HAL_UART_Receive_DMA()效果不一样,前者还是无法正常工作,后者可以正常进行下一次接收,忘大佬解答。

        最终效果图如下:

我的代码如下:

(7条消息) 基于HAL库的stm32f103单片机实现DMA+串口的接收、发送数据功能代码-单片机文档类资源-CSDN文库


网站公告

今日签到

点亮在社区的每一天
去签到