原理
IIC串行总线,只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL
如下图所示,IIC总线上可以挂多个器件,而每个器件都有唯一的地址,这样可以标识通信目标。数据的通信的方式采用主从方式,主机负责主动联系从机,而从机则被动回应数据
在多主机系统中,可能同时有几个主机企图启动总线传送数据。为了避免混乱,I2C总线要通过总线仲裁,以决定由哪一台主机控制总线。
在80C51单片机应用系统的串行总线扩展中,我们经常遇到的是以80C51单片机为主机,其它接口器件为从机的单主机情况
总线“线与关系”:I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,
即SDA = SDA1 & SDA2 & SDA3 & ...
, SCL=SCL1 & SCL2 & SCL3 & ...
起始信号和终止信号
SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;
SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。
数据位有效性
SCL为高电平期间,数据线上的数据必须保持稳定
只有SCL信号为低电平期间,SDA状态才允许变化。
IIC总线的传送与应答
每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节最后面都必须跟随一位应答位(即一帧共有9位)
主机通过从机发出的应答位来判断从机是否成功接收数据:
- 从机正忙于其他事情,发出应答1,表示没有收到
- 当从机空闲可以接收该字节数据时,从机会发出应答0
当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。
数据帧格式
在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用"0"表示主机发送数据(T),"1"表示主机接收数据(R)。
每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
软件模拟IIC总线传送数据
主机可以采用不带IIC总线接口的单片机,如80C51、STC89C52等单片机,利用软件实现IIC总线的数据传送,即软件与硬件结合的信号模拟。
为保证数据的可靠性,I2C总线的数据传送有严格的时序要求。I2C总线的起始信号、终止信号、发送“0”及发送“1”的模拟时序 :
模拟的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void I2cStart(){ SCL = 1; SDA = 1; delay5us(); SDA = 0; delay5us(); }
void I2cStop(){ SCL = 0; SDA = 0; SCL = 1; delay5us(); SDA = 1; delay5us(); }
|
第3、4个图表示先将SCL拉高5us,然后读取并返回SDA的值,读取后将SCL拉低,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| bit ReadACK(){ SCL = 1; delay5us(); if(SDA){ SCL = 0; return(1); } else{ SCL = 0; return(0); } }
|
发送数据i,在SCL为低时,将SDA拉高或拉低(根据发送的数据确定,如果发送1,则拉高,反之拉低);然后将SCL拉高,保持5us,再将SCL拉低,这样就发送了一位(bit)数据,最后释放总线
1 2 3 4 5 6 7 8 9 10 11 12
| void SendACK(bit i){ SCL = 0; if(i) SDA = 1; else SDA = 0; SCL = 1; delay5us(); SCL = 0; SDA = 1; }
|
串行EEPROM的扩展
Ateml公司的AT24C系列:
写入过程
**获取地址码:**AT24C系列E2PROM芯片地址的固定部分为1010,而当A2、A1、A0引脚接高、低电平后就得到确定的3位编码。形成的7位编码即为该器件的地址码。例如在开发板中A2, A1, A0都接地,则这7位码为 1010000
**写操作:**单片机进行写操作时,首先发送该器件的7位地址码和写方向位“0”(共8位,即一个字节),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为相应,单片机收到应答后就可以传送数据了。
传送数据:
-
这个过程单片机首先发送一个字节被写入器件的存储区首地址,收到存储器器件的应答后,单片机就逐个发送各数据字节,但每发送一个字节后都要等待应答。
-
AT24C系列器件片内地址在接收到每一个数据字节地址后自动加1(表示相对之前写入的位置往后移一位)
-
当要写入的数据传送完后,单片机应发出终止信号以结束写入操作。写入n个字节的数据格式如下:
说明:S为起始信号,然后是7位地址码+ 0
(0表示写操作),然后写入第一个数据存放位置的首地址,后面的写入的数据自动往后移动一位,写入1Byte数据后,器件发送应答A确认已写入,最后如果写入完成后发送终止信号P。
-
注意:在芯片的“一次装载字节数”(不同芯片字节数不同)限度内,只需输入首地址。装载字节数超过芯片的“一次装载字节数”时,数据地址将“上卷”,前面的数据将被覆盖。
读出过程
单片机先发送该器件的7位地址码和写方向位“0”(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应。
然后,再发一个字节的要读出器件的存储区的首地址,收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位(“1”),收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。
当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作。
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
#include <reg52.h> #include <intrins.h>
#define uint unsigned int #define uchar unsigned char #define At24c02ADDR 0XA0 #define I2cRead 1 #define I2cWrite 0
sbit DU = P2^6; sbit WE = P2^7; sbit SCL = P2^1; sbit SDA = P2^0; uchar num; bit AckFlag;
uchar code SMGduan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
uchar code SMGwei[] = {0xfe, 0xfd, 0xfb};
void delay(uint z){ uint x,y; for(x = z; x > 0; x--) for(y = 114; y > 0 ; y--); }
void display(uchar i){ static uchar wei; P0 = 0XFF; WE = 1; P0 = SMGwei[wei]; WE = 0; switch(wei){ case 0: DU = 1; P0 = SMGduan[i / 100]; DU = 0; break; case 1: DU = 1; P0 = SMGduan[i % 100 / 10]; DU = 0; break; case 2: DU = 1; P0 = SMGduan[i % 10]; DU = 0; break; } wei++; if(wei == 3) wei = 0; }
void timer0Init(){ EA = 1; ET0 = 1; TR0 = 1; TMOD |= 0X01; TH0 = 0xED; TL0 = 0xFF; }
void delay5us(){ _nop_(); }
void I2cStart(){
SCL = 1; SDA = 1; delay5us(); SDA = 0; delay5us(); }
void I2cStop(){
SCL = 0; SDA = 0; SCL = 1; delay5us(); SDA = 1; delay5us(); }
bit ReadACK(){ SCL = 0; SCL = 1; delay5us(); if(SDA){ SCL = 0; return(1); } else{ SCL = 0; return(0); } }
void SendACK(bit i){ SCL = 0; if(i) SDA = 1; else SDA = 0; SCL = 1; delay5us(); SCL = 0; SDA = 1; }
void I2cSendByte(uchar DAT){ uchar i; for(i=0; i<8; i++){ SCL = 0; if(DAT & 0x80) SDA = 1; else SDA = 0; SCL = 1; DAT <<= 1; } SCL = 0; SDA = 1; }
void At24c02Write(uchar ADDR, DAT){ I2cStart(); I2cSendByte(At24c02ADDR + I2cWrite); if(ReadACK()) AckFlag = 1; else AckFlag = 0; I2cSendByte(ADDR); if(ReadACK()) AckFlag = 1; else AckFlag = 0; I2cSendByte(DAT); if(ReadACK()) AckFlag = 1; else AckFlag = 0; I2cStop(); }
uchar I2cReadByte(){ uchar i, DAT; for(i=0; i<8; i++){ DAT <<= 1; SCL = 0; SCL = 1; if(SDA) DAT |= 0X01; } return(DAT); }
uchar At24c02Read(uchar ADDR){ uchar DAT; I2cStart(); I2cSendByte(At24c02ADDR + I2cWrite); if(ReadACK()) AckFlag = 1; else AckFlag = 0; I2cSendByte(ADDR); ReadACK(); I2cStart(); I2cSendByte(At24c02ADDR + I2cRead); if(ReadACK()) AckFlag = 1; else AckFlag = 0; DAT = I2cReadByte(); SendACK(1); I2cStop(); return(DAT); }
void main() timer0Init(); EA = 0; At24c02Write(3, 188); delay(1); num = At24c02Read(3); if(AckFlag) P1 = 0; else P1 = 0XFF; EA = 1; while(1); }
void timer0() interrupt 1 { TH0 = 0xED; TL0 = 0xFF; display(num); }
|