• HAL库——UART的DMA接收中的一些问题


    上篇简单的说明了如何通过DMA的方式接收UART数据,看着这个UART的DMA接收很简单,为了弄明白DMA中断和UART中断之间的关系,还是要看一下程序是如何完成这些看似简单的操作。首先先说一下整个接收的过程:

    启动UART的DMA接收(这里面还定义了DMA回调函数):HAL_UART_Receive_DMA
    接收完成后,请求DMA中断(判断中断的类型):HAL_DMA_IRQHandler
    DMA接收完成回调函数(同时关闭了DMA接收):UART_DMAReceiveCplt
    UART接收回调函数(处理数据,启动DMA接收):HAL_UART_RxCpltCallback
    看着还是挺简单的4个过程,现在写出来的这4个过程是我看了整整一天才弄明白(不太聪明的样子)。当时最大的困惑就是怎么一会UART中断,一会DMA中断;一会UART回调,一会DMA回调。当时的想法是就是既然用了DMA接收,为什么不直接在DMA的中断和回调里面完成。下面详细的说明一下这个过程:

    一:HAL_UART_Receive_DMA
    这是第一个执行的函数,所以先搞它,需要重点关注回调函数的定义:

    HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    {
    uint32_t *tmp;

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

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->pRxBuffPtr = pData; //看这里1
    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; //看这里2

    /* 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 */ //看这里3
    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);

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Parity Error Interrupt */
    SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);

    /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    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 */
    SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    return HAL_OK;
    }
    else
    {
    return HAL_BUSY;
    }
    }
    看着很长,但是这部分对理解整个过程很关键(重点关注”看这里“)。解释一下这段程序:

    函数的参数有三个,简单的说就是:UART的结构体 、接收数据的数组指针、接收数据的多少
    看这里1:将自定义的数组和size赋值给huart结构体,这样数据就会存储到我们定义的数组中了
    看这里2:自定义DMA接收完成的回调函数(通过函数指针的方式)
    看这里3:使能UART DMA数据流,就可以接收UART发送过来的数据了

    二: HAL_DMA_IRQHandler
    DMA中断请求函数,每一种外设都有很多类型的中断,但是只有一个中断请求的入口,这样就显得很简洁,好,看一下这个函数说了什么:

    void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
    {
    uint32_t tmpisr;
    __IO uint32_t count = 0U;
    uint32_t timeout = SystemCoreClock / 9600U;

    /* calculate DMA base and stream number */
    DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;

    tmpisr = regs->ISR;

    /* Transfer Error Interrupt management ***************************************/
    if ((tmpisr & (DMA_FLAG_TEIF0_4 << hdma->StreamIndex)) != RESET)
    {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TE) != RESET)
    {
    /* Disable the transfer error interrupt */
    hdma->Instance->CR &= ~(DMA_IT_TE);

    /* Clear the transfer error flag */
    regs->IFCR = DMA_FLAG_TEIF0_4 << hdma->StreamIndex;

    /* Update error code */
    hdma->ErrorCode |= HAL_DMA_ERROR_TE;
    }
    }
    /* FIFO Error Interrupt management ******************************************/
    if ((tmpisr & (DMA_FLAG_FEIF0_4 << hdma->StreamIndex)) != RESET)
    {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_FE) != RESET)
    {
    /* Clear the FIFO error flag */
    regs->IFCR = DMA_FLAG_FEIF0_4 << hdma->StreamIndex;

    /* Update error code */
    hdma->ErrorCode |= HAL_DMA_ERROR_FE;
    }
    }
    /* Direct Mode Error Interrupt management ***********************************/
    if ((tmpisr & (DMA_FLAG_DMEIF0_4 << hdma->StreamIndex)) != RESET)
    {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_DME) != RESET)
    {
    /* Clear the direct mode error flag */
    regs->IFCR = DMA_FLAG_DMEIF0_4 << hdma->StreamIndex;

    /* Update error code */
    hdma->ErrorCode |= HAL_DMA_ERROR_DME;
    }
    }
    /* Half Transfer Complete Interrupt management ******************************/
    if ((tmpisr & (DMA_FLAG_HTIF0_4 << hdma->StreamIndex)) != RESET)
    {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_HT) != RESET)
    {
    /* Clear the half transfer complete flag */
    regs->IFCR = DMA_FLAG_HTIF0_4 << hdma->StreamIndex;

    /* Multi_Buffering mode enabled */
    if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET)
    {
    /* Current memory buffer used is Memory 0 */
    if((hdma->Instance->CR & DMA_SxCR_CT) == RESET)
    {
    if(hdma->XferHalfCpltCallback != NULL)
    {
    /* Half transfer callback */
    hdma->XferHalfCpltCallback(hdma);
    }
    }
    /* Current memory buffer used is Memory 1 */
    else
    {
    if(hdma->XferM1HalfCpltCallback != NULL)
    {
    /* Half transfer callback */
    hdma->XferM1HalfCpltCallback(hdma);
    }
    }
    }
    else
    {
    /* Disable the half transfer interrupt if the DMA mode is not CIRCULAR */
    if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET)
    {
    /* Disable the half transfer interrupt */
    hdma->Instance->CR &= ~(DMA_IT_HT);
    }

    if(hdma->XferHalfCpltCallback != NULL)
    {
    /* Half transfer callback */
    hdma->XferHalfCpltCallback(hdma);
    }
    }
    }
    }
    /* Transfer Complete Interrupt management ***********************************/ // 看这里1
    if ((tmpisr & (DMA_FLAG_TCIF0_4 << hdma->StreamIndex)) != RESET)
    {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TC) != RESET)
    {
    /* Clear the transfer complete flag */
    regs->IFCR = DMA_FLAG_TCIF0_4 << hdma->StreamIndex;

    if(HAL_DMA_STATE_ABORT == hdma->State)
    {
    /* Disable all the transfer interrupts */
    hdma->Instance->CR &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);
    hdma->Instance->FCR &= ~(DMA_IT_FE);

    if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL))
    {
    hdma->Instance->CR &= ~(DMA_IT_HT);
    }

    /* Clear all interrupt flags at correct offset within the register */
    regs->IFCR = 0x3FU << hdma->StreamIndex;

    /* Process Unlocked */
    __HAL_UNLOCK(hdma);

    /* Change the DMA state */
    hdma->State = HAL_DMA_STATE_READY;

    if(hdma->XferAbortCallback != NULL)
    {
    hdma->XferAbortCallback(hdma);
    }
    return;
    }

    if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET)
    {
    /* Current memory buffer used is Memory 0 */
    if((hdma->Instance->CR & DMA_SxCR_CT) == RESET)
    {
    if(hdma->XferM1CpltCallback != NULL)
    {
    /* Transfer complete Callback for memory1 */
    hdma->XferM1CpltCallback(hdma);
    }
    }
    /* Current memory buffer used is Memory 1 */
    else
    {
    if(hdma->XferCpltCallback != NULL)
    {
    /* Transfer complete Callback for memory0 */
    hdma->XferCpltCallback(hdma); //看这里2
    }
    }
    }
    /* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */
    else
    {
    if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET)
    {
    /* Disable the transfer complete interrupt */
    hdma->Instance->CR &= ~(DMA_IT_TC);

    /* Process Unlocked */
    __HAL_UNLOCK(hdma);

    /* Change the DMA state */
    hdma->State = HAL_DMA_STATE_READY;
    }

    if(hdma->XferCpltCallback != NULL)
    {
    /* Transfer complete callback */
    hdma->XferCpltCallback(hdma);
    }
    }
    }
    }

    /* manage error case */
    if(hdma->ErrorCode != HAL_DMA_ERROR_NONE)
    {
    if((hdma->ErrorCode & HAL_DMA_ERROR_TE) != RESET)
    {
    hdma->State = HAL_DMA_STATE_ABORT;

    /* Disable the stream */
    __HAL_DMA_DISABLE(hdma);

    do
    {
    if (++count > timeout)
    {
    break;
    }
    }
    while((hdma->Instance->CR & DMA_SxCR_EN) != RESET);

    /* Process Unlocked */
    __HAL_UNLOCK(hdma);

    /* Change the DMA state */
    hdma->State = HAL_DMA_STATE_READY;
    }

    if(hdma->XferErrorCallback != NULL)
    {
    /* Transfer error callback */
    hdma->XferErrorCallback(hdma);
    }
    }
    }
    这个更长了,因为这里面处理了DMA所有的中断请求,肯定要查询一遍寄存器,进来的是哪个中断,然后对症下药,我们用到的是DMA接收UART数据完成的中断,所以关注这一部分就可以了。解释一下这段程序:

    看这里1:那个位置就是这段程序通过查询各个状态寄存器,终于找到了原来是你小子(DMA接收完成中断)在敲门
    看这里2:到这里是这段程序搞明白了开哪个门你能进来,这个门就是对应的回调函数
    当然,仔细看了之后会发现,有两个门很相似 hdma->XferCpltCallback(hdma)和hdma->XferM1CpltCallback(hdma),这是因为DMA可以配置成双缓冲模式,两个缓冲器交替工作,由于本例没有使用这种模式,所以程序会直接开第一个门
    在上面说到了,通过函数指针的方式,定义了DMA的接收完成回调函数,所以程序执行hdma->XferCpltCallback(hdma)时就会调用定义的UART_DMAReceiveCplt

    三:UART_DMAReceiveCplt
    DMA接收完成回调函数,主要实现两个任务:关闭DMA的接收,调用UART接收回调函数(在DMA发送时,这部分会不一样,下篇再见)。好,看一下程序:

    static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
    {
    UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
    /* DMA Normal mode*/
    if ((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
    {
    huart->RxXferCount = 0U;

    /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
    CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Disable the DMA transfer for the receiver request by setting the DMAR bit
    in the UART CR3 register */
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR); //看这里1

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;
    }
    #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) //看这里2
    /*Call registered Rx complete callback*/
    huart->RxCpltCallback(huart);
    #else
    /*Call legacy weak Rx complete callback*/
    HAL_UART_RxCpltCallback(huart);
    #endif /* USE_HAL_UART_REGISTER_CALLBACKS */
    }
    这段程序比较短,基本都是干货,解释几个点:

    看这里1:关闭了UART的DMA接收模式
    看这里2:这是一段条件编译,(USE_HAL_UART_REGISTER_CALLBACKS == 1)是说允许用户动态编写回调函数,在UART部分没有自定义回调函数,所以执行else,系统默认会有一个回调弱函数,不过我们会重新写这个函数,所以会转到执行我们写的HAL_UART_RxCpltCallback。

    四: HAL_UART_RxCpltCallback
    UART接收回调函数,完成数据处理和开启下次DMA接收的任务,示例如下:

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    RemoteDateProcess(rData);//数据处理

    HAL_UART_Receive_DMA(&huart1, rData, 18);//开启串口DMA的接收
    }
    总结:一图以蔽之


    ————————————————
    版权声明:本文为CSDN博主「jcsm__」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/jcsm__/article/details/102702717

  • 相关阅读:
    How to print GETDATE() in SQL Server with milliseconds in time?
    StarLink卫星网络如何工作
    Chinasat16
    天线增益计算
    Schemachine
    源代码之整洁代码
    关于进程内缓存与分布式缓存
    IOT物联网时代测试演化
    互联网大促运维实践案例
    Kubernetes应用场景
  • 原文地址:https://www.cnblogs.com/birdBull/p/16372152.html
Copyright © 2020-2023  润新知