【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收

1、STM32CubeMX配置部分

打开STM32CubeMX软件,点击ACCESS TO MCU SELECTOR,在Commercial Part Number 中输入MCU型号,例如我在这里输入了STM32L431RCT6。选中正确型号然后双击进入下一步的配置界面。
【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收_第1张图片
【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收_第2张图片

1.1 SYS配置如图

【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收_第3张图片

1.2 RCC配置如图

开启了外部晶振,若无则都选择Disable
【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收_第4张图片

1.3 USART1配置

  • NVIC Settings

在这里插入图片描述
注意:这里需要打开USART1 global interrupt 全局中断

  • DMA Settings

【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收_第5张图片

1.4 DMA配置

【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收_第6张图片

2、软件部分

HAL库中通过HAL_UARTEx_ReceiveToIdle_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 (M1-M0 = 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;
    }

    __HAL_LOCK(huart);

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

    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_FLAG(huart, UART_CLEAR_IDLEF);
        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;
  }
}

可以看到函数中大部分的内容是条件判断我们无需关注,
调用函数会将当前的串口接收类型设置为HAL_UART_RECEPTION_TOIDLE

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

调用UART_Start_Receive_DMA(),启用DMA将接收到的数据放在指针 pData 指向的位置。

在接收正确的情况下,会执行以下的内容

__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_IDLEF);		// 清除UART_CLEAR_IDLEF标志位
ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);	// 设置usart cr1寄存器的USART_CR1_IDLEIE标志位

在参考手册中关于CR1寄存器IDLEIE标志位的介绍:

IDLEIE: IDLE interrupt enable This bit is set and cleared by software. 开启IDLE中断。这个标志位由软件控制。
0: Interrupt is inhibited 中断被禁用。
1: A USART interrupt is generated whenever IDLE=1 in the USART_ISR register 串口空闲时产生串口中断

于是我们可以得知,调用HAL_UARTEx_ReceiveToIdle_DMA()函数后只要发生了串口空闲事件,就会产生串口中断。顺着这个线索,我们接下来看串口中断处理函数中的情况。

因为我们在STM32CubeMX中勾选了USART1串口全局中断,这里已经自动生成了代码。

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

接下来继续查看HAL_UART_IRQHandler(&huart1)中的内容
我们暂时忽略掉条件判断,查看与DMA串口空闲中断相关的部分

/**
  * @brief Handle UART interrupt request.
  * @param huart UART handle.
  * @retval None
  */
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
  ......
  
  /* Check current reception Mode :
     If Reception till IDLE event has been selected : */
  if ((huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
      && ((isrflags & USART_ISR_IDLE) != 0U)
      && ((cr1its & USART_ISR_IDLE) != 0U))
  {
    __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_IDLEF);

    /* Check if DMA mode is enabled in UART */
    if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
    {
      /* DMA mode enabled */
      /* Check received length : If all expected data are received, do nothing,
         (DMA cplt callback will be called).
         Otherwise, if at least one data has already been received, IDLE event is to be notified to user */
      uint16_t nb_remaining_rx_data = (uint16_t) __HAL_DMA_GET_COUNTER(huart->hdmarx);
      if ((nb_remaining_rx_data > 0U)
          && (nb_remaining_rx_data < huart->RxXferSize))
      {
        /* Reception is not complete */
        huart->RxXferCount = nb_remaining_rx_data;

        /* In Normal mode, end DMA xfer and HAL UART Rx process*/
        if (HAL_IS_BIT_CLR(huart->hdmarx->Instance->CCR, DMA_CCR_CIRC))
        {
          /* Disable PE and ERR (Frame error, noise error, overrun error) interrupts */
          ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
          ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

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

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

          ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);

          /* Last bytes received, so no need as the abort is immediate */
          (void)HAL_DMA_Abort(huart->hdmarx);
        }
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered Rx Event callback*/
        huart->RxEventCallback(huart, (huart->RxXferSize - huart->RxXferCount));
#else
        /*Call legacy weak Rx Event callback*/
        HAL_UARTEx_RxEventCallback(huart, (huart->RxXferSize - huart->RxXferCount));
#endif /* (USE_HAL_UART_REGISTER_CALLBACKS) */
      }
      return;
    }
  }
  
  ......
  
}

我们可以简单的理解为,在条件判断都满足的情况下,发生串口空闲中断以后,会开启DMA功能并调用回调函数HAL_UARTEx_RxEventCallback()。如果接收过程中发生错误,会调用HAL_UART_ErrorCallback();

我们先来分析HAL_UARTEx_RxEventCallback()这个回调函数

/**
  * @brief  Reception Event Callback (Rx event notification called after use of advanced reception service).
  * @param  huart UART handle
  * @param  Size  Number of data available in application reception buffer (indicates a position in
  *               reception buffer until which, data are available)
  * @retval None
  */
__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  UNUSED(Size);

  /* NOTE : This function should not be modified, when the callback is needed,
            the HAL_UARTEx_RxEventCallback can be implemented in the user file.
   */
}

这是一个__weak类型的函数,对于这种类型的函数我们不能在原位修改,需要在其他位置手动实现。

以下是我手动重新实现的HAL_UARTEx_RxEventCallback()函数,发生串口空闲中断后会被调用:

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef * huart, uint16_t Size)
{
    if(huart->Instance == USART1)
    {
        if (Size <= BUFF_SIZE)
        {
            HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buff, BUFF_SIZE); // 接收完毕后重启
            HAL_UART_Transmit(&huart1, rx_buff, Size, 0xffff);         // 将接收到的数据再发出
            memset(rx_buff, 0, BUFF_SIZE);							   // 清除接收缓存
        }
        else  // 接收数据长度大于BUFF_SIZE
        {
            
        }
    }
}

STM32CubeMX中,DMA的Mode我们选择了Normal,DMA会在完成一次接收后自动关闭,再次调用需要重新手动打开。

然后来看HAL_UART_ErrorCallback()这个回调函数,串口发生错误时会调用这个回调函数。

/**
  * @brief  UART error callback.
  * @param  huart UART handle.
  * @retval None
  */
__weak void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);

  /* NOTE : This function should not be modified, when the callback is needed,
            the HAL_UART_ErrorCallback can be implemented in the user file.
   */
}

同样这也是个__weak类型的函数,我们也来手动实现一下。
主要功能为:发生错误时调用HAL_UARTEx_ReceiveToIdle_DMA()来重新开启串口空闲中断和DMA。

void HAL_UART_ErrorCallback(UART_HandleTypeDef * huart)
{
    if(huart->Instance == USART1)
    {
		HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buff, BUFF_SIZE); // 接收发生错误后重启
        memset(rx_buff, 0, BUFF_SIZE);
        
    }
}

完整的main.c文件内容如下

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"

#define BUFF_SIZE	100

uint8_t rx_buff[BUFF_SIZE];

void SystemClock_Config(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  /* 需要在初始化时调用一次否则无法接收到内容 */
  HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buff, BUFF_SIZE);	
  while (1)
  {

  }
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSE;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef * huart, uint16_t Size)
{
    if(huart->Instance == USART1)
    {
        if (Size <= BUFF_SIZE)
        {
            HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buff, BUFF_SIZE); // 接收完毕后重启
            HAL_UART_Transmit(&huart1, rx_buff, Size, 0xffff);         // 将接收到的数据再发出
            memset(rx_buff, 0, BUFF_SIZE);							   // 清除接收缓存
        }
        else  // 接收数据长度大于BUFF_SIZE,错误处理
        {
            
        }
    }
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef * huart)
{
    if(huart->Instance == USART1)
    {
		HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buff, BUFF_SIZE); // 接收发生错误后重启
		memset(rx_buff, 0, BUFF_SIZE);							   // 清除接收缓存
        
    }
}

void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }

}

3、测试结果

【STM32 HAL库实战】串口DMA + 空闲中断 实现不定长数据接收_第7张图片

4、总结

利用HAL_UARTEx_ReceiveToIdle_DMA()可以方便的实现串口不定长数据接收,使用时有2个地方需要注意:

  • 接收缓存需要开大一些,防止数据大小大于缓存大小导致接收的数据出错
  • 记得手动实现HAL_UART_ErrorCallback()

你可能感兴趣的:(stm32,嵌入式硬件,单片机)