I2C通信

  I2C总线是PHLIPS公司推出的一种串行总线,它只有两根双向信号线。一根是数据线SDA(serial data I/o),另一根是时钟总线SCL(serial clock)。

如下图所示,I2C总线上可以挂接多个器件,而每个器件都有唯一的地址,这样可以标识通信目标。数据的通信方式采用主从方式,主机负责主动联系从机,而从机则被动回应数据。

I2C总线的搭建

因为任何带有I2C硬件接口的器件都是开漏I型I/O口,不具备输出高电平的能力,所以2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连接到总线上的任一器件输出的低电平都将使总线的信号变低,即各器件的SDA及SCL都是“线与”关系,如下图所示。

 I2C总线传输协议

在I2C总线通信的过程中,参与通信的双方互相之间所传输的信息种类归纳如下。

  主机向从机发送的信息种类有:启动信号、停止信号、7位地址码、读/写控制位、10位地址码、数据字节、重启动信号、应答信号、时钟脉冲。

   从机向主机发送的信息种类有:应答信号、数据字节、时钟低电平。

1、传送数据的有效性规定

SCL为高电平期间,数据线SDA上的数据必须保持稳定,只有当SCL信号为低电平期间,SDA状态才允许变化。在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。

时序图如下图所示

I2C的起始信号与终止信号

SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号,它标志着一次数据传输的开始。SCL为高电平期间,SDA线由低电平向高电平的变化表示终止信号,它标志着传输数据结束。

时序图如下图所示

I2C的字节传送与应答

每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一个应答位(即一帧共有9位)。应答位即主机向从机发送数据时,

时序图如下图所示

应答位(ACK)的作用

I2C总线上的所有数据都是以8位字节传送的,主机每发送一个字节,就在时钟脉冲9期间释放数据线,由从机反馈一个应答信号。

主机在发送数据时,每次发送一字节的数据,都需要读取从机的应答位,当从机空闲可以接收该字节数据时,从机会发出应答(一帧数据的第9位为“0”),当从机正忙于其它工作的处理来不及接收主机发送的数据时,从机会发出非应答(一帧数据的第9位为“1”),主机则应发出终止信号以结束数据的继续传送,主机通过从机发出的应答位来判断从机是否成功接收数据,对于反馈有效应答位ACK的要求是,从机在第9个时钟脉冲之前的低电平期间,将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。

当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号,这个信号是由对从机的“非应答”来实现的,然后,从机释放SDA线,以允许主机产生终止信号。

插入等待时间

如果从机需要延时下一个数据字节开始传送的时间,则可以通过把时钟总线SCL电平拉低并且保持,使主机进入等待状态,如果从机释放时钟总线,数据就可以被继续传输下去,这样就使得从机得到足够的时间转移已收到的数据,或者准备好即将发送的数据。

I2C写数据流程

 

 如上图所示,S代表START ,首先主机先发送一个起始信号,然后接着发送7位器件地址+第八位数据传送方向位0/1(“0”代表主机发送数据,“1”代表主机接收数据),发送完地址后,等待从机应答(灰色为主机所发送的,白色为从机负责发送的),当从机应答后,主机会发送向从机写入数据的首地址,从机应答后,主机即向从机发送数据,每发送完一位,等待一从机一次应答,数据传输完成后,主机发送一个停止信号即完成了数据的传输。

时钟总线为高电平期间,数据可以被读取,当SCL为低电平期间,可以向数据总线SDA线上写数据。

I2C读数据流程

主机线发送一个起始信号,然后发送一个从机地址代表选择读取哪个从机的数据,读写方向为写(因为是给从机发送地址信号),然后等待从机应答,应答后,主机选择要从从机的的哪个内存地址开始读,然后不管从机是否应答,主机即开始再次发送一个起始信号,然后接着发送从机地址,读写方向为读,然后从机应答后开始向主机发送数据,发送完成后主机发送一个非应答信号和停止信号,即完成了I2C读数据的流程。

使用I2C通信时通常要注意几点

下面即为软件模拟I2C时序

#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char
#define uint unsigned int

#define slave_address 0x00   //假设从机地址位0x00

sbit SDA = P2^0;                   //假设P2.0和P2.1为I2C的数据总线与时钟总线
sbit SCL = P2^1;

/******************************
** 函数名称:
** 函数功能:软件延时
*******************************/
void delay(uchar i)
{
  uchar j,k; 
  for(j=i;j>0;j--)
  for(k=125;k>0;k--);
}
/******************************
** 函数名称:
** 函数功能:I2C初始化
*******************************/
void I2C_Init()//I2C初始化,使总线处于空闲状态
{
        SCL = 1;  //拉高SDA、SCL,使总线处于空闲状态
        _nop_();
        SDA = 1;
        _nop_();
}
/******************************
** 函数名称:
** 函数功能:I2C起始信号
*******************************/
void I2C_Start() //SCL为高电平期间,SDA负跳变1->0
{
        SCL = 1;    
        _nop_();
        SDA = 1;
        delay(5);
        SDA = 0;
        delay(5);
}

/******************************
** 函数名称:
** 函数功能:I2C终止信号
*******************************/
void I2C_Stop()  //SCL为高电平期间,SDA正跳变          0->1
{
        SDA = 0;
      _nop_();
        SCL = 1;
        delay(5);
        SDA = 1;
        delay(5);
}

/******************************
** 函数名称:
** 函数功能:主机发送应答
*******************************/
void Master_ACK    (bit i)        //主机发送应答
{
        SCL = 0;             //拉低时钟总线允许SDA数据总线变化
        _nop_();                //让总线稳定
        if(i)                    //若果i等于1,则拉低时钟线,表示主机应答
        {
            SDA = 0;        //表示主机应答
        }
        else
        {
            SDA = 1;        //发送非应答
        }
        _nop_();            //让总线稳定
        SCL = 1;            //拉高时钟总线,让从机从SDA数据线上读走主机的应答信号
        delay(5);
        SCL = 0;            //拉低时钟总线,占用总线,使双方继续通信
        _nop_();
        SDA = 1;            //释放数据总线,交由从机控制
        _nop_();
        
}

/******************************
** 函数名称:
** 函数功能:应答检测
*******************************/
bit Test_ACK()
{
        SCL = 1;        //SCL时钟总线为高电平时才能读到SDA数据线上的数据
        delay(5);
        if(SDA)
        {
            SCL = 0;
            _nop_();
            I2C_Stop();
            return(0);        //如果为非应答返回0
        }
        else
        {
            SCL = 0;
            _nop_();
            return(1);
        }        
}
 
/******************************
** 函数名称:
** 函数功能:发送一个字节
*******************************/
void I2C_send_byte(uchar byte)
{
    uchar i;    
    for(i = 0 ; i < 8 ; i++)
    {
        SCL = 0;
        _nop_();
        if(byte & 0x80)
        {
            SDA = 1;
            _nop_();
        }
        else
        {
            SDA = 0;
            _nop_();
        }
        SCL = 1;
        _nop_();
        byte <<= 1;
    }
        SCL = 0;        //占用时钟总线
        _nop_();
        SDA = 1;        //释放数据总线
        _nop_();
}

/******************************
** 函数名称:
** 函数功能:i2c读一个字节
*******************************/
uchar I2C_read_byte()
{
        uchar I2C_data,i;
        SCL = 0;
        _nop_();
        SDA = 0;
        for(i = 0 ; i < 8 ; i++)
        {
            SCL = 1;
            _nop_();
            I2C_data <<= 1;  //读完最后一位数据时不会再次移位
            if(SDA)
            {
                I2C_data = I2C_data | 0x01; 
            }
            else
            {
                I2C_data = I2C_data & 0xfe;
            }
            _nop_();
            SCL = 0;
            _nop_();
        }
        return(I2C_data);
}

/******************************
** 函数名称:
** 函数功能:i2c发送数据
*******************************/
bit I2C_transmit_data(uchar ADDR,DAT)
{
        I2C_Start();    //SCL为高电平期间SDA负跳变
        I2C_send_byte(slave_address + 0); //发送从机地址
        if(!Test_ACK) //检测应答,如果为非应答Test_ACK则返回0,则条件成立
        {
            return(0);
        }
        I2C_send_byte(ADDR);//发送往从机的写地址
        if(!Test_ACK) //如果为非应答
        {
            return(0);
        }
        I2C_send_byte(DAT);//传送8位数据,一位一位传送,最高位先开始
        if(!Test_ACK) //检测应答,如果为非应答
        {
            return(0);
        }
        I2C_Stop();        //停止信号,SCL为高电平期间,SDA正跳变
        return(1);
}

/******************************
** 函数名称:
** 函数功能:i2c读数据
*******************************/
uchar I2C_receive_data(uchar ADDR)
{
        uchar DAT;
        I2C_Start();
        I2C_send_byte(slave_address + 0);  
        if(!Test_ACK) //如果为非应答
        {
            return 0;
        }
        I2C_send_byte(ADDR);
        if(!Test_ACK) //如果为非应答
        {
            return 0;
        }
        Master_ACK(0);
        I2C_send_byte(slave_address + 1);
        if(!Test_ACK) //如果为非应答
        {
            return 0;
        }
        DAT = I2C_read_byte();
        Master_ACK(0);
        I2C_Stop();
        return(DAT);
}
void main()
{
        
        I2C_Init();        //初始化,使总线保持空闲状态
        I2C_transmit_data(255,0xf0);
        delay(5);  //软件延时,为了使能够完整的读取出数据
        I2C_receive_data(255);
        while(1);
        
        
}

 

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值