cancel
Showing results for 
Search instead for 
Did you mean: 

More robust HAL USART implementation for RF module AT communication

Thomas LB
Associate III

Hello,

My message is too long so I put the question first and context after:

Any advice regarding the most robust way to implement HAL_UART_ErrorCallback() ?

For now I have:

 

// UART Error Handle
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{	
	HAL_UART_AbortReceive(&huart2);
	HAL_UART_DeInit(&huart2);
	MX_USART2_UART_Init();
	Ringbuf_Init(); // restart HAL_UARTEx_ReceiveToIdle_DMA(&UART, aRXBufferUser, RxBuf_SIZE);
}

 

I tried "upseting" the UART by sending data with wrong baudrate and it seems able to recover.

---------    Please don't tell me to trash HAL and go for LL 🙂 you are probably right but it's working well (simple project, no multiple thread) ------------

I have a working configuration based on https://github.com/STMicroelectronics/STM32CubeL4/tree/master/Projects/NUCLEO-L476RG/Examples/UART/UART_ReceptionToIdle_CircularDMA

1) For transmission I use this function:

 

void send_uart_cellular(char * buffer)
{	
	uint8_t cpt = 0;
	transmit_done = 0;
	memset(msg, 0, tx_buffer_size); // Reset transmit buffer
	snprintf(msg, tx_buffer_size, "%s\r\n", buffer); // Add CRLF after command
	HAL_UART_Transmit_DMA(&huart2, (uint8_t *)msg, strlen(msg));
	while((transmit_done != 1) && (cpt < 200)){ // 200ms timeout
		cpt++;
		HAL_Delay(1);
	}
}

 

 with "msg" declared global as

 

char msg[512] = {0};

 

and "transmit_done" declared global as

 

volatile uint8_t transmit_done = 0;

 

and being modified by

 

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)  
{
	if (huart->Instance == USART2){ // Current UART
		transmit_done = 1;
	}
}

 

2) For reception I use these functions:

 

void Ringbuf_Init (void)
{	
	pBufferReadyForReception = aRXBufferA;
  pBufferReadyForUser      = aRXBufferB;
  uwNbReceivedChars        = 0;
	old_pos 				 				 = 0;
	
	memset(MainBuf, '\0', MainBuf_SIZE);
	memset(aRXBufferUser, '\0', RxBuf_SIZE);
	memset(aRXBufferA, '\0', RxBuf_SIZE);
	memset(aRXBufferB, '\0', RxBuf_SIZE);

	Data_received_available_index = 0;
	Data_processed_available_index = 0;

	HAL_UARTEx_ReceiveToIdle_DMA(&UART, aRXBufferUser, RxBuf_SIZE) == HAL_OK);
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  uint8_t *ptemp;
  uint8_t i;

  /* Check if number of received data in recpetion buffer has changed */
  if (Size != old_pos)
  {
    /* Check if position of index in reception buffer has simply be increased
       or if end of buffer has been reached */
    if (Size > old_pos)
    {
      /* Current position is higher than previous one */
      uwNbReceivedChars = Size - old_pos;
      /* Copy received data in "User" buffer for evacuation */
      for (i = 0; i < uwNbReceivedChars; i++)
      {
        pBufferReadyForUser[i] = aRXBufferUser[old_pos + i];
      }
    }
    else
    {
      /* Current position is lower than previous one : end of buffer has been reached */
      /* First copy data from current position till end of buffer */
      uwNbReceivedChars = RxBuf_SIZE - old_pos;
      /* Copy received data in "User" buffer for evacuation */
      for (i = 0; i < uwNbReceivedChars; i++)
      {
        pBufferReadyForUser[i] = aRXBufferUser[old_pos + i];
      }
      /* Check and continue with beginning of buffer */
      if (Size > 0)
      {
        for (i = 0; i < Size; i++)
        {
          pBufferReadyForUser[uwNbReceivedChars + i] = aRXBufferUser[i];
        }
        uwNbReceivedChars += Size;
      }
    }
    /* Process received data that has been extracted from Rx User buffer */
    UserDataTreatment(huart, pBufferReadyForUser, uwNbReceivedChars);

    /* Swap buffers for next bytes to be processed */
    ptemp = pBufferReadyForUser;
    pBufferReadyForUser = pBufferReadyForReception;
    pBufferReadyForReception = ptemp;
  }
  /* Update old_pos as new reference of position in User Rx buffer that
     indicates position to which data have been processed */
  old_pos = Size;

}

void UserDataTreatment(UART_HandleTypeDef *huart, uint8_t* pData, uint16_t Size)
{
		if (Data_received_available_index + Size >= MainBuf_SIZE)  // If Data_received_available_index + new data size is greater than the main buffer size
		{
			uint16_t nb_datatocopy = MainBuf_SIZE - Data_received_available_index;  // Find out how much space is left in the main buffer
			
			memcpy ((uint8_t *)MainBuf + Data_received_available_index, pData, nb_datatocopy);  // In that remaining space in MainBuf, copy nb data, from RxBuf + Previous_DrA_RxBuf_index (only new data received)
			memcpy ((uint8_t *)MainBuf, pData + nb_datatocopy, Size - nb_datatocopy);  // Then copy at the beginning of MainBuf the rest of data (len_RxBuf_data - nb_datatocopy), from RxBuf + Previous_DrA_RxBuf_index + nb_datatocopy
			Data_received_available_index = Size - nb_datatocopy;  // Update the position of Data_received_available_index to (len_RxBuf_data - nb_datatocopy)
		}

		else
		{
			memcpy ((uint8_t *)MainBuf + Data_received_available_index, pData, Size); // Copy new received data at the current position in MainBuf from RxBuf + Previous_DrA_RxBuf_index
			Data_received_available_index = Data_received_available_index + Size; // Update the position of Data_received_available_index to Data_received_available_index + len_RxBuf_data
		}
		
		new_data_received++; // Flag to notify app that new data is available
}

 

With following global variables:

 

uint8_t aRXBufferUser[RxBuf_SIZE]; // 512
uint8_t aRXBufferA[RxBuf_SIZE]; // 512
uint8_t aRXBufferB[RxBuf_SIZE]; // 512
uint8_t MainBuf[MainBuf_SIZE]; // 1024

volatile uint32_t uwNbReceivedChars;
uint8_t *pBufferReadyForUser;
uint8_t *pBufferReadyForReception;

volatile uint16_t old_pos = 0;
volatile uint16_t Data_received_available_index = 0;
volatile uint16_t Data_processed_available_index = 0;
volatile uint16_t Data_processed_available_index_saved = 0;

volatile uint8_t new_data_received = 0;

/* Timeout is in milliseconds */
volatile int32_t TIMEOUT = 0;

 

and SysTick_Handler being modified in stm32l0xx_it.c to make TIMEOUT variable decrement by 1 every ms.

 

void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
	TIMEOUT--;
  /* USER CODE END SysTick_IRQn 1 */
}

 

I also have functions for handling answers from the module:

 

/* Waits for a particular string to arrive in the incoming buffer... It also increments the Data_processed_available_index
 * returns 1, if the string is detected
 * return 0, in case of timeout
 */
uint8_t waitFor (char *string, uint32_t Timeout)
{
	uint16_t string_len = strlen(string); // Get string length	
	uint8_t string_found = 0;							// Flag to notify that string is found
	
	TIMEOUT = Timeout;										// Initialize TIMEOUT (decreases by one each ms (see SysTick_Handler in stm32l0xx_it.c) to Timeout (parameter of this waitFor function)	
	new_data_received = 0;								// Reset new_data_received flag
	
	uint16_t Instant_Data_received_available_index = Data_received_available_index;
	uint16_t Instant_Data_processed_available_index = Data_processed_available_index; 	

	if (((Data_processed_available_index < Instant_Data_received_available_index) && ((Instant_Data_received_available_index - Data_processed_available_index) >= string_len)) || ((Data_processed_available_index > Instant_Data_received_available_index) && ((MainBuf_SIZE - Data_processed_available_index + Instant_Data_received_available_index) >= string_len))){
		string_found = search_string_in_Mainbuf(string, &Data_processed_available_index, Instant_Data_received_available_index); // Look for string in this unprocessed data
		if(0 == string_found){															// If not found reset Data_processed counter to check again after new data received (buffer may have been full and HAL_UARTEx_RxEventCallback is trigged by Transfert Completed event, or data of interest might be preceded by idle event with data of no interest)
			Data_processed_available_index = Instant_Data_processed_available_index;
		}
	}
	
	while ((0 == string_found) && (TIMEOUT > 0)){						// While string is not found in MainBuf (no more than Timeout)
		while ((0 == new_data_received) && (TIMEOUT > 0)){		// Wait for new_data_received flag (no more than Timeout)
			HAL_Delay(1);
		}
		if(TIMEOUT > 0){																			// If Timeout not reached means new data has been received
			Instant_Data_received_available_index = Data_received_available_index;
			new_data_received = 0;															// Reset new_data_received flag
			string_found = search_string_in_Mainbuf(string, &Data_processed_available_index, Instant_Data_received_available_index); // Look for string in this new available data
			if(0 == string_found){															// If not found reset Data_processed counter to check again after new data received (buffer may have been full and HAL_UARTEx_RxEventCallback is trigged by Transfert Completed event, or data of interest might be preceded by idle event with data of no interest)
				Data_processed_available_index = Instant_Data_processed_available_index;
			}
		}
	}
	
	return string_found;
}

uint8_t search_string_in_Mainbuf(char *string, volatile uint16_t *index, uint16_t index_end)
{	
	uint16_t so_far = 0;													// Reset "so_far" variable that will be used as index for string buffer
	uint16_t string_len = strlen(string);					// Get string length
	
	while(*index != index_end){
		while ((MainBuf[*index] != string[so_far]))	// Peek in MainBuf to see if we get the first character of string
		{
			*index = *index + 1;											// While not, increment index
			if (*index == MainBuf_SIZE) *index = 0;		// If index reaches MainBuf_SIZE, index is reset
			if (*index == index_end) return 0; 				// If index_end reached, return 0
		}

		while (MainBuf[*index] == string[so_far]) 	// While character in MainBuf matches character in string
		{
			so_far++;																	// Increment index in string buffer
			*index = *index + 1;											// Increment index in MainBuf
			if (*index == MainBuf_SIZE) *index = 0;		// If index reaches MainBuf_SIZE, index is reset
			if (so_far == string_len) return 1;				// If end of string is reached, string is found in MainBuf so return 1
		}
		so_far = 0;																	// If first characters in MainBuf was matching string but not entirely, reset string index and look again for string in MainBuf
	}
	
	return 0;
}

 

Any comments? Hints? Recommandation?

 

 

 

12 REPLIES 12
Thomas LB
Associate III

Hello mister "reference of UART". Thank you for your great job and documentation !

That's what I'm doing or am I missing something?

It's working well for the moment 🙂

 

Tilen MAJERLE
ST Employee

I was in fact replying to Karl!

Great that you found the docs useful 🙂

In response to your comment you left on my github

Please note that this example has a corner case:

  • Not possible to receive unknown data length - especially if it is more than one queue of length
  • It has a race condition since DMA is in "normal" mode therefore for some time DMA is disabled.
 

You're wrong. It can work with unknown data length which the Wiki explains. There is no race condition as I am using a pointer for the DMA to fill the queue.

 

Your way has to copy from the DMA main buffer to a secondary buffer during HT and TC. So while you're receiving data on the other half of the DMA circular buffer, HT or TC, you're just wasting time copying data. And so you can't process the data yet until the copying is done and you exit the callback. Where is the efficiency in that?

 

Short data packets of 32 or 64 bytes maybe ok. But what if you're receiving data that is 100's of bytes per packet? How efficient is that if you're copying that amount of data during HT or TC interrupt? 

 

My queue method is the DMA circular buffer. I increment a pointer and I exit the callback with no copying needed. I'm already processing the queue while the other queue is being filled by the DMA. It's a lot more efficient.