What can cause UART overrun error while using DMA for reception?
Hello,
I am implementing a Modbus Master on a STM32F722.
First, transmissions and receptions were managed through Rx/Tx interrupts. But as I got a lot of overrrun errors while getting my slave's responses, I decided to transfer received packet through DMA to my Rx buffer to avoid microcontroller's overuse. As my board is a modbus master, I use RTOR to generate an interrupt when slave finished its transfer, as adviced in STM32F7's reference manual. Here is my initialisation function:
static void USART2_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* USER CODE BEGIN USART2_MspInit 0 */
/* USER CODE END USART2_MspInit 0 */
/* Enable Peripheral clock */
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART2 GPIO Configuration
PA1 ------> USART2_DE
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = Modbus_System_USART2_DE_Pin|Modbus_System_USART2_TX_Pin|Modbus_System_USART2_RX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* Peripheral DMA init*/
hdma_usart2_tx.Instance = DMA1_Stream6;
hdma_usart2_tx.Init.Channel = DMA_CHANNEL_4;
hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart2_tx.Init.Mode = DMA_NORMAL;
hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
{
Error_Handler( );
}
hdma_usart2_rx.Instance = DMA1_Stream5;
hdma_usart2_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
// no need of circular mode as we are only expecting slave's responses
hdma_usart2_rx.Init.Mode = DMA_NORMAL;
hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_usart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
// register our transfer complete callback
if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
{
Error_Handler( );
}
// clear interrupt flags in USART
WRITE_REG(uartHandle->Instance->ICR,
(USART_ICR_CMCF |USART_ICR_EOBCF | USART_ICR_RTOCF | USART_ICR_CTSCF |
USART_ICR_LBDCF | USART_ICR_TCBGTCF | USART_ICR_TCCF | USART_ICR_IDLECF |
USART_ICR_ORECF | USART_ICR_NCF | USART_ICR_FECF | USART_ICR_PECF));
__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx);
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);
/* Peripheral interrupt init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_MspInit 1 */
/* USER CODE END USART2_MspInit 1 */
}Note: do not take care about DMA configuration for Tx. In practice it is not used
As I am in half duplex, I enable Rx reception as soon as transmission is complete :
case MODBUS_STATE_CORE_MASTER_FRAME_SEND:
if(DataRS485->Config.EmissionTrameEnCours == 0)
{
DataRS485->ModbusCoreState = MODBUS_STATE_CORE_MASTER_FRAME_WAIT_REPLY;
// lancement de l'ecoute pour la reception
startRxDma();
}
break;with
void startRxDma(void)
{
// set timeout on idle
MODIFY_REG(huart2.Instance->RTOR, USART_RTOR_RTO, MODBUS_TIMEOUT_IN_BITS);
// enable timeout IT
SET_BIT(huart2.Instance->CR2, USART_CR2_RTOEN);
HAL_UART_Receive_DMA(&huart2, RS485Master.RX, UART_RX_BUF_SIZE);
}Here is my UART interrrupt handler:
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->ISR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr2its = READ_REG(huart->Instance->CR2);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags;
uint32_t errorcode;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE));
if (errorflags == 0U)
{
// UART in mode Receiver on DMA : receiver timeout
if (((isrflags & USART_ISR_RTOF) != 0U)
&& ((cr2its & USART_CR2_RTOEN) != 0U))
{
// WARNING do not use HAL_UART_DMAStop(), as it stops ALL DMA streams on this UART (= Rx AND Tx)
// stop Rx DMA transfer
__HAL_DMA_DISABLE(huart->hdmarx);
// clear RTOEN bit to avoid phantom timeout its
CLEAR_BIT(huart->Instance->CR2, USART_CR2_RTOEN);
// clear RTOF flag to acknowledge receiver timeout it
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_RTOF);
// DMA IT will fire when transfer is complete
}
else if (((isrflags & USART_ISR_RXNE) != 0U)
&& ((cr1its & USART_CR1_RXNEIE) != 0U))
{
/* UART in mode Receiver on IT ---------------------------------------------------*/
if (huart->RxISR != NULL)
{
huart->RxISR(huart);
}
return;
}
}
/* If some errors occur */
if ((errorflags != 0U)
&& (((cr3its & USART_CR3_EIE) != 0U)
|| ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != 0U)))
{
/* UART parity error interrupt occurred -------------------------------------*/
if (((isrflags & USART_ISR_PE) != 0U) && ((cr1its & USART_CR1_PEIE) != 0U))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_PEF);
huart->ErrorCode |= HAL_UART_ERROR_PE;
}
/* UART frame error interrupt occurred --------------------------------------*/
if (((isrflags & USART_ISR_FE) != 0U) && ((cr3its & USART_CR3_EIE) != 0U))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_FEF);
huart->ErrorCode |= HAL_UART_ERROR_FE;
}
/* UART noise error interrupt occurred --------------------------------------*/
if (((isrflags & USART_ISR_NE) != 0U) && ((cr3its & USART_CR3_EIE) != 0U))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_NEF);
huart->ErrorCode |= HAL_UART_ERROR_NE;
}
/* UART Over-Run interrupt occurred -----------------------------------------*/
if (((isrflags & USART_ISR_ORE) != 0U)
&& (((cr1its & USART_CR1_RXNEIE) != 0U) ||
((cr3its & USART_CR3_EIE) != 0U)))
{
__HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF);
huart->ErrorCode |= HAL_UART_ERROR_ORE;
}
/* Call UART Error Call back function if need be --------------------------*/
if (huart->ErrorCode != HAL_UART_ERROR_NONE)
{
/* UART in mode Receiver ---------------------------------------------------*/
if (((isrflags & USART_ISR_RXNE) != 0U)
&& ((cr1its & USART_CR1_RXNEIE) != 0U))
{
if (huart->RxISR != NULL)
{
huart->RxISR(huart);
}
}
/* If Overrun error occurs, or if any error occurs in DMA mode reception,
consider error as blocking */
errorcode = huart->ErrorCode;
if ((HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR)) ||
((errorcode & HAL_UART_ERROR_ORE) != 0U))
{
/* Blocking error : transfer is aborted
Set the UART state ready to be able to start again the process,
Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
UART_EndRxTransfer(huart);
/* Disable the UART DMA Rx request if enabled */
if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* Abort the UART DMA Rx channel */
if (huart->hdmarx != NULL)
{
/* Set the UART DMA Abort callback :
will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
/* Abort DMA RX */
if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
{
/* Call Directly huart->hdmarx->XferAbortCallback function in case of error */
huart->hdmarx->XferAbortCallback(huart->hdmarx);
}
}
else
{
/* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
else
{
/* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
}
else
{
/* Non Blocking error : transfer could go on.
Error is notified to user through user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered error callback*/
huart->ErrorCallback(huart);
#else
/*Call legacy weak error callback*/
HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
huart->ErrorCode = HAL_UART_ERROR_NONE;
}
}
return;
} /* End if some error occurs */
/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_ISR_TXE) != 0U)
&& ((cr1its & USART_CR1_TXEIE) != 0U))
{
if (huart->TxISR != NULL)
{
huart->TxISR(huart);
}
return;
}
/* UART in mode Transmitter (transmission end) -----------------------------*/
if (((isrflags & USART_ISR_TC) != 0U) && ((cr1its & USART_CR1_TCIE) != 0U))
{
UART_EndTransmit_IT(huart);
return;
}
}My DMA transfer complete ISR:
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
uint32_t tmpisr;
__IO uint32_t count = 0;
uint32_t timeout = SystemCoreClock / 9600;
/* 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 ***********************************/
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);
}
}
}
/* 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;
}
dmaTransferComplete = 1;
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);
}
}
}Then my UART Rx complete callback notifies higher level that a new frame has been received:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
#ifndef DISABLE_RS485_MODBUS
#ifdef RECEIVE_IT
Handle_UartFrame_ForModbus_RxCpltCallback(huart, &UartFrameMHI);
// Handle_UartFrame_ForModbus_RxCpltCallback(huart, &UartFrameUser);
if(huart->Instance == USART2)
{
InterruptionRxModbus(0);
}
#else // reception on DMA
if(1 == dmaTransferComplete)
{
frameReceived = 1;
// get nr of bytes got during this transfer
remainingBytesToTransfer = __HAL_DMA_GET_COUNTER(huart->hdmarx);
dmaTransferComplete = 0;
}
#endif
#endif // DISABLE_RS485_MODBUS
}... But I am still getting overrun errors, and in this case, Rx buffer is void.
I am wondering how a Rx overrun can happen when reception is done through DMA, and Rx buffer is big enough (this is my case). As DMA transfer is supposed to start as soon as Rx register is filled. Right?
Any idea?
