51单片机 | 基于I2C总线的秒表模拟应用
————————————————————————————————————————————
参考地址:
http://blog.csdn.net/junyeer/article/details/46480863
http://blog.csdn.net/bob_fly1984/article/details/22690381
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
硬件结构:
- SDA数据线
- SCL时间线
-
上拉电阻
p.s. 当总线空闲状态时,两根线被上拉电阻拉高,保持高电平。连接总线上的任一器件输出的低电平都将使总线的信号变低
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
总线特征:
- I2C总线上的每一个设备都可以作为主设备或从设备
-
每个设备会对应一个唯一的地址,主从设备之间通过地址来确定与哪个器件通信
p.s. 地址分为7位或10位,此处只介绍7位
- 通常情况下,把CPU带有I2C总线接口的模块作为主设备,其他挂在总线上的作为从设备
- 主从设备之间以字节为单位进行双向数据传输
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
工作方式:
-
主从工作方式:主器件启动数据的发送,产生时钟信号,发出停止信号
p.s. 是没有I2C总线硬件接口的单片机采用软件模拟I2C总线常用的工作方式
- 多主工作方式:需要总线竞争或仲裁
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
通信时序(主从工作方式):
-
空闲状态:
- SCL和SDA保持高电平状态
-
忙状态:
p.s. SDA线必须在SCL时钟的高电平周期才能保持稳定,SDA数据线的高或低电平状态只能在SCL低电平时才能改变
-
起始条件 S:
- SCL为高电平,SDA下降沿1→0时,主器件产生起始信号
- 起始信号产生后,总线处于忙状态,其他I2C器件无法访问总线
-
停止条件 P:
-
SCL为高电平,SDA上升沿0→1时,主器件产生停止信号
p.s. 非应答信号规定:当主机为接收设备,主机对最后一个字节不应答,以向发送设备表示数据传送结束
- 停止条件产生后,主从设备释放总线,总线再次处于空闲状态
-
-
传输过程:
-
主设备在传输有效数据之前先指定从设备的地址,设备地址为7位时,再加一个最低位表示数据传输的方向,0表示主设备→从设备写数据,1表示主设备读数据。
7位地址码:其中DA3~DA0(硬件出厂时固有的地址编码)及A2~A0(用户设定)为从机地址
- 主设备在SCL线上产生每个时钟脉冲的过程中将在SDA线上传输一个bit
-
当一个字节按数据位从高到低顺序传输完之后,接收设备将SDA拉为低电平,表示数据传输正确,回传给主设备一个应答位。一个字节传输完毕。
p.s. 并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备回传一个非应答位
在一次传输中可以传输多个字节,图中只画出首字节
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
当主机向从机发送1字节数据时
p.s. 实际上主从机的SDA信号是在同一根线上的,分开画有助于理解各自的行为
-
当主机从从机接收1字节数据时
————————————————————————————————————————————
AT24C02
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
主要型号:
ATMEL公司生产的AT24C系列EEPROM中具有I2C总线接口。
这类芯片可以解决掉电而造成的数据丢失的问题,可以保存数据100年,擦写100w次以上。
芯片地址固定部分为1010
型号 |
存储容量 |
AT24C01 |
128*8 |
AT24C02 |
256*8 |
AT24C04 |
512*8 |
AT24C08 |
1024*4 |
AT24C16 |
2048*8 |
芯片特性:略
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
引脚描述:
- A0/A1/A2是3条地址线,用于确定芯片的硬件地址,在上图系统中均接地,选择000
- 第4脚和第8脚为正负电源
- 第5脚为SDA串行数据输入/输出,与单片机P3.5相连
- 第6脚为SCL串行时钟输入线,与单片机P3.6相连
- SDA和SCL都需要与正电源间接一个5.1k欧上拉电阻
- 第7脚写保护功能接地
- 24C02中带有片内地址寄存器,每写入或读出一个数据字节后,该地址寄存器自动+1,实现对下一个存储单元的读写。为降低总的写入时间,一次操作可以写入多达8个字节的数据。
————————————————————————————————————————————
AT24C02应用实例
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
设计要求:
- 采用定时中断方式,设计一个0~59s变化的秒表,将每次显示在数码管上的时间存入AT24C02中
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
设计思路:
- 通过定时器50ms触发中断,每次触发中断时中断计数,到达1s时flag标识为1
- 在死循环中始终显示当前秒数
- 每次秒数变化时写入flag清零并写入AT24C02中
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
硬件清单及连线情况:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
实现代码:
1 #include <reg51.h> 2 typedef unsigned char uchar; 3 typedef unsigned int uint; 4 sbit scl = P3 ^ 0; 5 sbit sda = P3 ^ 1; 6 uchar second = 0; 7 uchar count = 0; 8 bit flag = 0; 9 uchar code table[] = 10 { 11 0xFC, 0x60, 0xDA, 0xF2, 0x66, 0xB6, 0xBE, 0xE0, 0xFE, 0xF6, 0xEE, 0x3E, 0x9C, 0x7A, 0x9E, 0x8E 12 }; 13 void Init(); //初始化总线 14 void Start(); //开始 15 void Stop(); //停止 16 void Respons(); //响应位 17 uchar ReadByte(); //读数据 18 uchar ReadAddress(uchar address); //读地址 19 void WriteByte(uchar dat); //写字节 20 void WriteAddress(uchar address, uchar dat); //写地址 21 void Display(); //显示数码管 22 void Delay1ms(uchar m); //延时1ms 23 void Delay(); //延时2个机器周期 24 void main() 25 { 26 /* 初始化总线 */ 27 Init(); 28 /* 初始化LED */ 29 P1 = 0xff; 30 /* 设置定时器 */ 31 TMOD = 0x01; 32 TH0 = (65536 - 50000) / 256; 33 TL0 = (65536 - 50000) % 256; 34 /* 中断初始化 */ 35 EA = 1; 36 ET0 = 1; 37 /* 定时器开始 */ 38 TR0 = 1; 39 /* 显示数码管并写入 */ 40 while(1) 41 { 42 if (flag == 1) 43 { 44 flag = 0; 45 WriteAddress(2, second); 46 } 47 Display(); 48 } 49 50 } 51 void Init() 52 { 53 //空间状态时SCL与SDA均保持高电平 54 sda = 1; 55 Delay(); 56 scl = 1; 57 Delay(); 58 } 59 void Start() 60 { 61 //SCL高电平情况下,SDA下降沿启动 62 sda = 1; 63 Delay(); 64 scl = 1; 65 Delay(); 66 sda = 0; 67 Delay(); 68 } 69 void Stop() 70 { 71 //SCL高电平情况下,SDA上升沿停止 72 sda = 0; 73 Delay(); 74 scl = 1; 75 Delay(); 76 sda = 1; 77 Delay(); 78 } 79 void Respons() 80 { 81 //在SCL位为高电平时产生响应信号,数据传输正确时产生应答且SDA拉低 82 uchar i; 83 scl = 1; //此处scl拉高后在while循环中降低,参照时序图 84 Delay(); 85 while ((sda == 1) && (i < 255)) 86 { 87 //SDA拉低时跳出循环,表示数据传输正确,产生应答.如果在一段时间内未收到应答,则不再等待跳出循环 88 i++; 89 scl = 0; 90 Delay(); 91 } 92 } 93 void WriteAddress(uchar address, uchar dat) 94 { 95 Start(); //开始 96 WriteByte(0xA0); //写入 1010 0000 高4位为固定值,5-7位为A0 A1 A2,最低位0为写操作标识 97 Respons(); 98 WriteByte(address); //写入 0000 0010 99 Respons(); 100 WriteByte(dat); //写入数据(秒数) 101 Respons(); 102 Stop(); //停止 103 } 104 void WriteByte(uchar dat) 105 { 106 uchar i, temp; 107 temp = dat; //将dat读入temp 108 for (i = 0; i < 8; ++i) 109 { 110 temp = temp << 1; //temp左移,最高位移入CY 111 scl = 0; //SCL复位,等待最高位存入SDA后置位 112 Delay(); 113 sda = CY; //CY在头文件中已规定,通过SDA发送bit 114 Delay(); 115 scl = 1; //SCL置位,延迟2个机器周期后复位 116 Delay(); 117 } 118 scl = 0; //为最后一个SCL高电平复位 119 Delay(); 120 sda = 1; //一个字节数据发送完毕后置位准备响应 121 Delay(); 122 } 123 uchar ReadAddress(uchar address) 124 { 125 uchar byte; 126 Start(); 127 WriteByte(0xA0); 128 Respons(); 129 WriteByte(address); 130 Respons(); 131 Start(); 132 WriteByte(0xA1); //写入地址 1010 0001,读写标志位1为读 133 Respons(); 134 byte = ReadByte(); 135 Stop(); 136 return byte; 137 } 138 uchar ReadByte() 139 { 140 uchar i, k; 141 scl = 0; 142 Delay(); 143 sda = 1; 144 Delay(); 145 for (i = 0; i < 8; ++i) 146 { 147 scl = 1; 148 Delay(); 149 k = (k << 1) | sda; 150 scl = 0; 151 Delay(); 152 } 153 return k; 154 } 155 void Display() 156 { 157 P2 = 0xfe; 158 P1 = table[second % 10]; 159 Delay1ms(1); 160 P2 = 0xfd; 161 // P1 = table[second / 10 %10]; 162 P1 = table[second / 10]; 163 Delay1ms(1); 164 // P2 = 0xfb; 165 // P1 = table[second / 100]; 166 // Delay1ms(5); 167 } 168 void Delay() 169 { 170 ;; 171 } 172 void Delay1ms(uchar m) //使用1ms时二极管闪烁,此处将y=110改成y=50 173 { 174 uchar x, y; 175 for (x = m; x > 0; ++x) 176 for (y = 50; y > 0; --y) 177 ; 178 } 179 void time()interrupt 1 180 { 181 TH0 = (65536 - 50000) / 256; 182 TL0 = (65536 - 50000) % 256; 183 count++; 184 if (count == 20) 185 { 186 count = 0; 187 flag = 1; 188 second++; 189 if (second == 60) 190 second = 0; 191 } 192 }