协议规定,SCL时钟和SDA数据线都必须是双向开漏结构的,通过总线上的上拉电阻拉到逻辑高电平,空闲状态SCL和SDA应该被上拉到高电平,这样的结构可以实现线与的操作。 一般情况下I2C的SDA只有在SCL为低电平的时候才能改变,为高电平的时候需要保持。对应到芯片设计上则是上升沿采样,下降沿变化。两个例外情况由主机发出的总线起始条件START(SCL为高时SDA由高变低)和停止条件STOP(SCL为高时SDA由低变高)

MCU做主机SCL一直拉低 是因为SDA被从机锁死,主机拉低SCL想要控制SDA,导致的,加个超时错误,重新IIC失能就可以不复位MCU解决

SDA被拉低: 主机会发送9个clk尝试拉高SDA,如果不能成功,需要硬件复位 SCL被拉低: 主机主动拉低SCL是合理的行为,在IP初始化配置的时候,可以选择时钟展频功能。 如下图在从机访问地址与写入数据之间加入一点延时,就会出现SCL被主机拉低的情况

其它情况的SCL拉低就要考虑代码实现是否合理了: 1、I2C是否被意外关闭了 2、是否在某个位置有while等待,一直无法跳出循环

**线与&**结构,是I2C总线设计上最关键的特征,用了这种结构才能实现:

  • 多主机仲裁同步
  • 慢从机同步快主机

因为这个特性,只要总线上任何一个器件拉低了SDA或者SCL,其他器件都无法拉高它们,看到的都是低电平。如果有器件不释放总线,则整个总线上的通讯都会被暂停,我们成为I2C bus hangs:I2C总线挂死

因为I2C主机一般是可编程的器件,受我们控制,如果主机主动拉低了总线,我们可以通过调试代码了解原因,也可以很方便的通过复位I2C外设或者复位芯片来退出这种状态。而I2C从机往往不带RESET引脚,如果挂死了总线即使整个系统复位都无法解除,仅重新上下电才可以恢复。很多系统上是不可接受的,因此我们需要更加小心的处理I2C从机挂死的情况,下面分析也是针对I2C从机挂死来写的。

STM32手动发送I2C数据

//========【配置IIC总线的信号读写和时序】=======

//主机拉高SCL

#define TM1650_IIC_SCL_HIGH     GPIO_SetBits(TM1650_CLK_PORT,TM1650_CLK_PIN)

//主机拉低SCL

#define TM1650_IIC_SCL_LOW      GPIO_ResetBits(TM1650_CLK_PORT,TM1650_CLK_PIN)

//主机拉高SDA

#define TM1650_IIC_SDA_HIGH     GPIO_SetBits(TM1650_DIN_PORT,TM1650_DIN_PIN)

//主机拉低SDA

#define TM1650_IIC_SDA_LOW      GPIO_ResetBits(TM1650_DIN_PORT,TM1650_DIN_PIN)

//参数b为0时主机拉低SDA,非0则拉高SDA

#define TM1650_IIC_SDA_WR(b)    do{                                       \

                               if(b) GPIO_SetBits(TM1650_DIN_PORT,TM1650_DIN_PIN);   \

                               else  GPIO_ResetBits(TM1650_DIN_PORT,TM1650_DIN_PIN); \

                              }while(0)

//主机读取SDA线电平状态,返回值为0为低电平,非0则为高电平

#define TM1650_IIC_SDA_RD()    GPIO_ReadInputDataBit(TM1650_DIN_PORT,TM1650_DIN_PIN)

//软件延时2us

#define TM1650_IIC_DELAY_2US   do{for(int i=0;i<11;i++);}while(0)

//软件延时4us

#define TM1650_IIC_DELAY_4US   do{for(int i=0;i<21;i++);}while(0)

//================================

  

static void TM1650_IIC_start(void)

{

    TM1650_IIC_SCL_HIGH;     //SCL=1

    TM1650_IIC_SDA_HIGH;    //SDA=1

    TM1650_IIC_DELAY_4US;

    TM1650_IIC_SDA_LOW;     //SDA=0

    TM1650_IIC_DELAY_4US;

    TM1650_IIC_SCL_LOW;      //SCL=0

}

//产生IIC总线结束信号

static void TM1650_IIC_stop(void)

{

    TM1650_IIC_SCL_LOW;      //SCL=0

    TM1650_IIC_SDA_LOW;      //SDA=0

    TM1650_IIC_DELAY_4US;

    TM1650_IIC_SCL_HIGH;     //SCL=1

    TM1650_IIC_DELAY_4US;

    TM1650_IIC_SDA_HIGH;    //SDA=1

}

//通过IIC总线发送一个字节

static void TM1650_IIC_write_byte(uint8_t dat)

{

    uint8_t i;

    for(i=0;i<8;i++)

    {

        TM1650_IIC_SCL_LOW;

        if(dat&0x01)

        {

        TM1650_IIC_SDA_HIGH;

        }

        else

        {

        TM1650_IIC_SDA_LOW;

        }      

        TM1650_IIC_SCL_HIGH;

        TM1650_IIC_DELAY_2US;

        dat>>=1;

    }

}

  

//通过IIC总线接收从机响应的ACK信号

static uint8_t TM1650_IIC_wait_ack(void)

{

    uint8_t ack_signal = 0;

    TM1650_IIC_SDA_HIGH;    //SDA=1

    TM1650_IIC_DELAY_2US;

    TM1650_IIC_SCL_HIGH;

    TM1650_IIC_DELAY_2US;

    if(TM1650_IIC_SDA_RD()) ack_signal = 1;   //如果读取到的是NACK信号

    TM1650_IIC_SCL_LOW;

    TM1650_IIC_DELAY_2US;

    return ack_signal;

}