How to use UART DMA RX on STM32F103 MCU

On the last post, I explained how to use UART DMA on STM32F103 MCU using HAL Library. I’ll explain how to use UART DMA RX here.

First, thanks to Majerie Tilen and his tutorial and github repo regarding using UART DMA RX. His tutorial is very helpful for us to understand the mechanism of UART DMA RX on STM32 MCU.

Why uses UART DMA RX

When receiving a bulk data, using only interrupt may interfere MCU to process critical job due to a lot of interrupts. In this case, using UART DMA RX can reduce MCU intervention because only HT and TC interrupts occur even though how big data comes in. This is the reason of using UART DMA RX.

Consideration of using UART DMA RX

To use UART DMA RX, you should designate the size to read through UART DMa. The problem is that TC interrupt does not occur when the received data is smaller than the designated size. In this case, we can solve the problem when we monitor UART IDLE Interrupt. So, when using UART DMA RX, monitoring HT, TC and UART IDLE interrupt is strongly recommended.

Configuring UART DMA RX

In this example, I use UART3 and UART4 on STM32F103ZE MCU.

UART3 Rx and UART4 Tx are connected and UART3 can receive the data which UART4 sent in DMA mode.

First, select “Connectivity->USART3” on “Pinout & Configuration” section, and add “USART3_RX” in “Configuration->DMA Settings” section.

Then, set “DMA Request Settings->Mode” in “USART3_Rx” to “Circular”. As data may arrive anytime, it should be able to receive repeatedly without user’s operation.

Next, check whether all Interrupts are “Enabled” in “NVIC Settings”.

For high speed UART data processing, set baudrate to 2Mbps.

Do the same for UART4.

Coding

After selecting “Generate Code”, you will find some codes are added in main.c, stmref1xx_it.c.

The basic mechanism of UART DMA RX is as below.

  • Enable UART DMA RX.
  • HT and TC interrupts occur when Data arrives through UART DMA RX. Then copy data received in UART DMA RX buffer into user buffer in the relevant interrupt service routine.
  • When UART IDLE Interrupt occurs, copy the remains in UART DMA RX buffer into user buffer.

First, declare necessary variables as below.

uint8_t RxBuffer[MAX_RX_BUF + 16];
uint16_t wrPtr = 0;
uint16_t rdPtr = 0;
uint16_t rcvdLen = 0;
uint8_t rcvFlag = 0;
uint8_t RxBuf3[MAX_RX_BUF + 16];

uint32_t totalSentBytes = 0;
uint32_t totalRcvdBytes = 0;

uint16_t sentbytes = 0;

uint16_t _HT_Count = 0;
uint16_t _TC_Count = 0;
uint16_t _IDLE_Count = 0;

RxBuffer is UART DMA RX buffer, rdPtr and wrPtr are pointer variables to pointing the start and end of valid data in RxBuffer.

RxBuf3 is user buffer to store the received data.

Next is the code that enable UART IDLE Interrupt and activate UART DMA RX operation.

  __HAL_UART_ENABLE_IT(&huart3, UART_IT_IDLE);
  HAL_UART_Receive_DMA(&huart3, RxBuffer, MAX_RX_BUF + 16);

Then, add the interrupt service routine of UART DMA RX. Those are HAL_UART_RxHalfCpltCallback() for HT Interrupt, HAL_UART_RxCpltCallback() for HT Interrupt and UART_IDLECallback() for UART IDLE Interrupt.

... main.c ...

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	_TC_Count++;
	rcvFlag = 1;
}

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
	_HT_Count++;
	rcvFlag = 1;
}


void UART_IDLECallback(UART_HandleTypeDef *huart)
{
	_IDLE_Count++;
	rcvFlag = 1;
}

The first two functions are weak functions already defined in HAL library and you don’t need to define it except in main.c. But you should define UART_IDLECallback() and you can change its name with what you prefer.

When the Flag(rcvFlag) indicating that one of interrupts occurs is set in the above Callback, you need to call UART_Data_Process() in while loop. This function copies the received data in UART DMA RX buffer into user buffer.

... main.c ...

void UART_Data_Process(UART_HandleTypeDef *huart)
{
	wrPtr = ARRAY_LEN(RxBuffer) - huart->hdmarx->Instance->CNDTR;
	if(wrPtr != rdPtr)
	{
		memset(RxBuf3, 0, MAX_RX_BUF + 16);

		if (wrPtr > rdPtr)
		{
			rcvdLen = wrPtr - rdPtr;
			memcpy(RxBuf3, RxBuffer + rdPtr, rcvdLen);
		}else
		{
			rcvdLen = ARRAY_LEN(RxBuffer) - rdPtr;
			memcpy(RxBuf3, RxBuffer + rdPtr, rcvdLen);
			if(wrPtr > 0)
			{
				rcvdLen += wrPtr;
				memcpy(RxBuf3 + rcvdLen, RxBuffer, wrPtr);
			}
		}
		rdPtr = wrPtr;
	}
}

Next, clear UART IDLE Interrupt and call UART_IDLECallback() within UART Interrupt service routine.

...stmref1xx_it.c...

/**
  * @brief This function handles USART3 global interrupt.
  */
void USART3_IRQHandler(void)
{
  /* USER CODE BEGIN USART3_IRQn 0 */
  if(__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE) != RESET)
  {
	  __HAL_UART_CLEAR_IDLEFLAG(&huart3);
	  UART_IDLECallback(&huart3);
  }
  /* USER CODE END USART3_IRQn 0 */
  HAL_UART_IRQHandler(&huart3);
  /* USER CODE BEGIN USART3_IRQn 1 */

  /* USER CODE END USART3_IRQn 1 */
}

Then add the below code to activate DMA TX every second and check whether UART DMA RX occurred regularly in while() loop.

... main.c ...
  
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  if(onesecondElapsed)
	  {
		  onesecondElapsed = 0;
		  count++;	// increment count
		  HAL_GPIO_TogglePin(DEBUG_LED_GPIO_Port, DEBUG_LED_Pin);
		  memset(dma_buf, 0, MAX_RX_BUF + 16);
		  printf("message: [%d] bytes, %srn", strlen(message), message);
		  sprintf((char *)dma_buf, "UART4: %srn", message);
		  printf("dma_buf: %srn", dma_buf);
		  sentbytes = strlen((const char*)dma_buf);
		  HAL_UART_Transmit_DMA(&huart4, dma_buf, sentbytes);
		  printf("UART4 HAL_UART_Transmit_DMA sent: %d bytesrn", sentbytes);
		  totalSentBytes += sentbytes;
	  }
	  loopback_tcps(0, ethBuf0, 5000);
	  if(rcvFlag)
	  {
		  rcvFlag = 0;
		  UART_Data_Process(&huart3);
		  totalRcvdBytes += rcvdLen;
		  printf("RxBuf3: [%d] bytes, %srn", strlen(RxBuf3), RxBuf3);
		  printf("_HT_Count: %d, _TC_Count: %d, _IDLE_Count: %d, rdPtr: %d, wrPtr: %d, rcvdLen: %d, totalSentBytes: %d, totalRcvdBytes: %drn",
				  _HT_Count, _TC_Count, _IDLE_Count, rdPtr, wrPtr, rcvdLen, totalSentBytes, totalRcvdBytes);
	  }
  }
  /* USER CODE END 3 */

Build and Download

Then build and download the binary to WIZ145Sr using STM32CubeProgrammer

Result View

The below is the result view.

You can find there are three receive operations in every DMA TX.

Please refer to the below link for the whole project files

https://github.com/javakys/WIZ14xSR_Proj/releases/tag/Ver0.7

2 Comments

  1. Adnan says:

    Hi javakys, brilliant and thanks for sharing!

    Just a comment : On line when the circular buffer has rolled back to the start:

    rcvdLen += wrPtr;
    memcpy(RxBuf3 + rcvdLen, RxBuffer, wrPtr);

    Should it not rather be such:
    alreadyAddedLen = rcvdLen ;
    rcvdLen += wrPtr;
    memcpy(RxBuf3 + alreadyAddedLen , RxBuffer, wrPtr) ?

    Many Thanks

Leave a Comment