You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32G474RET6 I2C从机DMA接收未知数据长度问题求助

STM32G474 I2C从机DMA接收不定长数据的解决方案

针对你遇到的HAL_I2C_Slave_Receive_DMA需要固定长度、接收不定长数据时触发错误回调的问题,以下是几种可行的解决思路:

核心问题分析

你之前的两种尝试都会触发ErrorCallback的原因:

  • 传入缓冲区最大长度(如20):DMA会持续等待接收满指定长度,主机只发5字节后发送STOP,DMA未完成传输,HAL判定超时错误
  • 传入Size=1逐字节接收:每次接收1字节后DMA完成,但未及时重新配置下一次接收,后续无数据时触发超时,且未移动缓冲区指针导致数据覆盖

方法一:STOP中断+DMA循环模式(推荐,效率高)

利用I2C的STOP条件判断传输结束,结合DMA循环模式实现不定长接收:

  1. 配置硬件

    • 在CubeMX中开启I2C的STOP中断(NVIC设置里勾选I2C的STOP中断),或代码手动开启:
      __HAL_I2C_ENABLE_IT(&hi2c1, I2C_IT_STOPF);
      
    • 配置DMA为循环模式(Circular Mode),内存地址增量,外设地址固定(I2C数据寄存器),缓冲区设为最大接收长度(如#define MAX_RX_BUF 20)
  2. 启动DMA接收

    uint8_t rx_buf[MAX_RX_BUF];
    uint16_t rx_len = 0;
    
    // 启动循环模式的DMA接收
    HAL_I2C_Slave_Receive_DMA(&hi2c1, rx_buf, MAX_RX_BUF);
    
  3. 重写I2C中断处理函数,捕获STOP条件
    HAL默认不会处理STOP中断的回调,需在IRQHandler中手动检测并处理:

    void I2C1_IRQHandler(void)
    {
      // 检测STOP标志
      if (__HAL_I2C_GET_FLAG(&hi2c1, I2C_FLAG_STOPF))
      {
        __HAL_I2C_CLEAR_FLAG(&hi2c1, I2C_FLAG_STOPF);
        // 自定义STOP回调处理
        I2C_Slave_Stop_Callback(&hi2c1);
      }
      // 保留HAL原有中断处理逻辑
      HAL_I2C_IRQHandler(&hi2c1);
    }
    
  4. 实现STOP回调,处理接收数据

    void I2C_Slave_Stop_Callback(I2C_HandleTypeDef *hi2c)
    {
      // 停止DMA传输
      HAL_DMA_Abort(hi2c->hdmarx);
      // 计算实际接收长度:缓冲区总长度 - DMA剩余待传输字节数
      rx_len = MAX_RX_BUF - __HAL_DMA_GET_COUNTER(hi2c->hdmarx);
      
      // 这里处理rx_buf中的rx_len字节数据
      // ProcessReceivedData(rx_buf, rx_len);
      
      // 重新启动DMA接收,等待下一次传输
      HAL_I2C_Slave_Receive_DMA(hi2c, rx_buf, MAX_RX_BUF);
    }
    

方法二:逐字节接收+错误回调处理超时(逻辑简单,适合小数据量)

通过逐字节接收,利用超时错误判断传输结束,同时移动缓冲区指针避免数据覆盖:

  1. 定义全局变量

    uint8_t rx_buf[MAX_RX_BUF];
    uint8_t *rx_ptr = rx_buf;
    uint16_t rx_count = 0;
    
  2. 首次启动接收

    HAL_I2C_Slave_Receive_DMA(&hi2c1, rx_ptr, 1);
    
  3. 实现接收完成回调

    void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c)
    {
      if (hi2c->Instance == I2C1)
      {
        rx_count++;
        rx_ptr++;
        // 检查缓冲区是否已满,避免溢出
        if (rx_count < MAX_RX_BUF)
        {
          // 启动下一字节接收
          HAL_I2C_Slave_Receive_DMA(hi2c, rx_ptr, 1);
        }
      }
    }
    
  4. 实现错误回调,处理超时(即传输结束)

    void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
    {
      if (hi2c->Instance == I2C1 && hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT)
      {
        // 超时说明主机已发送STOP,rx_count为实际接收长度
        // ProcessReceivedData(rx_buf, rx_count);
        
        // 重置指针和计数,准备下一次接收
        rx_ptr = rx_buf;
        rx_count = 0;
        // 清除错误标志,重新启动接收
        __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_TIMEOUT);
        HAL_I2C_Slave_Receive_DMA(hi2c, rx_ptr, 1);
      }
    }
    

注意事项

  • 无论哪种方法,都必须依赖STOP条件或超时来判断传输结束,I2C从机本身无法主动知道主机要发多少字节
  • 若使用DMA循环模式,需确保缓冲区足够大,避免溢出
  • 处理完数据后必须重新启动接收,否则下一次主机传输不会被响应

内容的提问来源于stack exchange,提问作者Gunce

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 14:27:48