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

posted @ 2022-06-13 19:57  小小小p鱼  阅读(2586)  评论(0编辑  收藏  举报