Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use HAL_UARTEx_ReceiveToIdle_DMA - STM32

I am using an STM32L476 dev board to buffer data that it is receiving serially through the virtual com port, in circular mode. I have decided to use the DMA of the STM32 and perform an echo with UART2, specifically using the HAL_UARTEx_ReceiveToIdle_DMA() function. To perform the "echo", I am using a serial terminal app to send strings to the dev board, and I expect the same string back.

I have written the following code:

    /* DMA RX buffer */
uint8_t main_buffer[16];

/* Buffer used to process data received */
uint8_t final_buffer[1024];

/* Write index of DMA RX */
uint8_t wr_index;

/* Amount of data received into @final_buffer */
uint16_t item_count = 0;

int main(void)
{

  /*All init and config */

  /* Start DMA reception */
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, main_buffer, 16);

  while (1)
  {
      /* If test characters are found, its the end of the string and it should be processed */
      if(process)
      {
          process = false;
          HAL_UART_Transmit(&huart2, final_buffer, item_count, 10);

          /* Clear the string */
          memset(final_buffer, 0, item_count);

          /* Clear amount of data received */
          item_count = 0;

      }
    /* USER CODE BEGIN 3 */
  }
}


void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    /* Used to track amount of available blocks to write to in DMA buffer */
    uint16_t X = __HAL_DMA_GET_COUNTER(huart->hdmarx);

    switch(HAL_UARTEx_GetRxEventType(&huart2))
    {

        case HAL_UART_RXEVENT_IDLE:
        {
            /* Copy data from @main_buffer at its @wr_index, to the final buffer where @item_count and
             * @wr_index are set to zero outside of the main()
             * */
            memcpy(final_buffer + item_count, main_buffer + (wr_index), Size);

            /* Compute the current @wr_index i.e. the starting index of where the DMA will write data to, next
             * time around
             * */
            wr_index = (16 - X);

            /* Check for testing purposes */
            if(main_buffer[wr_index - 1] == 'r' && main_buffer[wr_index - 2] == 'q')
            {
                process = true;
            }
            /* Amount of bytes in @final_buffer */
            item_count += Size;
            break;
        }

        case HAL_UART_RXEVENT_TC:
        {
            /* Copy data from @main_buffer at its @wr_index, to the final buffer */
            memcpy(final_buffer + item_count, main_buffer + wr_index, Size);

            /* If @HAL_UART_RXEVENT_TC is set, that means the rx buffer is full and the next time data is written,
             * it will start from the beginning of the DMA rx buffer */
            wr_index = 0;

            /* Check for testing purposes */
            if(main_buffer[wr_index - 1] == 'r' && main_buffer[wr_index - 2] == 'q')
            {
                process = true;
            }

            /* Amount of bytes in @final_buffer */
            item_count += Size;
            break;
        }

        default:
            break;
    }
}

I am using the HAL_UARTEx_RxEventCallback() callback function to retrieve the data. When I try to use the code on my serial terminal, if i transmit a string:

'abcdefghijklmnopqr', i get the same string back, on the first TX. On the next TX of the same string, I get: 'abcdefghijklmnabopqr' Next TX, I get: 'abcdefghijklabcdmnopqr'.

I think I am tracking the index of which the DMA starts filling the buffer effectively but it appears not because characters are being appended to the string at an unknown index. I tried debugging this step by step, but I get weird output which is never correct from the first TX. I am not sure where I am going wrong. Any help would be appreciated.

like image 288
S23 Avatar asked Dec 18 '25 15:12

S23


1 Answers

The test string is longer then the DMA receive buffer. On the 16th character a TC interrupt is triggered, but while that data are being processed, DMA may still receive more characters into the same buffer, overwriting the previous data.

This is not the correct way of handling continuous transfers with DMA. The DMA controller must be provided with a sufficiently-sized buffer for writing new data, while the (other) buffer of received data is being processed by your code.

Possible options are: (a) two distinct buffers in double-buffered mode, or (b) a single, double-sized buffer using the half-transfer interrupt. Each individual or half buffer must be large enough to store new characters, while the other received half is being processed.

If the processing is not complete on a buffer when the DMA controller generates a TC interrupt on the other buffer, then that's an indication of a broken implementation. Larger buffers is the typical, cheap solution.

like image 57
Flexz Avatar answered Dec 21 '25 06:12

Flexz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!