借鉴于:蓝桥杯嵌入式快速通关篇,IIC通讯及EEPROM_穿上我的格子衫的博客-CSDN博客
IIC原理超详细讲解---值得一看_Z小旋的博客-CSDN博客_iic
总线空闲:SDA = 1,SCL = 1;
启动信号:SCL = 1,SDA 1 -> 0;
停止信号:SCL = 1,SDA 0 -> 1;
数据传输:SCL为1时,SDA必须保持稳定,即为0或1(停止时相反,所以在停止前要改变SDA都要先把SCL置为0);
SCL为0时,才允许改变SDA状态;
SCL在上升沿时写入数据,在下降沿时读出数据;
应答信号:为0时,是有效应答,为1时,是无效应答;
传输到最后一个字节后,要发送一个无效应答,再发送停止信号;
不管什么信号,其线的变化都要严格与对应时序吻合(包括起始,变化过程,结果)
总线封锁:将SCL锁定为0即可;
多次数据通信,采用复启动信号,即写一个停止信号,再接上一个启动信号;
单片机为主机,24C02为从机;
SCL时钟由主机控制;
初始化时仅需要设置端口为最基本的推挽模式(因为仅需要gpio输出最基本的高低电平)
写时应答信号从机给出;
读时由主机给出;
24C02器件地址:前四位默认为1010,后四位的前三位为A0,A1,A2,均已接地,即为0,
最后一位为读写保护位,写时为0(0xA0),读时为1(0xA1).
Start: IIC开始信号,表示开始传输。
DEVICE_ADDRESS:: 从设备地址,就是7位从机地址
R/W: W(write)为写,R(read)为读
ACK: 应答信号
WORD_ADDRESS : 从机中对应的寄存器地址 比方说访问 OLED中的 某个寄存器
DATA: 发送的数据
STOP: 停止信号。结束IIC
首先要知道的是读写分为总体部分与发送接收的单字节部分
SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上的“高”或“低”状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位ACK, 此时才认为一个字节真正的被传输完成 ,如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据。
在功能函数结束后不要将线的电平恢复(不要越界处理别的函数要做的事)
//void SDA_Output( uint16_t val ) val为1或0
//{
// if ( val ) {
// GPIO_SetBits(I2C_PORT,SDA_Pin);
// } else {
// GPIO_ResetBits(I2C_PORT,SDA_Pin);
// }
每次设置线上的电平高低后都要进行延时
IIC写数据(要注意的是读写模式不同,注意切换SDA线的GPIO模式):
//uint8_t SDA_Input()
//{
// return GPIO_ReadInputDataBit( I2C_PORT, SDA_Pin);
//}
从这两个图中我们可以看出在结束时SDA与SCL都为低电平恢复为高电平,这就是结束条件!
多数从设备的地址为7位或者10位,一般都用七位。
八位设备地址=7位从机地址+读/写地址,另加一个ack应答地址
再给地址添加一个方向位位用来表示接下来数据传输的方向,
0表示主设备向从设备(write)写数据,
1表示主设备向从设备(read)读数据
IIC的每一帧数据由9bit组成,其中数据有两种
如果是发送数据,则包含 8bit数据+1bit ACK,
如果是设备地址数据,则8bit包含7bit设备地址 1bit方向
在起始信号后必须传送一个从机的地址(7位) 1~7位为7位接收器件地址,第8位为读写位,用“0”表示主机发送数据(W),“1”表示主机接收数据 (R), 第9位为ACK应答位,紧接着的为第一个数据字节,然后是一位应答位,后面继续第2个数据字节。
IIC发送一个字节数据(8位):
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
//IIC_SCL=0;
//在SCL上升沿时准备好数据,进行传送数据时,拉高拉低SDA,因为传输一个字节,一个SCL脉冲里传输一个位。
//数据传输过程中,数据传输保持稳定(在SCL高电平期间,SDA一直保持稳定,没有跳变)
//只有当SCL被拉低后,SDA才能被改变
//总结:在SCL为高电平期间,发送数据,发送8次数据,数据为1,SDA被拉高,数据为0,SDA被拉低。
//传输期间保持传输稳定,所以数据线仅可以在时钟SCL为低电平时改变。
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT(); //设置为输出模式(PP模式)
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=txd&0x80; //获取最高位
//获取数据的最高位,然后数据左移一位
//如果某位为1,则SDA为1,否则相反
if(txd&0x80) //如果最高位(第八位)为一,则置SDA为高电平
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
或者:
//IIC_SDA=txd&0x80; //获取最高位
//获取数据的最高位,然后右移7位,假设为 1000 0000 右移7位为 0000 0001
// 假设为 0000 0000 右移7位为 0000 0000
//如果某位为1,则SDA为1,否则相反
IIC_SDA=((txd&0x80)>>7);
txd<<=1;
或者这么写:
//void I2CSendByte(unsigned char cSendByte)
//{
// unsigned char i = 8;
// while (i--)
// {
// SCL_Output(0);delay1(500);
// SDA_Output(cSendByte & 0x80); delay1(500);
// cSendByte += cSendByte; //二进制自加相当于左移一位
// delay1(500);
// SCL_Output(1);delay1(500);
// }
// SCL_Output(0);delay1(500);
//}
IIC读取一个字节数据:
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN(); //SDA设置为输入 (其实就是IPD模式)
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1; //左移一位
if(READ_SDA)receive++; //使最低位为一
delay_us(1);
}
if (!ack)
IIC_NAck(); //发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
具体表现为:
void i2c_write(u8 add ,u8 reg ,u8 data)
{ I2CStart();
I2CSendByte(add);
I2CWaitAck();
I2CSendByte(reg);
I2CWaitAck();
I2CSendByte(data);
I2CWaitAck();
I2CStop();
}
函数void i2c_write(u8 add ,u8 reg ,u8 data);
是iic通讯的写函数,第一个 add是设备的地址,如0xa0是EEPROM的地址,0x38 是扩展板上的三轴加速度计的通讯地址。
第二个形参是寄存器的地址,需要注意的是AT24C02的地址是从0x00~0xff,如果超出该地址值会出现错误。地址和写入或读取的数据的类型都是无符号字符型,其他类型的数据有可能出现错误。就是说,但个地址存入读出的值不会超过255,即存取的的数据范围是0 ~255
主机要从从机读数据时
- 主机首先产生START信号
- 然后紧跟着发送一个从机地址,注意此时该地址的第8位为0,表明是向从机写命令,
- 这时候主机等待从机的应答信号(ACK)
- 当主机收到应答信号时,发送要访问的地址,继续等待从机的应答信号,
- 当主机收到应答信号后,主机要改变通信模式(主机将由发送变为接收,从机将由接收变为发送)所以主机重新发送一个开始start信号,然后紧跟着发送一个从机地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据,
- 这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答信号,表示不在接收数据
- 主机进而产生停止信号,结束传送过程。
u8 i2c_read(u8 add ,u8 reg){
u8 data ;
I2CStart();
I2CSendByte(add);
I2CWaitAck();
I2CSendByte(reg);
I2CWaitAck();
I2CStart();
I2CSendByte(add+1);
I2CWaitAck();
data =I2CReceiveByte();
I2CWaitAck();
I2CStop();
return data;
}
24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。
A0,A1,A2:硬件地址引脚
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线
可
以看出对于不同大小的24Cxx,具有不同的从器件地址。由于24C02为2k容量,也就是说只需要参考图中第一行的内容:
芯片的寻址:
AT24C设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。A2~A0为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。
常见的EEPROM如24C02的大小为2Kbit(有的也称2KB)
2KB中,B表示单位bit,K表示1024。
单片机编程中常用的数据类型为unsigned char(u8)的变量的大小为1字节。
1字节=8bit
因此
2KB = 2*1024/8 = 256字节
也就是说,单片机编程中,一片2KB的EEPROM最多存储256(2的8次方,也就是八位)个u8(unsigned char)类型的数据。
AT24C设备地址,前四位固定为1010,而储存不受此限制,可以从0x00开始储存
也就是说如果是
写24C02的时候,从器件地址为10100000(0xA0);
读24C02的时候,从器件地址为10100001(0xA1)。
片内地址寻址:
芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
对应的修改 A2A1A0 三位数据即可
向AT24C02中写数据
操作时序:
- MCU先发送一个开始信号(START)启动总线
- 接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)
- 等待应答信号(ACK)
- 发送数据的存储地址。24C02一共有256个字节的存储空间,地址从0x00~0xFF,想把数据存储>在哪个位置,此刻写的就是哪个地址。
- 发送要存储的数据第一字节、第二字节、…注意在写数据的过程中,E2PROM每个字节都会>回应一个“应答位0”,老告诉我们写E2PROM数据成功,如果没有回应答位,说明写入不成功。
- 发送结束信号(STOP)停止总线
注意:
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!
所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以
从AT24C02中读数据
读当前地址的数据
2、读随机地址的数据
- MCU先发送一个开始信号(START)启动总线
- 接着跟上首字节,发送器件写操作地址(DEVICE ADDRESS)+写数据(0xA0)
注意:这里写操作是为了要把所要读的数据的存储地址先写进去,告诉E2PROM要读取哪个地址的数据。- 发送要读取内存的地址(WORD ADDRESS),通知E2PROM读取要哪个地址的信息。
- 重新发送开始信号(START)
- 发送设备读操作地址(DEVICE ADDRESS)对E2PROM进行读操作 (0xA1)这里发送读操作地址是为了让从设备知道主设备要读了,为什么不在第二部就发的原因是要先联系上从设备。
- E2PROM会自动向主机发送数据,主机读取从器件发回的数据,在读一个字节后,MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据
- 如果不想读了,告诉E2PROM不想要数据了,就发送一个“非应答位NAK(1)”。发送结束信号(STOP)停止总线
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答
**每发送一个字节(8个bit)**在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答,
//主机产生(发送ack)应答信号ACK
//1.先拉低SCL,再拉低SDA
//2.拉高SCL
//3.拉低SCL## 标题
void I2C_Ack(void)
{
IIC_SCL=0; //先拉低SCL,使得SDA数据可以发生改变
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
标准写法:
unsigned char I2CWaitAck(void)
{
unsigned short cErrTime = 5;
SDA_Input_Mode(); //设为IPU输入模式
delay1(500);
SCL_Output(1);delay1(500);
while(SDA_Input()) //如果一直读端口为1(GPIO_ReadInputDataBit( I2C_PORT, SDA_Pin)),表示没有接受到应答,返回失败
{
cErrTime--;
delay1(500);
if (0 == cErrTime)
{
SDA_Output_Mode(); //实际上是不必要的
I2CStop();
return FAILURE;
}
}
SDA_Output_Mode(); //实际上是不必要的
SCL_Output(0);delay1(500);
return SUCCESS;
}
//主机不产生应答信号,也就是NACK
//1.先拉低SCL,再拉高SDA
//2.拉高SCL
//3.拉低SCL
void I2C_NAck(void)
{
IIC_SCL=0; //先拉低SCL,使得SDA数据可以发生改变
IIC_SDA=1; //拉高SDA,不产生应答信号
delay_us(2);
IIC_SCL=1;
delay_us(5);
IIC_SCL=0;
}
3、连续读数据
E2PROM支持连续写操作,操作和单个字节类似,先发送设备写操作地址(DEVICE ADDRESS),然后发送内存起始地址(WORD ADDRESS),MCU会回应一个应答信号(ACK)后,E2PROM会继续传输下一个地址的数据,MCU不断回应应答信号可以不断读取内存的数据。E2PROM的地址指针会自动递增,数据会依次保存在内存中。不应答发送结束信号后终止传输。
IIC分为软件IIC和硬件IIC
软件IIC:软件IIC通信指的是用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。
硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,硬件(固件)I2C是直接调用内部寄存器进行配置。
硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
读取的操作结束后,都建议延迟几毫秒,实测如果不延迟数据极有可能会出错,延迟操作可以加到函数末尾,也可在函数调用后。
调用函数之前,要先使用i2c.c文件中已有的函数i2c_init()来初始化iic要使用到的时钟,引脚等。
读写函数并不能直接存取超过范围的数,如果存入的数不在0到255之间,肯定会出错,地址也一样,要注意范围。
在读取iic的值时,需要再进行一次I2CStart(),不然读取的值也可能会出错!