Approach for receiving unknown length of data
I'm not a professional, but I guess the only way is to receive 1 byte at a time, and copy it to a ring buffer or other buffer that can store multiple messages (or 1 if you can handle one message fast enough).
Than you have two possibilities:
If it is easy to find out if the end of a message is received (for example when it ends with a certain value, or if you store the expected number of bytes so you can check against that value), than verify this in the interrupt, and set a Boolean. This Boolean can be checked in the main (non interrupt) code to process the message and clear the message. A ring buffer is ideal for this.
If it is not easy to find out the end of a message, than set a Boolean that a new byte has been received, and in the main code verify if a complete message is received, if so, execute it and delete it.
Pseudo code for possibility 1:
Global
volatile uint8_t ringBuffer[MAX_BUFFFER];
volatile uint8_t … // Additional variables to keep track of ring buffer space
volatile bool uartMessageCompleted = false;
Initially:
HAL_UART_Receive_IT(1 byte)
Callback:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
Store byte in ring buffer
HAL_UART_Receive_IT(1 byte)
if (isCompleteUartMessageReceived())
{
uartMessageCompleted = true;
}
}
bool isCompleteUartMessageReceived
{
return true if a complete message is received
}
In Main (or a function called from main):
void main()
{
…
if (uartMessageCompleted )
{
excecuteUartMessage(); // Implement yourself
remove message from ring buffer
}
…
}
The best way, and the way recommended by ST in a blog post on their old forum, is to use IDLE line detection linked to the DMA controller.
A simple configuration would set the DMA to detect the Maximum possible UART message length you expect to handle, which would trigger the UART Rx Complete Callback. In addition you would enable the UART IDLE interrupt, and when that interrupt is triggered, you would force the same transfer complete callback (this is achieved on some STM32's by disabling the associated DMA Stream) but this time checking the DMA's NDTR (Number of Data Register) to read the received number of bytes in the UART Rx Complete Callback.
/**
* \brief Global interrupt handler for USART2
*/
void USART2_IRQHandler(void) {
/* Check for IDLE flag */
if (USART2->SR & USART_FLAG_IDLE) { /* We want IDLE flag only */
/* This part is important */
/* Clear IDLE flag by reading status register first */
/* And follow by reading data register */
volatile uint32_t tmp; /* Must be volatile to prevent optimizations */
tmp = USART2->SR; /* Read status register */
tmp = USART2->DR; /* Read data register */
(void)tmp; /* Prevent compiler warnings */
DMA1_Stream5->CR &= ~DMA_SxCR_EN; /* Disabling DMA will force transfer complete interrupt if enabled */
}
}
This blog post has a more detailed example and explanation.
The post by ST detailing the implementation seems to have been lost during their migration but try this link and click on the attachment to see a code example.
We can use very useful feature in UART peripheral, called IDLE line detection. Idle line is detected on RX line when there is no received byte for more than 1 byte time length. So, if we receive 10 bytes one after another (no delay), IDLE line is detected after 11th bytes should be received but it is not.
We are able to force DMA to call transfer complete interrupt when we disable DMA stream by hand, thus disabling enable bit in stream control register. In this case DMA will make an interrupt if they are enabled and we can read number of bytes we need to still receive by reading NDTR register in DMA stream. From here, we can calculate how many elements we already received.