[APM32F1] APM32F103硬件IIC错误恢复

[复制链接]
362|5
lc115647 发表于 2025-9-6 15:35 | 显示全部楼层 |阅读模式

一、 引言

  1. 背景介绍

    • APM32F103系列MCU作为一款与STM32F103高度兼容的国产芯片,其应用广泛性。
    • 硬件IIC外设的优势(效率高、节省CPU资源)与常见痛点(易锁死、抗干扰能力弱)。
  2. 问题阐述

    • 描述硬件IIC在通信过程中可能出现的错误场景:总线忙(BUSY)、仲裁丢失、地址无应答(ACK Failure)、数据无应答、总线错误等。
    • 这些错误如何导致IIC控制器进入“挂起”或“锁死”状态,表现为SDA或SCL线被拉低,整个通信瘫痪。
  3. 本文目标

    • 提供一套针对APM32F103硬件IIC的、系统性的错误检测、诊断和恢复方案,帮助开发者构建鲁棒的IIC通信程序。
  4. 软硬件IIC对比

    5.png

二、 APM32F103硬件IIC基础与错误寄存器分析

  1. 硬件IIC主要寄存器概览
    • 控制寄存器(IIC_CTLR1, IIC_CTLR2):配置使能、中断、ACK等。

      1.png

      3.png

    • 状态寄存器(IIC_STAR1, IIC_STAR2):核心重点,包含各种错误和事件状态标志。

  2. 关键错误状态标志详解
    • BUSY** (IIC_STAR2[1]):总线忙标志,指示IIC总线是否正处于通信状态。

    • BERR** (IIC_STAR1[8]):总线错误,检测到非法的起始或停止条件。

    • AL** (IIC_STAR1[9]):仲裁丢失,多主模式下失去总线控制权。

    • AE** (IIC_STAR1[10]):应答失败,发送地址或数据后未收到ACK。

    • OVRUR** (IIC_STAR1[11]):超载/溢出错误,数据寄存器读写过快。

    • TTE** (IIC_STAR1[14]):超时错误。

      4.png

三、 硬件IIC错误恢复的核心思想

  1. 根本原因:时钟线(SCL)或被错误拉低的数据线(SDA)导致总线死锁。
  2. 恢复核心模拟时钟脉冲。通过软件控制GPIO,产生一定数量的时钟信号(SCL),迫使从设备释放SDA线,从而解除总线锁死状态。
  3. 恢复流程三步走
    • 第1步:检测与诊断 - 通过状态寄存器判断错误类型。
    • 第2步:尝试软复位 - 操作IIC外设的SWRST位,尝试软件复位IIC控制器。
    • 第3步:硬件恢复(最后手段) - 如果软复位无效,则切换到GPIO模式,模拟时钟序列“撬开”总线。

四、 详细的错误恢复步骤与代码实现

  1. 错误检测
    • 在IIC中断服务程序(ISR)或状态查询中,检测上述错误标志位(BERR, AL, AE等)。
  2. 初步处理与软件复位
    • 清除错误标志。

    • 关闭IIC外设(I2CEN=0)。

    • 置位软件复位位(IIC_CTLR1中的SWRST位)。

    • 延时片刻后,清除SWRST位,重新配置并使能IIC外设(I2CEN=1)。

    • 代码示例

      void  I2C_Isr(void)
      {
          uint8_t det;
          char dat;
      
          if ( I2C_ReadIntFlag(I2C1,I2C_INT_FLAG_BERR) == SET)
          {
      	//清除错误标志。
              I2C_ClearIntFlag(I2C1,I2C_INT_FLAG_BERR);
      	//关闭IIC外设(I2CEN=0)
      	I2C_Disable(I2C1);
              //置位软件复位位(IIC_CTLR1中的SWRST位)
      	I2C_EnableSoftwareReset(I2C1);
      	Delay(5);
              //清除SWRST位
      	I2C_DisableSoftwareReset(I2C1);
              //重新配置并使能IIC外设(I2CEN=1)
      	I2C_Enable(I2C1);
          }
      
          if ( I2C_ReadIntFlag(I2C1,I2C_INT_FLAG_AE) == SET)
          {
              //清除错误标志。
      	I2C_ClearIntFlag(I2C1,I2C_INT_FLAG_AE);
              //关闭IIC外设(I2CEN=0)
      	I2C_Disable(I2C1);
              //置位软件复位位(IIC_CTLR1中的SWRST位)
      	I2C_EnableSoftwareReset(I2C1);
      	Delay(5);
              //清除SWRST位
      	I2C_DisableSoftwareReset(I2C1);
              //重新配置并使能IIC外设(I2CEN=1)
      	I2C_Enable(I2C1);
          }
      
          if ( I2C_ReadIntFlag(I2C1,I2C_INT_FLAG_AL) == SET)
          {
              //清除错误标志。
      	I2C_ClearIntFlag(I2C1,I2C_INT_FLAG_AL);
              //关闭IIC外设(I2CEN=0)
      	I2C_Disable(I2C1);
              //置位软件复位位(IIC_CTLR1中的SWRST位)
      	I2C_EnableSoftwareReset(I2C1);
      	Delay(5);
              //清除SWRST位
      	I2C_DisableSoftwareReset(I2C1);
              //重新配置并使能IIC外设(I2CEN=1)
      	I2C_Enable(I2C1);
          }
      }
      
  3. 终极硬件恢复(GPIO模拟时钟)
    • 前提:如果软复位后总线BUSY标志仍无法清除。

    • 步骤

      • a. 将IIC的SCL和SDA引脚配置为开漏输出模式的GPIO。
      • b. 确保初始状态:SCL输出高,SDA输出高(释放)。
      • c. 如果SDA被拉低(总线仍锁死),则开始模拟时钟:
        • 将SCL拉低,延时。
        • 将SCL释放(拉高),延时。
        • 重复以上过程若干次(如9-16次),同时监测SDA是否变为高电平。
      • d. 一旦SDA变高,立即发送一个“停止条件”(先拉低SDA->拉低SCL->释放SCL->释放SDA)。
      • e. 将GPIO重新映射回IIC外设。
      • f. 重新初始化IIC外设。
    • 代码示例

      void IIC_Recovery() {
        GPIO_Config_T gpioConfigStruct = {0};
        // 1. 配置SCL为开漏输出
        gpioConfigStruct.pin = GPIO_PIN_6;
        gpioConfigStruct.mode = GPIO_MODE_AF_OD;
        GPIO_Config(GPIOB, &gpioConfigStruct);
      
        // 2. 发生9个时钟脉冲
        for(int i=0; i<9; i++) {
          GPIO_SetBit(GPIOB, GPIO_PIN_6);
          Delay(5);
          GPIO_ResetBit(GPIOB, GPIO_PIN_6);
          Delay(5);
        }
      
         // 3. 发送停止条件
        GPIO_SetBit(GPIOB, GPIO_PIN_6);
        Delay(5);
        GPIO_SetBit(GPIOB, GPIO_PIN_7);
      
        // 4. 重新初始化I2C
        I2C_Init();
      } 
      

五、 预防优于治疗:IIC通信的稳定性设计建议

  1. 硬件设计
    • 确保上拉电阻阻值合适(通常4.7kΩ)。
    • 布线时注意远离干扰源,必要时添加屏蔽,走线距离不应该过长。
    • 在SCL和SDA线上添加适当的RC滤波或ESD保护器件。
  2. 软件设计
    • 添加超时机制:在任何等待标志位(如EV5, EV6)的地方加入超时判断,避免无限等待。

      uint8_t I2C_Write(char * pBuffer)
      {
          uint16_t I2CTimeout = I2CT_LONG_TIMEOUT;
      
          while(I2C_ReadStatusFlag(I2C1, I2C_FLAG_BUSBSY))
          {
              I2C_Init();
              if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);
          }
      
          I2C_DisableInterrupt(I2C1, I2C_INT_EVT);
          /* Send START condition */
          I2C_EnableGenerateStart(I2C1);
      
          I2CTimeout = I2CT_FLAG_TIMEOUT;
          /* EV5 */
          while(!I2C_ReadEventStatus(I2C1, I2C_EVENT_MASTER_MODE_SELECT))
          {
              if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);
          }
      
          /* Send address for write */
          I2C_Tx7BitAddress(I2C1, 0xB0, I2C_DIRECTION_TX);
      
          I2CTimeout = I2CT_FLAG_TIMEOUT;
          /* EV6 */
          while(!I2C_ReadEventStatus(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
          {
              if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);
          }
      
          /* While there is data to be written */
          while(*pBuffer != '\0')
          {
              /* Send the current byte */
              I2C_TxData(I2C1, *pBuffer);
      
              /* Point to the next byte to be written */
              pBuffer++;
      
              I2CTimeout = I2CT_LONG_TIMEOUT;
              /* EV8 */
              while (!I2C_ReadEventStatus(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
              {
                  if((I2CTimeout--) == 0)
                  {
                      return I2C_TIMEOUT_UserCallback(8);
                  }
              }
          }
      
          return 1;
      }
      
    • 错误重试机制:在发生非致命错误(如AF)时,进行有限次数的重试。

    • 定期监控:在多主系统中,定期检查总线状态。

六、 总结

硬件IIC高效但配置复杂,软件模拟灵活却消耗CPU资源。同时使用介绍硬件IIC错误的常见类型、核心恢复原理(模拟时钟)和分层恢复策略(软复位->硬件恢复)。因此建议必须添加总线锁死保护机制。同时如果处理数据量大,建议使用DMA处理。

心跳回响 发表于 2025-9-7 23:06 | 显示全部楼层
当I2C总线上出现错误的时候,解决办法基本要归为模拟时钟的方式。
银河漫步 发表于 2025-9-8 10:32 | 显示全部楼层
楼主对错误状态的总结好是全面啊!
我们当时就是NACK处理的不好,调试了好久。
空灵回声 发表于 2025-9-9 15:55 | 显示全部楼层
恢复三步走非常有参考意义,
如果I2C总线上有其它主设备,这可怎么办啊
星云狂想曲 发表于 2025-9-10 17:38 | 显示全部楼层
超时没有太多意义吧!如果总线挂了,仍然需要模拟GPIO的方式来释放总线吧
永恒回声 发表于 2025-9-12 20:59 | 显示全部楼层
精华帖子就是内容靠谱。
谢谢楼主的分享。一定注意I2C的错误处理
您需要登录后才可以回帖 登录 | 注册

本版积分规则

20

主题

29

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部