STM32串口DMA接收双缓冲

STM32高端MCU(F4、F7等)才支持DMA双缓冲,低端MCU(F1)不支持DMA双缓冲,不过有替代方案可实现类型效果。

一、MCU支持DMA双缓冲的情形

不再赘述,参见博客 STM32 串口DMA发送+DMA接收+硬件双缓冲区切换功能实现

二、MCU不支持DMA双缓冲,但可通过DMA传输半完成中断替代,以下代码已在F103上验证通过。

1.先通过STM32CubeMX生成串口初始化代码

串口接收DMA一定要选择Circular模式,并且使能串口接收中断

串口接收DMA一定要选择Circular模式

使能串口接收中断

2.使能空闲中断,空闲中断中取出接收数据

3.在DAM接收半完成、完成中断中取出接收数据

usart.c:

/* Includes ------------------------------------------------------------------*/
#include "usart.h"

/* USER CODE BEGIN 0 */

#define DE_UART_DMA_BUF_LEN    0x1E
static uint8_t RecvDMABuf[DE_UART_DMA_BUF_LEN];

/* USER CODE END 0 */

UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_tx;
DMA_HandleTypeDef hdma_usart1_rx;

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 DMA Init */
    /* USART1_TX Init */
    hdma_usart1_tx.Instance = DMA1_Channel4;
    hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_tx.Init.Mode = DMA_NORMAL;
    hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);

    /* USART1_RX Init */
    hdma_usart1_rx.Instance = DMA1_Channel5;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */

    /*Enable the IDLE Interrupt*/
    __HAL_UART_ENABLE_IT(uartHandle,UART_IT_IDLE);
    __HAL_UART_CLEAR_IDLEFLAG(uartHandle);
    
  /* USER CODE END USART1_MspInit 1 */
  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspDeInit 0 */

  /* USER CODE END USART1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_USART1_CLK_DISABLE();

    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);

    /* USART1 DMA DeInit */
    HAL_DMA_DeInit(uartHandle->hdmatx);
    HAL_DMA_DeInit(uartHandle->hdmarx);

    /* USART1 interrupt Deinit */
    HAL_NVIC_DisableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspDeInit 1 */

  /* USER CODE END USART1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */

/*
 * This function is called on DMA TC or HT events, and on UART IDLE (if enabled) event.
 */
void  UsartProcessData(const void* data, size_t len) 
{
     uint8_t* pData = (uint8_t*)data;
//    DebugPrintf("RecvLen:%d\n",len);
//    DebugHexPrint(pData,len);
}

void  UsartRxCheck(UART_HandleTypeDef *huart) 
{
    static uint16_t OldPos = 0;
    /* Calculate current position in buffer and check for new data available */
    uint16_t NewPos = DE_UART_DMA_BUF_LEN - __HAL_DMA_GET_COUNTER(huart->hdmarx);

    if (NewPos != OldPos)     /* Check change in received data */
    {                       
        if (NewPos > OldPos)  /* Current position is over previous one */
        {                    
            /*
             * Processing is done in "linear" mode.
             *
             * Application processing is fast with single data block,
             * length is simply calculated by subtracting pointers
             */
            UsartProcessData(&RecvDMABuf[OldPos], NewPos - OldPos);
        }
        else
        {
            /*
             * Processing is done in "overflow" mode..
             *
             * Application must process data twice,
             * since there are 2 linear memory blocks to handle
             */
            UsartProcessData(&RecvDMABuf[OldPos], DE_UART_DMA_BUF_LEN - OldPos);
            if (NewPos > 0) 
            {
                UsartProcessData(&RecvDMABuf[0], NewPos);
            }
        }
        OldPos = NewPos;   /* Save current position as old for next transfers */
    }
}

/**
  * @brief  Rx Half Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == huart1.Instance)
    {
        //DebugPrintf("HAL_UART_RxHalfCpltCallback\n");
        UsartRxCheck(huart);
    }
}

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == huart1.Instance)
    {
        //DebugPrintf("HAL_UART_RxCpltCallback\n");
        UsartRxCheck(huart);
    }
}

/**
  * @brief  Rx IDLE callback
  * @param  huart: UART handle. 
  * @note   This example shows a simple way to report end of DMA Tx transfer, and 
  *         you can add your own implementation. 
  * @retval None
  */
static void UART_RxIdleCallBack(UART_HandleTypeDef *huart)
{
    if(huart->Instance == huart1.Instance)
    {
        //DebugPrintf("UART_IDLE_CallBack\n");
        UsartRxCheck(huart);
    }
}

/**
  * @brief  UART_IDLE_IRQHandler
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval HAL status
  */
void UART_IDLE_IRQHandler(UART_HandleTypeDef *huart)
{
    if(__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET)
    {
         __HAL_UART_CLEAR_IDLEFLAG(huart);
         __HAL_UART_FLUSH_DRREGISTER(huart);
         UART_RxIdleCallBack(huart);
    }
}

void UART_StartReceive(void)
{
    HAL_UART_Receive_DMA(&huart1,RecvDMABuf,sizeof(RecvDMABuf));
}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

1、此时DMA缓冲区类似一个循环buffer,接收满后自动从头部接收。
2、在DMA接收半完成中断和DMA接收完成中断之后的空闲中断中不能重复取接收数据。
3、该方法也适用于F4、F7等高端MCU。

### STM32 DMA双缓冲工作原理 对于STM32高端MCU(如F4、F7系列),DMA控制器支持双缓冲功能,这使得数据传输更加高效可靠。当启用双缓冲模式时,DMA可以在两个内存区域之间交替操作,在一个缓冲区满的同时另一个可以立即被CPU处理[^1]。 具体来说,DMA通道配置成循环模式下的双缓冲机制后,能够自动切换当前使用的缓冲地址。每当完成一次完整的传输请求之后,DMA会触发中断通知处理器更换下一个目标位置继续写入新到来的数据流。这种方式不仅提高了效率还减少了因为等待而造成的延迟时间。 ```c // 配置DMA双缓冲模式 static void MX_DMA_Init(void) { __HAL_RCC_DMA2_CLK_ENABLE(); /* Configure the DMA handler for transmission */ hdma_usart1_tx.Instance = DMA2_Stream7; hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_tx.Init.Mode = DMA_CIRCULAR; // 循环模式 hdma_usart1_tx.Init.Priority = DMA_PRIORITY_HIGH; HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 0, 1); HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn); if (HAL_OK != HAL_DMA_Init(&hdma_usart1_tx)) Error_Handler(); } ``` 为了获取当前正在使用的缓冲区指针,可以通过调用`DMA_GetCurrentMemoryTarget()`来查询。此API返回的是当前活动的那个存储器的目标地址,这对于管理多个缓冲区非常有用[^2]。 在实际项目开发过程中,如果遇到不支持硬件级双缓冲特性的低功耗MCU型号,则可通过软件模拟的方式来达到相似的效果。例如通过定时器或其他事件驱动的方式手动控制两片外部SRAM作为临时缓存空间轮流读取/发送数据包给外设接口。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值