HAL_I2C_ER_IRQHandler函数解析

I2C_HandleTypeDef函数源码

c 复制代码
/**
  * @brief  本函数用于处理 I2C 的"错误中断请求"(Error Interrupt Request)。
  *         当 I2C 外设检测到总线错误/仲裁丢失/应答失败/溢出欠载等错误事件时,
  *         会触发错误中断,本函数在中断服务程序中被调用以完成:
  *         1) 读取错误标志
  *         2) 清除对应错误标志位
  *         3) 记录错误码并进入统一的错误处理流程(回调/状态机收尾等)
  *
  * @param  hi2c 指向 I2C_HandleTypeDef 结构体的指针,
  *             其中包含该 I2C 外设的配置与运行时状态信息(寄存器实例、状态、计数等)。
  * @retval 无返回值
  */
void HAL_I2C_ER_IRQHandler(I2C_HandleTypeDef *hi2c)
{
  /* tmp1:临时变量,用于保存当前 I2C 工作模式(主机/从机/MEM 等),避免重复访问结构体成员 */
  HAL_I2C_ModeTypeDef  tmp1;

  /* tmp2:临时变量,用于保存当前传输剩余计数 XferCount(剩余要发送/接收的字节数) */
  uint32_t             tmp2;

  /* tmp3:临时变量,用于保存当前 I2C 状态机状态 hi2c->State */
  HAL_I2C_StateTypeDef tmp3;

  /* tmp4:临时变量,用于保存前一个状态 hi2c->PreviousState,用来判断 LISTEN 之前来自哪个忙状态 */
  uint32_t             tmp4;

  /* sr1itflags:读取 SR1(状态寄存器1)的当前值,包含 BERR/ARLO/AF/OVR 等错误标志位 */
  uint32_t sr1itflags = READ_REG(hi2c->Instance->SR1);

  /* itsources:读取 CR2(控制寄存器2)的当前值,包含各种中断使能位(如 I2C_IT_ERR) */
  uint32_t itsources  = READ_REG(hi2c->Instance->CR2);

  /* error:本次进入中断后汇总的错误码(位或累加),初始为"无错误" */
  uint32_t error      = HAL_I2C_ERROR_NONE;

  /* CurrentMode:保存当前 I2C 模式(hi2c->Mode),后续多处判断会用到 */
  HAL_I2C_ModeTypeDef CurrentMode   = hi2c->Mode;

  /*======================== I2C Bus error(总线错误) =========================*/
  /* 触发条件:
     - SR1 中 BERR 置位:表示检测到总线错误(如非法 START/STOP、位错误等,具体见参考手册)
     - 且 CR2 中错误中断使能 I2C_IT_ERR 已打开:说明这确实是"错误中断源"触发 */
  if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_BERR) != RESET) &&
      (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_ERR) != RESET))
  {
    /* 将"总线错误"加入本次 error 汇总 */
    error |= HAL_I2C_ERROR_BERR;

    /* 清除 BERR 标志位(通过写 0/读写序列等方式实现,封装在宏中) */
    __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_BERR);

    /* 兼容性/规避措施(Workaround):
       "在一个错误放置的 STOP 之后,可能无法再生成 START"------
       某些情况下外设会卡住,因此这里触发一次软件复位(SWRST)来恢复 I2C 内部状态机。
       注:仅置位 SWRST 并不能永久复位,通常还需要后续代码清零 SWRST(由库内部其他流程处理)。 */
    SET_BIT(hi2c->Instance->CR1, I2C_CR1_SWRST);
  }

  /*================== I2C Arbitration Lost(仲裁丢失) ==================*/
  /* 触发条件:
     - SR1 中 ARLO 置位:多主机场景下仲裁丢失(本机驱动总线但被其他主机抢占)
     - 且错误中断使能已打开 */
  if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_ARLO) != RESET) &&
      (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_ERR) != RESET))
  {
    /* 将"仲裁丢失"加入本次 error 汇总 */
    error |= HAL_I2C_ERROR_ARLO;

    /* 清除 ARLO 标志位 */
    __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_ARLO);
  }

  /*==================== I2C Acknowledge failure(应答失败/NACK) ====================*/
  /* 触发条件:
     - SR1 中 AF 置位:Acknowledge Failure
       常见含义:
       1) 主机发送地址或数据后,对方没有应答(NACK)
       2) 从机发送数据时,主机在某一字节后发出 NACK(通常表示"主机不想再收了/结束接收")
     - 且错误中断使能已打开 */
  if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_AF) != RESET) &&
      (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_ERR) != RESET))
  {
    /* 这里先把关键运行时变量拷贝到临时变量,避免中断处理中多次读取结构体字段(也便于阅读/避免被修改) */
    tmp1 = CurrentMode;          /* tmp1 保存当前模式 */
    tmp2 = hi2c->XferCount;      /* tmp2 保存剩余传输字节数 */
    tmp3 = hi2c->State;          /* tmp3 保存当前状态 */
    tmp4 = hi2c->PreviousState;  /* tmp4 保存前一状态 */

    /* 特殊分支:从机发送器模式下的 AF 需要单独处理
       条件解释:
       - tmp1 == HAL_I2C_MODE_SLAVE:当前是从机模式
       - tmp2 == 0U:当前剩余传输计数为 0(通常代表从机最后一个字节已发送完)
       - 且状态属于以下任意一种:
         a) BUSY_TX:从机正在发送
         b) BUSY_TX_LISTEN:从机发送且处于监听状态
         c) LISTEN 且 PreviousState == I2C_STATE_SLAVE_BUSY_TX:
            即当前是 LISTEN 状态,但之前是从机发送忙(说明刚从发送流程退回监听)
       对于从机发送来说,AF 往往意味着主机在最后一个字节后发出了 NACK 来结束读操作,
       这在协议层是"正常的结束方式",不一定要按"错误"对待,所以 HAL 走 I2C_Slave_AF 专门收尾。 */
    if ((tmp1 == HAL_I2C_MODE_SLAVE) && (tmp2 == 0U) &&
        ((tmp3 == HAL_I2C_STATE_BUSY_TX) ||
         (tmp3 == HAL_I2C_STATE_BUSY_TX_LISTEN) ||
         ((tmp3 == HAL_I2C_STATE_LISTEN) && (tmp4 == I2C_STATE_SLAVE_BUSY_TX))))
    {
      /* 调用从机发送 AF 的专用处理函数:
         一般会做:
         - 清除 AF
         - 关闭/恢复 ACK
         - 更新状态机
         - 调用相应回调(如 TxCplt、ListenCplt 等)
         具体行为取决于 HAL 版本实现 */
      I2C_Slave_AF(hi2c);
    }
    else
    {
      /* 普通分支:把 AF 当成错误处理 */

      /* 清除 AF 标志位(必须清除,否则会持续触发错误中断或阻塞后续传输) */
      __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);

      /* 将"应答失败(AF)"加入本次 error 汇总 */
      error |= HAL_I2C_ERROR_AF;

      /* 注释翻译:在"从机接收模式"且"传输未结束(不是最后阶段)"出现 NACK 时,不要生成 STOP。
         但实际代码逻辑是:
         - 若当前是主机模式 或 内存模式(MEM) -> 生成 STOP
         也就是说:
         - 对于主机/MEM:出现 AF,通常需要结束本次传输,因此发 STOP
         - 对于从机:不在这里发 STOP(从机通常不能随意发 STOP),具体由其他流程处理 */
      if ((CurrentMode == HAL_I2C_MODE_MASTER) || (CurrentMode == HAL_I2C_MODE_MEM))
      {
        /* 生成 STOP:通过设置 CR1 的 STOP 位请求外设在总线释放时发出 STOP */
        SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
      }
    }
  }

  /*================== I2C Over-Run/Under-Run(溢出/欠载) ==================*/
  /* 触发条件:
     - SR1 中 OVR 置位:Overrun/Underrun
       常见含义:
       - 接收时:数据来得太快,DR/RX 缓冲没及时读走导致溢出
       - 发送时:没及时写入下一个数据,导致欠载
     - 且错误中断使能已打开 */
  if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_OVR) != RESET) &&
      (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_ERR) != RESET))
  {
    /* 将"溢出/欠载"加入本次 error 汇总 */
    error |= HAL_I2C_ERROR_OVR;

    /* 清除 OVR 标志位 */
    __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_OVR);
  }

  /*========================= 统一错误回调/收尾处理 =========================*/
  /* 如果本次中断中检测到了任意错误(汇总 error 不为 NONE),就进入统一的错误处理 */
  if (error != HAL_I2C_ERROR_NONE)
  {
    /* 将本次汇总到的错误码 OR 到 hi2c->ErrorCode 中(累积保存历史错误) */
    hi2c->ErrorCode |= error;

    /* 进入 I2C 的中断错误处理通用入口:
       典型行为(依 HAL 版本而定):
       - 结束/中止当前传输
       - 更新状态机(State/Mode/PreviousState)
       - 释放锁/设置 READY
       - 调用 HAL_I2C_ErrorCallback 或用户注册的回调
       - 必要时关闭相关中断源 */
    I2C_ITError(hi2c);
  }
}

这个函数比较简单,只有应答错误部分比较复杂。那么我就主要讲解一下应答错误部分:

AF(Acknowledge Failure)之所以"看起来更复杂/更负责",核心原因就一句话:AF 既可能是真错误(通信失败),也可能是协议允许的"正常结束信号"(尤其是从机发送时主机用 NACK 结束读取)而 BERR/ARLO/OVR 基本都属于"明确的硬错误",没有"正常语义"的空间。

对比其他错误:

BERR(Bus Error)

  • 表示总线时序/电气层面的异常(非法 START/STOP、位错误等)
  • 不可能是"正常结束"
    所以:记错 → 清标志 → 可能做复位 workaround → 统一错误处理。
    ARLO(Arbitration Lost)
  • 多主机仲裁丢失
  • 本质就是"我输了",是明确异常事件
    所以:记错 → 清标志 → 统一错误处理。
    OVR(Overrun/Underrun)
  • 数据没及时读/写导致溢出欠载
  • 属于明确的软件/时序处理不到位引发的错误
    所以:记错 → 清标志 → 统一错误处理。
    这些都没有"这其实可能是正常流程"的情况。

两种完全不同的含义

AF 场景 A:主机写(Master Transmit)时收到 NACK ------ 真错误居多

例如:

  • 主机发地址,对方没在(设备不存在/地址错/没上电)→ NACK
  • 主机发数据,对方拒收(寄存器不支持/内部忙等)→ NACK
    这通常需要:结束事务(STOP)+ 报错 + 回调
    所以 HAL 在 "else 分支"里会:
  • 清 AF
  • error |= HAL_I2C_ERROR_AF
  • 若是 MASTER 或 MEM:发 STOP

AF 场景 B:从机发(Slave Transmit)时主机用 NACK 结束读取 ------ 常常是"正常收尾"

I2C 规则:主机在读最后一个字节后,会用 NACK 告诉从机"我读完了",然后主机再发 STOP。

  • 对从机来说:它看到的就是"我送数据后对方没 ACK(NACK)",硬件就把 AF 置位。
  • 但这不是"通信失败",而是 主机在告诉你:结束了
    所以 HAL 不能把这类 AF 一律当错误,否则你会遇到:
  • 主机正常读完,从机却报错回调
  • 从机状态机被错误中止,监听/回调顺序错乱
  • 后续 LISTEN/下一次地址匹配可能异常
    因此 AF 必须"辨别语义":这是正常结束?还是异常?
c 复制代码
/*==================== I2C Acknowledge failure(应答失败/NACK) ====================*/
  /* 触发条件:
     - SR1 中 AF 置位:Acknowledge Failure
       常见含义:
       1) 主机发送地址或数据后,对方没有应答(NACK)
       2) 从机发送数据时,主机在某一字节后发出 NACK(通常表示"主机不想再收了/结束接收")
     - 且错误中断使能已打开 */
  if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_AF) != RESET) &&
      (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_ERR) != RESET))
  {
    /* 这里先把关键运行时变量拷贝到临时变量,避免中断处理中多次读取结构体字段(也便于阅读/避免被修改) */
    tmp1 = CurrentMode;          /* tmp1 保存当前模式 */
    tmp2 = hi2c->XferCount;      /* tmp2 保存剩余传输字节数 */
    tmp3 = hi2c->State;          /* tmp3 保存当前状态 */
    tmp4 = hi2c->PreviousState;  /* tmp4 保存前一状态 */

    /* 特殊分支:从机发送器模式下的 AF 需要单独处理
       条件解释:
       - tmp1 == HAL_I2C_MODE_SLAVE:当前是从机模式
       - tmp2 == 0U:当前剩余传输计数为 0(通常代表从机最后一个字节已发送完)
       - 且状态属于以下任意一种:
         a) BUSY_TX:从机正在发送
         b) BUSY_TX_LISTEN:从机发送且处于监听状态
         c) LISTEN 且 PreviousState == I2C_STATE_SLAVE_BUSY_TX:
            即当前是 LISTEN 状态,但之前是从机发送忙(说明刚从发送流程退回监听)
       对于从机发送来说,AF 往往意味着主机在最后一个字节后发出了 NACK 来结束读操作,
       这在协议层是"正常的结束方式",不一定要按"错误"对待,所以 HAL 走 I2C_Slave_AF 专门收尾。 */
    if ((tmp1 == HAL_I2C_MODE_SLAVE) && (tmp2 == 0U) &&((tmp3 == HAL_I2C_STATE_BUSY_TX) ||(tmp3 == HAL_I2C_STATE_BUSY_TX_LISTEN) ||((tmp3 == HAL_I2C_STATE_LISTEN) && (tmp4 == I2C_STATE_SLAVE_BUSY_TX))))
    {
      /* 调用从机发送 AF 的专用处理函数:
         一般会做:
         - 清除 AF
         - 关闭/恢复 ACK
         - 更新状态机
         - 调用相应回调(如 TxCplt、ListenCplt 等)
         具体行为取决于 HAL 版本实现 */
      I2C_Slave_AF(hi2c);
    }
    else
    {
      /* 普通分支:把 AF 当成错误处理 */

      /* 清除 AF 标志位(必须清除,否则会持续触发错误中断或阻塞后续传输) */
      __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);

      /* 将"应答失败(AF)"加入本次 error 汇总 */
      error |= HAL_I2C_ERROR_AF;

      /* 注释翻译:在"从机接收模式"且"传输未结束(不是最后阶段)"出现 NACK 时,不要生成 STOP。
         但实际代码逻辑是:
         - 若当前是主机模式 或 内存模式(MEM) -> 生成 STOP
         也就是说:
         - 对于主机/MEM:出现 AF,通常需要结束本次传输,因此发 STOP
         - 对于从机:不在这里发 STOP(从机通常不能随意发 STOP),具体由其他流程处理 */
      if ((CurrentMode == HAL_I2C_MODE_MASTER) || (CurrentMode == HAL_I2C_MODE_MEM))
      {
        /* 生成 STOP:通过设置 CR1 的 STOP 位请求外设在总线释放时发出 STOP */
        SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
      }
    }
  }

其中

c 复制代码
  if ((I2C_CHECK_FLAG(sr1itflags, I2C_FLAG_AF) != RESET) &&
      (I2C_CHECK_IT_SOURCE(itsources, I2C_IT_ERR) != RESET))

#define I2C_IT_ERR                      I2C_CR2_ITERREN

表示发生应答错误并且出错中断使能:

  • ITERREN:出错中断使能 (Error interrupt enable)
    • 0:禁止出错中断;
    • 1:允许出错中断。
  • AF:应答失败 (Acknowledge failure)
    • 0:没有应答失败;
    • 1:应答失败。
c 复制代码
if ((tmp1 == HAL_I2C_MODE_SLAVE) && (tmp2 == 0U) &&((tmp3 == HAL_I2C_STATE_BUSY_TX) ||(tmp3 == HAL_I2C_STATE_BUSY_TX_LISTEN) ||((tmp3 == HAL_I2C_STATE_LISTEN) && (tmp4 == I2C_STATE_SLAVE_BUSY_TX))))

(tmp1 == HAL_I2C_MODE_SLAVE)表示必须是从机模式(SLAVE)

因为只有从机发送时,"主机 NACK 表示读完"这种"正常语义"才最典型。

(tmp2 == 0U) 表示必须 XferCount == 0

这个条件非常关键:

  • XferCount == 0 表示 从机已经把自己承诺要发的字节都发完了
    这时主机 NACK 很可能就是"最后一个字节读完了"。------正常结束。
    反过来:
  • 如果 XferCount != 0,说明你还没发完主机就 NACK
    这更像"主机提前终止/异常中止",HAL 就倾向于当 error AF。

((tmp3 == HAL_I2C_STATE_BUSY_TX) ||(tmp3 == HAL_I2C_STATE_BUSY_TX_LISTEN) ||((tmp3 == HAL_I2C_STATE_LISTEN) && (tmp4 == I2C_STATE_SLAVE_BUSY_TX)))表示状态必须在发送相关状态(BUSY_TX / BUSY_TX_LISTEN / LISTEN+PreviousState)

这是为了区分 "我确实处在从机发送流程里":

  • BUSY_TX:明确在发
  • BUSY_TX_LISTEN:发并监听
  • LISTEN && PreviousState == SLAVE_BUSY_TX:从发状态退回监听的边界场景(避免漏判)

满足这些,才把 AF 当作"从机发送的正常收尾事件",交给 I2C_Slave_AF() 去做"正确收尾"。

c 复制代码
/* 注释翻译:在"从机接收模式"且"传输未结束(不是最后阶段)"出现 NACK 时,不要生成 STOP。
         但实际代码逻辑是:
         - 若当前是主机模式 或 内存模式(MEM) -> 生成 STOP
         也就是说:
         - 对于主机/MEM:出现 AF,通常需要结束本次传输,因此发 STOP
         - 对于从机:不在这里发 STOP(从机通常不能随意发 STOP),具体由其他流程处理 */
      if ((CurrentMode == HAL_I2C_MODE_MASTER) || (CurrentMode == HAL_I2C_MODE_MEM))
      {
        /* 生成 STOP:通过设置 CR1 的 STOP 位请求外设在总线释放时发出 STOP */
        SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
      }

在hal库IIC里:

  • STOP 是主机的动作(正常情况下从机不应该主动发 STOP)
  • 主机侧出现 AF(NACK)时,往往需要尽快释放总线,发 STOP 结束事务
  • 从机侧即使遇到 AF,也通常不能随意"抢总线发 STOP",只能清标志、复位状态、等待下一次地址匹配/STOPF等事件
    只有主机/内存读写的路径,HAL 才能也才应该去发 STOP。

I2C_Slave_AF函数源码

c 复制代码
/**
  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
  *         the configuration information for I2C module
  * @retval None
  */
static void I2C_Slave_AF(I2C_HandleTypeDef *hi2c) /* 中文:静态函数 I2C_Slave_AF,用于处理 I2C "从机模式下的 AF(Acknowledge Failure,应答失败/NACK) 事件" */
{                                                 /* 中文:函数体开始 */

  /* Declaration of temporary variables to prevent undefined behavior of volatile usage */
  /* 中文:声明临时变量以防止直接使用 volatile 字段可能带来的未定义行为/时序问题(HAL 常用写法:先拷贝再判断,避免反复读取寄存器/状态被中断修改) */
  HAL_I2C_StateTypeDef CurrentState = hi2c->State;      /* 中文:读取并保存当前 I2C 状态机状态(hi2c->State)到临时变量 CurrentState */
  uint32_t CurrentXferOptions       = hi2c->XferOptions;/* 中文:读取并保存当前传输选项(hi2c->XferOptions)到临时变量 CurrentXferOptions(用于判断序列帧/最后帧等) */

  if (((CurrentXferOptions ==  I2C_FIRST_AND_LAST_FRAME) || (CurrentXferOptions == I2C_LAST_FRAME)) && \ /* 中文:条件1(传输选项):当前是"首帧且末帧"(单帧完成) 或 "末帧"(最后一帧) */
      (CurrentState == HAL_I2C_STATE_LISTEN))                                                  /* 中文:条件2(状态):当前 HAL 状态机处于 LISTEN(监听/地址监听状态) */
  {                                                                                            /* 中文:进入分支A:LISTEN 且这次传输属于最后帧语义 -> 把 AF 当成监听用例的结束信号来收尾 */

    hi2c->XferOptions = I2C_NO_OPTION_FRAME;                                                   /* 中文:清除/复位传输选项:设置为"无选项帧",表示退出序列帧流程,不再处于 FIRST/NEXT/LAST 等阶段 */

    /* Disable EVT, BUF and ERR interrupt */
    /* 中文:关闭 I2C 的事件中断(EVT)、缓冲区中断(BUF)、错误中断(ERR):
       - EVT:事件类(ADDR、BTF、STOPF、SB、ADD10 等)
       - BUF:缓冲类(RXNE、TXE 等)
       - ERR:错误类(BERR、ARLO、AF、OVR 等)
       收尾阶段关闭它们,避免继续进中断导致状态机被再次推进或重复处理 */
    __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR);                            /* 中文:宏操作:在 CR2 中清除对应中断使能位(按位或组合关闭) */

    /* Clear AF flag */
    /* 中文:清除 AF 标志位(Acknowledge Failure,应答失败/NACK 标志)。
       如果不清除,AF 会持续置位,可能导致:
       - 错误中断反复进入
       - 后续通信被阻塞/状态无法恢复 */
    __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);                                                    /* 中文:宏操作:清 SR1.AF(具体清除方式由宏封装,通常是读 SR1/写寄存器序列) */

    /* Disable Acknowledge */
    /* 中文:禁用 ACK(应答使能)。
       在从机收尾时常把 ACK 关掉,避免继续对总线事务进行自动应答,确保外设回到"等待下一次事务"的安全状态 */
    CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_ACK);                                                /* 中文:寄存器操作:CR1.ACK = 0 */

    hi2c->PreviousState = I2C_STATE_NONE;                                                       /* 中文:把 PreviousState 置为 NONE:清空"前一状态"记录,表示当前没有历史忙状态需要追踪 */
    hi2c->State         = HAL_I2C_STATE_READY;                                                  /* 中文:把当前状态置为 READY:状态机回到就绪态,可以接受下一次收发/监听 */
    hi2c->Mode          = HAL_I2C_MODE_NONE;                                                    /* 中文:把当前模式置为 NONE:退出从机/主机/MEM 等模式,标记当前无活动模式 */

    /* Call the Listen Complete callback, to inform upper layer of the end of Listen usecase */
    /* 中文:调用"监听完成"回调,用于通知上层:本次 Listen 用例结束(例如:监听到一次地址匹配并完成了相应处理后退出监听) */
#if (USE_HAL_I2C_REGISTER_CALLBACKS == 1)                                                       /* 中文:如果启用了 HAL I2C 注册回调机制(回调函数可通过句柄注册) */
    hi2c->ListenCpltCallback(hi2c);                                                             /* 中文:调用用户/上层注册的 ListenCpltCallback:通知监听结束 */
#else                                                                                           /* 中文:否则使用默认弱函数回调(用户可重写 __weak 函数) */
    HAL_I2C_ListenCpltCallback(hi2c);                                                           /* 中文:调用默认的 HAL_I2C_ListenCpltCallback:通知监听结束 */
#endif /* USE_HAL_I2C_REGISTER_CALLBACKS */                                                     /* 中文:预编译条件结束 */
  }                                                                                             /* 中文:分支A结束(LISTEN + LAST 语义的 AF 收尾) */

  else if (CurrentState == HAL_I2C_STATE_BUSY_TX)                                               /* 中文:分支B条件:当前状态为 BUSY_TX(从机发送忙) */
  {                                                                                             /* 中文:进入分支B:从机发送过程中出现 AF(通常意味着主机用 NACK 结束读取)-> 按"发送完成收尾"处理 */

    hi2c->XferOptions   = I2C_NO_OPTION_FRAME;                                                  /* 中文:清除/复位传输选项:退出 FIRST/NEXT/LAST 序列帧状态 */
    hi2c->PreviousState = I2C_STATE_SLAVE_BUSY_TX;                                              /* 中文:记录 PreviousState 为 SLAVE_BUSY_TX:标记刚刚结束的是"从机发送忙"流程(便于后续 LISTEN 场景判断来源) */
    hi2c->State         = HAL_I2C_STATE_READY;                                                  /* 中文:当前状态回到 READY:从机发送流程结束,准备下一次事务 */
    hi2c->Mode          = HAL_I2C_MODE_NONE;                                                    /* 中文:当前模式置为 NONE:退出从机发送模式 */

    /* Disable EVT, BUF and ERR interrupt */
    /* 中文:同样关闭事件/缓冲/错误中断,避免收尾过程中继续触发中断推进状态机或重复处理错误 */
    __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR);                            /* 中文:宏:CR2 清中断使能位 */

    /* Clear AF flag */
    /* 中文:清除 AF 标志位(主机 NACK 结束读取时,从机会看到 AF=1;此时通常不是"错误",而是"主机读完了"信号,因此清掉并按完成收尾) */
    __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);                                                    /* 中文:宏:清 SR1.AF */

    /* Disable Acknowledge */
    /* 中文:禁用 ACK:收尾动作之一,防止从机继续对后续字节/事务自动应答,保证外设处于可控状态 */
    CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_ACK);                                                /* 中文:CR1.ACK=0 */

    /* Clear TXE flag */
    /* 中文:清理/冲刷数据寄存器 DR,以"清 TXE/发送缓冲相关状态"。
       目的:
       - 避免 DR 中残留数据或 TXE 状态影响下一次从机发送/地址匹配后的行为
       - 有些情况下需要通过读写 DR 或读 SR 寄存器序列来释放内部状态机
       注意:这里的具体动作由 I2C_Flush_DR() 实现(不同 HAL 版本实现细节略有差异) */
    I2C_Flush_DR(hi2c);                                                                        /* 中文:调用内部私有函数:Flush/清理 I2C 数据寄存器相关状态 */

#if (USE_HAL_I2C_REGISTER_CALLBACKS == 1)                                                       /* 中文:如果启用注册回调机制 */
    hi2c->SlaveTxCpltCallback(hi2c);                                                           /* 中文:调用用户/上层注册的"从机发送完成"回调:通知上层 Tx 已完成 */
#else                                                                                           /* 中文:否则调用默认弱回调 */
    HAL_I2C_SlaveTxCpltCallback(hi2c);                                                         /* 中文:调用默认的 HAL_I2C_SlaveTxCpltCallback:从机发送完成通知 */
#endif /* USE_HAL_I2C_REGISTER_CALLBACKS */                                                     /* 中文:预编译条件结束 */
  }                                                                                             /* 中文:分支B结束(BUSY_TX 的 AF 收尾) */

  else                                                                                          /* 中文:分支C:不满足上面两类"需要收尾"的情况 */
  {                                                                                             /* 中文:进入分支C:仅清除 AF,不改变状态机/不回调 */

    /* Clear AF flag only */
    /* 中文:仅清除 AF 标志位(只做最低限度处理,防止 AF 悬挂导致重复中断) */
    /* State Listen, but XferOptions == FIRST or NEXT */
    /* 中文:典型语义:处于 LISTEN,但 XferOptions 是 FIRST 或 NEXT(说明还没到"最后帧/结束阶段")
       此时 AF 可能是阶段性 NACK/边界事件:
       - HAL 不在这里直接结束整个 listen/序列流程
       - 后续状态推进可能由 ADDR/STOPF 等事件中断继续完成 */
    __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF);                                                    /* 中文:宏:清 SR1.AF,仅此一行(不做其它动作) */
  }                                                                                             /* 中文:分支C结束 */

}                                                                                               /* 中文:函数结束 */

I2C_ITError函数源码

c 复制代码
/**
  * @brief  I2C 中断错误处理流程
  * @param  hi2c I2C 句柄
  * @retval 无
  */
static void I2C_ITError(I2C_HandleTypeDef *hi2c)
{
  /* 使用临时变量保存关键状态,避免直接反复读取易变字段带来的时序/一致性问题 */
  HAL_I2C_StateTypeDef CurrentState = hi2c->State;   /* 当前 HAL 状态机状态 */
  HAL_I2C_ModeTypeDef  CurrentMode  = hi2c->Mode;    /* 当前工作模式(主机/存储器/从机等) */
  uint32_t             CurrentError;                 /* 当前错误码快照 */

  /* 主机/存储器接收中发生错误:需要清除 POS 位,避免接收序列(ACK/NACK 时序)异常 */
  if (((CurrentMode == HAL_I2C_MODE_MASTER) || (CurrentMode == HAL_I2C_MODE_MEM)) &&
      (CurrentState == HAL_I2C_STATE_BUSY_RX))
  {
    /* 错误发生在主机/存储器中断接收流程时,关闭 CR1.POS(避免后续接收/NACK 时序受影响) */
    hi2c->Instance->CR1 &= ~I2C_CR1_POS;
  }

  /* 若当前处于 LISTEN(监听)状态,则保持 LISTEN,不把状态改为 READY */
  if (((uint32_t)CurrentState & (uint32_t)HAL_I2C_STATE_LISTEN) == (uint32_t)HAL_I2C_STATE_LISTEN)
  {
    /* 保持监听状态:清空 PreviousState,但 State 仍保持 LISTEN */
    hi2c->PreviousState = I2C_STATE_NONE;
    hi2c->State         = HAL_I2C_STATE_LISTEN;
  }
  else
  {
    /* 若正在进行 ABORT 过程,则暂时不改变状态;状态将在后续 ABORT 收尾时再处理 */
    if ((READ_BIT(hi2c->Instance->CR2, I2C_CR2_DMAEN) != I2C_CR2_DMAEN) &&
        (CurrentState != HAL_I2C_STATE_ABORT))
    {
      /* 非 DMA 模式且不在 ABORT 中:把状态回到 READY,并清除工作模式 */
      hi2c->State = HAL_I2C_STATE_READY;
      hi2c->Mode  = HAL_I2C_MODE_NONE;
    }
    /* 清空 PreviousState(不保留历史忙状态) */
    hi2c->PreviousState = I2C_STATE_NONE;
  }

  /* 若启用 DMA,则需要先中止 DMA 传输(错误处理中必须保证 DMA 停止,避免继续搬运数据) */
  if (READ_BIT(hi2c->Instance->CR2, I2C_CR2_DMAEN) == I2C_CR2_DMAEN)
  {
    /* 关闭 I2C 的 DMA 使能 */
    hi2c->Instance->CR2 &= ~I2C_CR2_DMAEN;

    /* 若发送 DMA 正在运行,则优先中止发送 DMA */
    if (hi2c->hdmatx->State != HAL_DMA_STATE_READY)
    {
      /* 配置 DMA 中止回调:DMA 中止完成后将进入 I2C_DMAAbort,从而触发错误回调流程 */
      hi2c->hdmatx->XferAbortCallback = I2C_DMAAbort;

      /* 以中断方式请求中止 DMA */
      if (HAL_DMA_Abort_IT(hi2c->hdmatx) != HAL_OK)
      {
        /* DMA 中止请求失败:关闭 I2C 外设,防止缓冲区出现无效数据 */
        __HAL_I2C_DISABLE(hi2c);

        /* 将状态置为 READY,表示结束当前流程 */
        hi2c->State = HAL_I2C_STATE_READY;

        /* 发生异常时直接调用 DMA 中止回调,走统一的收尾/回调路径 */
        hi2c->hdmatx->XferAbortCallback(hi2c->hdmatx);
      }
    }
    else
    {
      /* 否则中止接收 DMA */
      hi2c->hdmarx->XferAbortCallback = I2C_DMAAbort;

      /* 以中断方式请求中止 DMA */
      if (HAL_DMA_Abort_IT(hi2c->hdmarx) != HAL_OK)
      {
        /* 若接收缓冲中仍有数据未读取(RXNE=1),先把最后一个字节读出保存到缓冲区 */
        if (__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET)
        {
          /* 从 DR 读取一个字节存入缓冲区 */
          *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;

          /* 缓冲指针后移 */
          hi2c->pBuffPtr++;
        }

        /* DMA 中止请求失败:关闭 I2C 外设,防止缓冲区出现无效数据 */
        __HAL_I2C_DISABLE(hi2c);

        /* 将状态置为 READY */
        hi2c->State = HAL_I2C_STATE_READY;

        /* 发生异常时直接调用 DMA 中止回调,走统一的收尾/回调路径 */
        hi2c->hdmarx->XferAbortCallback(hi2c->hdmarx);
      }
    }
  }
  else if (hi2c->State == HAL_I2C_STATE_ABORT)
  {
    /* 非 DMA 且处于 ABORT:完成 ABORT 的最终收尾 */
    hi2c->State     = HAL_I2C_STATE_READY;
    hi2c->ErrorCode = HAL_I2C_ERROR_NONE;

    /* 若接收缓冲中仍有数据未读取(RXNE=1),先把最后一个字节读出保存到缓冲区 */
    if (__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET)
    {
      /* 从 DR 读取一个字节存入缓冲区 */
      *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;

      /* 缓冲指针后移 */
      hi2c->pBuffPtr++;
    }

    /* 关闭 I2C 外设,防止缓冲区出现无效数据 */
    __HAL_I2C_DISABLE(hi2c);

    /* 调用 ABORT 完成回调,通知上层 ABORT 结束 */
#if (USE_HAL_I2C_REGISTER_CALLBACKS == 1)
    hi2c->AbortCpltCallback(hi2c);
#else
    HAL_I2C_AbortCpltCallback(hi2c);
#endif /* USE_HAL_I2C_REGISTER_CALLBACKS */
  }
  else
  {
    /* 一般错误处理路径(非 DMA,且不在 ABORT 完成分支) */

    /* 若接收缓冲中仍有数据未读取(RXNE=1),先把最后一个字节读出保存到缓冲区 */
    if (__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET)
    {
      /* 从 DR 读取一个字节存入缓冲区 */
      *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;

      /* 缓冲指针后移 */
      hi2c->pBuffPtr++;
    }

    /* 调用错误回调,通知上层发生错误(由上层根据 ErrorCode 决定处理策略) */
#if (USE_HAL_I2C_REGISTER_CALLBACKS == 1)
    hi2c->ErrorCallback(hi2c);
#else
    HAL_I2C_ErrorCallback(hi2c);
#endif /* USE_HAL_I2C_REGISTER_CALLBACKS */
  }

  /* 某些错误(NACK、总线错误、仲裁丢失、溢出欠载)发生时,外设未必会自动置位 STOPF;
     为避免继续进入事件/缓冲/错误中断,这里按错误码决定统一关闭这些中断源 */
  CurrentError = hi2c->ErrorCode;

  if (((CurrentError & HAL_I2C_ERROR_BERR) == HAL_I2C_ERROR_BERR) || \
      ((CurrentError & HAL_I2C_ERROR_ARLO) == HAL_I2C_ERROR_ARLO) || \
      ((CurrentError & HAL_I2C_ERROR_AF)   == HAL_I2C_ERROR_AF)   || \
      ((CurrentError & HAL_I2C_ERROR_OVR)  == HAL_I2C_ERROR_OVR))
  {
    /* 关闭事件/缓冲/错误中断,防止错误状态下中断持续触发 */
    __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR);
  }

  /* 若错误为 AF,并且当前仍在 LISTEN,说明监听阶段被 NACK 错误处理打断;
     需要退出监听并通知上层监听结束 */
  CurrentState = hi2c->State;

  if (((hi2c->ErrorCode & HAL_I2C_ERROR_AF) == HAL_I2C_ERROR_AF) &&
      (CurrentState == HAL_I2C_STATE_LISTEN))
  {
    /* 退出监听相关状态,回到 READY/NONE */
    hi2c->XferOptions   = I2C_NO_OPTION_FRAME;
    hi2c->PreviousState = I2C_STATE_NONE;
    hi2c->State         = HAL_I2C_STATE_READY;
    hi2c->Mode          = HAL_I2C_MODE_NONE;

    /* 调用监听完成回调,通知上层监听用例结束 */
#if (USE_HAL_I2C_REGISTER_CALLBACKS == 1)
    hi2c->ListenCpltCallback(hi2c);
#else
    HAL_I2C_ListenCpltCallback(hi2c);
#endif /* USE_HAL_I2C_REGISTER_CALLBACKS */
  }
}
相关推荐
想放学的刺客3 小时前
单片机嵌入式试题(第19期)嵌入式系统故障诊断与固件升级设计
c语言·stm32·嵌入式硬件·物联网·51单片机
自动化控制仿真经验汇总3 小时前
Simulink电机控制安全-PART-直流电机-限位器
单片机·嵌入式硬件·安全
一路往蓝-Anbo4 小时前
第46期:低功耗设计:软硬件协奏曲
驱动开发·stm32·单片机·嵌入式硬件
TEC_INO4 小时前
stm32_1:FreeRTOS
单片机·嵌入式硬件
不能跑的代码不是好代码4 小时前
STM32 标准外设库中关于 GPIO(通用输入输出) 模块的函数声明
stm32·单片机·嵌入式硬件
仰泳之鹅4 小时前
【天气时钟】第一课:工程模板的搭建
单片机·嵌入式硬件
Moonquakes5404 小时前
嵌入式开发基础学习笔记(LED实验C语言实现、蜂鸣器实验、SDK裸机驱动、链接脚本、BSP工程管理)
stm32·单片机·嵌入式硬件
思茂信息4 小时前
CST仿真实例:手机Type-C接口ESD仿真
c语言·开发语言·单片机·嵌入式硬件·智能手机·cst·电磁仿真
我是海飞4 小时前
杰理 AC792N 使用 WebSocket 连接百度语音大模型,实现 AI 对话
c语言·单片机·嵌入式·ai对话·杰理·websockey