4
\$\begingroup\$

I'm using STM32F407 and I would like to receive unknown length of data. My current approach uses a fixed known size to receive the data from UART5.

How do I change it to receive unknown length of data.

/**
  * @brief This function handles UART5 global interrupt.
  */
void UART5_IRQHandler(void)
{

    if (!bytesAvailable(&_writeBuffer))
    {
        __HAL_UART_DISABLE_IT(&huart5, UART_IT_TXE);
    }

    HAL_UART_IRQHandler(&huart5);

    /* USER CODE BEGIN UART5_IRQn 1 */

    /* USER CODE END UART5_IRQn 1 */
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart5)
    {
        tx_finished = 1;
        HAL_UART_Receive_IT(&huart5, _readBuffer.buffer, expected_replay_length);
    }
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart == &huart5)
    {
        if (!isFull(&_readBuffer))
        {
            uint16_t counter = 0;
            while (counter < expected_replay_length)
            {
                push_single(&_readBuffer, _readBuffer.buffer[counter++]);
            }
        }
    }
}
\$\endgroup\$
  • 3
    \$\begingroup\$ What condition indicates end-of-data? \$\endgroup\$ – rdtsc Oct 28 '19 at 13:50
  • \$\begingroup\$ Do you mean that the length is variable or do you mean that the length can not be determined at compile time? Do you need to store all of the bytes until the end of the message or can they be processed as they arrive? You have left out critical information. \$\endgroup\$ – Elliot Alderson Oct 28 '19 at 14:32
  • \$\begingroup\$ @ElliotAlderson I need to store all the data that is in DR Register that is there \$\endgroup\$ – Andre Oct 28 '19 at 14:34
  • \$\begingroup\$ Is there a known maximum length? \$\endgroup\$ – DKNguyen Oct 28 '19 at 14:42
  • \$\begingroup\$ no, I just use a modem, and I need to get whatever the data that I got from it \$\endgroup\$ – Andre Oct 28 '19 at 14:44
5
\$\begingroup\$

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:

  1. 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.

  2. 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
   }
   …
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ How to do that in HAL STM32F4 \$\endgroup\$ – Andre Oct 28 '19 at 14:47
  • \$\begingroup\$ Can you show a pseudo code \$\endgroup\$ – Andre Oct 28 '19 at 14:47
  • \$\begingroup\$ I updated my answer \$\endgroup\$ – Michel Keijzers Oct 28 '19 at 14:55
  • \$\begingroup\$ HAL_UART_Receive_IT(1 byte) should be called in the main loop ? or just before the main loop ? \$\endgroup\$ – Andre Oct 28 '19 at 15:00
  • 1
    \$\begingroup\$ I did it :) Many thanks \$\endgroup\$ – Andre Oct 28 '19 at 15:26
4
\$\begingroup\$

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.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

First of all, in embedded systems there is always a maximum data size -- the size you preallocate in the MCU RAM. (Unless you process the data gradually, in other words use streaming data. This is more advanced.) The normal case can still be that the data doesn't fill all available space.

One way is to stop receiving data based on what has been received so far, for example looking for a \r\n pattern that indicates a newline. To do this, you must examine each byte when it has arrived.

The other way is to do a inter-byte timeout. Set a hardware or software timer after each received byte. If a new byte arrives, set the timer again to postpone it. When the timer triggers, reception is done.

In both cases, you also need to handle the case that your buffer fills up. The action depends on your application; consider reception complete, overwrite the received data, throw away all data until a newline is received or trigger some error handling.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.