注意器件的读取地址区别:
A0 A A2 直接取0
我觉得上面右图的A3是标错了,应该是A2 ,因为24C02前4位是固定的1010,A3已经是0啦!
总结一下,各个脉冲之间的时间,我们保持2μS就足够了
详细看下图;
7个底层驱动函数学完了,再看看按器件地址,芯片地址写入/读出几个数据
主机在检测到总线为“空闲状态”(即SDA、SCL 线均为高电平)时,发送一个启动信号"S”,开始一次通信的开始主机接着发送一个命令字节。该字节由 7 位的外围器件地址和 1 位读写控制位R/W组成(此时RW=0为写)相对应的从机收到命令字节后向主机回馈应答信号ACK(ACK=0)主机收到从机的应答信号后开始发送第一个字节的数据从机收到数据后返回一个应答信号 ACK主机收到应答信号后再发送下一个数据字节当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信。空闲的意思就是说SDA和SCL都是高电平。应答信号ACK(ACK=0)表示从芯片写入成功。接着看主机读数据:主机发送启动信号后,接着发送命令字节(其中R/W=1)对应的从机收到地址字节后,返回一个应答信号并向主机发送数据主机收到数据后向从机反馈一个应答信号从机收到应答信号后再向主机发送下一个数据当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ACK=1的非应答信号后便停止发送主机发送非应答信号后,再发送一个停止信号,释放总线结束通信。
到此为止,所有理论都准备好了,可以进入程序编写了
先搞地产的7个驱动函数:
这里我尽量注释详细,为节约时间,我写完直接复制过来:
#ifndef __IIC_H
#define __IIC_H
#include "COMM/stc.h" //调用头文件
#include "COMM/stc32_stc8_usb.h"
#defineSLAVE_24C02 0XA0
//------------------------------引脚定义------------------------------
#defineSCL P25
#defineSDA P24
//------------------------------变量声明------------------------------
//------------------------------函数声明------------------------------
void IIC_DELAY(); //@22.1184MHz 2US
void IIC_START(void); //IIC开始,启动函数
void IIC_STOP(void); //IIC结束
void IIC_SENDACK(void); //发送ACK
void IIC_SENDNACK(void); //发送NACK
void IIC_WAITACK(void); //等到从机ACK
void IIC_SENDBYTE(u8 dat); //发送一个字节
u8 IIC_READBYTE(void); //读取一个字节
void IIC_Write_NByte( u8 slave,u8 addr,u8 *p,u8 number ); //iic写入连续的几个字节
void IIC_Read_NByte( u8 slave,u8 addr,u8 *p,u8 number ); //iic读取连续的几个字节
#endif
#include "iic.h"
u8ack = 0; //保留从机ACK,ack=0表示正确,ack=1表示错误
void IIC_DELAY() //@22.1184MHz 2US
{
unsigned long i;
_nop_();
_nop_();
_nop_();
i = 9UL;
while (i) i--;
}
void IIC_START(void) //IIC开始,启动函数
{
SCL = 1;
SDA = 1;
IIC_DELAY(); //先保证总线是空闲高电平
SDA = 0; //数据线拉低
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 0; //时钟总线拉低
IIC_DELAY(); //保持2us(实际是3.75us)
}
void IIC_STOP(void) //主机最后后发送停止信号“P”,IIC结束
{
SCL = 0; //一开始是低电平
SDA = 0; //一开始是低电平
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 1; //时钟总线拉高
IIC_DELAY(); //保持2us(实际是3.75us)
SDA = 1; //数据线拉高
IIC_DELAY(); //保持2us(实际是3.75us)
}
void IIC_SENDACK(void) //发送ACK
{
SDA = 0; //数据线拉低
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 1; //时钟总线拉高
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 0; //时钟总线拉低
IIC_DELAY(); //保持2us(实际是3.75us),之后SDA变化自由
}
void IIC_SENDNACK(void) //发送NACK
{
SDA = 1; //数据线拉高
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 1; //时钟总线拉高
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 0; //时钟总线拉低
IIC_DELAY(); //保持2us(实际是3.75us),之后SDA变化自由
}
void IIC_WAITACK(void) //等待从机应答ACK,结果由ask保存,为0就正确,为1就错误
{
SDA = 1; //主机先把数据线拉高,如果从机正确写入,从机会把SDA拉低发回给主机,否则不会。
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 1; //时钟总线拉高
IIC_DELAY(); //保持2us(实际是3.75us)
ack = SDA; //SDA的电平给ack保留
IIC_DELAY(); //保持2us(实际是3.75us)
SCL = 0; //时钟总线拉低
IIC_DELAY(); //保持2us(实际是3.75us),之后SDA变化自由
}
void IIC_SENDBYTE(u8 dat) //主机向芯片24C02发送一个字节
{
u8 i=8;
do
{
if( dat& 0x80 )//把要发送的数据和0x1000 0000相与,取出最高位
SDA = 1;
else
SDA = 0;
IIC_DELAY(); //保持2us(实际是3.75us)
dat<<=1;
SCL = 1; //时钟总线拉高,从此句到第三句是发送功能
IIC_DELAY(); //保持2us(实际是3.75us),送出数据
SCL = 0; //时钟总线拉低,从前第三句到此句是一个下降沿是发送功能
IIC_DELAY(); //保持2us(实际是3.75us)
}
while(--i); //循环8次,送出8位刚好1个字节
}
u8 IIC_READBYTE(void) //主机从芯片24C02读取一个字节
{
u8 i=8,dat=0;
SDA = 1; //数据线拉高
do
{
SCL = 1; //时钟总线拉高
IIC_DELAY(); //保持2us(实际是3.75us),稳定后直接读SDA
dat<<=1;
if( SDA ) //此位是1
dat |= 1; //把1或(按位加)进去dat,0的话,直接移位就ok
SCL = 0; //时钟总线拉低,给芯片24C02改写SDA上数据的时间
IIC_DELAY(); //保持2us(实际是3.75us),在此延时内芯片24C02写数据到SDA
}
while(--i); //循环8次,读出8位刚好1个字节放在dat里
return dat; //返还从芯片24C02读出的dat
}
//--------------------------------功能函数--------------------------------
void IIC_Write_NByte( u8 slave,u8 addr,u8 *p,u8 number ) //向iic写入连续的几个字节
{
IIC_START(); //发送一个启动信号"S”,开始一次通信的开始
IIC_SENDBYTE(slave); //主机发送命令字。该字由 7 位的外围器件地址和 1 位读写控制位R/W组成(此时RW=0为写)
//slave 由 7 位的外围器件地址和 1 位读写控制位R/W组成(此时RW=0为写)
IIC_WAITACK();//等待从机应答ACK,结果由ask保存,为0就正确,为1就错误
if( !ack ) //如果芯片24C02回答正确
{
IIC_SENDBYTE(addr);//要写入芯片24C02的数据从片内addr开始写
IIC_WAITACK(); //等待从机应答ACK,结果由ask保存,为0就正确,为1就错误
if( !ack ) //如果芯片24C02回答正确
{
do //把number个数据写入芯片24C02
{
IIC_SENDBYTE(*p);//p是具有number个成员的数组
p++;
IIC_WAITACK();
if( ack )
break;
}
while(--number); //把p中的number个成员依次写入芯片24C02的片内addr开始的位置
}
}
IIC_STOP(); //完成后主机发送结束标志,释放数据+时钟总线(都为1)
}
// 发送开始命令 发送器件地址(写) 发送数据地址 发送开始命令 发送器件地址(读)读数据
//头尾2个器件地址(第1个最后一位写0,第2个最后一位写1),数据片内地址只发1次
void IIC_Read_NByte( u8 slave,u8 addr,u8 *p,u8 number ) //iic读取连续的几个字节
{
IIC_START(); //发送一个启动信号"S”,开始一次通信的开始
IIC_SENDBYTE(slave); //主机发送命令字。该字由 7 位的外围器件地址和 1 位读写控制位R/W组成(此时RW=1为读)
//slave 由 7 位的外围器件地址和 1 位读写控制位R/W组成(此时RW=1为读)
IIC_WAITACK(); //等待从机应答ACK,结果由ask保存,为0就正确,为1就错误
if( !ack ) //如果芯片24C02回答正确
{
IIC_SENDBYTE(addr); //要写入芯片24C02的读取数据片内地址addr
IIC_WAITACK(); //等待从机应答ACK,结果由ask保存,为0就正确,为1就错误
if( !ack ) //如果芯片24C02回答正确
{
IIC_START();
IIC_SENDBYTE(slave+0x01); //主器件发送器件地址(读/写选择位为"1")
IIC_WAITACK(); //芯片24C02应答ACK,并随时钟送出数据
if( !ack ) //如果芯片24C02回答正确
{
do
{
*p = IIC_READBYTE(); //第1圈P 第2圈 P 第3圈P第4圈P第5圈P第6圈P.......
p++;
if( number!=1 ) //不是最后一位
IIC_SENDACK(); //等待从机应答ACK,结果由ask保存,为0就正确,为1就错误
}
while(--number);
IIC_SENDNACK();
}
}
}
IIC_STOP();//主机发送停止信号“P”
}
冲哥的voidIIC_Read_NByte( u8 slave,u8 addr,u8 *p,u8 number ) //iic读取连续的几个字节实际是随机读:
2.随机读随机读需先写一个目标字地址,一旦EEPROM接收器件地址和字地址并应答了ACK,主器件就产生一个重复的起始条件。然后,主器件发送器件地址(读/写选择位为"1"),EEPROM应答ACK,并随时钟送出数据。主器件无需应答"0",但需发送停止条件(见图13)。 主机发送启动信号→发送7位地址+1位W写命令(形成寻址写命令字)→主机等待芯片应答“A” →主机收到应答“A”后发送芯片内要读出的地址→主机等待芯片应答“A” →主机收到应答“A”后发送启动信号(可以内含有条件)→发送7位地址+1位R读命令(形成寻址读命令字)→主机等待芯片应答“A” →主机收到应答“A” →24C04应答ACK后,并随时钟送出数据。主器件无需应答"0"……….→主机最后后发送停止信号“P”
基本一致,就是多了一个应答"0",其实这里主器件无需应答"0"……….直接→主机最后后发送停止信号“P”。不过习惯了应答,看上去顺眼点(老实讲,20年前至今许多学习这里也是应答“0”的)
还有一个:
这个延时2μs
void IIC_DELAY() //@22.1184MHz 2US
{
unsigned long i;
_nop_();
_nop_();
_nop_();
i = 9UL;
while (i) i--;
}
我们上机看看:
Debug 运行一下:
0.00050329-0.00049954=0.00000375s=0.00375ms=3.75us
0.00050704-0.00050329=0.00000375s=0.00375ms=3.75us
怎么感觉误差好大?不过时间长点就求稳,我也检测了时钟主频,是22.1184啊
不过不用管它啦!