ST意法半导体
直播中

汪潇潇

8年用户 920经验值
私信 关注
[问答]

STM32F407骄阳电机版用DMA双缓存接收串口数据时,上电第一次接收区是memory1而不是memory0?为什么?

STM32 F407骄阳电机版用DMA双缓存接收串口数据时,上电第一次接收区是memory1而不是memory0?

回帖(2)

bozai602

2025-6-12 16:42:19
基于双缓冲做DMA接收时,第一次用哪个缓冲区,是可以配置的。


我这边查看了STM32 HAL库相关API函数,我们可以通过调整那两个目标内存地址的位置来


决定先用哪个内存区。你不妨结合代码再看看和验证。
举报

万航渡路

2025-6-18 17:52:29

STM32F407 在使用 DMA 双缓冲区(Double Buffer)模式接收串口数据时,上电后第一次接收发生在 Memory1 而不是 Memory0,这是 由DMA控制器在双缓冲模式下的初始状态和HAL库的启动机制共同决定的正常行为,并非错误。主要原因是:


? 核心原因:DMA控制寄存器 DMA_SxCR 中的 CT (Current Target) 位上电复位值




  1. 双缓冲关键机制 - CT 位:



    • 在双缓冲模式下,DMA 控制器内部有一个关键的控制位叫做 CT (Current Target)。

    • CT 位决定了 下一次 DMA 传输的目标内存缓冲区是 Memory0 (CT=0) 还是 Memory1 (CT=1)。

    • 关键点: 根据 STM32F4 参考手册(RM0090),DMA 流控制寄存器 DMA_SxCR 中的 CT在上电/系统复位后通常是 1。这意味着 DMA 控制器的初始预期是下一次传输的目标是 Memory1




  2. HAL_DMA_Start_IT() / HAL_UARTEx_ReceiveToIdle_DMA() 的启动过程:



    • 当你调用 HAL_DMA_Start_IT() 或更常用的 HAL_UARTEx_ReceiveToIdle_DMA(&huartX, m0_addr, size) 启动 UART DMA 接收时,库函数内部会进行一系列配置:

      • 设置外设地址(如 USART_DR)。

      • 设置主存储器地址(Memory0 的起始地址 m0_addr)。

      • 设置 DMA 传输的 NDTR (Number of Data register) 为你要接收的总数据量 size

      • 设置传输方向(外设到内存)。

      • 关键步骤:最后启用 DMA 流 (EN 位) 之前或同时,库会配置双缓冲功能。


    • 双缓冲配置的具体细节 (HAL 的实现):

      • 库会设置 DMA_SxM0AR 寄存器为 Memory0 的地址 (m0_addr)。

      • 库会设置 DMA_SxM1AR 寄存器为 Memory1 的地址 (m1_addr)。

      • 首次启用双缓冲(通过设置 DMA_SxCR 中的 DBM 位)并且同时启用 DMA 流 (EN 位) 时,DMA 控制器会 使用当前 CT 位的值 来确定哪个内存缓冲区 (M0ARM1AR) 将被用作 第一个传输目标

      • 由于 上电后 CT 的初始值通常是 1,DMA 控制器会选择 Memory1 (DMA_SxM1AR) 作为第一个传输的目标。

      • 在第一个 MEMSIZE (半传输或传输完成) 中断发生时,DMA 控制器会自动 翻转 CT (CT = 1 - CT)。此时:

        • 如果第一次用了 Memory1 (CT 由初始 1 变为 0),下一次就指向 Memory0

        • DMA 将配置新的传输计数器 (NDTR) 为 size,但这次的目标地址是 Memory0 (DMA_SxM0AR)。






? 为什么用户可能期待第一次是 Memory0


用户通常这样配置:HAL_UARTEx_ReceiveToIdle_DMA(&huartX, m0_addr, size),其中 m0_addr 被命名为 Memory0 的地址。这可能会让人直觉认为第一次传输应该使用传入的这个 m0_addr 地址。然而:



  • 传入的 m0_addr 实际上只是设置了 DMA_SxM0AR 寄存器的值。

  • 哪个缓冲区 被使用 (M0AR 还是 M1AR) 是由当前 CT 位的值和双缓冲启用时的逻辑决定的,而不是由传入顺序或参数名决定的。

  • HAL_UARTEx_ReceiveToIdle_DMA 这个 API 设计的目标是将 m0_addrm1_addr 配置为两个平等的、可交替使用的缓冲区,而非指定“第一个”必须是哪一个。它依赖的是 DMA 控制器内部的 CT 位切换机制。


✅ 结论


STM32F407 的 DMA 控制器在双缓冲模式下,由于其控制寄存器 CT 位上电复位值通常为 1 (1),导致在首次调用库函数启动 DMA 传输时(如 HAL_UARTEx_ReceiveToIdle_DMA),第一次数据接收操作的目标内存地址指向 DMA_SxM1AR(即 Memory1)。之后,每完成一次缓冲区传输(通过半传输或传输完成中断),CT 位会自动翻转,数据就会在 Memory0Memory1 之间稳定交替进行后续的接收。


如何正确使用




  1. 接受这种设计: 认识到第一次是 Memory1 是正常硬件行为,不是 BUG。你的代码在处理 DMA 传输完成中断(HAL_UARTEx_RxEventCallback 或相关回调函数)时:


    void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
      // 使用 HAL_DMAEx_GetCurrentMemoryTarget 获取当前被填满的缓冲区
      uint32_t current_mem = HAL_DMAEx_GetCurrentMemoryTarget(huart->hdmarx);

      if (current_mem == 0) {
        // 处理 Memory0 中的数据,长度 Size
      } else if (current_mem == 1) {
        // 处理 Memory1 中的数据,长度 Size
      }

      // 处理完成后,库和硬件会自动准备接收下一个缓冲区
    }


  2. 不要依赖第一次是Memory0 你的数据处理逻辑应完全基于 HAL_DMAEx_GetCurrentMemoryTarget() 返回的值来确定当前哪个缓冲区有效。

  3. 重新启动传输: 在处理完数据后,库函数会在回调中自动准备下一次传输(设置新的 NDTR 等)。无需再手动调用 HAL_UARTEx_ReceiveToIdle_DMA,否则会重新初始化 DMA 传输状态机,可能导致混乱。


简而言之,STM32F4 DMA 双缓冲的设计逻辑就是首次使用初始 CT 指向的缓冲区(通常是 Memory1),然后不断翻转交替。理解了这个机制并在代码中正确使用 HAL_DMAEx_GetCurrentMemoryTarget() 来区分缓冲区,就能保证双缓冲功能稳定运行。?

举报

更多回帖

发帖
×
20
完善资料,
赚取积分