【STM32CUBE】硬件 IIC 死锁问题

最近在项目中设计了一个 IIC 模拟从机的程序。为了图方便,我随便拿了个 STM32F207 的开发板做 IIC Master,用 STM32CUBE 做了个程序,Master 的 数据发送和接收,都是直接调用 HAL 库的函数。
通过逻辑分析仪测试发现,每次主机出现错误后,IIC SDA 会被拉低,导致整个 IIC 总线被锁死了。后续的数据传输异常。现象如下图所示:

这里写图片描述

这里写图片描述

后来我查看了 HAL 库的 IIC 的 HAL_I2C_Master_Transmit 函数。
发现:当出现 TIMEOUT 或 ERROR 时,STM32 Master 并不会产生 STOP 信号,或者,将总线释放(SDA 和 SCL 置高)。这样就会导致,当出现 TIMEOUT 或者 ERROR 后, 下一次进入HAL_I2C_Master_Transmit ,Master 会认为 IIC 总线为 BUSY,而放弃通讯,造成 SDA 被锁死的现象。
然后,我在 HAL_I2C_Master_Transmit 函数做了些改动,如下面的程序所示。
NOTE:如果是用 HAL_I2C_Master_Transmit 生成的程序,做修改时,必须把这段程序复制出来,保存到别的文件中,不然,在使用 STM32CUBE 再修改程序时,原来的修改会被覆盖掉。

/**
  * @brief  Transmits in master mode an amount of data in blocking mode.
  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
  *                the configuration information for the specified I2C.
  * @param  DevAddress Target device address: The device 7 bits address value
  *         in datasheet must be shifted to the left before calling the interface
  * @param  pData Pointer to data buffer
  * @param  Size Amount of data to be sent
  * @param  Timeout Timeout duration
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint32_t tickstart = 0x00U;

  /* Init tickstart for timeout management*/
  tickstart = HAL_GetTick();

  if(hi2c->State == HAL_I2C_STATE_READY)
  {
    /* Wait until BUSY flag is reset */
    // 下面的代码就是用于检测 IIC 总线是否为 BUSY,当 SDA 和 SCL 同时为高,才会被认为是空闲(IDLE),否则,会被认为是 BUSY。
    if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK)
    {
      return HAL_BUSY;
    }

    /* Process Locked */
    __HAL_LOCK(hi2c);

    /* Check if the I2C is already enabled */
    if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE)
    {
      /* Enable I2C peripheral */
      __HAL_I2C_ENABLE(hi2c);
    }

    /* Disable Pos */
    hi2c->Instance->CR1 &= ~I2C_CR1_POS;

    hi2c->State     = HAL_I2C_STATE_BUSY_TX;
    hi2c->Mode      = HAL_I2C_MODE_MASTER;
    hi2c->ErrorCode = HAL_I2C_ERROR_NONE;

    /* Prepare transfer parameters */
    hi2c->pBuffPtr    = pData;
    hi2c->XferCount   = Size;
    hi2c->XferOptions = I2C_NO_OPTION_FRAME;
    hi2c->XferSize    = hi2c->XferCount;

    /* Send Slave Address */
    if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK)
    {
      if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)
      {
        /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/
        hi2c->Instance->CR1 |= I2C_CR1_STOP;
        /* Process Unlocked */
        __HAL_UNLOCK(hi2c);

        return HAL_ERROR;
      }
      else
      {

        /*此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/
        hi2c->Instance->CR1 |= I2C_CR1_STOP;
         /* Process Unlocked */
        __HAL_UNLOCK(hi2c);
        return HAL_TIMEOUT;
      }
    }

    /* Clear ADDR flag */
    __HAL_I2C_CLEAR_ADDRFLAG(hi2c);

    while(hi2c->XferSize > 0U)
    {
      /* Wait until TXE flag is set */
      if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
      {
        if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)
        {
          /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/
          hi2c->Instance->CR1 |= I2C_CR1_STOP;
          return HAL_ERROR;
        }
        else
        {
            /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/
            hi2c->Instance->CR1 |= I2C_CR1_STOP;
          return HAL_TIMEOUT;
        }
      }

      /* Write data to DR */
      hi2c->Instance->DR = (*hi2c->pBuffPtr++);
      hi2c->XferCount--;
      hi2c->XferSize--;

      if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U))
      {
        /* Write data to DR */
        hi2c->Instance->DR = (*hi2c->pBuffPtr++);
        hi2c->XferCount--;
        hi2c->XferSize--;
      }

      /* Wait until BTF flag is set */
      if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
      {
        if(hi2c->ErrorCode == HAL_I2C_ERROR_AF)
        {
          /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/
          hi2c->Instance->CR1 |= I2C_CR1_STOP;
          return HAL_ERROR;
        }
        else
        {
            /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/
            hi2c->Instance->CR1 |= I2C_CR1_STOP;
          return HAL_TIMEOUT;
        }
      }
    }

    /* Generate Stop */
    hi2c->Instance->CR1 |= I2C_CR1_STOP;

    hi2c->State = HAL_I2C_STATE_READY;
    hi2c->Mode = HAL_I2C_MODE_NONE;

    /* Process Unlocked */
    __HAL_UNLOCK(hi2c);

    return HAL_OK;
  }
  else
  {
    /* 此处为我自行添加的部分,当出现错误时,产生 Stop 信号,以释放总线。*/
    hi2c->Instance->CR1 |= I2C_CR1_STOP;
    /* Process Unlocked */
    __HAL_UNLOCK(hi2c);
    return HAL_BUSY;
  }
}

下面是修改后的效果
这里写图片描述

希望对您有帮助~

猜你喜欢

转载自blog.csdn.net/xiuhua_wu/article/details/80628874