首页 > 技术文章 > IIC学习

dongry 2018-12-21 10:57 原文

1 概述:

  IIC是用两条双向的线,一条SDA(serial data line),一条SCL(serial clock).

  SCL:上升沿将数据输入到每个EEPROM器件中,下降沿驱动EEPROM器件输出数据(边沿触发)

  SDA:双向数据线,为OD门,与其它任意数量的OD与OC门成“线与”关系

2 输出级

每一个IIC总线器件内部的SDA、SCL引脚电路结构都是一样的,引脚的输出驱动与输入缓存连在一起。其中输出为漏极开路的场效应管,输入缓存为一只高输入阻抗的同向器,这种电路具有两个特点

  1)由于SDA、SCL为漏极开路结构,因此它们必须接有上拉电阻,阻值大小为1K8(1800o)、4K7(4700o)、10K,但1K8时性能最好;当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线与关系。

  2)引脚在输出信号的同时还将引脚上的电平进行检测,检测是否与刚才输出一致,为“时钟同步”和“总线仲裁”提供了硬件条件

3 主设备与从设备

  系统中的所有外围器件都具有一个7位的“从器件专用地址码”,其中高4位为器件类型,由生产厂家制定,低3位为器件引脚定义地址,由使用者定义。主控器件通过地址码建立多机通信的机制,因此IIC总线省去了外围器件的片选线,这样无论总线上挂接多少个器件,其系统仍然为简约的二线结构。终端挂载在总线上,有主端和从端之分,主端必须是带有CPU逻辑模块,在同一总线上同一时刻使能有一个主端,可以有多个从端,从端的数量受地址空间和总线的最大电容400pF的限制。 

  主端主要用来驱动SCL line;

  从设备对主设备产生响应;

  二者都可以传输数据,但是从设备不能发起传输,且传输是受到主设备控制的。

4 速率

  普通模式:100kHz

  快速模式:400kHz

  高速模式:3.4MHz

5协议

5.1 空闲状态

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

5.2 起始信号与停止信号

  起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号

  停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号

5.3 ACK

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

如下图逻辑分析仪的采样结果:释放总线后,如果没有应答信号,SDA应该一直维持在高电平,但是如图中蓝色虚线部分所示,它被拉低为低电平,证明收到了应答信号。

  总结:1)接收器在SCL的上升沿到来之前的低电平期间拉低SDA;

        2)应答信号一直保持到SCL的下降沿结束;

5.4 数据的有效性

  IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

  个人理解:虽然只要求在高电平期间保持稳定,但是要有一个提前量,也就是数据在SCL的上升沿到来之前就准备好,因为数据在SCL的上升沿打入到器件(EEPROM)中的

5.5 数据的传送:

  在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发.

6 工作过程

  总线上的所有通信都是由主控器引发的。在一次通信中,主控器与被控器总是在扮演着两种不同的角色。

6.1 主设备向从设备发送数据

  主设备发送起始位,这会通知总线上的所有设备传输开始了,接下来主机发送设备地址,与这一地址匹配的slave将继续这一传输过程,而其他slave将会忽略接下来的传输并等待下一次传输开始。主设备寻址到从设备后,发送它所要读取或写入的从设备的内部寄存器地址;之后,发送数据。数据发送完毕后,发送停止位。

      写入过程如下:

     1 发送起始位

     2 发送从设备的地址和读/写选择位;释放总线,等到EEPROM拉低总线进行应答;如果EEPROM接收成功,则进行应答;若没有握手成功或者发送的数据错误时EEPROM不产生应答,此时要求重发或者终止

     3 发送想要写入的内部寄存器地址,EEPROM对其发出应答

     4 发送数据

     5 发送停止位

     6 EEPROM收到停止信号后,进入到一个内部的写入周期,大概需要10ms,此期间任何操作都不会被EEPROM响应:(因此以这种方式的两次写入之间要插入一个延时,否则会导致失败)

 

  详细

  说明:主控器通过发送地址码与对应的被控器建立了通信关系,而挂接在总线上的其他被控器虽然同时也收到了地址码,但因为与其自身的地址不相符合,因此提前退出与主控器的通信

6.2 主控器读取数据的过程

  在从slave读取数据前,必须先要告诉它那个内部寄存器是你想要读取的,因此必须先对其进行写入(dummy write)

   读取过程

     1 发送起始位

     2 发送slave地址 + write bit set

     3 发送内部寄存器地址

     4 重新发送起始位,即restart

     5 重新发送slave地址 + write bit set

     6 读取数据

        主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。

     7 发送停止位

 

  详细

6.3 页写

 

6.3.1 页写数据过程

 

  不同的器件页写的字节数也不同,2402是按8字节/页写,2404-08-16是按16字节/页写。而2432-2464则是按32字节页写;

  页写初始化和字节写相同,只是主器件不会在第一个数据后发送停止条件,而是EEPROM的ACK以后,接着发送(7个)、(15个)、(31个)数据。EEPROM在收到数据后每个都应答0,最后仍需要主器件发送停止条件。终止页写条件。

  在接收或发送每个数据后,字地址的(低3位)、(低4位)、(低5位)内部自动加1,高位地址不变,维持当前页。当内部产生的字地址达到该页边界地址时,随后的数据将写入该页的页首,这是会将先前的字节覆盖掉,要注意。

6.3.2 读页数据过程

 

  主器件接收到一个数据后,应答ACK。只要EEPROM接收到ACK,将自动增加字地址并继续随时钟发送后面的数据。若达到存储器的地址末尾,地址自动回转到0.仍可继续顺序读取数据。主器件不应答0,而发送停止条件,即可结束顺序读操作。

7 实例程序

  1 void SCL_Init() 
  2 {
  3     GPIO_InitTypeDef GPIO_InitStructure;
  4     
  5     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  6     
  7     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
  8     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  9     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
 10     GPIO_Init(GPIOB,&GPIO_InitStructure);
 11 }
 12 
 13 void SDA_Out_Init()
 14 {
 15     GPIO_InitTypeDef GPIO_InitStructure;
 16     
 17     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
 18     
 19     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
 20     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
 21     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
 22     GPIO_Init(GPIOB,&GPIO_InitStructure);
 23 }
 24 
 25 void SDA_In_Init()
 26 {
 27     GPIO_InitTypeDef GPIO_InitStructure;
 28     
 29     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
 30     
 31     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
 32     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
 33     GPIO_Init(GPIOB,&GPIO_InitStructure);
 34 }
 35 
 36 void IIC_Init() //初始化总线
 37 {
 38     SCL_OUT=1;
 39     SDA_OUT=1;
 40     delay_us(2);
 41 }
 42 
 43 void IIC_Start() //开始发送 SCL为高,SDA由高变低
 44 {
 45     SDA_Out_Init();
 46     SCL_OUT=1;
 47     SDA_OUT=1;
 48     
 49     delay_us(5);
 50     SDA_OUT=0;
 51     delay_us(6);
 52     
 53     SCL_OUT=0;
 54 }
 55 
 56 void IIC_Stop() //停止  SCL为高,SDA由低变高
 57 {
 58     SDA_Out_Init();
 59     SCL_OUT=0;
 60     SDA_OUT=0;
 61     SCL_OUT=1;
 62     delay_us(6);
 63     
 64     SDA_OUT=1;
 65     delay_us(6);
 66 }
 67 
 68 u8 IIC_Wait_Ack() //等待应答
 69 {
 70     u8 time=0;
 71     
 72     SDA_OUT=1;
 73     delay_us(1);
 74     SDA_In_Init();
 75     SCL_OUT=1;
 76     delay_us(1);
 77     while(SDA_IN)  //#define SDA_IN PBin(11)
 78     {
 79         time++;
 80         if(time>250)  //如果超过250还无应答,IIC停止
 81         {
 82             IIC_Stop();
 83             return 1;
 84         }
 85     }
 86     SCL_OUT=0;
 87     return 0;
 88 }
 89 
 90 void IIC_Ack()  //应答信号
 91 {
 92     SCL_OUT=0;
 93     SDA_Out_Init();
 94     SDA_OUT=0;
 95     delay_us(2);
 96     
 97     SCL_OUT=1;
 98     delay_us(5);
 99     SCL_OUT=0;
100 }
101 
102 void IIC_NAck()  //非应答
103 {
104     SCL_OUT=0;
105     SDA_Out_Init();
106     SDA_OUT=1;
107     delay_us(2);
108     
109     SCL_OUT=1;
110     delay_us(5);
111     SCL_OUT=0;
112 }
113 
114 void IIC_Write_Byte(u8 data) //写一个字节数据
115 {
116     int i,receive=0;
117     SDA_Out_Init();
118     
119     receive=data;
120     SCL_OUT=0;
121     for(i=0;i<8;i++)  //相当于SCL低电平期间准备好要写入的数据,SCL跳变为高电平进行发送
122     {
123         if(receive&0x80)
124             SDA_OUT=1;
125         else
126             SDA_OUT=0;
127         receive=receive<<1;
128         delay_us(2);
129         
130         SCL_OUT=1;
131         delay_us(2);
132         SCL_OUT=0;
133         delay_us(2);
134     }
135 }
136 
137 u8 IIC_Read_Byte(u8 ack) //读一个字节
138 {
139     int i,txdata=0;
140     SDA_In_Init();
141 
142     SCL_OUT=0; //
143     delay_us(2);
144     for(i=0;i<8;i++)
145     {
146         SCL_OUT=1;  //高电平保持数据的稳定;来一个上升沿,数据就将要读取的一位放在数据线SDA上,所以接下来就得左移,才能读取下一位数据
147         delay_us(2);
148         txdata=txdata<<1;
149         if(SDA_IN)
150             txdata++; 
151         delay_us(1);    
152         
153         SCL_OUT=0;
154         delay_us(2);
155     }
156     if(!ack)
157         IIC_NAck();
158     else
159         IIC_Ack();
160     return txdata;
161 }
162 
163 void write_addr(u8 address,u8 data)
164 {
165     IIC_Start();
166     IIC_Write_Byte(0xA0); //发送从器件芯片的地址,由于IIC可以挂接好多芯片,所以需要先找到该芯片
167     IIC_Ack();
168     
169     IIC_Write_Byte(address);//发送芯片的字节地址,需要确定将数据发送到芯片的某个位置,芯片大小有2K
170     IIC_Ack();
171     
172     IIC_Write_Byte(data); //将数据写入到芯片内
173     IIC_Ack();
174   
175     IIC_Stop();
176     delay_ms(10); //数据写完之后需等10ms时间才能再次发送起始信号,这个时间叫写周期
177 }
178 
179 u8 read_addr(u8 address)
180 {
181     u8 temp;
182     
183     IIC_Start();
184     IIC_Write_Byte(0xA0); //器件地址
185     IIC_Ack();
186     IIC_Write_Byte(address);//想要读取的器件中的地址0到255
187     IIC_Ack();
188     
189     IIC_Start();
190     IIC_Write_Byte(0xA1); //1表示读取
191     IIC_Ack();
192     
193     temp=IIC_Read_Byte(0);
194     IIC_Ack();
195   
196     IIC_Stop();
197     
198     return temp;
199 }
200 
201 void write_len(u16 address,u32 data,u8 len)
202 {
203     u8 i;
204     for(i=0;i<len;i++)
205     write_addr(address+i,(data<<(8*i)&0xff));
206 }
207 
208 u32 read_len(u16 address,u8 len)
209 {
210     u8 j;
211     u32 temp;
212     for(j=0;j<len;j++)
213     {
214         temp=temp<<8;
215         temp=temp+read_addr(address+len-j-1);
216     }
217     return temp;
218 }
219 
220 /**************************************
221 将数据写入指定的地址里,num要写入数据个数
222 *************************************/
223 void write(u8 addr,u8 *buffer,u8 num)
224 {
225     while(num--)
226     {
227         write_addr(addr,*buffer); //含有了stop
228         addr++;
229         buffer++;
230     }
231 }
232 
233 /**************************************
234 在指定的地址读取数据
235 *******************************************/
236 u8 read(u8 addr,u8 *buffer,u8 num)
237 {
238     while(num)
239     {
240         *buffer++=read_addr(addr++);    
241         num--;
242     }
243     return *buffer;
244 } 
245 
246 void write_twobyte(u8 address)
247 {
248     u8 i;
249     u8 tx[3]={11,12,13};
250     IIC_Start();
251     IIC_Write_Byte(0xA0); //发送的是从器件芯片地址,由于IIC可以挂接好多芯片,所以需要先找到该芯片
252     IIC_Ack();
253     
254     IIC_Write_Byte(address);//发送芯片的字节地址,需要确定将数据发送到芯片的某个位置,芯片大小2K
255     IIC_Ack();
256     
257     for(i=0;i<3;i++)
258     {
259         IIC_Write_Byte(tx[i]); //将数据写入到芯片内
260       IIC_Ack();
261     }
262     
263     IIC_Stop();
264     delay_ms(10);
265 }

 

推荐阅读