You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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界面会在中断阻塞时出现冻结。

待咨询问题

  1. 该问题是否存在快速临时解决方案?这是否属于HAL驱动的已知bug?
  2. 是否必须自行实现完全非阻塞的I2C读取函数?该函数与HAL库I2C模块耦合度高,自行实现开发量较大。

现有思路与疑惑

  • 初步实现思路:重写相关逻辑,将while轮询替换为基于状态机的异步流程,通过I2C各阶段中断回调传递状态参数推进传输,传输完成后调用注册的回调函数,即可实现真正的非阻塞运行。但当前项目交付时间紧张,希望能找到更快捷的实现方案。
  • 核心疑惑:HAL库中带_IT后缀、标注为中断模式的函数,为何会存在while轮询阻塞逻辑?这完全违背了中断模式函数的设计初衷——毕竟HAL库已经提供了实现更简单的阻塞版I2C读取接口。

内容的提问来源于stack exchange,提问作者Harry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 04:09:15