一、问题描述
我使用GD32F407RET6(以下简称主机)的硬件IIC与AT24C16(EEPROM,以下简称从机)通信,但是在初始化硬件IIC,开始发送IIC的启动信号前,busy位一直是1。
// 宏定义
#define AT24C16_RCU_IIC RCU_I2C0 // enable iic0
#define AT24C16_RCU_GPIO RCU_GPIOB // enable GPIOB
#define AT24C16_IIC I2C0 // iic0
#define AT24C16_GPIO GPIOB // GPIOB
#define AT24C16_SDA GPIO_PIN_9 // iic SDA
#define AT24C16_SCL GPIO_PIN_8 // iic SCL
#define AT24C16_IIC_CLK_Speed 100000 // iic clock speed
// iic init
void IIC_Init(void)
{
// enable clock
rcu_periph_clock_enable(AT24C16_RCU_IIC);
rcu_periph_clock_enable(AT24C16_RCU_GPIO);
// i2c_deinit(AT24C16_IIC);
// gpio config
gpio_af_set(AT24C16_GPIO, GPIO_AF_4, AT24C16_SCL);
gpio_af_set(AT24C16_GPIO, GPIO_AF_4, AT24C16_SDA);
gpio_mode_set(AT24C16_GPIO, GPIO_MODE_AF, GPIO_PUPD_PULLUP, AT24C16_SCL);
gpio_output_options_set(AT24C16_GPIO, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, AT24C16_SCL);
gpio_mode_set(AT24C16_GPIO, GPIO_MODE_AF, GPIO_PUPD_PULLUP, AT24C16_SDA);
gpio_output_options_set(AT24C16_GPIO, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, AT24C16_SDA);
// iic config
i2c_clock_config(AT24C16_IIC, AT24C16_IIC_CLK_Speed, I2C_DTCY_2); // 100kHz;时钟占空比,选择Tlow/Thigh = 2。
i2c_mode_addr_config(AT24C16_IIC, I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, 0x71); // IIC;应答地址,选择7位,从机模式下才有效;自身地址,从机模式下才有效。
i2c_enable(AT24C16_IIC); // enable
i2c_ack_config(AT24C16_IIC, I2C_ACK_ENABLE); // enable ack
}
二、原因分析:
在网上搜索的大家经历过的情况,有人说,如果在硬件IIC与软件模拟IIC的情况下,busy位都是1,极大情况下是IIC硬件存在问题;如果在硬件IIC的情况下,busy位是1,在软件模拟IIC的情况下,busy是0,极大可能是硬件IIC初始化配置时存在问题;所以我用软件模拟IIC与从机通信,发现可以正常通信。
又找了很多资料,发现有一种IIC死锁现象和我遇到的情况类似,于是有了解决方案。
大概意思是正常情况下,主机开始启动IIC通信前,SDA与SCL都是高电平,busy位置0;但是有些特殊情况,如主机在IIC通信过程中异常重启等,此时从机未复位,导致SCL与SDA不都为高电平,此时busy位置1。
三、解决方案:
解决方案有两种:我本人使用的是第一种,亲测有效。
方案一:强制拉高SDA与SCL。主机将SDA与SCL配置成普通GPIO的推挽输出,拉高SDA、SCL;然后配置成IIC的复用功能。
/*!
\brief reset i2c bus
\param[in] none
\param[out] none
\retval none
*/
void i2c_bus_reset()
{
gpio_bit_reset(AT24C16_GPIO, AT24C16_SDA | AT24C16_SCL);
gpio_mode_set(AT24C16_GPIO, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, AT24C16_SDA | AT24C16_SCL);
gpio_output_options_set(AT24C16_GPIO, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, AT24C16_SDA | AT24C16_SCL);
__nop();
__nop();
__nop();
__nop();
__nop();
gpio_bit_set(AT24C16_GPIO, AT24C16_SCL);
__nop();
__nop();
__nop();
__nop();
__nop();
gpio_bit_set(AT24C16_GPIO, AT24C16_SDA);
// gpio config
gpio_af_set(AT24C16_GPIO, GPIO_AF_4, AT24C16_SDA | AT24C16_SCL);
gpio_mode_set(AT24C16_GPIO, GPIO_MODE_AF, GPIO_PUPD_PULLUP, AT24C16_SDA | AT24C16_SCL);
gpio_output_options_set(AT24C16_GPIO, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, AT24C16_SDA | AT24C16_SCL);
}
// busy
int IIC_Busy(void)
{
uint32_t time_out = 1000;
while (i2c_flag_get(I2C0, I2C_FLAG_I2CBSY) == SET)
{
i2c_bus_reset();
if (--time_out == 0)
{
return 0; // busy
}
}
return 1; // not busy
}
方案二:SCL 时钟信号释放总线。主机将SCL配置成普通GPIO的推挽输出,连续发送 9 个时钟脉冲,保证后续 I2C 正常通信,先将 I2C 模块复位,再置位,然后配置成IIC的复用功能。
/*!
\brief reset i2c bus
\param[in] none
\param[out] none
\retval none
*/
void i2c_bus_reset()
{
uint8_t I = 0;
gpio_mode_set(AT24C16_GPIO, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, AT24C16_SCL);
gpio_output_options_set(AT24C16_GPIO, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, AT24C16_SCL);
/* SCL output clock signal */
for(I = 0; I < 10; i++)
{
gpio_bit_reset(AT24C16_GPIO, AT24C16_SCL);
delay_1us(2);
gpio_bit_set(AT24C16_GPIO, AT24C16_SCL);
delay_1us(2);
}
/* reset I2C */
i2c_software_reset_config(AT24C16_IIC, I2C_SRESET_RESET);
i2c_software_reset_config(AT24C16_IIC, I2C_SRESET_SET);
gpio_af_set(AT24C16_GPIO, GPIO_AF_4, AT24C16_SDA | AT24C16_SCL);
gpio_mode_set(AT24C16_GPIO, GPIO_MODE_AF, GPIO_PUPD_PULLUP, AT24C16_SDA | AT24C16_SCL);
gpio_output_options_set(AT24C16_GPIO, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, AT24C16_SDA | AT24C16_SCL);
}
/*!
\brief check the I2C is or not busy
\param[in] none
\param[out] none
\retval none
*/
void check_bus_status(void)
{
while(i2c_flag_get(AT24C16_IIC,I2C_FLAG_I2CBSY))
{
if(--time_out == 0){
i2c_bus_reset();
}
}