I2C总线

来源:互联网 发布:淘宝的营销手段 编辑:程序博客网 时间:2024/09/21 09:26

 

 

1  I2C总线

I2C总线全称是Inter-Integrated Circuit总线,有时也写为IIC总线,它是由菲利浦公司开发出来的一种串行总线协议。单片机系统中常常使用这个总线连接RAM、EEPROM以及LCD控制器等设备。

I2C总线因为协议成熟、引脚简单、传输速率高、支持的芯片多,并且有利于实现电路的标准化和模块化,而受到开发者的青睐。

I2C总线最主要的优点是其简单性和有效性。由于接口直接在组件之上,因此I2C总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbit/s的最大传输速率支持40个组件。I2C总线的另一个优点是支持多主控(multimastering),其中任何能够进行发送和接收的设备都可以成为主控设备。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

I2C总线的最新版本是2000年出的2.1版,电源电压是2V,传输速率是0~3.4Mbps。I2C总线通过双向的数据线SDA和时钟线SCL两个连线完成全双工的通信,其他的具有I2C接口的IC器件都直接连在这两根总线上,如图13-1所示。

 

图13-1  I2C总线

13.1.1  I2C总线的基本结构

I2C规程运用主/从双向通信原则。器件发送数据到总线上,则定义为发送器。器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。因此,I2C总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。总线必须由主器件(通常为微控制器)控制。

I2C总线中的SDA和SCL均为双向I/O线,通过上拉电阻接正电源。当总线空闲时,两根线都是高电平。连接总线的器件输出级必须是集电极或漏极开路,以形成线“与”功能。

I2C总线上的主器件在SCL信号线上产生时钟脉冲,在SDA信号线上产生寻址信号、开始条件和停止条件。每个具有I2C接口的设备都有一个唯一的7位或者10位的地址,以便于主控制器寻访。在信息的传输过程中,CPU发出的控制信号分为地址码和控制量两部分,地址码用来选址,即接通需要控制的电路,确定控制的种类;控制量决定该调整的类别及需要调整的量。这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。

早期的I2C总线数据传输速率最高为100Kbits/s,采用7 位寻址。但是由于数据传输速率和应用功能的迅速增加,I2C总线也增强为快速模式(400Kbits/s)和10 位寻址,以满足更高速度和更大寻址空间的需求。

I2C总线始终和先进技术保持同步,但仍然保持其向下兼容性。并且最近还增加了高速模式,其速度可达3.4Mbits/s。它使得I2C总线能够支持现有以及将来的高速串行传输应用,例如EEPROM 和Flash 存储器。

13.1.2  起始和停止条件

在数据传送过程中,必须确认数据传送的开始和结束。在I2C总线规范中,规定起始条件指的是当SCL为高电平的时候,SDA从高电平向低电平转换;停止条件指的是当SCL是高电平的时候SDA从低电平向高电平转换。一个起始条件可以在SDA上面传输一个字节的数据,停止条件可以停止数据的传输。在起始条件以后I2C总线就被认为是处于忙状态,直到停止条件以后的几个时钟周期I2C总线才被认为重新处于空闲状态。

图13-2所示是I2C总线起始和停止条件信号定义图。

 

图13-2  数据传输的起始和停止条件

13.1.3  数据传输

在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位。读写位为1时为读操作,即主器件将从从器件读信息;为0时为写操作,即主器件把信息写到所选择的从器件中。

在主器件发出器件的控制字节后,系统中的各个器件将自己的地址和主器件送到总线上的地址进行比较,如果与主器件发送到总线上的地址一致,则该器件即为被主器件寻址的器件,是收信息还是发送信息则由主器件发出的控制字节的最后一位决定。

地址传输完成以后就是数据的传输,合法的数据传输的格式如图13-3所示。

图13-3  数据传输格式

I2C总线上每次传送的数据字节数不限,但每一字节必须是8位。串行数据的传输总是从最高位开始传输的,每个数据帧后面都有一个确认位,也叫应答位(ACK)。确认位的作用是用来结束一个字节的传输,它是由接收方在数据传输开始后的第九个时钟周期发送的。如果一接收器件在完成其他功能(如一内部中断)前不能接收另一数据的完整字节时,它可以保持时钟线SCL为低,以促使发送器进入等待状态,当接收器准备好接受数据的其他字节并释放时钟SCL后,数据传输会继续进行。I2C总线数据传送时序如图13-4所示。

 

图13-4  I2C 总线数据传送时序

I2C总线还具有广播呼叫地址,用于寻址总线上所有器件。如果一个器件需要广播呼叫寻址中提供的数据,那么应该对地址发出响应,其表现为一个接收器。如果该器件不需要广播呼叫寻址中所提供的任何数据,则可以忽略该地址不发出响应。

13.1.4  总线竞争的仲裁

I2C总线上可能挂接多个器件,这样有时候就会发生两个或多个主器件同时想占用总线的情况。下面分两种情况分析。

(1)同步。所有主机都会在SCL 线上产生它们自己的时钟来传输I2C总线上的报文,数据只在时钟的高电平周期有效,因此需要一个确定的时钟进行逐位仲裁。

I2C总线系统中,SCL 线的高到低切换会使器件开始数它们的低电平周期,而且一旦器件的时钟变低电平,它会使SCL 线保持这种状态直到时钟高电平到达,如图13-5所示。但是如果另一个时钟仍处于低电平周期,这个时钟的低到高切换不会改变SCL线的状态,因此SCL线被有最长低电平周期的器件保持低电平,此时低电平周期短的器件会进入高电平的等待状态。

 

图13-5  仲裁过程中的时钟同步

只有当所有有关的器件数完了它们的低电平周期后,时钟线被释放并变成高电平。之后,器件时钟和SCL线的状态没有差别。而且所有器件会开始数它们的高电平周期。首先完成高电平周期的器件会再次将SCL线拉低。

这样,产生的同步SCL 时钟的低电平周期由低电平时钟周期最长的器件决定,而高电平周期由高电平时钟周期最短的器件决定。

(2)仲裁。主机只能在总线空闲的时侯启动传输。但是在两个或多个主机系统中,可能会在某一时刻有两个单片机要同时向总线发送数据,这时便发生了总线竞争。

I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁。在SCL 线是高电平时,如果某个主器件在SDA 线上发送高电平,而其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平与它自己的电平不相同。也就是说,发送电平与此时SDA总线电平不符的那个器件将自动关闭其输出级。

总线竞争的仲裁是在两个层次上进行的。首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位的比较,以保证竞争仲裁的可靠性。

如果每个主机都尝试寻址相同的器件,仲裁会继续比较数据位(如果主机是发送器)或者比较响应位(如果主机是接收器)。因为I2C总线的地址和数据信息由赢得仲裁的主机决定,在仲裁过程中不会丢失信息。

丢失仲裁的主机必须立即切换到它的从机模式。

图13-6所示是两个主机的仲裁过程。

 

图13-6  两个主机的仲裁过程

 

 

#include

#define uchar unsigned char
#define uint unsigned int
#define ulong unsigned long

sbit WDOG=P3^4;        //看门狗
sbit I2C_SCK=P1^5;     //24cxx的时钟线
sbit I2C_SDA=P1^6;     //24CXX的数据线

bit   I2C_Start(void);
void  I2C_Stop(void);
void  I2C_Ack(void);
void  I2C_Nack(void);
bit   I2C_Send_Byte( uchar);
uchar I2C_Receive_Byte(void);
void  AT24C64_R(void *mcu_address,uint AT24C64_address,uint count);
void  AT24C64_W(void *mcu_address,uint AT24C64_address,uint count);

void FeedDog(void)
{
     WDOG=!WDOG;
}
void  Delay_10_uS(void)
{
        char i=10;
        while(i--);
}
void Delay_N_mS( uint n_milisecond)  /* n mS delay */
{
        uchar i;
        while(n_milisecond--)
        {
                i=37;
                while(i--);
        }
}
void I2C_Init(void)
{
        I2C_SCK=0;
        I2C_SDA=0;
}
bit I2C_Start(void)
{
        Delay_10_uS();
        I2C_SDA =1;
        Delay_10_uS();
        I2C_SCK =1;
        Delay_10_uS();
        if ( I2C_SDA == 0) return 0;
        if ( I2C_SCK == 0) return 0;
        I2C_SDA = 0;
        Delay_10_uS();
        I2C_SCK = 0;
        Delay_10_uS();
        return 1;
}
void  I2C_Stop(void)
{
        Delay_10_uS();
        I2C_SDA = 0;
        Delay_10_uS();
        I2C_SCK = 1;
        Delay_10_uS();
        I2C_SDA = 1;
        Delay_10_uS();
}
void I2C_Ack(void)
{
        Delay_10_uS();
        I2C_SDA=0;
        Delay_10_uS();
        I2C_SCK=1;
        Delay_10_uS();
        I2C_SCK=0;
        Delay_10_uS();
}
void I2C_Nack(void)
{
        Delay_10_uS();
        I2C_SDA=1;
        Delay_10_uS();
        I2C_SCK=1;
        Delay_10_uS();
        I2C_SCK=0;
        Delay_10_uS();
}
bit I2C_Send_Byte( uchar d)
{
        uchar i = 8;
        bit bit_ack;
        while( i-- )
        {
                Delay_10_uS();
                if ( d &0x80 )   I2C_SDA =1;
                else             I2C_SDA =0;
                Delay_10_uS();
                I2C_SCK = 1;
                Delay_10_uS();
                I2C_SCK = 0;
                d = d << 1;
        }
        Delay_10_uS();
        I2C_SDA = 1;
        Delay_10_uS();
        I2C_SCK = 1;
        Delay_10_uS();
        bit_ack = I2C_SDA;
        I2C_SCK =0;
        Delay_10_uS();
        return bit_ack;
}
uchar I2C_Receive_Byte(void)
{
        uchar i = 8, d;
        Delay_10_uS();
        I2C_SDA = 1;
        while ( i--)
        {
                d = d << 1;
                Delay_10_uS();
                I2C_SCK =1;
                if ( I2C_SDA ) d++;
                Delay_10_uS();
                I2C_SCK =0;
        }
        return d;
}
void AT24C64_W(void *mcu_address,uint AT24C64_address,uint count)
{
        FeedDog();
        while(count--)
        {
                I2C_Start();
                /*I2C_Send_Byte( 0xa0 + AT24C64_address /256 *2);*/  /* 24C16  USE */
                I2C_Send_Byte( 0xa0 );
                I2C_Send_Byte(  AT24C64_address/256 );
                I2C_Send_Byte( AT24C64_address %256 );
                I2C_Send_Byte( *(uchar*)mcu_address );
                I2C_Stop();
                Delay_N_mS(10);       /* waiting for write cycle to be completed */
                ((uchar*)mcu_address)++;
                AT24C64_address++;
        }
}
void AT24C64_R(void *mcu_address,uint AT24C64_address,uint count)
{
       FeedDog();
       while(count--)
       {
                I2C_Start();
                /*I2C_Send_Byte( 0xa0 + AT24C64_address / 256 *2 );*/   /* 24C16 USE */
                I2C_Send_Byte( 0xa0 );
                I2C_Send_Byte( AT24C64_address/256 );
                I2C_Send_Byte( AT24C64_address % 256 );
                I2C_Start();
                /*I2C_Send_Byte( 0xa1 + AT24C64_address /256 *2 );*/
                I2C_Send_Byte( 0xa1 );
                *(uchar*)mcu_address = I2C_Receive_Byte();
                I2C_Nack();
                I2C_Stop();
                ((uchar*)mcu_address)++;
                AT24C64_address++;
       }
}

原创粉丝点击