I2C读写出错经常卡死在中断里的处理stm32f103

张开发
2026/4/14 18:15:05 15 分钟阅读

分享文章

I2C读写出错经常卡死在中断里的处理stm32f103
I2C读写出错经常卡死在中断里的处理stm32芯片stm32f103rct6日期2026年4月13日概览程序调试笔记在电动轮椅的电驱项目中偶尔会出现重启的问题。经查是看门狗在重启。进一步排查发现程序频繁进入I2C事件中断卡死在了I2C中断里。后边通过修改代码不再重启问题得到解决。具体过程第一步看重启原因在main()函数开始增加一个函数Reset_Cause_Check()检测重启原因。int main(void) { Reset_Cause_Check(); // 增加一个函数检测复位原因 /* USER CODE BEGIN 1 */ // IROM1: 0x8000000, 0x20000 // before // IROM1: 0x8004000, 0x1C000 // after // fromelf --bin -o $LL.bin #L //SCB-VTOR FLASH_BASE | 0x4000; HAL_DeInit(); /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); ... ...复位原因的检查函数如下// 复位原因检测函数 uint8_t reset_cause 0; void Reset_Cause_Check(void) { //uart_printf(复位原因); // 基于HAL库__HAL_RCC_GET_FLAG宏检测复位标志对应stm32f1xx_hal_rcc.h定义 if(__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) ! RESET) { //uart_printf(独立看门狗复位(IWDG)\r\n); reset_cause 1; } else if(__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) ! RESET) { //uart_printf(窗口看门狗复位(WWDG)\r\n); reset_cause 2; } else if(__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST) ! RESET) { //uart_printf(上电/掉电复位(POR/PDR)\r\n); reset_cause 3; } else if(__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST) ! RESET) { //uart_printf(外部引脚复位(NRST)\r\n); reset_cause 4; } else if(__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST) ! RESET) { //uart_printf(软件复位\r\n); reset_cause 5; } else { //uart_printf(未知复位\r\n); reset_cause 6; } // 清除复位标志HAL库原生方式对应stm32f1xx_hal_rcc.c中RCC-CSR | RCC_CSR_RMVF __HAL_RCC_CLEAR_RESET_FLAGS(); }故障重启后DEBUG会进入到Reset_Cause_Check()函数中观察到reset_cause1 or 2发现是看门狗复位引起的重启。第二步看中断函数屏蔽所有看门狗不让芯片重启然后继续测试。在stm32f1xx_it.c文件中挨个打断点发现程序不断进入void I2C2_EV_IRQHandler(void)中断中。其他地方程序跑不进。定位是I2C出问题了。不用说应该是I2C出现了读写错误导致的。就我以往的程序I2C读取非常的可靠。各种产品出货都有10万只了代码也使用了9年了都一直很可靠的工作从未出过问题。这次的问题只可能是一个原因就是这次的电驱板子有直流电机驱动H桥导致I2C受到了干扰频繁出现了错误帧。加之此前程序没有对错误进行处理才导致不断重启的问题。问题修复第一步I2C_EV_IRQ中断增加I2C复位在中断中增加一个i2c_irq_count计数进入中断错误次数达到800那么启用I2C的RESET重置。/** * brief This function handles I2C2 event interrupt. */ uint16_t i2c_irq_count 0; void I2C2_EV_IRQHandler(void) { /* USER CODE BEGIN I2C2_EV_IRQn 0 */ //static uint16_t i2c_irq_count 0; // 每次进入中断 1异常时会一直累加 i2c_irq_count; // 连续触发太多 死循环 → 强制复位自救 if (i2c_irq_count 800) { i2c_irq_count 0; // 先清零防止递归 I2C2_Reset_Bus(); // 直接退出不再执行 HAL 处理 return; } /* USER CODE END I2C2_EV_IRQn 0 */ // 执行 HAL 库中断处理 HAL_I2C_EV_IRQHandler(hi2c2); /* USER CODE BEGIN I2C2_EV_IRQn 1 */ // 关键点 // 只有 I2C 状态回到 READY才说明正常结束 → 清零计数 if (hi2c2.State HAL_I2C_STATE_READY) { i2c_irq_count 0; } // 如果 State 还是 BUSY说明没处理完 → 不清零 // 下次中断继续累加 → 达到阈值触发自救 /* USER CODE END I2C2_EV_IRQn 1 */ }第二步I2C错误中断增加I2C错误中断增加I2C复位处理void I2C2_ER_IRQHandler(void) { /* 必须先调用HAL库错误处理 */ HAL_I2C_ER_IRQHandler(hi2c2); /* 发生任何错误 → 强制复位I2C防止卡死 */ if (hi2c2.ErrorCode ! HAL_I2C_ERROR_NONE) { I2C2_Reset_Bus(); } }第三步复位函数增加复位函数void I2C2_Reset_Bus(void) { // 硬件复位 I2C2唯一能救死锁的方法 __HAL_I2C_DISABLE(hi2c2); __HAL_RCC_I2C2_FORCE_RESET(); __HAL_RCC_I2C2_RELEASE_RESET(); HAL_I2C_DeInit(hi2c2); HAL_I2C_Init(hi2c2); // 恢复 HAL 状态机 hi2c2.State HAL_I2C_STATE_READY; hi2c2.ErrorCode HAL_I2C_ERROR_NONE; // 重新开启错误中断 __HAL_I2C_ENABLE_IT(hi2c2, I2C_IT_ERR); }第四步I2C初始化时要开启ERR中断之前配置I2C工程的时候没有开启错误中断在I2C初始化代码里面增加了一条指令__HAL_I2C_ENABLE_IT(hi2c2,I2C_IT_ERR);static void MX_I2C2_Init(void) { /* USER CODE BEGIN I2C2_Init 0 */ /* USER CODE END I2C2_Init 0 */ /* USER CODE BEGIN I2C2_Init 1 */ /* USER CODE END I2C2_Init 1 */ hi2c2.Instance I2C2; hi2c2.Init.ClockSpeed 400000; hi2c2.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c2.Init.OwnAddress1 0; hi2c2.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c2.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; hi2c2.Init.OwnAddress2 0; hi2c2.Init.GeneralCallMode I2C_GENERALCALL_DISABLE; hi2c2.Init.NoStretchMode I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(hi2c2) ! HAL_OK) { Error_Handler(); } /* USER CODE BEGIN I2C2_Init 2 */ // 开启 I2C 错误中断 必须加 __HAL_I2C_ENABLE_IT(hi2c2, I2C_IT_ERR); /* USER CODE END I2C2_Init 2 */ }第五步I2C读写代码I2C读和写的代码也进行了必要修改如下/******************************************************************* I2C 读 *******************************************************************/ bool i2cdevRead(uint8_t devAddress, uint8_t memAddress, uint8_t *data, uint16_t len) { bool status false; // 默认失败 // 1. 拿信号量 if(xSemaphoreTake(xI2CSemaphore, pdMS_TO_TICKS(100)) pdTRUE) { // // 第一步发送地址DMA // if (HAL_I2C_Master_Transmit_DMA(hi2c2, (uint16_t)(devAddress 1), memAddress, 1) HAL_OK) { // 等待完成带超时非常重要 uint32_t timeout 100; while (HAL_I2C_GetState(hi2c2) ! HAL_I2C_STATE_READY timeout-- 0) { vTaskDelay(1); } // 超时 总线死锁 if (timeout 0) { I2C2_Reset_Bus(); // 必须复位 xSemaphoreGive(xI2CSemaphore); // 漏了这句任务直接死 return false; } } else { I2C2_Reset_Bus(); xSemaphoreGive(xI2CSemaphore); return false; } vTaskDelay(1); // 你原来的延时保留 // // 第二步读取数据DMA // if (HAL_I2C_Master_Receive_DMA(hi2c2, (uint16_t)(devAddress 1), data, len) HAL_OK) { uint32_t timeout 100; while (HAL_I2C_GetState(hi2c2) ! HAL_I2C_STATE_READY timeout-- 0) { vTaskDelay(1); } if (timeout 0) { I2C2_Reset_Bus(); xSemaphoreGive(xI2CSemaphore); return false; } status true; // 成功 } else { I2C2_Reset_Bus(); } // // 最终无论如何都释放信号量 // xSemaphoreGive(xI2CSemaphore); } return status; } /******************************************************************* I2C 写 *******************************************************************/ bool i2cdevWrite(uint8_t devAddress, uint8_t memAddress, uint8_t *data, uint16_t len) { bool status false; // 默认失败 static uint8_t buffer[32]; int i; uint16_t fill; uint16_t writeLen 0; uint16_t offsetAddr 0; if (len 0) return false; // 电源不稳禁止写 if (READ_BIT(PWR-CSR, PWR_CSR_PVDO) ! 0) { return false; } HAL_GPIO_WritePin(EE_WP_GPIO_Port, EE_WP_Pin, GPIO_PIN_RESET); // 解除写保护 // 拿信号量带超时禁止永久阻塞 if (xSemaphoreTake(xI2CSemaphore, pdMS_TO_TICKS(200)) pdTRUE) { status true; // 假设成功中途出错再改 offsetAddr 0; while (true) { fill 16 - memAddress % 16; if (len fill) { writeLen fill; } else { writeLen len; } if (writeLen 0) break; // 组装数据 buffer[0] memAddress; for (i 0; i writeLen; i) { buffer[i 1] data[i offsetAddr]; } // -------------------------- 发送数据DMA -------------------------- uint32_t timeout; if (HAL_I2C_Master_Transmit_DMA(hi2c2, (uint16_t)(devAddress 1), buffer, writeLen 1) HAL_OK) { // 等待 DMA 完成带超时 timeout 100; while (HAL_I2C_GetState(HI2C2) ! HAL_I2C_STATE_READY timeout-- 0) { vTaskDelay(1); } // 超时 总线死锁 if (timeout 0) { I2C2_Reset_Bus(); status false; break; } } else { // 发送失败 I2C2_Reset_Bus(); status false; break; } vTaskDelay(5); // EEPROM 内部写入时间必须保留 // 地址偏移更新 memAddress writeLen; offsetAddr writeLen; len - writeLen; if (writeLen 0) break; } // 无论如何都释放信号量 xSemaphoreGive(xI2CSemaphore); } HAL_GPIO_WritePin(EE_WP_GPIO_Port, EE_WP_Pin, GPIO_PIN_SET); // 加写保护 return status; }

更多文章