STM32:未对齐的循环 DMA UART 缓冲区

Posted

技术标签:

【中文标题】STM32:未对齐的循环 DMA UART 缓冲区【英文标题】:STM32: unaligned circular DMA UART buffer 【发布时间】:2019-09-24 03:40:26 【问题描述】:

对于我的 STM32L053 微控制器应用程序,我需要一个稳定的 UART RX 缓冲区,并用于来自 github 的 DMA 实现,它基于 ST HAL:https://github.com/akospasztor/stm32-dma-uart。

如果 RX 输入数据根据相应的缓冲区大小对齐,则此实现非常稳定。例如,如果缓冲区大小为 24 字节,并且所有传入数据请求的大小是此缓冲区长度的倍数,例如每个请求 8 字节,则缓冲区溢出可以正常工作。

我的应用程序使用不同的消息长度,因此我发现此实现存在未对齐缓冲区溢出的弱点。例如,如果缓冲区长度设置为 23 字节,则前两个 8 字节消息都正确传递,但下一个 8 字节消息没有正确传输。

为此,我扩展了HAL_UART_RxCpltCallback 例程,通过使用CNDTR DMA 寄存器并记住变量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;

到目前为止,一切都可以无缝运行,但后来我发现了缓冲区溢出时 CNDTR 寄存器的奇怪行为: 如果我在将CNDTR 寄存器值分配给变量currCNDTR 后在断点处暂停,然后将CNDTR 寄存器的当前寄存器值与调试器中提到的变量进行比较,则该变量始终比CNDTR的寄存器值,虽然没有其他变量赋值?!

有人可以帮我弄清楚我在这里做错了什么吗?

【问题讨论】:

【参考方案1】:

对齐没有什么共同点。您需要处理两个事件 - DMA 传输结束 - 它发生在 CNDR 达到零和来自 USART 的 IDLE 以发现 usart 传输结束时。 NTDR 将低于传输发生在后台并且触发断点需要一些时间是很合乎逻辑的。

【讨论】:

如果不存在调试/断点,问题也会存在。您能否解释一下,为什么此实现适用于 24 字节的缓冲区大小和 8 个字节的读取请求而没有问题(我进行了几个小时的测试运行没有任何问题)但是如果我将缓冲区大小减小到 23 个字节并且仍然请求8 个字节,每个缓冲区溢出都会导致传入数据损坏(接下来的 2 个请求会按估计再次工作,直到下一个损坏的缓冲区溢出等等......)?【参考方案2】:

因为 ARM 处理器的整个 Mx 系列(又名 Thumb)在 AMBA 总线 DMA pcore 上具有至少 4 个地址线,因此所有与 DMA 相关的缓冲区都必须对齐32 个字节。

gcc 编译器示例:

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

此外,在处理 DMA 时,请始终记住至少双缓冲区 - 一个由 DMA 使用,另一个由应用程序使用。

【讨论】:

以上是关于STM32:未对齐的循环 DMA UART 缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

STM32:在DMA模式下实现UART

stm32f4 dma + uart idle + double 调试小记

STM32F411E-DISCO Uart 循环缓冲区中断

STM32 ADC_DMA_UART 数据传输

stm32f412 SPI dma接收和UART dma发送问题解决

stm32dma串口没有收到包头