这次利用单片机通过软件模拟I2C总线协议, 并对基于I2C协议的AT24C02 EEPROM进行读写操作, 具体说明与功能见代码注释.
AT24C02与单片机的连接电路图如下:


单片机利用P2.0模拟SDA, P2.1模拟SCL.
程序代码:

I2C总线协议
1
//用软件方法模拟I2C总线协议来读写AT24C02 EEPROM
2
//每次上电后, 继续按照上次的计数结果计数, 每半秒计数一次
3
#include <reg52.H>
4
#include <intrins.H>
5
6
#define uchar unsigned char
7
sbit SDA = P2^0;
8
sbit SCL = P2^1;
9
10
//仿真延时5.43us
11
void delay5us()
12

{
13
_nop_();
14
}
15
16
//延时10ms, 仿真约11ms
17
//实际测试中, 对于写一个字节, 只须2ms
18
void delay10Ms()
19

{
20
uchar a,b;
21
for(a=50;a>0;a--)
22
for(b=100;b>0;b--);
23
}
24
25
//延时2ms
26
void delay2Ms()
27

{
28
uchar a,b;
29
for(a=10;a>0;a--)
30
for(b=100;b>0;b--);
31
}
32
33
/**//***************************** I2C总线协议 ************************************/
34
//起始信号
35
void start()
36

{
37
SDA = 1; //启动I2C总线
38
SCL = 1;
39
delay5us(); //延时
40
SDA = 0; //起始信号
41
delay5us(); //SDA拉低时间至少4us后, 才能拉低SCL
42
SCL = 0;
43
}
44
45
//终止信号
46
void stop()
47

{
48
SDA = 0;
49
SCL = 1; //SCL拉高至少4us后, 才能拉高SDA, 产生终止信号
50
delay5us();
51
SDA = 1;
52
delay5us(); //保持SDA拉高4.7us以上
53
//终止后, 总线处于空闲状态
54
}
55
56
//发送应答
57
void ack()
58

{
59
SDA = 0;
60
SCL = 1;
61
delay5us();
62
SCL = 0;
63
SDA = 1;
64
}
65
66
//发送非应答
67
void nack()
68

{
69
SDA = 1;
70
SCL = 1;
71
delay5us();
72
SCL = 0;
73
}
74
75
//获取应答, 有应答返回0, 非应答返回1
76
bit getAck()
77

{
78
bit flag;
79
SDA = 1;
80
SCL = 1;
81
flag = SDA;
82
SCL = 0;
83
return flag;
84
}
85
86
//发送一字节数据, 有应答返回0, 非应答返回1
87
bit sendByte(uchar dat)
88

{
89
uchar i;
90
for(i = 0; i < 8; i++)
91
{
92
dat = dat << 1; //左移, 最高位将移到CY中
93
SDA = CY;
94
SCL = 1;
95
delay5us();
96
SCL = 0;
97
}
98
return getAck();
99
}
100
101
//接收一字节数据
102
uchar recvByte()
103

{
104
uchar i, tmp, dat;
105
for(i = 0; i < 8; i++)
106
{
107
SCL = 1;
108
delay5us();
109
if(SDA == 1)
110
{
111
tmp = 1;
112
}
113
else
114
tmp = 0;
115
dat = (dat << 1) | tmp;
116
SCL = 0;
117
delay5us();
118
}
119
return dat;
120
}
121
122
/**//***************************** I2C总线协议 ************************************/
123
124
125
/**//***************************** AT24C02 EEPROM的读写操作 ************************************/
126
//AT24C02 EEPROM 在TX1-B实验板上的地址是 1010 000B
127
128
//向EEPROM指定地址写一字节数据
129
void writeByte(uchar dat, uchar add)
130

{
131
start();
132
sendByte(0xa0); //找出EEPROM芯片, 写数据
133
sendByte(add); //先写地址
134
sendByte(dat); //读数据
135
stop(); //释放总线
136
delay2Ms(); //发送完写数据, stop()后, 需要延时10ms让芯片完成内部写周期
137
//实际测试中, 写一字节, 只须2ms
138
}
139
140
//连续写指定长度的字节流(Page Write)
141
void writeBytes(uchar * dats, uchar length, uchar add)
142

{
143
uchar i;
144
start();
145
sendByte(0xa0);
146
sendByte(add);
147
for(i = 0; i < length; i++)
148
{
149
sendByte(dats[i]);
150
}
151
stop();
152
delay10Ms();
153
}
154
155
//读取EEPROM指定地址的一字节数据(Random Read)
156
uchar readByte(uchar add)
157

{
158
uchar dat;
159
start();
160
sendByte(0xa0); //找出EEPROM芯片, 写数据
161
sendByte(add); //先写地址
162
start();
163
sendByte(0xa1); //读数据
164
dat = recvByte();
165
nack(); //非响应
166
stop(); //释放总线
167
return dat;
168
}
169
170
//连续读指定大小的字节数(Sequential Read)
171
void readBytes(uchar * buffer, uchar size, uchar add)
172

{
173
uchar i, count = size - 1;
174
start();
175
sendByte(0xa0);
176
sendByte(add);
177
start();
178
sendByte(0xa1);
179
for(i = 0; i < count; i++)
180
{
181
buffer[i] = recvByte();
182
ack(); //应答
183
}
184
buffer[count] = recvByte();
185
nack(); //非应答, 结束
186
stop();
187
}
188
189
/**//***************************** AT24C02 EEPROM的读写操作 ************************************/
190
sbit wela = P2^7; //数码管位选
191
sbit dula = P2^6; //数码管段选
192
193
//0-F数码管的编码(共阴极)
194
unsigned char code table[]=
{0x3f,0x06,0x5b,0x4f,0x66,
195
0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
196
void display(uchar v)
197

{
198
unsigned char count;
199
unsigned char datas[] =
{0, 0, 0};
200
datas[0] = v / 100;
201
datas[1] = v % 100 / 10;
202
datas[2] = v % 10;
203
for(count = 0; count != 3; count++)
204
{
205
//关位选, 去除对上一位的影响
206
P0 = 0xff;
207
wela = 1; //打开锁存, 给它一个下降沿量
208
wela = 0;
209
//段选
210
P0 = table[datas[count]];
211
dula = 1; //打开锁存, 给它一个下降沿量
212
dula = 0;
213
//位选
214
P0 = _crol_(0xfe, count); //选择第(count + 1) 个数码管
215
wela = 1; //打开锁存, 给它一个下降沿量
216
wela = 0;
217
delay2Ms();
218
}
219
}
220
221
uchar value, th, tl, tCount;
222
void main()
223

{
224
value = readByte(0); //向EEPROM地址0读一字节数据
225
tCount = 0;
226
EA = 1; //开中断
227
ET1 = 1; //允许T1中断
228
TMOD = 0x10; //工作方式1
229
TH1 = th = 0x4b;//(65536 - 50000/1.085) / 256; //定时50ms
230
TL1 = tl = 0xfd;//(65536 - 50000/1.085) - th * 256;
231
TR1 = 1; //T1开始计时
232
while(1)
233
{
234
if(tCount > 10) //满500ms, 加1
235
{
236
value++;
237
writeByte(value, 0); //将当前值保存在EEPROM地址0中
238
tCount = 0;
239
}
240
display(value);
241
}
242
}
243
244
void time1() interrupt 3
245

{
246
TH1 = th;
247
TL1 = tl;
248
tCount++;
249
}
经过两个星期的单片机学习, 对单片机有了一个深层次的了解. 而这次以后, 将不会连续关注单片机, 会将重心放回Asp.net开发上....