STM32F401RBT作为I2C从机:接收数据后LED异常及通信中断问题
STM32F401 I2C从机回调异常分析与解决
问题原因分析
未重新启动I2C接收中断
HAL库的HAL_I2C_SlaveRxCpltCallback是单次触发回调,仅在完成一次指定长度的接收后执行。若处理完当前数据后未重新调用HAL_I2C_Slave_Receive_IT()开启下一次接收中断,STM32会停止监听主机通信请求,后续主机发送数据时不会返回ACK,表现为“不再响应通信”。中断上下文使用阻塞延时
HAL_Delay()依赖SysTick中断实现,而I2C回调函数运行在中断上下文。如果I2C中断优先级高于SysTick中断,HAL_Delay()执行时会因SysTick无法触发进入无限等待,直接导致系统卡死,LED持续点亮无法切换状态。回调中意外破坏I2C状态或硬件冲突
若回调函数误修改I2C相关寄存器,或LED驱动电路对I2C的SDA/SCL引脚造成电平干扰,可能导致第一次通信后I2C外设进入错误状态,无法响应后续请求。
对应解决方法
重启I2C接收中断
在HAL_I2C_SlaveRxCpltCallback函数末尾添加重新启动中断的代码,示例:void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) { // 处理接收到的数据(判断是否为'3') if(rx_buffer[0] == '3') { // 设置闪烁标志位,而非直接调用阻塞延时 start_led_blink = 1; } // 重新开启下一次接收中断,确保能响应主机后续通信 HAL_I2C_Slave_Receive_IT(hi2c, rx_buffer, 1); }替换阻塞延时为非阻塞逻辑
把LED闪烁逻辑移到主循环中,用时间戳实现非阻塞延时:// 全局变量 uint32_t led_last_tick = 0; uint8_t led_state = 0; uint8_t start_led_blink = 0; int main(void) { // 初始化代码... while(1) { if(start_led_blink) { if(HAL_GetTick() - led_last_tick >= 100) { led_last_tick = HAL_GetTick(); led_state = !led_state; HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, led_state); // 可添加闪烁次数限制,比如闪烁5次后停止 // static uint8_t blink_count = 0; // if(++blink_count >= 10) { start_led_blink = 0; blink_count = 0; } } } // 其他主循环任务... } }排查硬件与寄存器状态
- 确认LED引脚与I2C引脚(SDA/SCL)无电气连接冲突,避免LED驱动时干扰I2C引脚电平。
- 若仍有异常,可在回调函数末尾添加错误标志清除代码:
__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF | I2C_FLAG_ARLO | I2C_FLAG_BERR);
内容的提问来源于stack exchange,提问作者Swiss Gnome
相关产品推荐
相关产品推荐

