码迷,mamicode.com
首页 > 其他好文 > 详细

I2C 总线协议分析

时间:2015-09-10 15:36:35      阅读:266      评论:0      收藏:0      [点我收藏+]

标签:

  由于大规模集成电路技术的发展,在单个芯片集成 CPU 以及组成一个单独工作系统所必须的 ROM、RAM、I/O 端口、A/D、D/A 等外围电路和已经实现,这就是常说的单片机或微控制器。目前,世界上许多公司生产单片机,品种很多:包括各种字长的 CPU,各种容量和品种的 ROM、RAM,以及功能各异的 I/O 等等。但是,单片机品种规格有限,所以只能选用某种单片机再进行扩展。扩展的方法有两种:一种是并行总线,另一种是串行总线。由于串行总线连线少,结构简单,往往不用专用的母板和插座而直接用导线连接各个设备即可。因此,采用串行总线大大简化了系统硬件设计。PHILIPS 公司早在十几年就前推出了I2C 串行总线,它是具备多主机系统所需的包括裁决和高低速设备同步等功能的高性能串行总线。

  总线对设备接口电路的制造工艺和电平都没有特殊的要求(NMOS、CMOS 都可以兼容)。数据传送率按I2C 总线可高达每秒十万位,高速方式可高达每秒四十万位。总线上允许连接的设备数以总线上的电容量不超过 400pF 为限。

  总线的运行(数据传输)由主机控制。所谓主机即启动数据的传送(发出启动信号),发出时钟信号,传送结束时发出停止信号的设备,通常主机是微处理器。被主机寻访的设备都称为从机。为了进行通讯,每个接到I2C 总线的设备都有一个唯一的地址,以便于主机寻访。主机和从机的数据传送,可以由主机发送数据到从机,也可以是从机发到主机。凡是发送数据到总线的设备称为发送器,从总线上接收数据的设备被称为接受器。

  I2C 总线上允许连接多个微处理器及各种外围设备,如存储器、LED 及 LCD驱动器、A/D 及 D/A 转换器等。为了保证数据可靠地传送,任一时刻总线只能有由某一台主机控制一个微处理器应该在总线空闲时发启动数据,为了妥善解决多台微处理器同时发启数据传送(总线控制权)的冲突,并决定由哪一台微处理器控制总线。I2C 总线允许连接不同传送速率的设备,多台设备之间时钟信号的同步过程称为同步化。 

  I2C 串行总线有两根信号线:一根双向的数据线 SDA;另一根是时钟线 SCL。所有接到I2C总线上的设备的串行数据都接到总线的SDA线,各设备的时钟线SCL接到总线的 SCL。

  为了避免总线信号的混乱,要求各设备连接到总线的输出端必须是开漏输出或集电极开路输出的结构。设备上的串行数据线 SDA 接口电路应该是双向的,输出电路用于向总线上发数据,输入电路用于接收总线上的数据。串行时钟线也应是双向的,作为控制总线数据传送的主机要通过 SCL 输出电路发送时钟信号,同时要检测总线上 SCL 上的电平以决定什么时候发下一个时钟脉冲电平;作为接受主机命令的从机,要按总线上的 SCL 的信号发出或接收 SDA 上的信号,也可以向 SCL 线发出低电平信号以延长总线时钟信号周期。总线空闲时,因各设备都是开漏输出,上拉电阻 RP 使 SDA 和 SCL 线都保持高电平。任一设备输出的低电平都使相应的总线信号线变低,也就是说各设备的 SDA 是“与”关系,SCL 也是“与”关系。

  在 I2C 总线通信的过程中,参与通信的双方互相之间所传输的信息种类归纳如下。主控器向被控器发送的信息种类有:启动信号、停止信号、7 位地址码、读/写控制位、10 位地址码、数据字节、重启动信号、应答信号、时钟脉冲。被控器向主控器发送的信息种类有:应答信号、数据字节、时钟低电平。下面对 I2C 总线通信过程中出现的几种信号状态和时序进行分析

  ①总线空闲状态

  I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

  ②启动信号

  在时钟线 SCL 保持高电平期间,数据线 SDA 上的电平被拉低(即负跳变),定义为 I2C 总线总线的启动信号,它标志着一次数据传输的开始。启动信号是一种电平跳变时序信号,而不是一个电平信号。启动信号是由主控器主动建立的,在建立该信号之前 I2C 总线必须处于空闲状态。

  ③停止信号

  在时钟线 SCL 保持高电平期间,数据线 SDA 被释放,使得 SDA 返回高电平(即正跳变),称为 I2C总线的停止信号,它标志着一次数据传输的终止。停止信号也是一种电平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,建立该信号之后,I2C 总线将返回空闲状态。

  ④数据位传送

  在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。进行数据传送时,在 SCL 呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据 0,高电平为数据 1。只有在 SCL 为低电平期间,才允许 SDA 上的电平改变状态。逻辑 0 的电平为低电压,而逻辑 1 的电平取决于器件本身的正电源电压 VDD(当使用独立电源时)。

  ⑤应答信号。

  I2C 总线上的所有数据都是以 8 位字节传送的,发送器每发送一个字节,就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是,接收器在第9个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个 NACK 信号,以通知被控发送器结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号P。

  ⑥插入等待时间

  如果被控器需要延迟下一个数据字节开始传送的时间,则可以通过把时钟线 SCL 电平拉低并且保持,使主控器进入等待状态。一旦被控器释放时钟线,数据传输就得以继续下
去,这样就使得被控器得到足够时间转移已经收到的数据字节,或者准备好即将发送的数据字节。带有CPU的被控器在对收到的地址字节做出应答之后,需要一定的时间去执行中断服务子程序,来分析或比较地址码,其间就把 SCL 线钳位在低电平上,直到处理妥当后才释放 SCL 线,进而使主控器继续后续数据字节的发送。

  ⑦重启动信号

  在主控器控制总线期间完成了一次数据通信(发送或接收)之后,如果想继续占用总线再进行一次数据通信(发送或接收),而又不释放总线,就需要利用重启动 Sr 信号时序。重启动信号 Sr 既作为前一次数据传输的结束,又作为后一次数据传输的开始。利用重启动信号的优点是,在前后两次通信之间主控器不需要释放总线,这样就不会丢失总线的控制权,即不让其他主器件节点抢占总线。

  ⑧时钟同步

  如果在某一I2C总线系统中存在两个主器件节点,分别记为主器件 1 和主器件 2,其时钟输出端分别为CLK1 和 CLK2,它们都有控制总线的能力。假设在某一期间两者相继向 SCL 线发出了波形不同的时钟脉冲序列 CLK1 和 CLK2(时钟脉冲的高、低电平宽度都是依靠各自内部专用计数器定时产生的),在总线控制权还没有裁定之前这种现象是可能出现的。鉴于 I2C 总线的“线与”特性,使得时钟线 SCL 上得到的时钟信号波形,既不像主器件 1 所期望的CLK1,也不像主器件 2 所期望的 CLK2,而是两者进行逻辑与的结果。
CLKI 和 CLK2 的合成波形作为共同的同步时钟信号,一旦总线控制权裁定给某一主器件,则总线时钟信号将会只由该主器件产生。

  ⑨总线冲突和总线仲裁

  假如在某 I2C 总线系统中存在两个主器件节点,分别记为主器件 1 和主器件 2,其数据输出端分别为DATA1 和 DATA2,它们都有控制总线的能力,这就存在着发生总线冲突(即写冲突)的可能性。假设在某一瞬间两者相继向总线发出了启动信号,鉴于:I2C 总线的“线与”特性,使得在数据线 SDA上得到的信号波形是 DATA1 和 DATA2 两者相与的结果,该结果略微超前送出低电平的主器件 1,其 DATA1的下降沿被当做 SDA 的下降沿。在总线被启动后,主器件 1 企图发送数据“101……”,主器件 2 企图发送数据据“100101……”。两个主器件在每次发出一个数据位的同时都要对自己输出端的信号电平进行抽检,只要抽检的结果与它们自己预期的电平相符,就会继续占用总线,总线控制权也就得不到裁定结果。主器件 1 的第 3 位期望发送“1”,也就是在第 3 个时钟周期内送出高电平。
  在该时钟周期的高电平期间,主器件 1 进行例行抽检时,结果检测到一个不相匹配的电平“0”,这时主器件 1 只好决定放弃总线控制杈;因此,主器件 2 就成了总线的惟一主宰者,总线控制权也就最终得出了裁定结果,从而实现了总线仲裁的功能。
  从以上总线仲裁的完成过程可以得出:仲裁过程主器件1和主器件2都不会丢失数据;各个主器件没有优先级别之分,总线控制权是随机裁定的,即使是抢先发送启动信号的主器件1最终也并没有得到控制杈。系统实际上遵循的是“低电平优先”的仲裁原则,将总线判给在数据线上先发送低电平的主器件,而其他发送高电平的主器件将失去总线控制权。

  ⑩总线封锁状态。

  在特殊情况下,如果需要禁止所有发生在 I2C 总线上的通信活动,封锁或关闭总线是一种可行途径,只要挂接于该总线上的任意一个器件将时钟线 SCL 锁定在低电平上即可。

      最后,附上用STM32的GPIO口模拟的I2C总线协议的代码。

 

  1 #define     I2C_GPIO        GPIOB
  2 #define     I2C_SCL         GPIO_Pin_10
  3 #define     I2C_SDA         GPIO_Pin_11
  4 #define     I2C_RCC         RCC_APB2Periph_GPIOB
  5 
  6 #define     SCL_H()         I2C_GPIO->BSRR  = I2C_SCL
  7 #define     SCL_L()         I2C_GPIO->BRR   = I2C_SCL
  8 #define     SDA_H()         I2C_GPIO->BSRR  = I2C_SDA
  9 #define     SDA_L()         I2C_GPIO->BRR   = I2C_SDA
 10 #define     SCL_Read()      I2C_GPIO->IDR   & I2C_SCL
 11 #define     SDA_Read()      I2C_GPIO->IDR   & I2C_SDA
 12 
 13 void I2C_GPIO_Init(void)    //I2C引脚进行初始化                                                                                                                              
 14 {
 15     GPIO_InitTypeDef  GPIO_InitStructure; 
 16     RCC_APB2PeriphClockCmd(I2C_RCC, ENABLE );
 17     GPIO_InitStructure.GPIO_Pin =  I2C_SCL;
 18     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 19     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  
 20     GPIO_Init(I2C_GPIO, &GPIO_InitStructure);
 21 
 22     GPIO_InitStructure.GPIO_Pin =  I2C_SDA;
 23     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 24     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
 25     GPIO_Init(I2C_GPIO, &GPIO_InitStructure);        
 26 }
 27 
 28 void I2C_Delay(void)    //延时函数,用空函数进行简单延时
 29 {
 30     
 31 }
 32 
 33 int I2C_Start(void)     //I2C启动信号
 34 {
 35     SDA_H();
 36     SCL_H();
 37     I2C_Delay();
 38     if(!SDA_Read)       //SDA线为低电平则总线忙,退出
 39     return 0;    
 40     SDA_L();
 41     I2C_Delay();
 42     if(SDA_Read)        //SDA线为高电平则总线出错,退出
 43     return 0;    
 44     SDA_L();
 45     I2C_Delay();
 46 
 47     return 1;    
 48 }
 49 
 50 void I2C_Stop(void)    //I2C停止信号
 51 {
 52     SCL_L();
 53     I2C_Delay();
 54     SDA_L();
 55     I2C_Delay();
 56     SCL_H();
 57     I2C_Delay();
 58     SDA_H();
 59     I2C_Delay();
 60 }
 61 
 62 void I2C_Ack(void)    //I2C应答信号
 63 {    
 64     SCL_L();
 65     I2C_Delay();
 66     SDA_L();
 67     I2C_Delay();
 68     SCL_H();
 69     I2C_Delay();
 70     SCL_L();
 71     I2C_Delay();
 72 } 
 73 
 74 void I2C_NoAck(void)    //I2C无应答信号
 75 {    
 76     SCL_L();
 77     I2C_Delay();
 78     SDA_H();
 79     I2C_Delay();
 80     SCL_H();
 81     I2C_Delay();
 82     SCL_L();
 83     I2C_Delay();
 84 } 
 85 
 86 int I2C_WaitAck(void)    //I2C等待应答信号 返回值:1有ACK,0无ACK
 87 {
 88     SCL_L();
 89     I2C_Delay();
 90     SDA_H();            
 91     I2C_Delay();
 92     SCL_H();
 93     I2C_Delay();
 94     if(SDA_Read)
 95     {
 96           SCL_L();
 97           I2C_Delay();
 98           return 0;
 99     }
100     SCL_L();
101     I2C_Delay();
102 
103     return 1;
104 }
105 
106 void I2C_SendByte(unsigned int SendByte)      //I2C发送一个字节
107 {
108     unsigned int i=8;
109     while(i--)
110     {
111         SCL_L();
112         I2C_Delay();
113         if(SendByte&0x80)
114             SDA_H();  
115         else 
116             SDA_L();   
117         SendByte<<=1;
118         I2C_Delay();
119         SCL_H();
120         I2C_Delay();
121     }
122     SCL_L();
123 } 
124 
125 unsigned int I2C_ReadByte(void)       //I2C读取一个字节
126 { 
127     unsigned int i=8;
128     unsigned int ReceiveByte=0;
129 
130     SDA_H();                
131     while(i--)
132     {
133           ReceiveByte<<=1;      
134           SCL_L();
135           I2C_Delay();
136           SCL_H();
137           I2C_delay();    
138           if(SDA_Read)
139           {
140                ReceiveByte|=0x01;
141           }
142     }
143     SCL_L();
144 
145     return ReceiveByte;
146 } 
147 
148 int I2C_Single_Write(unsigned int SlaveAddress,unsigned  int REG_Address,unsigned int REG_Data)        //I2C单字节写入
149 {
150       if(!I2C_Start())
151       return 0;
152       I2C_SendByte(SlaveAddress);  
153       if(!I2C_WaitAck())
154       {
155         I2C_Stop(); 
156         return 0;
157       }
158       I2C_SendByte(REG_Address );   //设置低起始地址      
159       I2C_WaitAck();    
160       I2C_SendByte(REG_Data);
161       I2C_WaitAck();   
162       I2C_Stop(); 
163 
164       return 1;
165 }
166 
167 int  I2C_Single_Read(unsigned int SlaveAddress,unsigned int REG_Address)    //I2C单字节读出
168 {   
169     unsigned char REG_Data;         
170     if(!I2C_Start())
171       return 0;
172     I2C_SendByte(SlaveAddress); 
173     if(!I2C_WaitAck())
174     {
175         I2C_Stop();
176         return 0;
177     }
178     I2C_SendByte((unsigned int) REG_Address);      
179     I2C_WaitAck();
180     I2C_Start();
181     I2C_SendByte(SlaveAddress+1);
182     I2C_WaitAck();
183 
184     REG_Data= I2C_ReadByte();
185     I2C_NoAck();
186     I2C_Stop();
187                                      
188     return REG_data;
189 }
190 
191 int I2C_Mult_Read(unsigned int SlaveAddress,unsigned int REG_Address,unsigned int * ptChar,unsigned int size)    //I2C多字节读出
192 {
193     unsigned int i;
194     
195     if(size < 1)
196             return 0;
197     if(!I2C_Start())
198             return 0;
199     I2C_SendByte(SlaveAddress);
200     if(!I2C_WaitAck())
201     {
202       I2C_Stop();
203       return 0;
204     }
205     I2C_SendByte(REG_Address);    
206     I2C_WaitAck();
207     
208     I2C_Start();
209     I2C_SendByte(SlaveAddress+1);
210     I2C_WaitAck();
211     
212     for(i=1;i<size; i++)
213     {
214         *ptChar++ = I2C_ReadByte();
215         I2C_Ack();
216     }
217     *ptChar++ = I2C_ReadByte();
218     I2C_NoAck();
219     I2C_Stop();
220 
221     return 1;    
222 }    
223 
224 
225 
226 
227                 

 

I2C 总线协议分析

标签:

原文地址:http://www.cnblogs.com/liang-tech/p/4796359.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!