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循环模式实现不定长接收:
配置硬件
- 在CubeMX中开启I2C的STOP中断(NVIC设置里勾选I2C的STOP中断),或代码手动开启:
__HAL_I2C_ENABLE_IT(&hi2c1, I2C_IT_STOPF); - 配置DMA为循环模式(Circular Mode),内存地址增量,外设地址固定(I2C数据寄存器),缓冲区设为最大接收长度(如
#define MAX_RX_BUF 20)
- 在CubeMX中开启I2C的STOP中断(NVIC设置里勾选I2C的STOP中断),或代码手动开启:
启动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);重写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); }实现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); }
方法二:逐字节接收+错误回调处理超时(逻辑简单,适合小数据量)
通过逐字节接收,利用超时错误判断传输结束,同时移动缓冲区指针避免数据覆盖:
定义全局变量
uint8_t rx_buf[MAX_RX_BUF]; uint8_t *rx_ptr = rx_buf; uint16_t rx_count = 0;首次启动接收
HAL_I2C_Slave_Receive_DMA(&hi2c1, rx_ptr, 1);实现接收完成回调
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); } } }实现错误回调,处理超时(即传输结束)
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
相关产品推荐
相关产品推荐

