# IIC 通讯协议

# 简介

IIC 通讯主要依靠两线 : SDA (数据) 和 SCL (时钟) 分别对应管脚 P20 和 P21

使用 IIC 可以实现主机和多个从机之间的通讯 每个从机拥有唯一的地址 (地址一共八位 前七位唯一确定从机 最后一位确定主机与从机之间谁发送谁读取数据)

我看到过这样的问题 要是正好有多个从机被设置成了同一个地址 是不是就可以实现一个主机和多个从机之间的同时通讯 (答案是不能 从机之间会互相争抢与主机之间的通讯机会)

主机和从机建立联系的方式是:主机在 SDA 上发送从机的地址 所有从机收到该地址后与自己的地址进行比对 如果相一致便与主机建立联系

1124009-20170331094740789-36160489

之前提到过的“线与”关系

# IIC 传输规则

自己回忆的 可能有偏差

  1. 数据传输开始和结束必须由规定的方式
  2. 每传输完一字节的数据后 接收端如果发送应答信号 表示继续接收数据 未应答表示不再接收数据
  3. 传输数据过程中 SCL 置于高电平的时候 SDA 保持稳定 (不能改变) SCL 置于低电平的时候 SDA 可以改变
  4. 传输每一位数据都必须达到规定的时间以上

以上就是我暂时能回忆起来的 IIC 规则 想起来再添加 (不过好像也只有这些)

# 开始信号

IIC 开始信号:当 SCL 处于高电平 (1) 时 (时长大于 4.7us) SDA 由高电平变为低电平 并保持至少 4us 则将其视为开始信号

微信截图_20210305174149

原来在这里还有一个疑问:之前说的当 SCL 处于 1 时 SDA 不能变化 那么这里又怎么又可以变为 0 呢

那个规则在传输过程中使用 这种特殊情况便于区分开始和结束这种特殊信号

# 代码如下

1
2
3
4
5
6
7
8
9
10
11
void iicStart()
{
SDA = 1;
delay();
SCL = 1;
delay();
SDA = 0;
delay();
SCl = 0;
delay();
}

# 发送数据

IIC 只能一位一位的发送数据 所以如果我们要发一个字节的数据 就要循环 8 次

# 代码如下

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
void iicSendData(char dat)
{
int i,index = 0;

for (i = 0; i < 8; i++)
{
SDA = dat >> 7; //因为之前已经将SCL置零 所以可以直接更改SDA的值
dat <<= 1;
SCL = 1; //让SDA的值稳定下来
delay(); //IIC要求必须延时4.7us以上以确定数据
SCL = 0; //便于传输下一位数据
delay();
}

SDA = 1; //拉高SDA以检测从机是否应答
SCL = 1; //稳定SDA
delay();

while (SDA) //非应答 如果应答的话从机会把SDA的值拉低
{
index++;

if (index > 200)
{
SCL = 0;
delay();
return;
}
}

SCL = 0;
delay();
return;
}

# 接收数据

接收数据和发送一样 只不过把发送数据的过程反过来了

# 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
char iicReadData()
{
char dat;
int i;

for (i = 0; i < 8; i++)
{
SCL = 1;
delay();
dat <<= 1;
dat |= SDA;
delay();
SCL = 0;
delay();
}

SDA = 0; /*停止信号*/
delay(); /* */
SCL = 1; /* */
delay(); /* */
SDA = 1; /* */
delay(); /* */
}

# 停止信号

IIC 停止信号:当 SCL 处于低电平 (1) 时 (时长大于 4us) SDA 由低电平变为高电平 并保持至少 4.7us 则将其视为停止信号

QQ截图20210305180827

# 代码如下

1
2
3
4
5
6
7
8
9
void iicStop()
{
SDA = 0;
delay();
SCL = 1;
delay();
SDA = 1;
delay();
}

# 写入数据

单片机的 EEPROM 是 at24c02 芯片

写入需要几个要素:从机地址 写入的地址 写入的数据

# 代码如下

1
2
3
4
5
6
7
8
void at24c02Write(char addr,char dat)
{
iicStart(); //开始传输
iicSendData(0xa0 + 0); //从机地址 + 0(主机写从机读)
iicSendData(addr); //选择写入的地址
iicSendData(dat); //写入数据
iicStop(); //停止传输
}

# 读取数据

读取数据看的几点是 谁发送 谁接受 数据在哪个地址

# 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
char at24c02Read(char addr)
{
char dat;
iicStart();
iicSendData(0xa0 + 0);
iicSendData(addr);
iicStart();
iicSendData(0xa0 + 1);
dat = iicReadData();

return dat;
}

# 整个工程

代码如下

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
#include<reg52.h>
#include<intrins.h>
#define uchar unsigned char

sbit dula = P2^6;
sbit wela = P2^7;
sbit SDA = P2^0;
sbit SCL = P2^1;

char code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
char code location[] = {0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};

void dis_delay(int z)
{
int x,y;

for (x = 114; x; x--)
{
for (y = z; y; y--);
}
}

void delay()
{
_nop_();
_nop_();
}

void iicStart()
{
SDA = 1;
delay();
SCL = 1;
delay();
SDA = 0;
delay();
SCL = 0;
delay();
}

void iicStop()
{
SDA = 0;
delay();
SCL = 1;
delay();
SDA = 1;
delay();
}

void iicSendData(uchar dat)
{
int i,index;

for (i = 0; i < 8; i++)
{
SDA = dat>> 7;
dat <<= 1;
SCL = 1;
delay();
SCL = 0;
delay();
}

SDA = 1;
delay();
SCL = 1;
delay();

while (SDA)
{
index++;

if (index > 200)
{
SCL = 0;
delay();
//P1 = 0;
return ;
}
}

SCL = 0;
delay();
return ;
}

uchar iicReadData()
{
int i;
uchar dat;

for (i = 0; i < 8; i++)
{
SCL = 1;
delay();
dat <<= 1;
dat |= SDA;
delay();
SCL = 0;
delay();
}

SDA = 1;
delay();
SCL = 1;
delay();
SCL = 0;
delay();

return dat;
}

void at24c02Write(uchar addr,uchar dat)
{
iicStart();
iicSendData(0x90 + 0);
iicSendData(addr);
iicSendData(dat);
iicStop();
}

uchar at24c02Read(uchar addr)
{
uchar dat;
iicStart();
iicSendData(0x90 + 0);
iicSendData(addr);
iicStart();
iicSendData(0x90 + 1);
dat = iicReadData();
iicStop();

return dat;
}

void display(int num)
{
int i,j;
int temp,a[8];

if (num > 0)
{
for (i = 0, temp = num; temp; i++, temp /= 10)
{
a[i] = temp % 10;
}

for (j = 0; j < i; j++)
{
P0 = 0xff;
wela = 1;
P0 = location[j];
wela = 0;

P0 = 0;
dula = 1;
P0 = table[a[j]];
dis_delay(0);
dula = 0;
}
}

else if (num < 0)
{
for (i = 0, temp = num; temp; i++, temp /= 10)
{
a[i] = temp % 10;
}

for (j = 0; j <= i; j++)
{
P0 = 0xff;
wela = 1;
P0 = location[j];
wela = 0;

if (j == i)
{
P0 = 0;
dula = 1;
P0 = 0x40;
dis_delay(0);
dula = 0;
}
P0 = 0;
dula = 1;
P0 = table[a[j]];
dis_delay(0);
dula = 0;
}
}

else
{
for (j = 0; j < i; j++)
{
P0 = 0xff;
wela = 1;
P0 = location[0];
wela = 0;

P0 = 0;
dula = 1;
P0 = table[0];
dis_delay(0);
dula = 0;
}
}
}

void main()
{
uchar num;

at24c02Write(1,2);
dis_delay(5);
num = at24c02Read(0);

while (1)
{
display(num);
}
}

# 这里面的大坑

IIC 里面有个大坑 我被带到坑里去整整一天才反应过来

当时所有的变量都用的是 char 类型 写出来的时候没有什么问题 因为简单调试也只是试试 100 一下的数字 可是当我想利用定时器自动增加写到 EEPROM 里面去的时候 到 128 就出现问题了 一开始以为是自己程序的问题 就一直检查显示函数 IIC 协议有没有写对 但是一直没有发现问题 (因为自己根据原来的程序改了一下 只向 EEPROM 里面读取数据 但是始终都是可以读出来的) 之后 我想到了可以借助 LED 来帮助调试  可以把走 IIC 读出来的数用 LED 表示出来 再观察 LED 的亮灭写出对应二进制码 就可以知道 IIC 的数字了(串口通讯来调试也可以 但是当时懒得再写那些东西了 相比之下 LED 更加容易写)

但是又出现了一个问题 8 颗 LED 最多能显示的数字就是 128 (准确的说是 127 因为 128 的时候灯一颗都不亮) 不过想到可以通过减去已知的数字来显示 (比如 IIC 走的 129 LED 显示不出来 那我可以让 LED 显示 IIC 减去 2 之后的值 如果正确的话那也就没问题了) 不过也正是因为这个想法 弄得我越走越偏

用 LED 调试的时候 不论发送什么值 做了相应的减法之后都可以正确的显示 这就很奇怪了 所以我又把焦点投向了显示函数 知道最后我才发现 是 char 变量取值范围的问题 (char 类型变量范围为 - 128 至 + 127) 将所有的类型转变为 unsigned char 之后 什么问题都没有了 : )

(怪不得调试的时候把 128 显示成 - 1😂)

更新于 阅读次数