最近在项目中设计了一个 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;
}
}
下面是修改后的效果:
希望对您有帮助~