2024-05-22 02:06 AM
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?
Solved! Go to Solution.
2024-05-29 02:44 AM
You're doing more work than needed. Use HAL_UARTEx_ReceiveToIdle_DMA and HAL_UARTEx_RxEventCallback instead.
See this project for better understanding how to use HAL_UARTEx_ReceiveToIdle_DMA to your advantage
https://github.com/karlyamashita/Nucleo-G431RB_Three_UART/wiki
In your case you don't need a ring buffer as the receive buffer won't get overwritten until you transmit another message to the slave.
2024-05-23 12:21 AM
Hi,
While investigating further, I found that all interrupts on my board were at highest priority level. So I decreased the one for DMA transfer to SPI. Now I do not get overrun errors any more, but I am facing very strange behavior from DMA:
_ sometimes it is "repeating" the last received byte in Rx buffer, whereas the received frame (from my modbus spy point of view) is correct, and so the value read in NDTR after the end of the transfer is 1 byte too little. The thing that astonishes me, is that when I read this register with ST link after my modbus module has reported an erroneous frame, it is at the expected value, ie without the extra byte count :thinking_face:
_ I stop DMA transfer after having received a frame, and start it again after having sent a new question to my slave. So the next DMA transfer should restart from the beginning of my Rx buffer... But sometimes it is starting from Rx[1] instead... New frame error from my modbus module point of view... :thinking_face:
I am really wondering what I made wrong on my DMA configuration...
2024-05-23 06:02 PM
More than likely an issue in your Handle_UartFrame_ForModbus_RxCpltCallback that you're not showing what you're doing in that function..
2024-05-28 01:07 AM
Hi Karl,
Thank you for your reply. The function Handle_UartFrame_ForModbus_RxCpltCallback is just setting high-level variables, so it can not be the cause of my trouble.
In the meanwhile, I found that there were still overrun errors, but I did not see them at first glance. I found that they were due to the fact that I did not start modbus reception immediately after having finished transmission, but at next main loop. And as the microcontroller is overloaded, I may miss answers from my slave. After having started UART Rx DMA transfer in my Tx complete callback, I succeded to remove overrun errors.
But I am still getting troubles receiving my 3rd frame: the byte count I am getting in HAL_UART_RxCpltCallback (called when DMA transfer is complete) is not correct, although the received frame is the expected one in the Rx buffer.
After having set a breakpoint in this function, I saw that it was called during UART transmission, so it was returning the size of previous received frame. Seems like I am still getting synchronization errors in my code...
2024-05-28 12:16 PM
Can't help you if you don't show all your relevant code. Is it a fixed size data length you are receiving? Is the device sending data being continuously streamed or sent as packets? How are you saving the data when the callback is called? Are you using a ring buffer for the received data? Too many things are unknown.
2024-05-29 02:13 AM
Hi Karl,
I do not know the size of the data I am receiving. It depends on the frame I am sending before, and the response type of my slave (OK/Error). That is why I am using RTOR to detect end of frame, as adviced in ST32F7xx's reference manual:
This is done in my startRxDma() function. The only difference between now and my first post is that I moved its call in UART's transfer complete callback, and added check of HAL's return state:
uint8_t 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);
if(HAL_OK != HAL_UART_Receive_DMA(&huart2, RS485Master.RX, UART_RX_BUF_SIZE))
{
return 0;
}
else
{
return 1;
}
}
Now called by this function:
void CallbackEndTXModbus(TYPE_MODBUS *handle)
{
handle->Config.EmissionTrameEnCours = 0; //Flag pour indiquer qu'une émission est en cours
// start Rx DMA now, otherwise we may miss slave's response
if(1 == startRxDma())
{
handle->TimerTimeout = 0;
handle->ModbusCoreState = MODBUS_STATE_CORE_MASTER_FRAME_WAIT_REPLY;
}
//Statistiques
//------------
if(handle->NbTramesTx < UINT32_MAX)
{
handle->NbTramesTx++;
}
}
Called by
void InterruptionTxModbus(void)
{
SetPinMode(&RS485Master, RS485_LISTEN_MODE);
CallbackEndTXModbus(&RS485Master);
//Vu que l'on vient d'envoyer un octet, le bus n'est pas libre
RS485Master.Config.BusLibre = 0;
RS485Master.TimerBusLibre = 0;
}
Called by
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART2)
{
InterruptionTxModbus();
}
}
If startRxDma fails to start DMA transfer, an attempt will be done in my modbus' state machine at each call until it succeeds :
case MODBUS_STATE_CORE_MASTER_FRAME_SEND:
if(DataRS485->Config.EmissionTrameEnCours == 0)
{
// lancement de l'ecoute pour la reception si pas encore fait
if(1 == startRxDma())
{
DataRS485->TimerTimeout = 0;
DataRS485->ModbusCoreState = MODBUS_STATE_CORE_MASTER_FRAME_WAIT_REPLY;
}
// else we will try DMA reception again at next call
}
break;
Modbus is a master/slave protocol, where slaves can "speak" only when master is sending them a question, so I am expecting only a packet from my slave (no continuous streaming). Moreover, reception and transmission cannot be done simultaneously, as my hardware is half duplex, ie transmission and reception are done on the same line. So I enable reception only after the end of transmission of a packet.
Which callback are you talking about? There are so many...
Received packets are supposed to be transfered to Rx buffer (RS485Master.RX) thanks to DMA as soon as they arrive in UART's reception register, and I do not need circular buffer mode, as I am only expecting one frame per reception, as stated in my DMA's initialisation function (please look at my first post).
UART will fire a receiver timeout interrupt when an idle state of 24 bits length (value of MODBUS_TIMEOUT_IN_BITS) is detected on the line after the reception of the last character. My HAL_UART_IRQHandler will handle it:
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;
}
}
....
As explained in STM32F7's reference manual, DMA transfer may continue after a software disabling of the stream. The software shall wait that DMA's transfer complete interrupt fires before reading data, to ensure that all of them have been successfully transfered (sorry, I did not tell that in my first post). Here is my DMA transfer complete handler:
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;
}
if(&hdma_usart2_rx == hdma)
{
// for UART Rx DMA: warn higher level that a frame has been received
if(hdma->ErrorCode != HAL_DMA_ERROR_NONE)
{
// error during reception -> ignore current frame
ignoreNextReceivedFrame = 1;
}
else if(0 == ignoreNextReceivedFrame)
{
dmaTransferComplete = 1;
}
else
{
ignoreNextReceivedFrame = 0;
}
}
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);
}
}
}
dmaTransferComplete and ignoreNextReceivedFrame are declared as volatile.
My DMA transfer complete callback:
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
{
UART_HandleTypeDef *huart = (UART_HandleTypeDef *)(hdma->Parent);
/* DMA Normal mode */
if (hdma->Init.Mode != DMA_CIRCULAR)
{
huart->RxXferCount = 0U;
/* Disable 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 resetting the DMAR bit
in the UART CR3 register */
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
}
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*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 */
}
With
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// reception on DMA
if((huart->Instance == USART2) && (1 == dmaTransferComplete))
{
// also disable DMA error flags
huart->hdmarx->Instance->CR &= ~( DMA_IT_TE | DMA_IT_DME);
huart->hdmarx->Instance->FCR &= ~(DMA_IT_FE);
frameReceived = 1;
// get nr of bytes got during this transfer
remainingBytesToTransfer = __HAL_DMA_GET_COUNTER(huart->hdmarx);
dmaTransferComplete = 0;
}
}
frameReceived is declared as volatile too.
My modbus state machine is watching frameReceived:
case MODBUS_STATE_CORE_MASTER_FRAME_WAIT_REPLY:
//On regarde si il faut décoder la trame
//**************************************
// DMA reception + timeout on idle
if(1 == frameReceived)
{
frameReceived = 0;
DataRS485->ModbusCoreState = MODBUS_STATE_CORE_MASTER_FRAME_DECODING_REPLY;
}
//si pas de reponse
if(DataRS485->TimerTimeout >= MODBUS_WRITE_RX_TIMEOUT)
{
if(0 == DataRS485->MaxTimeOutAllowed) // Ne pas mettre de Breakpoint ici pour éviter une Ré-Initialisation intempestive des Variables !
{
HandleModbusMasterTimeOut(DataRS485);
// stop ongoing DMA transfer !!!
stopRxDma();
DataRS485->ModbusCoreState = MODBUS_STATE_CORE_MASTER_FRAME_SWAP_WRITE_READ;
} else { DataRS485->MaxTimeOutAllowed--; } // Permet d'accepter des Délais de réponses plus long si nécessaire (certaines fonctions spéciales)
if(DataRS485->TimerDataAvailable == 0)
{
DataRS485->TimerDataAvailable = 2; //Relance la tempo pour pouvoir prendre en compte les datas recus
}
DataRS485->TimerTimeout = 0;
}
break;
... then in state MODBUS_STATE_CORE_MASTER_FRAME_DECODING_REPLY, a frame check will be done (last 2 bytes of a modbus frame are a CRC16 checksum):
case MODBUS_STATE_CORE_MASTER_FRAME_DECODING_REPLY:
//Statistiques
if(DataRS485->NbTrameRx < _U_LONG_MAX)
{
DataRS485->NbTrameRx++; //Stats
}
if(CheckGlobalFrameIsOk(DataRS485) == CHECK_TRAME_OK)
{
DecodageModbusMaster(DataRS485);
memset(DataRS485->RX, 0, SIZE_RX_RS485);
}
else
{
HandleModbusMasterTimeOut(DataRS485); // Simule un TimeOut pour Clôturer la requête ...
}
DataRS485->Config.DebutTrame = 0;
DataRS485->IndiceRx = 0;
DataRS485->ModbusCoreState = MODBUS_STATE_CORE_MASTER_FRAME_SWAP_WRITE_READ;
break;
With
MODBUS_CHECK_TRAME CheckGlobalFrameIsOk(TYPE_MODBUS *DataRS485)
{
uint16_t RX_CRC;
#ifndef RECEIVE_IT
// dans le cas d'une reception par DMA, la taille recue est deduite du nb d'octets restants a transmettre
DataRS485->IndiceRx = UART_RX_BUF_SIZE - remainingBytesToTransfer;
remainingBytesToTransfer = 0;
#endif
if(DataRS485->IndiceRx > 3)
{
//Calcul du CRC
RX_CRC = CRC16((unsigned char *)DataRS485->RX, (DataRS485->IndiceRx) - 2,0xFFFF);
//Si le CRC est correct
if ( ((unsigned char) (RX_CRC) == DataRS485->RX[(DataRS485->IndiceRx) - 2]) && ((unsigned char) (RX_CRC >> 8) == DataRS485->RX[(DataRS485->IndiceRx) - 1]))
{
//Statistique
if(DataRS485->NbTrameRxCrcOk < _U_LONG_MAX)
{
(DataRS485->NbTrameRxCrcOk)++;
}
if ( (DataRS485->RX[0] == pCurModBus->Address) || (DataRS485->RX[0] == MODBUS_ADR_BROADCAST) )
{
//Statistique
if(DataRS485->NbTrameRxCrcOkAdrOk < _U_LONG_MAX)
{
(DataRS485->NbTrameRxCrcOkAdrOk)++;
}
if(DataRS485->CurrentCptReceptionOKPerSecond < _U_INT_MAX)
{
DataRS485->CurrentCptReceptionOKPerSecond++;
}
DataRS485->TimerTimeout = 0;
return CHECK_TRAME_OK;
}
return CHECK_TRAME_BAD_ADRESS;
}
return CHECK_TRAME_BAD_CRC;
}
return CHECK_TRAME_TOO_SHORT;
}
remainingBytesToTransfer is volatile too.
But at reception of 3rd frame, the value stored in remainingBytesToTransfer is not the same as the one in DMA's NDTR register, although the frame in Rx buffer is correct... :thinking_face: I wonder how this can happen...
2024-05-29 02:44 AM
You're doing more work than needed. Use HAL_UARTEx_ReceiveToIdle_DMA and HAL_UARTEx_RxEventCallback instead.
See this project for better understanding how to use HAL_UARTEx_ReceiveToIdle_DMA to your advantage
https://github.com/karlyamashita/Nucleo-G431RB_Three_UART/wiki
In your case you don't need a ring buffer as the receive buffer won't get overwritten until you transmit another message to the slave.
2024-05-31 01:48 AM
Hi Karl,
I updated my HAL & CMSIS drivers to be able to use the functions you adviced, and now it works perfectly! Thak you for your help.
Regards,
Agnes