IIC协议介绍 IIC协议

https://www.cnblogs.com/bixiaopengblog/p/7469536.html

写的挺好的IIC协议,包括其实位,终止位,应答位(接收端对发送端而言,程序一般包含获取应答、发送应答两部分), IIC地址传输(最后一位1读0写控制),IIC寄存器传输读写,字节数据读写(读取数据后产生的是无应答位)。

对比摩托罗拉的SPI而言,飞利浦的IIC器件都有自己的器件地址,所以IIC总线上可以直接并行挂多个IIC设备。由于读写同时使用一根线,所以增加起始位,终止位,应答位。有博友提示使用IIC设备的时候需要用到外置上拉电阻。

IIC协议

 

 总线信号 : 

  SDA :串行数据线

  SCL  :串行时钟

总线空闲状态 :

  SDA :高电平

  SCL :高电平

起始位:SCL为高电平期间    SDA出现下降沿

终止位:SCL为高电平期间 SDA出现上升沿

数据传输 :SDA的数据在SCL高电平期间被写入从机。所以SDA的数据变化要发生在SCL低电平期间。

IIC时钟频率:不高于400K

应答:当IIC主机(不一定是发送端还是接受端)将8位数据或命令传出后,会将SDA信号设置为输入,等待从机应答(等待SDA由高电平拉为低电平)

   若从机正确应答,表明数据或者命令传输成功,否则传输失败,注意,应答信号是数据接收方发送给数据发送方的。

IIC器件地址:每一个IIC器件都有一个器件地址,有的器件地址在出厂时地址就设定好了,用户不可以更改,比如OV7670的

      地址为0x42。有的器件例如EEPROM,前四个地址已经确定为1010,后三个地址是由硬件链接确定的,所以一

      个IIC总线最多能连8个EEPROM芯片。

    图上开始信号之后,七位地址代表器件地址,第八位代表读或者写,0为写,1代表读,然后跟着响应位。

 IIC器件单字节写时序:

IIC器件多字节地址写时序:多字节地址比单字节地址在时序上就多了一块写地址

单字节器件读时序:注意最后产生无应答信号,另外多字节地址读时序跟单字节类似,只不过是多了几个地址字节而已。

//产生起始信号
void I2C_Start(void)
{
    I2C_SDA_OUT();//配置一下引脚,引脚设置为输出
 
    I2C_SDA_H;//把数据线拉高
    I2C_SCL_H;//把时钟线拉高
    delay_us(5);//延时5微秒,要求大于4.7微秒
    I2C_SDA_L;  //拉低,产生下降沿
    delay_us(6);//这个过程大于4微秒
    I2C_SCL_L;//最后一定要把这个时钟线拉低,因为只有时钟线拉低的时候才允许数据        变化。
}
//产生停止信号
void I2C_Stop(void)
{
   I2C_SDA_OUT();
 
   I2C_SCL_L;
   I2C_SDA_L;
   I2C_SCL_H;
   delay_us(6);
   I2C_SDA_H;
   delay_us(6);
}
//主机产生应答信号ACK
void I2C_Ack(void)
{
   I2C_SCL_L;
   I2C_SDA_OUT();
   I2C_SDA_L;
   delay_us(2);
   I2C_SCL_H;
   delay_us(5);
   I2C_SCL_L;
}
 
 
//主机不产生应答信号NACK
void I2C_NAck(void)
{
   I2C_SCL_L;
   I2C_SDA_OUT();
   I2C_SDA_H;
   delay_us(2);
   I2C_SCL_H;
   delay_us(5);
   I2C_SCL_L;
}
//等待从机应答信号,我们只负责主机应答信号的产生,从机应答信号
//我们不控制。
//返回值:1 接收应答失败
//  0 接收应答成功
u8 I2C_Wait_Ack(void)
{
    u8 tempTime=0;
 
    I2C_SDA_IN(); //配置为上拉输入。
    I2C_SDA_H;  //主机释放数据总线,等待从机产生应答信号
    delay_us(1);
    I2C_SCL_H;
    delay_us(1);
    //等待从机对数据总线的操作。低电平代表应答
    while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
    {
        tempTime++;
        //这个属于软件延时,不一定准确。
        if(tempTime>250) //如果时间超时,没有应答就停止。
    {
        I2C_Stop();
        return 1;  //没有响应的话返回1.
    } 
}
    I2C_SCL_L;
    return 0; //如果有响应的话就返回0.
}

再增加几个操作函数

/************************************************
函数名称 : I2C_Delay
功    能 : I2C延时(非标准延时,请根据MCU速度 调节大小)
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
static void I2C_Delay(void)
{
  uint16_t cnt = 10;

  while(cnt--);
}

/************************************************
函数名称 : I2C_GPIO_Configuration
功    能 : I2C引脚配置(开漏输出)
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_GPIO_Configuration(void)
{
  GPIO_Init(PORT_I2C_SCL, (GPIO_Pin_TypeDef)PIN_I2C_SCL, GPIO_MODE_OUT_OD_HIZ_FAST);
  GPIO_Init(PORT_I2C_SDA, (GPIO_Pin_TypeDef)PIN_I2C_SDA, GPIO_MODE_OUT_OD_HIZ_FAST);
}

/************************************************
函数名称 : I2C_Initializes
功    能 : I2C初始化
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_Initializes(void)
{
  I2C_GPIO_Configuration();

  I2C_SCL_HIGH;                                  //置位状态
  I2C_SDA_HIGH;
}
/************************************************
函数名称 : I2C_WriteByte
功    能 : I2C写一字节
参    数 : Data --- 数据
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_WriteByte(uint8_t Data)//时钟低的时候先准备好数据,再打开时钟(读取应答位无判断?)
{
  uint8_t cnt;

  for(cnt=0; cnt<8; cnt++)
  {
    I2C_SCL_LOW;                                 //SCL低(SCL为低电平时变化SDA有效)
    I2C_Delay();

    if(Data & 0x80)
      I2C_SDA_HIGH;                              //SDA高
    else
      I2C_SDA_LOW;                               //SDA低
    Data <<= 1;
    I2C_Delay();

    I2C_SCL_HIGH;                                //SCL高(发送数据)
    I2C_Delay();
  }
  I2C_SCL_LOW;                                   //SCL低(等待应答信号)
  I2C_Delay();

  I2C_GetAck();                                  //读取应答位
}

/************************************************
函数名称 : I2C_ReadByte
功    能 : I2C读一字节
参    数 : ack --------- 产生应答(或者非应答)位
返 回 值 : data -------- 读取的一字节数据
作    者 : strongerHuang

*************************************************/
uint8_t I2C_ReadByte(uint8_t ack)//时钟低的时候先准备好数据,然后地址也弄高。然后等时钟高了再读取
{
  uint8_t cnt;
  uint8_t data;

  I2C_SCL_LOW;                                   //SCL低
  I2C_Delay();

  I2C_SDA_HIGH;                                  //释放SDA(开漏模式有效)

  for(cnt=0; cnt<8; cnt++)
  {
    I2C_SCL_HIGH;                                //SCL高(读取数据)
    I2C_Delay();

    data <<= 1;
    if(I2C_SDA_READ)      data |= 0x01; //读取SDA的状态
                                       //SDA为高(数据有效)

    I2C_SCL_LOW;                                 //SCL低
    I2C_Delay();
  }

  I2C_PutAck(ack);                               //产生应答(或者非应答)位

  return data;                                   //返回数据
}

 总线信号 : 

  SDA :串行数据线

  SCL  :串行时钟

总线空闲状态 :

  SDA :高电平

  SCL :高电平

起始位:SCL为高电平期间    SDA出现下降沿

终止位:SCL为高电平期间 SDA出现上升沿

数据传输 :SDA的数据在SCL高电平期间被写入从机。所以SDA的数据变化要发生在SCL低电平期间。

IIC时钟频率:不高于400K

应答:当IIC主机(不一定是发送端还是接受端)将8位数据或命令传出后,会将SDA信号设置为输入,等待从机应答(等待SDA由高电平拉为低电平)

   若从机正确应答,表明数据或者命令传输成功,否则传输失败,注意,应答信号是数据接收方发送给数据发送方的。

IIC器件地址:每一个IIC器件都有一个器件地址,有的器件地址在出厂时地址就设定好了,用户不可以更改,比如OV7670的

      地址为0x42。有的器件例如EEPROM,前四个地址已经确定为1010,后三个地址是由硬件链接确定的,所以一

      个IIC总线最多能连8个EEPROM芯片。

    图上开始信号之后,七位地址代表器件地址,第八位代表读或者写,0为写,1代表读,然后跟着响应位。

 IIC器件单字节写时序:

IIC器件多字节地址写时序:多字节地址比单字节地址在时序上就多了一块写地址

单字节器件读时序:注意最后产生无应答信号,另外多字节地址读时序跟单字节类似,只不过是多了几个地址字节而已。

//产生起始信号
void I2C_Start(void)
{
    I2C_SDA_OUT();//配置一下引脚,引脚设置为输出
 
    I2C_SDA_H;//把数据线拉高
    I2C_SCL_H;//把时钟线拉高
    delay_us(5);//延时5微秒,要求大于4.7微秒
    I2C_SDA_L;  //拉低,产生下降沿
    delay_us(6);//这个过程大于4微秒
    I2C_SCL_L;//最后一定要把这个时钟线拉低,因为只有时钟线拉低的时候才允许数据        变化。
}
//产生停止信号
void I2C_Stop(void)
{
   I2C_SDA_OUT();
 
   I2C_SCL_L;
   I2C_SDA_L;
   I2C_SCL_H;
   delay_us(6);
   I2C_SDA_H;
   delay_us(6);
}
//主机产生应答信号ACK
void I2C_Ack(void)
{
   I2C_SCL_L;
   I2C_SDA_OUT();
   I2C_SDA_L;
   delay_us(2);
   I2C_SCL_H;
   delay_us(5);
   I2C_SCL_L;
}
 
 
//主机不产生应答信号NACK
void I2C_NAck(void)
{
   I2C_SCL_L;
   I2C_SDA_OUT();
   I2C_SDA_H;
   delay_us(2);
   I2C_SCL_H;
   delay_us(5);
   I2C_SCL_L;
}
//等待从机应答信号,我们只负责主机应答信号的产生,从机应答信号
//我们不控制。
//返回值:1 接收应答失败
//  0 接收应答成功
u8 I2C_Wait_Ack(void)
{
    u8 tempTime=0;
 
    I2C_SDA_IN(); //配置为上拉输入。
    I2C_SDA_H;  //主机释放数据总线,等待从机产生应答信号
    delay_us(1);
    I2C_SCL_H;
    delay_us(1);
    //等待从机对数据总线的操作。低电平代表应答
    while(GPIO_ReadInputDataBit(GPIO_I2C,I2C_SDA))
    {
        tempTime++;
        //这个属于软件延时,不一定准确。
        if(tempTime>250) //如果时间超时,没有应答就停止。
    {
        I2C_Stop();
        return 1;  //没有响应的话返回1.
    } 
}
    I2C_SCL_L;
    return 0; //如果有响应的话就返回0.
}

再增加几个操作函数

/************************************************
函数名称 : I2C_Delay
功    能 : I2C延时(非标准延时,请根据MCU速度 调节大小)
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
static void I2C_Delay(void)
{
  uint16_t cnt = 10;

  while(cnt--);
}

/************************************************
函数名称 : I2C_GPIO_Configuration
功    能 : I2C引脚配置(开漏输出)
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_GPIO_Configuration(void)
{
  GPIO_Init(PORT_I2C_SCL, (GPIO_Pin_TypeDef)PIN_I2C_SCL, GPIO_MODE_OUT_OD_HIZ_FAST);
  GPIO_Init(PORT_I2C_SDA, (GPIO_Pin_TypeDef)PIN_I2C_SDA, GPIO_MODE_OUT_OD_HIZ_FAST);
}

/************************************************
函数名称 : I2C_Initializes
功    能 : I2C初始化
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_Initializes(void)
{
  I2C_GPIO_Configuration();

  I2C_SCL_HIGH;                                  //置位状态
  I2C_SDA_HIGH;
}
/************************************************
函数名称 : I2C_WriteByte
功    能 : I2C写一字节
参    数 : Data --- 数据
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_WriteByte(uint8_t Data)//时钟低的时候先准备好数据,再打开时钟(读取应答位无判断?)
{
  uint8_t cnt;

  for(cnt=0; cnt<8; cnt++)
  {
    I2C_SCL_LOW;                                 //SCL低(SCL为低电平时变化SDA有效)
    I2C_Delay();

    if(Data & 0x80)
      I2C_SDA_HIGH;                              //SDA高
    else
      I2C_SDA_LOW;                               //SDA低
    Data <<= 1;
    I2C_Delay();

    I2C_SCL_HIGH;                                //SCL高(发送数据)
    I2C_Delay();
  }
  I2C_SCL_LOW;                                   //SCL低(等待应答信号)
  I2C_Delay();

  I2C_GetAck();                                  //读取应答位
}

/************************************************
函数名称 : I2C_ReadByte
功    能 : I2C读一字节
参    数 : ack --------- 产生应答(或者非应答)位
返 回 值 : data -------- 读取的一字节数据
作    者 : strongerHuang

*************************************************/
uint8_t I2C_ReadByte(uint8_t ack)//时钟低的时候先准备好数据,然后地址也弄高。然后等时钟高了再读取
{
  uint8_t cnt;
  uint8_t data;

  I2C_SCL_LOW;                                   //SCL低
  I2C_Delay();

  I2C_SDA_HIGH;                                  //释放SDA(开漏模式有效)

  for(cnt=0; cnt<8; cnt++)
  {
    I2C_SCL_HIGH;                                //SCL高(读取数据)
    I2C_Delay();

    data <<= 1;
    if(I2C_SDA_READ)      data |= 0x01; //读取SDA的状态
                                       //SDA为高(数据有效)

    I2C_SCL_LOW;                                 //SCL低
    I2C_Delay();
  }

  I2C_PutAck(ack);                               //产生应答(或者非应答)位

  return data;                                   //返回数据
}

猜你喜欢

转载自www.cnblogs.com/icaowu/p/10543607.html