Serial data receiving and transmitting in DMA cycle mode of HAL library version

At https://my.oschina.net/machinor/blog/3162697 First, set up the serial port to receive and send data through the serial port. In the HAL library generated by STM32CubeMX, there are three types of serial data receiving and transmitting interfaces, namely blocking mode, non blocking mode and DMA mode. The text mainly analyzes DMA mode and proposes a more practical serial data receiving and transmitting scheme based on the interface provided. Through the search and analysis of online data, there are two main problems as follows:

1. In the process of data receiving, the idle interrupt of serial port is used to realize the receiving of indefinite length data in DMA mode, but the limitation is that the length of single receiving data must be less than the length of DMA buffer. If the length of received data is greater than the length of DMA buffer, the data will be lost.

2. In the process of data transmission, DMA can no longer enable DMA transmission in the transmission phase, that is, after calling HAL UART transmit DMA interface in HAL library, the interface can no longer be called before DMA transmission data is completed. The common way is to determine whether it can be transmitted by a flag variable, but if there is logic such as waiting flag variable in the program, it will drop Low efficiency of program operation.

In this paper, DMA cycle mode is used to cooperate with DMA transmission completion interrupt and serial idle interrupt to solve the problem that the length of DMA buffer limits the amount of data received by the serial port. This paper uses the logic of similar recursion to solve the problem that the serial port sends data waiting to be sent.

1 hardware configuration

Firstly, STM32CubeMX is used to configure the serial port,

Check usart1 in Connectivity, and the specific pin is determined according to the hardware. It is important to note that check the USART1 global interrupt enable; add the DMA to receive and send, set the DMA of usart1 ﹣ Rx to Circular mode, and set the DMA of usart1 ﹣ TX to Normal mode.

2 software implementation plan

2.1 initialization preparation

The application layer calls the interface of the driver layer to realize the data transmission and acquisition, and realizes the data interaction through two buffers. In this paper, a group of cyclic FIFO is used to realize the data buffer. Create a new set of files to provide the middleware interface for receiving and sending data to the serial port. Define the serial port middleware attributes in the header file:

typedef struct 
{ 
	 UART_HandleTypeDef  *handle;    /*HAL Serial port handle provided by the library*/
	 int16_t TransFlag;              /*Data sending flag bit*/      
	 int32_t DmaSize;                /*DMA Size of buffer*/
	 int32_t DamOffset;              /*Get the offset of data in DMA buffer*/
	 uint8_t *pReadDma;              /*First address to receive DMA buffer*/
	 uint8_t *pWriteDma;             /*First address to send DMA buffer*/
	 CFIFO ReadCFifo;                /*Circular buffer to receive data*/
	 CFIFO WriteCFifo;               /*Circular buffer for sending data*/
}MW_UART_ATTR;	 

The above property values include buffer, DMA and other parameters. The use of each parameter will be described in the following implementation. In this paper, only one serial port is configured for demonstration. In practice, id parameter is added to the attribute to realize the management of multiple serial ports, or other methods are used.

#define MW_TRANS_IDLE        0
#define MW_TRANS_BUSY        1

#define MW_UART_BUFFER_LEN   1024
#define MW_UART_DMA_LEN      256

static uint8_t Uart1TxBuff[MW_UART_BUFFER_LEN] = {0};	
static uint8_t Uart1RxBuff[MW_UART_BUFFER_LEN] = {0};	

static uint8_t Uart1TxDma[MW_UART_DMA_LEN] = {0};	
static uint8_t Uart1RxDma[MW_UART_DMA_LEN] = {0};

static MX_UART_ATTR sUartAttr;

​int8_t MW_UART_Init(UART_HandleTypeDef *handle)
{ 
    /*Attach initial value to parameter of attribute*/
	MX_UART_ATTR *pUartAttr = &sUartAttr;	
	pUartAttr->handle = handle;
	pUartAttr->DamOffset = 0;
	pUartAttr->TransFlag = MW_TRANS_IDLE;
	pUartAttr->DmaSize = MW_UART_DMA_LEN;
	pUartAttr->pReadDma = Uart1RxDma;
	pUartAttr->pWriteDma = Uart1TxDma;
	CFIFO_Init(&pUartAttr->ReadCFifo, Uart1RxBuff, MW_UART_BUFFER_LEN);
	CFIFO_Init(&pUartAttr->WriteCFifo, Uart1TxBuff, MW_UART_BUFFER_LEN);
	
	/*Enable serial port idle interrupt*/
	__HAL_UART_ENABLE_IT(handle, UART_IT_IDLE);
	/*Configure DMA parameters and enable interrupt*/
	if(HAL_OK != HAL_UART_Receive_DMA(pUartAttr->handle, pUartAttr->pReadDma, MW_UART_DMA_LEN))
	{
		return MW_FAIL;
	}

	return MW_SUCCESS;
}

The MW_UART_Init interface implements the initialization function of the serial port. The MX_USART1_UART_Init interface provided by the HAL library only configuring the hardware, and MW_UART_Init is used to enable the serial port after the interface. Because CFIFO initialized circular buffer is used to cache DMA received data, the length of DMA buffer is smaller than the length of data buffer. By enabling the serial port idle interrupt and DMA, the serial port data can be received.

2.2 serial port data receiving scheme

In this paper, a scheme of data receiving by using serial port idle interrupt and DMA receiving is provided. Assuming that the size of DMA buffer is 16, if the length of data received at one time is less than the length of DMA buffer, the data will be acquired through serial port idle interrupt; if the length of data received at one time is greater than 16, assuming 20, the first 16 bytes will be acquired from DMA buffer by DMA completion interrupt, and the remaining 4 bytes will be acquired from DMA buffer by serial port idle interrupt.

Based on the above scheme, the buffer should be reloaded automatically after DMA buffer is full, so DMA cycle mode is adopted. In cyclic mode, it is necessary to pay attention to the storage location of the received data after DMA buffer is full. For example, a string to be received by the serial port is' 1234567890abcdefghij ', a total of 20 bytes. When the DMA buffer receives 16 characters, the data in the DMA buffer is' 1234567890abcdef', and a DMA completion interrupt is generated. We can use this interrupt to obtain data. Due to the cyclic mode, it does not need to be reconfigured. After the DMA buffer is full, it continues to receive the remaining 4 bytes of data. After receiving the four bytes of data, the 16 bytes of data in the DMA buffer are '567890abcdefghij' and a serial port idle interrupt is generated. We can use this interrupt to obtain the data in the next four sections. It is worth noting that the 4-byte data to be acquired is in the last four bytes of DMA buffer, so special attention should be paid when acquiring. Therefore, the parameter DmaOffset is defined in the attribute to declare the offset position of data obtained in DMA buffer. The specific implementation is as follows:

In the interrupt processing provided by HAL library, idle interrupt is not processed, so the interface for idle interrupt processing needs to be provided in the interrupt:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
  MW_UART_IRQHandler(&huart1);
  /* USER CODE END USART1_IRQn 1 */
}

Add the MW UART irqhandler interface to the usart1 irqhandler interface provided by Hal library to handle the idle interrupt of serial port (the interface provided by Hal UART irqhandler to handle the interrupt).

void32 MW_UART_IRQHandler(UART_HandleTypeDef *huart)
{
	int32_t RecvNum = 0;
	int32_t WriteNum = 0;
	int32_t DmaIdleNum = 0;
	
	MX_UART_ATTR *pUartAttr = &sUartAttr;

	if((__HAL_UART_GET_FLAG(huart,UART_FLAG_IDLE) != RESET))
	{
        /*Clear the idle interrupt flag bit and accept the serial port idle interrupt again*/ 
		__HAL_UART_CLEAR_IDLEFLAG(huart);

		/*Calculate the data length to be acquired in DMA buffer*/
		DmaIdleNum = __HAL_DMA_GET_COUNTER(huart->hdmarx);
		RecvNum = pUartAttr->DmaSize - DmaIdleNum - pUartAttr->DamOffset;
		/*Put the acquired data into the data receiving buffer*/                                           
		WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pUartAttr->pReadDma + pUartAttr->DamOffset,RecvNum);
		if(WriteNum != RecvNum)
		{
			loge("Uart ReadFifo is not enough\r\n");
		}
        /*Calculate the offset of the acquired data location*/
		pUartAttr->DamOffset += RecvNum;
	}
}

By rewriting HAL_UART_RxCpltCallback's weak function in the HAL library, DMA can complete the interrupt processing. Although the function is declared in stm32f4xx_hal_uart.c, DMA completes the interrupt, and finally calls the callback function completed by the serial port.

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{			
	int32_t DmaLen = 0;
	int32_t WriteNum = 0;
	MX_UART_ATTR *pUartAttr = &sUartAttr;

	/*Calculate the length of data to be obtained*/
	DmaLen = pUartAttr->DmaSize - pUartAttr->DamOffset;
	/*Store acquired data in data buffer*/
	WriteNum = CFIFO_Write(&pUartAttr->ReadCFifo,pUartAttr->pReadDma + pUartAttr->DamOffset,DmaLen);
	if(WriteNum != DmaLen)
	{
		loge("Uart ReadFifo is not enough\r\n");
	}
    /*Reset DMA offset*/
	pUartAttr->DamOffset = 0;
}

In this interface, the length of received data should also be calculated. The original idea is that if the last idle interrupt of serial port received 4 bytes of data, another 12 bytes of data would be received to generate DMA transmission completion interrupt. Therefore, the length of DMA buffer 16 minus the 4 assigned by serial interrupt to DmaOffset, and the actual received data length is 12.

In the two interrupts, the DMA buffer data is moved to the data cycle buffer to be used for acquisition and processing:

int32_t MW_UART_Receive(uint8_t* buffer,int32_t len)
{
	int32_t RecvNum = 0;
	MX_UART_ATTR *pUartAttr = &sUartAttr;
	/*Get data from data loop buffer*/
	RecvNum = CFIFO_Read(&pUartAttr->ReadCFifo, buffer, len);

	return RecvNum;
}

In the application, call the MW UART receive interface to obtain data and process the data. The above scheme avoids the restriction of the length of DMA buffer to the length of single receive data. If there is "Uart ReadFifo is not enough", it shows that the frequency of calling MW_UART_Receive interface is not fast enough, or the receiving buffer is not big enough. In a word, the integrity of data receiving should depend on the realization of application logic, rather than the sufficient size of DMA buffer application, which is also the principle of serial port data receiving scheme in this paper.

2.3 serial data transmission scheme

In the serial port data transmission, the similar scheme in the receiving is adopted. First, the data is temporarily saved in the circular buffer, and then the data is sent out through DMA. In the application, MW_UART_Transmit is called to send data. The amount of data transmitted in the traditional scheme must be less than the size of the DMA buffer. The amount of data transmitted in this scheme should be less than the margin of the transmit buffer.

int32_t MW_UART_Transmit(uint8_t* buffer,int32_t len)
{
	int32_t TransNum = 0;
	int32_t TransLen = 0;
	MX_UART_ATTR *pUartAttr = &sUartAttr;
	/*write data in cfifo for temporary storage*/
	TransNum = CFIFO_Write(&pUartAttr->WriteCFifo, buffer, len);

	/*if dam is not in using,get data form cfifo to transmit*/
	if(pUartAttr->TransFlag == MW_TRANS_IDLE)
	{
		TransLen = CFIFO_Read(&pUartAttr->WriteCFifo,pUartAttr->pWriteDma,pUartAttr->DmaSize);
		if(TransLen > 0)
		{
			pUartAttr->TransFlag = MW_TRANS_BUSY;
			if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransLen))
			{
				loge("Uart Trans_DMA failed\r\n");
			}
		}					
	}

	return TransNum;
}

        In this interface, if the TransFlag is MW trans idle, the longest data not exceeding the DMA buffer length will be obtained from the circular buffer, and the HAL UART transmit DMA interface provided by HAL library will be called to send the data, and the TransFlag will be set to MW trans business. After sending, the response to the interruption of serial transmission completion will be given, and the HAL UART txcplcallback weak function interface provided by HAL library will be rewritten to realize the serial Port send completes the interrupted operation.

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
	int32_t TransNum = 0;
	MX_UART_ATTR *pUartAttr = &sUartAttr;

	/*Get data from send loop buffer*/
	TransNum = CFIFO_Read(&pUartAttr->WriteCFifo,pUartAttr->pWriteDma,pUartAttr->DmaSize);
	if(TransNum > 0)
	{		
		if(HAL_OK != HAL_UART_Transmit_DMA(pUartAttr->handle,pUartAttr->pWriteDma,TransNum))
		{
			loge("Uart Trans_DMA failed\r\n");
		}
	}
	else
	{
		pUartAttr->TransFlag = MW_TRANS_IDLE;
	}
}

After a group of data is sent, continue to get data from the buffer. If the data is obtained, continue to call Hal UART transmit DMA to send the data. If the data is not obtained, set the TransFlag to MW trans idle, and then call MW UART transmit to enable the data to be sent again. In this scheme, if it is in the process of DMA transmission, the data to be sent will be put into the data cycle buffer, and the data will be acquired and sent in the transmission completion interrupt, avoiding the logic technology of waiting for DMA transmission to complete.

The serial port data receiving and sending scheme mentioned in this paper is originally because the RAM of MCU is very small, and we want to reduce the length of all kinds of buffers as much as possible, so we have completed the overall scheme design.

Keywords: less Attribute

Added by PABobo on Sun, 02 Feb 2020 18:56:58 +0200