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 */
}
}