1
votes

for my STM32L053 microcontroller application I need a stable UART RX buffer and used for that the DMA implementation from github, which is based on the ST HAL: https://github.com/akospasztor/stm32-dma-uart.

This implementation works quite stable if the RX input data is aligned according to the corresponding buffer size. For example if the buffer size is 24 bytes and all incoming data requests have a multiple size of this buffer length is, let`s say for example 8 bytes per requests, a buffer overrun works fine without problems.

My application uses different message lengths, so I discovered that this implementation has a weakness for unaligned buffer overrun. For example if the buffer length is set to 23 bytes the first both 8 byte messages are passed correctly, but the next 8 bytes message are not correctly transmitted.

For this I extended the HAL_UART_RxCpltCallback routine for handling an unaligned buffer overrun by using the CNDTR DMA register and remembering it´s last value in the variable dma_uart_rx.prevCNDTR:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle)
{
  uint16_t i, pos, start, length;
  uint32_t currCNDTR = huart.hdmarx->Instance->CNDTR;

  /* Ignore IDLE Timeout when the received characters exactly filled up the DMA buffer and DMA Rx Complete IT is generated, but there is no new character during timeout */
  if((dma_uart_rx.flag && currCNDTR == DMA_BUF_SIZE) || error_flag)
  {
    error_flag = RESET;
    dma_uart_rx.flag = 0;
    return;
  }

  /* Determine start position in DMA buffer based on previous CNDTR value */
  start = (dma_uart_rx.prevCNDTR < DMA_BUF_SIZE) ? (DMA_BUF_SIZE - dma_uart_rx.prevCNDTR) : 0;

  if (dma_uart_rx.flag)    /* Timeout event */
  {
    /* Determine new data length based on previous DMA_CNDTR value:
     *  If previous CNDTR is less than DMA buffer size: there is old data in DMA buffer (from previous timeout) that has to be ignored.
     *  If CNDTR == DMA buffer size: entire buffer content is new and has to be processed.
    */
    length = (dma_uart_rx.prevCNDTR < DMA_BUF_SIZE) ? (dma_uart_rx.prevCNDTR - currCNDTR) : (DMA_BUF_SIZE - currCNDTR);
    dma_uart_rx.prevCNDTR = currCNDTR;
    dma_uart_rx.flag = 0;
  }
  else                /* DMA Rx Complete event */
  {
    // My buffer overrun handling
    if (currCNDTR > dma_uart_rx.prevCNDTR) 
    {
      length = dma_uart_rx.prevCNDTR + DMA_BUF_SIZE - currCNDTR;

      // 1st rx data part
      for (i=0, pos=DMA_BUF_SIZE - dma_uart_rx.prevCNDTR; pos < DMA_BUF_SIZE; ++i,++pos)
      {
        data[i] = dma_rx_buf[pos];
      }

      // 2nd rx data part
      for (pos=0; pos < DMA_BUF_SIZE - currCNDTR; ++i,++pos)
      {
        data[i] = dma_rx_buf[pos];
      }

      receivedBytes = length;
      dma_uart_rx.prevCNDTR = currCNDTR;
      return;
    }

    length = DMA_BUF_SIZE - start;
    dma_uart_rx.prevCNDTR = DMA_BUF_SIZE;
  }

  /* Copy and Process new data */
  for (i=0,pos=start; i<length; ++i,++pos)
  {
    data[i] = dma_rx_buf[pos];
  }

  receivedBytes = length;
}

Until this point everything works seamless, but then I figured out a strange behavior of the CNDTR register on a buffer overrun: If I halt on a breakpoint after assigning the CNDTR register value to the variable currCNDTR and then compare the current register value of the CNDTR register with the mentioned variable in the debugger, the variable is always 1 byte higher than the register value of CNDTR, although there are no other assignments of the variable?!

enter image description here

Can someone please help me figuring out, what I am doing wrong here?

2

2 Answers

0
votes

there is nothing commony with the alignment. You need to handle two events - end of the DMA transmition - it happens when CNDR reaches zero and IDLE from the USART to discover end of the usart transmition. It is quite logical that the NTDR will be lower than as the transmition happens in the background and the triggering a breakpoint takes some time.

0
votes

because the whole Mx series (aka Thumb) of the ARM processor having least 4 address lines on AMBA bus DMA pcore skipped, all DMA related buffers has to be aligned with the 32 bytes.

example for the gcc compiler:

uint8_t dumpBuffer[2][DUMP_LIMIT] __attribute__ ((aligned(32)));

also, always remember about at least double-buffer when dealing with the DMA - the one is used by DMA the other one is used by the application.