
恩,这个问题困扰了两天,终于找到原因并且顺利解决了!原因是:单片机接收应答信号(向AT24C02写数据时)和发送应答信号(AT24C02向单片机发送数据)的代码是不一样的!
void ack()//acknowledge (单片机接收应答信号)
{
uchar i;
scl=1;
delay();
while((sda == 1)&&(i > 250))
i++;
scl=0;//注意此处,此处表示第九个时钟结束,不能不写
delay();
}
void ack2(bit response)//(单片机发送的应答信号,response=1时是非应答,response=0时是应答!)
{
sda=response;
scl=1;
delay();
scl=0;
delay();
}
//然后严格遵循datasheet的 *** 作时序
//读数据的代码是这样的()
void read_add(uchar address)
{
start();
write_byte(0xa0);
ack();
write_byte(address);
ack();//dummy write
start();
write_byte(0xa1);
ack();
P1=read_byte();
ack2(0);//这个应答才正确
// ack();这个应答不正确
P1=read_byte();
ack2(1);
delay();
stop();
}
//这里注意,我用郭天祥老师的TX-1C,给P1口赋值让lcd灯来检验我的应答信号是否正确
#ifndef __AT24C02_H__
#define __AT24C02_H__
#include<stc89h> //包含单片机寄存器的头文件
#include<intrinsh> //包含NOP空指令的头文件
#define uchar unsigned char
#define uint unsigned int
/宏定义器件地址/ //AT24C器件ID 1 0 1 0 A2 A1 A0 RW
#define AT24_ID0 0XA0 //AT24C器件ID 1 0 1 0 0 0 0 RW
#define AT24_ID1 0XA2 //AT24C器件ID 1 0 1 0 0 0 1 RW
#define AT24_ID2 0XA4 //AT24C器件ID 1 0 1 0 0 1 0 RW
#define AT24_ID3 0XA6 //AT24C器件ID 1 0 1 0 0 1 1 RW
#define AT24_ID4 0XA8 //AT24C器件ID 1 0 1 0 1 0 0 RW
#define AT24_ID5 0XAA //AT24C器件ID 1 0 1 0 1 0 1 RW
#define AT24_ID6 0XAE //AT24C器件ID 1 0 1 0 1 1 1 RW
/其他参数/
#define AT24_INQ 255 //器件应答最大检测次数 超过此数量 无应答也会退出循环 以避免程序死循环
/I2C总线驱动IO口/
sbit I2C_SCL=P1^5;//串行时钟输入。SCL同步数据传输,上升沿数据写入,下降沿数据读出
sbit I2C_SDA=P3^6;//穿行地址和数据输入/输出。SDA是双向串行数据传输引脚,漏极开路,需外接上拉电阻到VCC(典型值10K)
/最终使用的函数/
void at24cxx_weite(uchar I2C_IDRW,I2C_ADDRESS,I2C_DATA);//写入AT24 I2C_IDRW器件地址 I2C_ADDRESS数据地址 I2C_DATA数据
uchar at24cxx_read(uchar I2C_IDRW,I2C_ADDRESS) ; //带地址的读取AT24 I2C_IDRW器件地址 I2C_ADDRESS数据地址
/被调用的子函数/
uchar at24cxx_rd(I2C_IDRD); //当前地址读取AT24 由 读取函数调用 I2C_IDRD 为器件地址
void at24cxx_com(uchar I2C_DATA); //AT24写公用函数 由写入读取函数调用 I2C_DATA 为要写入的变量(器件ID,数据地址,数据)
void at24cxx_start(); //AT24起始信号 由写入读取函数调用
void at24cxx_stop(); //AT24停止信号 由写入读取函数调用
void at24cxx_inquires(uchar I2C_IDRW);//AT24应答检测 由写入读取函数调用
void at24cxx_response(); //I2C总线应答检测 由写入读取函数调用
void at24cxx_ricom(); //器件应答与总线应答检测 公用函数 由器件应答和总线应答函数调用
/字节写 *** 作 起始条件 器件地址 写 字地址 数据停止条件/
void at24cxx_weite(uchar I2C_IDRW,I2C_ADDRESS,I2C_DATA)//写入AT24I2C_IDRW器件地址 I2C_ADDRESS数据地址 I2C_DATA数据
{
//at24cxx_start(); //起始条件 开始写入数据
//at24cxx_com(I2C_IDRW&0xfe); //写入芯片ID及读写选择位 0
//at24cxx_response(); //总线应答
at24cxx_inquires(I2C_IDRW&0xfe);//器件应答检测 并写入器件ID以及读写选项 内部有起始条件
at24cxx_com(I2C_ADDRESS); //写入数据地址
at24cxx_response(); //总线应答
at24cxx_com(I2C_DATA); //写入数据
at24cxx_response(); //总线应答
at24cxx_stop(); //结束条件 终止写入数据
}
/函数流程 起始条件 器件地址 写 字地址 起始条件 器件地址 读 读数据 停止条件/
uchar at24cxx_read(uchar I2C_IDRW,I2C_ADDRESS) //带地址的读取AT24 I2C_IDRW器件地址 I2C_ADDRESS数据地址
{ uchar I2C_DATA; //最终读取的数据
//at24cxx_start(); //起始条件 开始写入数据
//at24cxx_com(I2C_IDRW&0xfe); //写入芯片ID及读写选择位 0
//at24cxx_response(); //总线应答
at24cxx_inquires(I2C_IDRW&0xfe); //器件应答检测 并写入器件ID以及读写选项 内部有起始条件
at24cxx_com(I2C_ADDRESS); //写入数据地址
at24cxx_response(); //总线应答
I2C_DATA=at24cxx_rd(I2C_IDRW|0x01);//写入芯片ID及读写选择位 1 程序内部有起始停止功能
return(I2C_DATA); //返回数据
}
/起始条件 器件地址 读 数据 停止条件/
/当前地址读 起始条件 器件地址 读 数据 停止条件/
/接收器件地址(读/写选择为为1)、EEPROM应答ACK后,当前地址的数据就随时钟送出主器件无需应答0,但需要发送停止条件/
uchar at24cxx_rd(I2C_IDRD) //当前地址读取AT24 I2C_IDRD为器件地址
{ uchar I2C_NUM,I2C_DAT; //定义8位变量用来做循环移位读取数据
at24cxx_start(); //起始条件
at24cxx_com(I2C_IDRD|0x01); //写入芯片ID及读写选择位 1
at24cxx_response(); //总线应答
for(I2C_NUM=0;I2C_NUM<8;I2C_NUM++)
{ I2C_DAT=I2C_DAT<<1; //数据左移位
I2C_SCL=1; //拉高时钟线得到 要读的数据
_nop_(); //一个机器周期指令
if(I2C_SDA==1) //判断数据为1
{I2C_DAT=I2C_DAT|0X01;} //将数据变量赋值
I2C_SCL=0; //拉低时钟线 以获得新数据
nop_(); //一个机器周期指令
}
at24cxx_stop(); //结束条件 终止接收数据
return(I2C_DAT); //返回数据
}
/由读写函数调用 目的:节省空间/
void at24cxx_com(uchar I2C_DATA) //AT24写公用函数
{ uchar I2C_NUM,I2C_DAT; //定义8位变量用来做循环写入位
for(I2C_NUM=0;I2C_NUM<8;I2C_NUM++)
{
I2C_DAT=(I2C_DATA>>7);//获得数据最高位
I2C_DAT=I2C_DAT&0X01; //取数据的最高位数据
I2C_SCL=0; //拉低时钟线数据传输开始
_nop_(); //一个机器周期指令
I2C_SDA=I2C_DAT; //I2C数据线赋值
_nop_(); //一个机器周期指令
I2C_SCL=1; //拉高时钟线数据存储
_nop_(); //一个机器周期指令
I2C_DATA=I2C_DATA<<1; //数据移位
}
}
/当SCL为高电平时SDA的下降沿(高到低)叫做起始条件(START)起始条件保持时间 最小值18V 06US:5V 025US 起始条件建立时间18V 06US:5V 025US /
void at24cxx_start()//AT24起始信号
{
I2C_SCL=1; //空闲总线
_nop_(); //一个机器周期指令
I2C_SDA=1; //空闲总线
_nop_(); //一个机器周期指令
I2C_SDA=0; //拉低数据线 发起起始信号
_nop_(); //一个机器周期指令
}
/SDA的上升沿(低到高)叫做停止条件(STOP) 停止条件建立时间 最小值18V 06US:5V 025US /
void at24cxx_stop() //AT24停止信号
{
I2C_SDA=0; //拉低数据线 以便形成上升沿
_nop_(); //一个机器周期指令
I2C_SCL=1; //
_nop_(); //一个机器周期指令
I2C_SDA=1; //老高数据线 发起停止信号
_nop_(); //一个机器周期指令
}
/一旦内部写周期启动,EEPROM输入无效,此时即可启动应答查询:发送起始条件和器件地址(读/写位为期望的 *** 作)。
只有内部写周期完成EEPROM才会应答“0”。之后可继续读/写 *** 作
应答查询流程:1发送写命令 2发送停止条件启动写周期 3发送起始条件 4发送控制字节R/W=0 5器件是否应答(ACK=0) 是 5下一 *** 作 否 重复2-4 *** 作
/
void at24cxx_inquires(uchar I2C_IDRW)//AT24应答查询
{ uchar AT24_NUM; //定义变量用来跳出循环应答检测
AT24_NUM=AT24_INQ; //赋值应答最大检测次数 避免无应答时程序死循环
while(AT24_NUM--) //循环判断应答
{ at24cxx_start(); //发送起始条件
at24cxx_com(I2C_IDRW); //写入芯片ID及读写选择位
at24cxx_ricom(); //调用应答公用检测函数
if(I2C_SDA==0) //应答成功
{break;} //退出循环
}
I2C_SCL=0; //应答结束拉低时钟线
_nop_(); //一个机器周期指令
I2C_SDA=1; //应答结束把数据线重新拉高
_nop_(); //一个机器周期指令
}
/接收器拉低SDA线表示应答,应在应答脉冲器件保持稳定的低电平。当主器件做接收器时,必须发出数据传输结束的信号给发送器,
即它在最后一个字节之后的应答脉冲期间不会产生应噶信号(不拉低SDA)。这种情况下,发送器必须释放SDA线为高以便主器件产生停止条件/
void at24cxx_response()//总线应答
{
at24cxx_ricom(); //调用应答公用检测函数
I2C_SCL=0; //应答结束拉低时钟线
_nop_(); //一个机器周期指令
I2C_SDA=1; //应答结束把数据线重新拉高
_nop_(); //一个机器周期指令
}
/器件应答与总线应答公用函数 由于这部分代码相同 所以写成一个函数 以节省空间/
void at24cxx_ricom() //器件应答与总线应答检测 公用函数
{
uchar AT24_NUM; //定义变量 用来无应答时跳出循环判断应答
I2C_SCL=0; //总线应答额外脉冲
_nop_(); //一个机器周期指令
I2C_SDA=1; //拉高数据线 以检测应答
_nop_(); //一个机器周期指令
I2C_SCL=1; //应答时钟脉冲
_nop_(); //一个机器周期指令
while(I2C_SDA) //判断应答
{
if(AT24_NUM<255)//判断累加 总线应答检测最大时间(次数) 避免无应答时程序死循环
{AT24_NUM++;} //没有到则自+
else //否则
{break;} //退出循环判断应答
}
}
以上就是关于AT24C02写一个数据然后读取一个数据是正确的,但是当写入多个数据时,读出数据就不正确,求指教全部的内容,包括:AT24C02写一个数据然后读取一个数据是正确的,但是当写入多个数据时,读出数据就不正确,求指教、单片机中,用c语言对at24c02进行 *** 作时,判断应答信号是否有效时,有效则return(0),返回0有什么用、等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)