# IIC 通讯协议
# 简介
IIC 通讯主要依靠两线 : SDA (数据) 和 SCL (时钟) 分别对应管脚 P20 和 P21
使用 IIC 可以实现主机和多个从机之间的通讯 每个从机拥有唯一的地址 (地址一共八位 前七位唯一确定从机 最后一位确定主机与从机之间谁发送谁读取数据)
我看到过这样的问题 要是正好有多个从机被设置成了同一个地址 是不是就可以实现一个主机和多个从机之间的同时通讯 (答案是不能 从机之间会互相争抢与主机之间的通讯机会)
主机和从机建立联系的方式是:主机在 SDA 上发送从机的地址 所有从机收到该地址后与自己的地址进行比对 如果相一致便与主机建立联系
![1124009-20170331094740789-36160489]()
之前提到过的“线与”关系# IIC 传输规则
自己回忆的 可能有偏差
- 数据传输开始和结束必须由规定的方式
- 每传输完一字节的数据后 接收端如果发送应答信号 表示继续接收数据 未应答表示不再接收数据
- 传输数据过程中 SCL 置于高电平的时候 SDA 保持稳定 (不能改变) SCL 置于低电平的时候 SDA 可以改变
- 传输每一位数据都必须达到规定的时间以上
以上就是我暂时能回忆起来的 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; dat <<= 1; SCL = 1; delay(); SCL = 0; delay(); } SDA = 1; SCL = 1; delay(); while (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); 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(); 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😂)