【STM32cubeide HAL库】DMA + 空闲中断 实现UART不定长数据接收(自用)

目录

1.stm32cubeide配置

2.DMA运行逻辑

3 .串口中断

4.调试

5.关于dma


1.stm32cubeide配置

本篇选用stm32f103c8t6最小系统,配置向导如下:

1.1 SYS

1.2 RCC

开启外部高速晶振(根据板子外设选择外部时钟)

1.3 UART

选择串口2,开启串口2全局中断

1.4配置dma

2.DMA运行逻辑

需要下面几个函数:
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, U2_rx_buffer, sizeof(U2_rx_buffer));//串口中断+dma
  __HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);//关闭dma接收半满中断函数
  void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
//需要使用该回调函数

2.1 函数说明 

HAL_UARTEx_ReceiveToIdle_DMA()

函数可以实现uart串口空闲中断,但是该函数中调用 status =  UART_Start_Receive_DMA(huart, pData, Size);函数会使能dma的接收中断(传输完成、半传输、传输错误),导致dma中断调用回调函数,容易出现问题。

/**
  * @brief Receive an amount of data in DMA mode till either the expected number of data is received or an IDLE event occurs.
  * @note   Reception is initiated by this function call. Further progress of reception is achieved thanks
  *         to DMA services, transferring automatically received data elements in user reception buffer and
  *         calling registered callbacks at half/end of reception. UART IDLE events are also used to consider
  *         reception phase as ended. In all cases, callback execution will indicate number of received data elements.
  * @note   When the UART parity is enabled (PCE = 1), the received data contain
  *         the parity bit (MSB position).
  * @note   When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M = 01),
  *         the received data is handled as a set of uint16_t. In this case, Size must indicate the number
  *         of uint16_t available through pData.
  * @param huart UART handle.
  * @param pData Pointer to data buffer (uint8_t or uint16_t data elements).
  * @param Size  Amount of data elements (uint8_t or uint16_t) to be received.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  HAL_StatusTypeDef status;

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Set Reception type to reception till IDLE Event*/
    huart->ReceptionType = HAL_UART_RECEPTION_TOIDLE;
    huart->RxEventType = HAL_UART_RXEVENT_TC;

    status =  UART_Start_Receive_DMA(huart, pData, Size);

    /* Check Rx process has been successfully started */
    if (status == HAL_OK)
    {
      if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
      {
        __HAL_UART_CLEAR_IDLEFLAG(huart);
        ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
      }
      else
      {
        /* In case of errors already pending when reception is started,
           Interrupts may have already been raised and lead to reception abortion.
           (Overrun error for instance).
           In such case Reception Type has been reset to HAL_UART_RECEPTION_STANDARD. */
        status = HAL_ERROR;
      }
    }

    return status;
  }
  else
  {
    return HAL_BUSY;
  }
}

UART_Start_Receive_DMA(huart, pData, Size)

//此函数会把DMA中断传输完成、半传输、传输错误全部开启;

/**
  * @brief  Start Receive operation in DMA mode.
  * @note   This function could be called by all HAL UART API providing reception in DMA mode.
  * @note   When calling this function, parameters validity is considered as already checked,
  *         i.e. Rx State, buffer address, ...
  *         UART Handle is assumed as Locked.
  * @param  huart UART handle.
  * @param  pData Pointer to data buffer (u8 or u16 data elements).
  * @param  Size  Amount of data elements (u8 or u16) to be received.
  * @retval HAL status
  */
HAL_StatusTypeDef UART_Start_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t *tmp;

  huart->pRxBuffPtr = pData;
  huart->RxXferSize = Size;

  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->RxState = HAL_UART_STATE_BUSY_RX;

  /* Set the UART DMA transfer complete callback */
  huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;

  /* Set the UART DMA Half transfer complete callback */
  huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;

  /* Set the DMA error callback */
  huart->hdmarx->XferErrorCallback = UART_DMAError;

  /* Set the DMA abort callback */
  huart->hdmarx->XferAbortCallback = NULL;

  /* Enable the DMA stream */
  tmp = (uint32_t *)&pData;
  HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);

  /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
  __HAL_UART_CLEAR_OREFLAG(huart);

  if (huart->Init.Parity != UART_PARITY_NONE)
  {
    /* Enable the UART Parity Error Interrupt */
    ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
  }

  /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

  /* Enable the DMA transfer for the receiver request by setting the DMAR bit
  in the UART CR3 register */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

  return HAL_OK;
}

UART_Start_Receive_DMA(huart, pData, Size) 在接收数组缓存到达数组一半时,会执行下面此函数:

/* Set the UART DMA Half transfer complete callback */
  huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;

UART_DMARxHalfCplt:dma半回调函数

UART_DMARxHalfCplt调用HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize / 2U);从而在接收数据缓存区一半时进入HAL_UARTEx_RxEventCallback回调函数,此时是不经过串口中断进入。

上述说明一个问题:由于使用stm32cubeide生成的代码,使用HAL_UARTEx_ReceiveToIdle_DMA就会默认开启DMA(DMA_IT_TC | DMA_IT_HT | DMA_IT_TE),这样接收一次数据会进两次HAL_UARTEx_RxEventCallback回调函数。

为了避免出现这样的情况,需要调用__HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);//关闭dma接收半满中断函数,这样我们在接收一组数据时就不会触发半满中断,dma就可以接收一组数据

3 .串口中断

HAL_UARTEx_ReceiveToIdle_DMA()在关闭接收半满中断后,接收一组数据,会开启IDLEF标志位,如果串口空闲标志位置1将会进入串口中断void USART2_IRQHandler(void),void USART2_IRQHandler(void)再调用HAL_UARTEx_RxEventCallback回调函数。

void USART2_IRQHandler(void)

由于dma采用的模式:Normal,DMA会在完成一次接收后自动关闭,再次需要重新手动打开

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, U2_rx_buffer, sizeof(U2_rx_buffer));//重新开启串口空闲中断和DMA接收
  __HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);//关闭dma接收半满中断函数
  /* USER CODE END USART2_IRQn 1 */
}

HAL_UARTEx_RxEventCallback

这是一个__weak类型的函数,需要重新调用使用。

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART2)//usart2
    {
    	if(U2_Recv_End_Flag == 0)
    	{
    		u2_rx_size=u2_buffer_size-__HAL_DMA_GET_COUNTER(&hdma_usart2_rx);//接收长度
    		U2_Recv_End_Flag=1;
    	}
    }
}

4.调试

main函数初始化阶段先初始化dma,再初始化串口

由于cubemx生成的代码会开启dma接收半满中断,所以我需要关闭dma接收半满中断,有两种方法:

第一种:

由于dma配置为单次接收,所以在开启dma接收时手动关闭dma半满中断

在main函数开头使能dma接收,也可以在串口初始化的时候手动加入,此时已经开启dma接收中断,这样dma就不会在接收半满时进入串口回调函数

  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, U2_rx_buffer, sizeof(U2_rx_buffer));//串口中断+dma
 __HAL_DMA_DISABLE_IT(&hdma_usart2_rx,DMA_IT_HT);//关闭dma接收半满中断函数

在"stm32f1xx_it.c"函数中找到void USART2_IRQHandler(void)重新开启中断,加入上述函数,这样就可以完美解决掉dma触发半满中断

接下来就可以在回调函数中添加需要处理内容

第二种方法:

关闭dma接收中断,这样dma只需要搬运数据,当触发串口中断标志位时将会进入回调函数进行处理。

5.关于dma

1个字节由8个比特组成,对于115200波特率,每个比特的持续时间为1/115200秒,每个字节的传输时间为8 * 8.68微秒 = 约69.4微秒。115200bps波特率,1s传输11520字节,大约69us需响应一次中断。串口一般来说是低速通信,波特率通常小于等于115200bps。因此对于数据量不大的通信场景,一般没必要使用DMA,完全不需要。

对于数量大,或者波特率提高时,频繁的进入中断可能会导致出现问题,所以采用dma进行搬运。但是dma使用时需要注意一些细节:

dma收一半还是接收固定字节

使用dma+串口空闲中断时,如果使用AL_UARTEx_ReceiveToIdle_DMA()实现串口不定长数据接收,需要手动关闭dma接收半满中断或者关闭dma接收中断。

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值