打印
[资料干货]

转载—I2C总线协议简要代码分析

[复制链接]
699|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
EvanYuen|  楼主 | 2020-2-16 12:03 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
转载来自公众号:  yuanfeng8586

I2C总线大家应该比较熟悉,即便没有写过驱动代码,但总也听过他的大名吧,哈哈,该总线广泛应用于各种通用芯片,诸如LCD驱动、数码管驱动、移动电源芯片、存储器等等,可以节省IO口数量,方便连线布板。



下面列举一下I2C的特点:

1,双线串行总线,由SCL时钟线和SDA数据线组成。

2,SCL和SDA都是双线总线,即要求参与通信的MCU IO口可以配置为输入和输出模式。

3,每次传输到总线的数据必须是8位,即一个字节,且高位在前。

4,连接到总线的器件有唯一的地址识别码,以便实现包含多I2C器件的系统控制。

5,数据传输时以“应答”机制来进行校验和同步。

6,支持多主机通信,采用仲裁方案协调。



下面以单主机单从机模型简要分析I2C总线数据传输时序,并用代码实现驱动。



I2C总线的硬件连接电路如下:



一,如以上原理图,总线空闲时SCL和SDA都为高电平。先做定义:



sbit  I2C_SCL  =  IO1;
sbit  I2C_SDA  =  IO2;
void  SET_SDA_PIN_MODE(char mode);  //当mode=IN时,SDA线为输入模式,当mode=OUT时,SDA线为输出模式
void  READ_SDA(void);  //读取SDA线电平状态
//设置I2C为空闲状态
void I2C_SET_IDLE(void)
{
  I2C_SCL=1;
  I2C_SDA=1;
}


二,I2C起始与停止时序,下图是时序图:



从时序上分析我们可以看到,I2C总线起始信号是当时钟线SCL为高电平时,数据线SDA由高电平跳变到电平。



//I2C起始函数
void I2C_START(void)
{
  SET_SDA_PIN_MODE(OUT);  //设置SDA线为输出模式
  I2C_SCL=1;  //根据时序图SCL线设置高电平
  I2C_SDA=1;  //根据时序图SDA线设置高电平,以产生下降沿
  delay_us(5);  //保持
  I2C_SDA=0;  //产生下降沿
  delay_us(5);
  I2C_SCL=0;  //拉低时钟线,准备通信
}
I2C停止信号从时序图上可以看出,是当SCL为高电平时,SDA线由低电平产生到高电平的一个上升沿,以后都是高电平,进入空闲状态。



//I2C停止函数
void I2C_STOP(void)
{
  SET_SDA_PIN_MODE(OUT);  //设置SDA线为输出模式
  I2C_SCL=1;  //设置SCL为高电平
  I2C_SDA=0;  //设置SDA为低电平,以便产生上升沿
  delay_us(5);  //保持
  I2C_SDA=1;  //产生上升沿
  delay_us(5);
}  


三,I2C传输数据时必须产生应答信号,作为校验和同步传输使用。从机产生应答信号的时序如下图:



可以看出,应答信号是在一个字节数据传输完成后的下一个时钟产生,应答条件为SCL时钟变化过程中要求SDA数据线保持低电平。

//I2C产生应答函数
void  I2C_SEND_ACK(void)
{
  SET_SDA_PIN_MODE(OUT);  //设置SDA线为输出模式
  I2C_SDA=0;  //设置SDA为低电平
  I2C_SCL=0;  //设置SCL为低电平,以便产生时钟跳变
  delay_us(5);
  I2C_SCL=1;  //产生SCL时钟跳变
  delay_us(5);
  I2C_SCL=0;
}


如果从机由于内存不足或者其他原因不能接受数据,也可以发送非应答信号给主机,通知主机停止数据传输。时序如下图:



可以看出,非应答信号和应答信号正好相反,是在传输完一个字节数据后,在时钟线SCL跳变时,SDA一直保持为高电平。

//I2C产生非应答信号
void  I2C_SEND_NACK(void)
{
  SET_SDA_PIN_MODE(OUT);  //设置SDA线为输出模式
  I2C_SDA=1;  //设置SDA为高电平
  I2C_SCL=0;  //设置SCL为低电平,以便产生时钟跳变
  delay_us(5);  //保持
  I2C_SCL=1;
  delay_us(5);  
  I2C_SCL=0;
}  
四,主机等待应答,当接收到应答信号时继续传输数据,若接收不到则终止传输。时序图和产生应答信号一致,反向处理,即当时钟线SCL产生跳变时检测数据线SDA,检查SDA是否在这一过程中一直处于低电平,若是则是从机有应答,否则为无应答。

//I2C等待应答信号,若返回为0则为有应答,返回为1无应答
unsigned char I2C_WAIT_ACK(void)
{
  unsigned char time_out=0;
  SET_SDA_PIN_MODE(IN);  //设置SDA线为输入模式,以便读取数据线状态
  I2C_SCL=0;
  delay_us(5);
  I2C_SCL=1;
  delay_us(5);
  while(READ_SDA())  //在SCL时钟跳变时读取SDA数据线电平状态
  {
    time_out++;  //滤掉毛刺,若SDA始终为高电平,则为非应答
    if(time_out>1000)
    {
      time_out=0;
      I2C_STOP();  //等待不到应答信号则产生停止信号,终止传输数据
      return  1;
    }
  }
  I2C_SCL=0;
  return  0;
}  
五,进行数据传输,在I2C起始信号以后,可以进行数据传输,传输数据时需要注意,在SDA数据线上传输的数据必须在SCL时钟信号周期内保持稳定,否则数据无效。SDA数据线的高低电平变化只可以在SCL时钟线为低电平时变化。且每次8位,先传输高位。时序图如下:



参照时序图,写出发送一个字节的驱动代码:



//I2C发送单个字节函数,函数参数接收要发送的数据
void  I2C_SEND_BYTE(unsigned char dat)
{
  unsigned char i=0;
  SET_SDA_PIN_MODE(OUT);  //设置SDA线为输出模式
  for(i=0;i<8;i++)
  {
    //在时钟线SCL跳变之前先准备好数据待传输
    if(dat&0x80)  //分离出待传输数据的最高位
      I2C_SDA=1;  //若最高位是1则SDA线是高电平,否则是低电平
    else
      I2C_SDA=0;
    //产生时钟跳变,发送数据
    I2C_SCL=0;
    delay_us(5);
    I2C_SCL=1;
    delay_us(5);
    I2C_SCL=0;
    //发送完一个位后数据移位,发送下一个位的数据
    dat<<=1;
}   


从SDA数据线上读取数据时操作过程和上面正好相反,即在时钟线SCL每发生一次跳变时可以从SDA数据线读取到一位数据,先读取到的是高位。驱动代码如下:



//I2C从SDA数据线读取一个字节函数,返回数据
unsigned char I2C_READ_BYTE(void)
{
  unsigned char i=0,dat=0;
  SET_SDA_PIN_MODE(IN);  //设置SDA线为输入模式,以便读取数据线状态
  for(i=0;i<8;i++)
  {
    //时钟线产生上升沿
    I2C_SCL=0;
    delay_us(5);
    I2C_SCL=1;
    delay_us(5);
    dat<<=1;  //接收到的数据先左移一位,等待然后接收下一位,这样接收完成后最开始接收到的数据就移位到最高位了
    if(READ_SDA())
      dat++;
    I2C_SCL=0;  //SCL产生下降沿,时钟跳变完成
  }
  I2C_SEND_ACK();  //读取完成一个字节返回一个应答
}


以上基本的总线驱动已经完成,应用到具体芯片,只需要跟进读写时序进行调用即可,注意一般支持I2C的芯片地址为7位,第8位是读写位,一般0为写操作,1为读操作。比如一个芯片的七位地址为0B1010,000,十六进制为0XA0,考虑读写位时,进行读时需要先写入0XA1,进行写操作时先写入0XA0,如下参考代码:



//实例应用代码
void  I2C_WRITE_DAT_to_CHIP(unsigned char dat)  //主机MCU发送数据给芯片
{
  I2C_START();  //起始信号,准备传输数据
  I2C_SEND_BYTE(0XA0);  //寻址,写入写命令;
  I2C_WAIT_ACK();  //等待应答
  I2C_SEND_BYTE(dat); //写入需要的数据
  I2C_STOP();  //发送完成后停止
}

unsigned char I2C_READ_DAT_FORM_CHIP(void)  //主机MCU从芯片读取数据
{
  unsigned char dat;
  I2C_START();  //起始信号,准备接收数据
  I2C_SEND_BYTE(0XA1);  //寻址,写入读命令;
  I2C_WAIT_ACK();  //等待应答
  dat=I2C_READ_BYTE(); //写入需要的数据
  I2C_SEND_ACK(); //接收完一个字节产生一个应答
  I2C_STOP();  //接收完成后停止
  return dat;
}

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

7

主题

30

帖子

1

粉丝