STM32 HAL如何实现真正非阻塞模式的I2C存储器读取
STM32 HAL库I2C中断模式函数阻塞问题咨询
场景背景
- 硬件选型:采用PCA9555芯片作为输入采集器件,当芯片输入引脚的信号状态发生变化时,会向外发送中断信号,主控需通过I2C总线读取芯片寄存器,获取当前输入状态。
- 业务约束:当引脚电平变化触发中断时,需要完成芯片状态读取、识别具体变化的引脚、向应用层发送状态通知全流程;中断处理程序绝对不允许阻塞MCU运行。
问题现象
最初选择调用HAL_I2C_Mem_Read_IT()实现上述逻辑,代码编写完成后初步单功能测试正常;但在新增每100ms周期读取芯片的逻辑后,系统运行出现明显卡顿:指示灯闪烁滞涩,甚至会出现1~2秒的停顿。经排查确认,HAL_I2C_Mem_Read_IT()实际存在阻塞逻辑,会阻塞中断响应导致MCU运行卡顿。
根因定位
查阅STM32 HAL库源码发现,该函数内部调用的I2C_RequestMemoryRead()存在3处阻塞等待逻辑,相关源码如下:
static HAL_StatusTypeDef I2C_RequestMemoryRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart) { I2C_TransferConfig(hi2c, DevAddress, (uint8_t)MemAddSize, I2C_SOFTEND_MODE, I2C_GENERATE_START_WRITE); /* Wait until TXIS flag is set */ if (I2C_WaitOnTXISFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) { return HAL_ERROR; } /* If Memory address size is 8Bit */ if (MemAddSize == I2C_MEMADD_SIZE_8BIT) { /* Send Memory Address */ hi2c->Instance->TXDR = I2C_MEM_ADD_LSB(MemAddress); } /* If Memory address size is 16Bit */ else { /* Send MSB of Memory Address */ hi2c->Instance->TXDR = I2C_MEM_ADD_MSB(MemAddress); /* Wait until TXIS flag is set */ if (I2C_WaitOnTXISFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) { return HAL_ERROR; } /* Send LSB of Memory Address */ hi2c->Instance->TXDR = I2C_MEM_ADD_LSB(MemAddress); } /* Wait until TC flag is set */ if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_TC, RESET, Timeout, Tickstart) != HAL_OK) { return HAL_ERROR; } return HAL_OK; }
正如函数名I2C_WaitOnTXISFlagUntilTimeout()的含义所示,该类等待函数会执行忙等操作,本质是通过while循环轮询标志位,阻塞当前执行线程直到标志位置位,相关源码如下:
static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout, uint32_t Tickstart) { while (__HAL_I2C_GET_FLAG(hi2c, Flag) == Status) { /* Check for the Timeout */ if (Timeout != HAL_MAX_DELAY) { if (((HAL_GetTick() - Tickstart) > Timeout) || (Timeout == 0U)) { hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; hi2c->State = HAL_I2C_STATE_READY; hi2c->Mode = HAL_I2C_MODE_NONE; /* Process Unlocked */ __HAL_UNLOCK(hi2c); return HAL_ERROR; } } } return HAL_OK; }
上述这类带阻塞等待的逻辑共有3处,对于当前应用场景是致命缺陷:系统依赖实时事件响应,且搭载的GUI界面会在中断阻塞时出现冻结。
待咨询问题
- 该问题是否存在快速临时解决方案?这是否属于HAL驱动的已知bug?
- 是否必须自行实现完全非阻塞的I2C读取函数?该函数与HAL库I2C模块耦合度高,自行实现开发量较大。
现有思路与疑惑
- 初步实现思路:重写相关逻辑,将while轮询替换为基于状态机的异步流程,通过I2C各阶段中断回调传递状态参数推进传输,传输完成后调用注册的回调函数,即可实现真正的非阻塞运行。但当前项目交付时间紧张,希望能找到更快捷的实现方案。
- 核心疑惑:HAL库中带
_IT后缀、标注为中断模式的函数,为何会存在while轮询阻塞逻辑?这完全违背了中断模式函数的设计初衷——毕竟HAL库已经提供了实现更简单的阻塞版I2C读取接口。
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

