说明
本人使用的是清翔的51单片机开发板,如果型号相同最方便,但是如果型号不同也可以参考,因为芯片都是一样的,只是外设不同而已,使用时只需要对照自己的开发板原理图稍微修改下引脚即可。
本次笔记对应视频教程的第35,,3,37 IIC总线EEPORM(理论+实践)
如果笔记之中有任何错误,请在评论区指出,谢谢
由于51单片机没有自带硬件IIC,所以本此使用模拟IIC,需要完全了解IIC的通信协议。本节使用板载AT24C02来使用IIC协议。在查阅资料的过程中看到了这篇文章,很不错嵌入式硬件入门——EEPROM(AT24C02+I2C协议)http://t.csdn.cn/S9sCM
网上找的AT24C02中文数据手册可以参考一下https://www.semiee.com/file/Source10/IDCHIP-AT24C02.pdf
目录
一、常用的串行总线协议
- UART:异步通信方式(一条数据输入线,一条数据输出线)
- 1-Wire:即单总线(只有1条线)
- IIC:同步串行2线方式进行通信(一条时钟线一条数据线)
- SPI:同步串行3线方式进行通信(一条时钟,线一条数据输入线,一条数据输出线)
IIC可以多主机多从机。多从机需要通过寻址来找到对应的器件
二、IIC总线相关知识
2.1 概念
2.2 数据传输过程
多主机用的很少,初学者可以忽略。
2.3 传输过程规定
2.3.1 起始/终止信号
在SCL=1期间,SDA从高电平变成低电平,表示起始信号
在SCL=1期间,SDA从低电平变成高电平,表示终止信号
2.3.2 数据的传输结构
注意,发送数据时先发送最高位
每发送1个字节,需要跟一个响应位(ACK)。ACK就是主机释放SDA(让SDA=1),让数据接收方控制SDA产生一个低电平,则表示有响应
数据传输的完整过程
注:时序图中ACK并没有释放总线的过程,实际上是需要释放总线的,图里面省略了
写数据
读数据
不发送停止信号,直接再次发送起始信号
先发送从机地址(7位),再跟一个方向位,0表示要向从机写数据,1表示要从从机读取数据。后面跟要传输的数据,每传输1个字节要跟一个响应位,传输完成由主机产生停止信号。如果主机扔想继续在总线上通讯,可以不产生终止信号,而是直接再次产生一个起始信号,并对从机进行寻址,再传输数据。
在IIC传输数据的过程中,在传输数据时,SDA只能在SCL = 0时改变,在SCL = 1时SDA必须保持稳定。
2.3.3 寻址的详细过程
还有广播信号,初学不需要太多了解,不多介绍。
再给出另一个IIC器件数据手册中关于IIC的部分
Co(继续位):用于指示是否有多个字节需要连续传输。如果Co位为1,则表示还有更多字节需要传输;如果为0,则表示当前是最后一个字节。
D/C#(数据/命令选择位):用于指示发送或接收的数据是数据还是命令。如果D/C#位为1,则表示发送的是数据;如果为0,则表示发送的是命令。
ACK(确认):在I2C通信中,从设备在成功接收到一个字节的数据后会生成一个确认信号。ACK用于表示数据已被正确接收。
SA0(从设备地址位):用于指示从设备的地址。SA0位是从设备地址的最低有效位之一,其余位通过主设备发送来选择特定的从设备进行通信。
R/W#(读/写选择位):用于指示主设备对从设备进行读操作还是写操作。如果R/W#位为1,则表示进行读操作;如果为0,则表示进行写操作。
S(起始条件)/P(停止条件):起始条件表示I2C通信即将开始,由主设备发出;停止条件表示I2C通信结束,由主设备发出。起始条件和停止条件用于定义I2C通信的开始和结束点。
线与:所有器件都是高,SDA才为高,又任何一个器件为低,那么SDA就为低,相当于逻辑与的关系。
IIC介绍差不多到这里就结束了
三、原理图
器件地址说明
根据原理图可见,A2,A1,A0全部都接地,是0,那么板载的AT24C02地址就是b1010 000,所以在起始信号之后,要发送A0(表示写数据)或A1(表示读数据)进行寻址
四、编程-信号部分
4.1 起始信号
void I2Cstart()
{
SDA = 1;
SCL = 1;
delay5us();
SDA = 0;
delay5us();
}
4.2 终止信号
void I2Cstop()
{
SCL = 0;
SDA = 0;
SCL = 1;
delay5us();
SDA = 1;
delay5us();
}
4.3 主机读应答
bit ReadACK()
{
SCL = 0;
SDA = 1; //释放SDA
SCL = 1; //给一个脉冲让从机发送ACK
delay5us();
if (SDA) //SDA=1,表示无应答
{
SCL = 0;
return (1);
}
else
{
SCL = 0;
return (0);
}
}
SDA = 1表示非应答,SDA = 0表示应答
4.4 主机发送应答
void SendACK(bit i)
{
SCL = 0;
SDA = i;
SCL = 1;
delay5us();
SCL = 0;
SDA = 1;
}
SDA = 1表示非应答,SDA = 0表示应答
五、编程
5.1 创建工程
复制上一份工程文件夹,修改名称为“12.IIC通信AT24C02”,进入项目文件夹,打开工程文件,删除main.c函数的内容。
5.2 main.c
#include <intrins.h>
uchar num;
void main()
{
timer0init(0x01, 0xed,0xff);
EA = 0;
AT24C02Write(0x02, 0xed); //在0x02地址写入0xed(237)
delay(1); //刚写入数据,需要时间处理才能读取,否则读取不到
num = AT24C02Read(0x02);
EA = 1;
while(1);
}
void delay5us()
{
_nop_();
}
void I2Cstart() //产生起始信号
{
SCL = 0;
SDA = 1;
SCL = 1;
delay5us();
SDA = 0;
delay5us();
SCL = 0;
}
void I2Cstop() //产生终止信号
{
SCL = 0;
SDA = 0;
SCL = 1;
delay5us();
SDA = 1;
delay5us();
}
bit ReadACK() //读从机应答
{
SCL = 0;
SDA = 1; //释放SDA
SCL = 1; //给一个脉冲让从机发送ACK
delay5us();
if (SDA) //SDA=1,表示无应答
{
SCL = 0;
return (1);
}
else //SDA=0表示应答
{
SCL = 0;
return (0);
}
}
void SendACK(bit i) //写应答
{
SDA = i;
SCL = 1;
delay5us();
SCL = 0;
SDA = 1; //释放总线
}
void I2CSendByte(uchar DAT) //发送1个字节
{
uchar i;
for (i=0;i<8;i++)
{
SCL = 0;
SDA = DAT&0x80;
SCL = 1;
DAT <<= 1;
}
ACKflag = ReadACK(); //看从机是否应答
}
uchar I2CReadByte() //读取1个字节
{
uchar i;
uchar DAT;
for (i=0;i<8;i++)
{
//不能把DAT放在DAT |= SDA;的后面,若放在它后面,那么当接收最后一位后,DAT还会左移,最高位就丢了
DAT <<= 1;
SCL = 0; //SCL=0才允许SDA变化,给从机时间让它调整SDA状态
SCL = 1;
DAT |= SDA;
}
return (DAT);
}
void AT24C02Write(uchar address, uchar DAT) //在指定地址写入指定数据
{
I2Cstart();
I2CSendByte(0xA0); //AT24C02的地址,A0表示写入,A1表示读取
I2CSendByte(address); //发送要把数据写入到AT24C02的哪里
I2CSendByte(DAT); //要写入的数据是什么
I2Cstop(); //发送停止信号
}
uchar AT24C02Read(uchar address) //在指定地址读取1个字节数据
{
uchar DAT;
I2Cstart();
I2CSendByte(0xA0); //AT24C02的地址,A0表示写入,A1表示读取
I2CSendByte(address); //发送要把读AT24C02哪里的数据
I2Cstart();
I2CSendByte(0xA1); //AT24C02的地址,A0表示写入,A1表示读取
DAT = I2CReadByte();
SendACK(1); //发送非应答
I2Cstop(); //发送停止信号
return DAT;
}
开始关闭了中断是为了避免中断影响到AT24C02的数据写入和读取。
定时器0中断函数(初始化函数直接复制之前的就行)
//定时器0中断
void timer0() interrupt 1
{
SEG_DIS3(num);
TH0 = 0xed;
TL0 = 0xff;
}
注意,EEPROM每个区域是有写入次数寿命的,大概10万到100万之间的写入次数,读取不限。因此要特别注意不要在循环中使用写入,否则循环几分钟EEPROM就被写坏了。
5.3 main.h
void delay5us();
void I2Cstart(); //产生起始信号
void I2Cstop(); //产生终止信号
bit ReadACK(); //读应答
void SendACK(bit i); //发送应答
void I2CSendByte(uchar DAT); //发送1个字节
uchar I2CReadByte(); //读取1个字节
void AT24C02Write(uchar address, uchar DAT); //在指定地址写入指定数据
uchar AT24C02Read(uchar address); //在指定地址读取1个字节数据
bit ACKflag;
写入函数声明和变量声明
5.4 现象
当运行一次程序之后,把mian函数中写入数据的部分删除,数码管依然能正常显示,说明数据已经被永久存储在芯片中了
void main()
{
timer0init(0x01, 0xed,0xff);
EA = 0;
//AT24C02Write(0x02, 0xed); //在0x02地址写入0xed(237)
//delay(1);
num = AT24C02Read(0x02);
EA = 1;
while(1);
}
可以利用此功能存储一些重要的系统数据文件,下次启动时直接调用上次运行结束后的数据。
本次笔记对应视频教程的第35,,3,37 IIC总线EEPORM(理论+实践),到此结束。我觉得对于IIC的讲解已经比较明晰了,启动,结束,应答,数据传输过程中的规定等都有讲到,初学者要再认真看一下数据手册,熟悉数据传输的过程。
下次笔记将对应视频教程的第38,39,40集 数模转换与模数转换(理论+实践)
如果笔记之中有任何错误,请在评论区指出,谢谢