STM32
直播中

哔哔哔-

9年用户 1289经验值
擅长:电源/新能源
私信 关注
[问答]

如何解决HAL库中存在DMA发送和接收的问题?

如何解决HAL库中存在DMA发送和接收的问题?

回帖(1)

谷菁

2021-12-10 10:43:10
前言

本文是在使用 STM32L4 的串口 DMA 功能时,使用 HAL 库出现的一些问题,通过以下方式解决了 HAL 库中存在 DMA 发送和接收的一些问题。
STM32L4 的 DMA 简介

DMA Mapping



























DMA 相关配置及使用
以下根据 STM32L43xxx 系列进行 USART2 + DMA 的开发。





串口配置
    sg_USART2_HandleStruct.Instance                       = USART2;
    sg_USART2_HandleStruct.Init.BaudRate                  = bound;
    sg_USART2_HandleStruct.Init.WordLength                = UART_WORDLENGTH_8B;       /* 字长为8位数据格式 */
    sg_USART2_HandleStruct.Init.StopBits                  = UART_STOPBITS_1;          /* 一个停止位 */
    sg_USART2_HandleStruct.Init.Parity                    = UART_PARITY_NONE;         /* 无奇偶校验位 */
    sg_USART2_HandleStruct.Init.Mode                      = UART_MODE_TX_RX;          /* 收发模式 */
    sg_USART2_HandleStruct.Init.HwFlowCtl                 = UART_UART_HWCONTROL_NONE_RTS_CTS;
    sg_USART2_HandleStruct.Init.OverSampling              = UART_OVERSAMPLING_16;
    sg_USART2_HandleStruct.Init.OneBitSampling            = UART_ONE_BIT_SAMPLE_DISABLE;
    sg_USART2_HandleStruct.AdvancedInit.AdvFeatureInit    = UART_ADVFEATURE_NO_INIT;
    HAL_UART_Init(&sg_USART2_HandleStruct) ;



I/O 配置
   


    /* USART2 clock enable */
    __HAL_RCC_USART2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /**USART2 GPIO Configuration   
        PA2     ------> USART2_TX
        PA3     ------> USART2_RX
    */
    GPIO_InitStruct.Pin         = GPIO_PIN_2 | GPIO_PIN_3;
    GPIO_InitStruct.Mode        = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull        = GPIO_PULLUP;
    GPIO_InitStruct.Speed       = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate   = GPIO_AF7_USART2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);



配置并使用 TX + DMA
DMA 配置
根据 DMA MAP 表可知,USART2 TX 可使用 DMA1 通道 7 (1-7),通道请求为 2 (0-7),方向为存储器到外设,并且设置字节长度。


    /* Configure DMA Tx parameters */
    sg_USART2_TxDMAHandleStruct.Instance                 = DMA1_Channel7;
    sg_USART2_TxDMAHandleStruct.Init.Request             = DMA_REQUEST_2;
    sg_USART2_TxDMAHandleStruct.Init.Direction           = DMA_MEMORY_TO_PERIPH;
    sg_USART2_TxDMAHandleStruct.Init.PeriphInc           = DMA_PINC_DISABLE;
    sg_USART2_TxDMAHandleStruct.Init.MemInc              = DMA_MINC_ENABLE;
    sg_USART2_TxDMAHandleStruct.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    sg_USART2_TxDMAHandleStruct.Init.MemDataAlignment    = DMA_PDATAALIGN_BYTE;
    sg_USART2_TxDMAHandleStruct.Init.Priority            = DMA_PRIORITY_HIGH;
    sg_USART2_TxDMAHandleStruct.Init.Mode                = DMA_NORMAL;

    /* Associate the DMA handle */
    __HAL_LINKDMA(uartHandle, hdmatx, sg_USART2_TxDMAHandleStruct);

    /* Stop any ongoing transfer and reset the state*/
    HAL_DMA_DeInit(&sg_USART2_TxDMAHandleStruct);

    /* Configure the DMA Channel */
    HAL_DMA_Init(&sg_USART2_TxDMAHandleStruct);
DMA TX 使用(禁止中断)
定义一个发送函数(该函数没有使用发送完成中断处理,在下次进入该函数时检测 DMA 相关标志并清除,因此,须确保每次调用该函数的间隔时间能完成上次的数据传输),传输完成必须关闭串口 DMA ,否则不能启动下一次 DMA 传输。


void USART2_UART_Transmit(uint8_t *pData, uint16_t len)
{
    if (__HAL_DMA_GET_FLAG(&sg_USART2_HandleStruct, DMA_FLAG_TC7))
    {
        __HAL_DMA_CLEAR_FLAG(&sg_USART2_HandleStruct, DMA_FLAG_TC7);    /* 清除DMA1_Steam7传输完成标志 */
        HAL_UART_DMAStop(&sg_USART2_HandleStruct);                      /* 传输完成以后关闭串口DMA */            
    }
            
    HAL_UART_Transmit_DMA(&sg_USART2_HandleStruct, pData, len);
}
DMA TX 使用(使能中断)
暂无



配置并使用 RX + DMA
DMA 配置
根据 DMA MAP 表可知,USART2 RX 可使用 DMA1 通道 6 (1-7),通道请求为 2 (0-7),方向为外设到存储器,并且设置字节长度。


    /* Configure DMA Rx parameters */
    sg_USART2_RxDMAHandleStruct.Instance                 = DMA1_Channel6;
    sg_USART2_RxDMAHandleStruct.Init.Request             = DMA_REQUEST_2;
    sg_USART2_RxDMAHandleStruct.Init.Direction           = DMA_PERIPH_TO_MEMORY;
    sg_USART2_RxDMAHandleStruct.Init.PeriphInc           = DMA_PINC_DISABLE;
    sg_USART2_RxDMAHandleStruct.Init.MemInc              = DMA_MINC_ENABLE;
    sg_USART2_RxDMAHandleStruct.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    sg_USART2_RxDMAHandleStruct.Init.MemDataAlignment    = DMA_PDATAALIGN_BYTE;
    sg_USART2_RxDMAHandleStruct.Init.Priority            = DMA_PRIORITY_HIGH;
    sg_USART2_RxDMAHandleStruct.Init.Mode                = DMA_NORMAL;

    /* Associate the DMA handle */
    __HAL_LINKDMA(uartHandle, hdmarx, sg_USART2_RxDMAHandleStruct);

    /* Stop any ongoing transfer and reset the state*/
    HAL_DMA_DeInit(&sg_USART2_RxDMAHandleStruct);

    /* Configure the DMA Channel */
    HAL_DMA_Init(&sg_USART2_RxDMAHandleStruct);
接收中断配置
使用 RX DMA,通常采用 DMA + RX + UART_IT_IDLE(空闲中断)用来接收不定长的数据内容。


DMA RX 使用
定义接收中断函数,做相关处理。


void USART2_IRQHandler(void)
{
    uint32_t tmp;
   
    if ((__HAL_UART_GET_IT(&sg_USART2_HandleStruct, UART_IT_IDLE) != RESET))
    {
        /* 清除相关标志 */
        __HAL_UART_CLEAR_IDLEFLAG(&sg_USART2_HandleStruct);
        tmp = sg_USART2_HandleStruct.Instance->ISR;/* 通过读取该寄存器来清除 */
        tmp = sg_USART2_HandleStruct.Instance->RDR;/* 通过读取该寄存器来清除 */
        HAL_UART_DMAStop(&sg_USART2_HandleStruct);
        
        /* 获取实际接收长度 */
        tmp = __HAL_DMA_GET_COUNTER(&sg_USART2_RxDMAHandleStruct);
        sg_USART2_RxLen = USART_MAX_RX_BUF_SIZE - tmp;
        
        g_Usart2RecvFinish = 1;

        /* 重新启动接收 */
        HAL_UART_Receive_DMA(&sg_USART2_HandleStruct, sg_USART2_RxBuffer, USART_MAX_RX_BUF_SIZE);
    }
   
    HAL_UART_IRQHandler(&sg_USART2_HandleStruct);
}
注意事项
1、串口接收中断中若通过函数 HAL_UART_Receive 读取串口数据,会出现没有正常读取数据,导致不停地进入接收中断,造成程序无法正常运行。



void USART2_IRQHandler(void)
{
    uint32_t tmp;
   
    if ((__HAL_UART_GET_IT(&sg_USART2_HandleStruct, UART_IT_RXNE) != RESET))
    {
        将函数
        HAL_UART_Receive(&sg_USART2_HandleStruct, sg_USART2_RxBuffer[sg_USART2_RxLen++], 1, 10);
        修改为
        sg_USART2_RxBuffer[sg_USART2_RxLen++] = USART2->RDR;
    }
   
    HAL_UART_IRQHandler(&sg_USART2_HandleStruct);
}



2、在同时使用 DMA RX 和 DAM TX 的时候,在其中一个完成后会关闭串口 DMA,导致另一个受其影响,导致发送或接收异常。
原因:


由于使用了函数 HAL_UART_DMAStop(&sg_USART2_HandleStruct) 用来关闭 UASRT DMA 将TX 和 RX 均给关闭了,导致影响了另一 DMA 正常传输。


解决方案:


基于函数 HAL_UART_DMAStop(UART_HandleTypeDef *huart) 为原型,定义一个函数 MY_HAL_UART_DMAStop(UART_HandleTypeDef *huart, uint8_t obj),


用来可单独关闭其中一个 DMA 的传输,函数如下,其中函数  My_UART_EndTxTransfer 和 My_UART_EndRxTransfer 也是分别基于函数 HAL_UART_DMAStop(UART_HandleTypeDef *huart) 内部中调用的 UART_EndTxTransfer 和 UART_EndRxTransfer 为原型,除了函数名称,没有其他任何改动(在不改动 HAL 库的情况下可这样自定义三个函数,其中 USART_TX_DMA 是自定义的一个宏)。


/**
  * @brief  End ongoing Tx transfer on UART peripheral (following error detection or Transmit completion).
  * @note   函数原型: UART_EndTxTransfer(UART_HandleTypeDef *huart), 函数功能完全一样
  * @param  huart UART handle.
  * @retval None
  */
static void My_UART_EndTxTransfer(UART_HandleTypeDef *huart)
{
#if defined(USART_CR1_FIFOEN)
    /* Disable TXEIE, TCIE, TXFT interrupts */
    CLEAR_BIT(huart->Instance->CR1, (USART_CR1_TXEIE_TXFNFIE | USART_CR1_TCIE));
    CLEAR_BIT(huart->Instance->CR3, (USART_CR3_TXFTIE));
#else
    /* Disable TXEIE and TCIE interrupts */
    CLEAR_BIT(huart->Instance->CR1, (USART_CR1_TXEIE | USART_CR1_TCIE));
#endif /* USART_CR1_FIFOEN */

    /* At end of Tx process, restore huart->gState to Ready */
    huart->gState = HAL_UART_STATE_READY;
}

/**
  * @brief  End ongoing Rx transfer on UART peripheral (following error detection or Reception completion).
  * @note   函数原型: UART_EndRxTransfer(UART_HandleTypeDef *huart), 函数功能完全一样
  * @param  huart UART handle.
  * @retval None
  */
static void My_UART_EndRxTransfer(UART_HandleTypeDef *huart)
{
  /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
#if defined(USART_CR1_FIFOEN)
    CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE_RXFNEIE | USART_CR1_PEIE));
    CLEAR_BIT(huart->Instance->CR3, (USART_CR3_EIE | USART_CR3_RXFTIE));
#else
    CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
#endif /* USART_CR1_FIFOEN */

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

    /* Reset RxIsr function pointer */
    huart->RxISR = NULL;
}

/**
  * @brief  Stop the DMA Transfer.
  * @note   函数原型: HAL_UART_DMAStop(UART_HandleTypeDef *huart), 可单独关闭 TX/RX 其中一个DMA
  * @param  huart UART handle.
  * @param  obj   USART_TX_DMA or USART_RX_DMA.
  * @retval HAL status
  */
HAL_StatusTypeDef MY_HAL_UART_DMAStop(UART_HandleTypeDef *huart, uint8_t obj)
{
    const HAL_UART_StateTypeDef gstate = huart->gState;
    const HAL_UART_StateTypeDef rxstate = huart->RxState;

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

            /* Abort the UART DMA Tx channel */
            if (huart->hdmatx != NULL)
            {
                if (HAL_DMA_Abort(huart->hdmatx) != HAL_OK)
                {
                    if (HAL_DMA_GetError(huart->hdmatx) == HAL_DMA_ERROR_TIMEOUT)
                    {
                        /* Set error code to DMA */
                        huart->ErrorCode = HAL_UART_ERROR_DMA;

                        return HAL_TIMEOUT;
                    }
                }
            }

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

            /* Abort the UART DMA Rx channel */
            if (huart->hdmarx != NULL)
            {
                if (HAL_DMA_Abort(huart->hdmarx) != HAL_OK)
                {
                    if (HAL_DMA_GetError(huart->hdmarx) == HAL_DMA_ERROR_TIMEOUT)
                    {
                        /* Set error code to DMA */
                        huart->ErrorCode = HAL_UART_ERROR_DMA;

                        return HAL_TIMEOUT;
                    }
                }
            }

            My_UART_EndRxTransfer(huart);
        }
    }

    return HAL_OK;
}
举报

更多回帖

发帖
×
20
完善资料,
赚取积分