更加深入理解I2C总线、协议及应用


他们说

虹涛犹珷,天道酬勤!】说:望大神后面能出下IIC和SPI的使用,我是看了你的教程入门STM8的。


Was】说:您好,从发现这个公众号,我一直在看您的文章学习,现在在学iic通信,对如何确定一个陌生芯片的从机地址不理解,比如我现在用的UM220。请赐教!拜托了。


既然你们想了解关于I2C的知识,我就成全你们,让你们慢慢地了解I2C。因前面是写关于STM8S的文章,本文也提供基于STM8S单片机的I2C例程供大家参考与学习。

I2C是一种协议,也是一种总线,它与什么处理器关系不是很大,下面结合协议、时序及源代码来讲述关于I2C的知识。


开始和停止条件

SCL时钟电平为高

SDA数据线由高 -> 低 为总线开始条件

SDA数据线由低 -> 高 为总线结束条件

注意:开始之后将SCL变为低电平,防止误操作SDA使其通信停止,见源代码


时序图


源代码程序

/************************************************

函数名称 : I2C_Start

功    能 : I2C开始

参    数 : 无

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void I2C_Start(void)

{

  I2C_SCL_HIGH;    //SCL高

  I2C_Delay();


  I2C_SDA_HIGH;    //SDA高 -> 低

  I2C_Delay();

  I2C_SDA_LOW;     //SDA低

  I2C_Delay();


  I2C_SCL_LOW;     //SCL低(待写地址/数据)

  I2C_Delay();

}


/************************************************

函数名称 : I2C_Stop

功    能 : I2C停止

参    数 : 无

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void I2C_Stop(void)

{

  I2C_SDA_LOW;     //SDA低 -> 高

  I2C_Delay();


  I2C_SCL_HIGH;    //SCL高

  I2C_Delay();


  I2C_SDA_HIGH;    //SDA高

  I2C_Delay();

}


数据位传输

SCL时钟电平为低, 可以改换SDA数据线的电平,在SCL上升沿的过程将SDA数据发送出去。

切记:请先将SCL变为低电平,再改变SDA电平状态。 主要用于I2C读写Byte函数,这两个函数网上很多人写的不规范,引用需注意,在下面我会举例说明


时序图


发送一位“”数据流程:

SCL_LOW时钟低 ->  SDA_HIGH数据 ->  SCL_HIGH时钟高


应答位信息

I2C是以字节(8位)的方式进行传输,总线上每传输完1字节之后会有一个应答信号,主器件(主机)需要产生对应的一个额外时钟


应答位产生及接收:

1.在(主机)写数据的时候是从机应答(给主机),主机检测;

2.在(主机)读数据的时候是主机应答(给从机),从机检测;

我们借助I2C读写函数一起理解


1.主机写,从机应答,主机读取应答

时序图:


源代码:

/************************************************

函数名称 : I2C_GetAck

功    能 : I2C主机读取应答(或非应答)位

参    数 : 无

返 回 值 : I2C_ACK ----- 应答

            I2C_NOACK --- 非应答

作    者 : strongerHuang

*************************************************/

uint8_t I2C_GetAck(void)

{

  uint8_t ack;


  I2C_SCL_LOW;     //SCL低 -> 高

  I2C_Delay();


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

  I2C_Delay();


  I2C_SCL_HIGH;    //SCL高(读取应答位)

  I2C_Delay();


  if(I2C_SDA_READ)

    ack = I2C_NOACK;//非应答

  else

    ack = I2C_ACK; //应答


  I2C_SCL_LOW;     //SCL低

  I2C_Delay();


  return ack;

}


2.主机读,主机产生应答

时序图:


源代码:

/************************************************

函数名称 : I2C_PutAck

功    能 : I2C主机产生应答(或非应答)位

参    数 : I2C_ACK ----- 应答

            I2C_NOACK --- 非应答

返 回 值 : 无

作    者 : strongerHuang

*************************************************/

void I2C_PutAck(uint8_t Ack)

{

  I2C_SCL_LOW;     //SCL低

  I2C_Delay();


  if(I2C_ACK == Ack)

    I2C_SDA_LOW;   //应答

  else

    I2C_SDA_HIGH;  //非应答

  I2C_Delay();


  I2C_SCL_HIGH;    //SCL高 -> 低

  I2C_Delay();

  I2C_SCL_LOW;     //SCL低

  I2C_Delay();

}


I2C写一字节

这里说的I2C写,是主机往从机接入1Byte的数据;


“写”要求按照上面的“数据为传输”来操作:在SCL时钟为低电平时准备好,待SCL为高电平时发送出去。


写完一字节(8位)之后,读取从机的应答位:

若为0,表示从机应答,可以继续下一步操作; 

若为1,表示从机非应答,不能进行下一步操作。


注意

I2C写一字节,不是EEPROM写一字节(需要区分开来)


写一字节时序(前面8位数据 + 最后1为应答)


源代码

/************************************************

函数名称 : 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写数据函数”的不规范写法, 或许整个I2C驱动能通信成功,但各个函数之间依赖关系很强,不便理解,也不是标准的函数。


1.首先将SCL置高

void I2C_WriteByte(uint8_t Data)

{

  uint8_t cnt;

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

  {

    I2C_SCL_HIGH; 


    if(Data & 0x80)

      I2C_SDA_HIGH;

    else

      I2C_SDA_LOW; 

    Data <<= 1;


I2C_SCL_LOW;

  }

  I2C_GetAck();

}

这种程序的写法有一个致命的地方(有可能停止,或重新开始I2C通信):

首先将SCL置高:

A.若之前SDA是低电平,第一位写入高电平,将停止I2C通信。

B.若之前SDA是高电平,第一位写入低电平,将重新开始I2C通信。


2.写完8位数据之后,未将SCL置低(也就是SCL保持高电平状态)

由于写完8位数据之后,将要读取应答信号,也就是要SDA将从输出状态变为输入状态。

这个时候SCL为高,如果SDA最后一位是低且SDA是开漏模式,需要将SDA释放,也就是要将SDA置位高,那么,这个时候就进行了一个停止操作。


3.时序混乱

void I2C_WriteByte(uint8_t Data)

{

  uint8_t cnt;

    I2C_SCL_HIGH; 


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

  {

    if(Data & 0x80)

      I2C_SDA_HIGH;

    else

      I2C_SDA_LOW; 

    Data <<= 1;


    I2C_SCL_LOW;

    I2C_SCL_HIGH; 

  }

  I2C_GetAck();

}

多种问题的例子,有可能产生以下问题:

A.有可能多写1位数据;

B.有可能停止I2C通信;

C.有可能重新开始I2C通信。


I2C读一字节

I2C的读一字节函数,其实和“写一字节”类似,只是数据传输方向相反,应答的方向也是相反。


完一字节(8位)之后,由主机产生应答(或非应答)位:

若产生应答,表示可以继续读下一字节操作(从设备地址指向下一字节); 

若产生非应答,表示不可以继续读下一字节操作;


网上I2C读数据程序和“写数据”类似,存在很多不标准的版本,参考时请注意。

读一字节时序(主机读取前面8位数据 + 主机产生1为非应答<连续读,主机产生应答位>):


源代码

/************************************************

函数名称 : 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为高(数据有效)


    I2C_SCL_LOW;   //SCL低

    I2C_Delay();

  }


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


  return data;     //返回数据

}


源代码工程下载

STM8S-A10_I2C读写EEPROM(模拟):

http://pan.baidu.com/s/1c2EcRo0


提示:如果网盘链接失效,可以微信公众号“底部菜单”查看更新链接。


最后

微信搜索“EmbeddDeveloper” 或者扫描下面二维码、关注,在我的底部菜单查看更多精彩内容!

长按识别二维码 关注



轻轻的你来了 点个赞再走吧!