Bootstrap

【GD32F4 硬件IIC】关于解决GD32F4配置硬件IIC后,busy位一直置1的问题。

一、问题描述

我使用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();
	 }
}
;