大侠们,求救啊,好几天不知道该怎么办了!小弟万分感谢啊
#include <reg51.h>
#include <intrins.h> //因为用到_nop_();
#define uchar unsigned char
#define uint unsigned int
sbit SCL = P3^2; //注意P1、P2、P3口有内部上拉电阻,可直接连SDA和SCL,若想用P0需外接上拉电阻,否则连上无法输出高电平!
sbit SDA = P3^3;
/*********74H573控制端*********/
sbit dula = P2^7;//段选
sbit wela = P2^6;//位选的数据
sbit LED = P2^5;//秒显示
uchar j=0; //用于计数50ms的个数的全局变量
//写入24C02的一组数据,8个字节对应24C02的一页(共32页),这里把这些要验证的常数放到程序存储区
uchar code ToSDAdataBuffer[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15
};
uchar ReceivedData[16];//用于存储接收的8个字节数据(1页)的数组
//本例51为单主机,24C02为从机,不需要总线裁决
//延时5us子程序
/*******数码管段选_时间********/
uchar code table_duan[]={0x3f,0x06,0x5b,0x4f,0x66,
0x6d,0x7d,0x07,0x7f,0x6f};
/*********数码管位选**********/
uchar code table_wei[]={0xfe,0xfd,0xfb,0xf7,0xef};
uchar temp,a,b;
uchar tt=0;
///////////////////////////////////////////////////////////////////////////
void delay1(uint x)
{
uint i,j ;
for(i=x;i--;i>0)
for(j=110;j--;j>0);
}
/**************************** 显示函数 **************************/
void display(uchar wee,uchar H_hour,uchar L_hour,uchar H_min,uchar L_min)
{
dula=1;
P0=table_duan[wee];//数码管5位,星期
dula=0;
wela=1;
P0=table_wei[0];
wela=0;
delay1(1);
dula=1;
P0=table_duan[H_hour];//数码管4位,小时十位
dula=0;
wela=1;
P0=table_wei[1];
wela=0;
delay1(1);
dula=1;
P0=table_duan[L_hour];//数码管3位,小时个位
dula=0;
wela=1;
P0=table_wei[2];
wela=0;
delay1(1);
dula=1;
P0=table_duan[H_min];//数码管2位,分钟十位
dula=0;
wela=1;
P0=table_wei[3];
wela=0;
delay1(1);
dula=1;
P0=table_duan[L_min];//数码管1位,分钟个位
dula=0;
wela=1;
P0=table_wei[4];
wela=0;
delay1(1);
}
///////////////////////////////////////////////////////////////////////////
void delay5us(void)
{
_nop_(); //时序图要求开始建立时间tSU.STA大于4.7us,开始保持时间tHD.STA大于4us。51中每个_nop_ ();延时1个CPU cycle,即1us。
_nop_(); //如考虑不同CPU频率不同,可用带参数的延时,参数在前面宏定义。
_nop_();
_nop_();
_nop_();
}
//约2ms的延时
void delay(uchar t)
{
uchar x,y;
for(x=0;x<t;x++)
for(y=0;y<250;y++);
}
//I2C初始化
void InitI2C(void)
{
SDA = 1;//总线空闲时,因各设备都是集电极或漏极开路,上拉电阻使SDA和SCL线都保持高电平。
SCL = 1;
delay5us();
}
//产生I2C开始信号
void StartI2C(void)
{
SDA = 1;//SDA在SCL为高期间由高变低表示开始,所以先要高
SCL = 1;
delay5us();//时序图要求tSU.STA(Start Set-up Time)大于4.7us
SDA = 0; //注意SDA拉低前后都要维持5us以上!
delay5us(); //tHD.STA(Start Hold Time)大于4us
SCL = 0; //拉低SCL,准备发送或接收数据(这两句也可在写或读字节的程序中先将SCL置0,延时)
delay5us();
}
//产生I2C结束信号
void StopI2C(void)
{
SDA = 0; //SDA在SCL为高期间由低变高,说明结束
SCL = 1;
delay5us();
SDA = 1;
delay5us();
}
//发送方在发完一个字节后检测接收方有没有应答。返回应答成功否。
bit ChkAck(void)
{
bit SDAtemp;
SDA = 1; //释放SDA(置1),然后等待接收方应答将它拉低。确切的说,应是24C02发送字节最后一位的第8个时钟周期下降沿后经tAA
//(SCL变低到SDA OUT有效的时间)约0.1-4.5us后拉低SDA,并随第9个时钟后结束。所以24C02正常时,SDA为1并不体现
//(第8脉冲后马上被拉低了),但若器件坏了,就需要靠这个置1后不变来判断!(若不置1而上次发的数据最后一位为0就不好判断了)
//从24C02的Block Diagram看,它只能在SDA为1时通过控制内部的Dout来把SDA拉低,但不能在SDA为0时将其置高!故主机要常将SDA置1,而SCl置0。
SCL = 1; //WriteI2CByte中写完一字节后又将SCL拉低,这里拉高产生第9个时钟上升沿,然后在SCL为高期间对SDA进行检测
delay5us();
SDAtemp = SDA; //如果不用暂存变量,直接return SDA,就不会执行后面的SCL = 0,检测期间的第9个时钟就不完整了
SCL = 0;
delay5us();
return SDAtemp;
}
//51作为主机时,如果接收数据,模拟产生应答时序。形参Ack为0,则应答0,为1不应答。
void AckAsMaster(bit Ack)
{
if(!Ack)
SDA = 0;
else
SDA = 1;
delay5us();
SCL = 1; //主机控制SCL时序。关键是保证在SCL脉冲上升沿之前SDA数据已稳定即可。
delay5us();
SCL = 0;
delay5us();
}
//往I2C总线写一个字节的数据(即将一个字节的数据发送到SDA上)
void WriteI2CByte(uchar ByteData)
{
uchar i,temp;
temp = ByteData;
// (StartI2C()最后已经先将SCL变0了):
for(i=0;i<8;i++)
{
temp <<= 1; //左移一位,I2C要求由MSB最高位开始,移出的CY即要发送到SDA上的数据。下面考虑时序:
SDA = CY; //此时SCL已为低,每次移一位送出去(下次进循环后SDA还保持着上次发出去的数据)
delay5us(); //SDA IN数据变化中点SCL上升沿中点的一段时间是tSU.DAT,即数据建立时间Data In Set-up Time,需大于200ns,多延无所谓
SCL = 1;
delay5us(); //tHIGH即Clock Pulse Width High,最小4us
SCL = 0;
delay5us(); //tLOW即Clock Pulse Width Low,最小4.7us
}
}
//读取I2C总线一个字节的数据
uchar ReadI2CByte() //串行总线,51一位位接收从机发送到SDA上的数据,这里只考虑数据已在SDA上时如何存下来这几位,组成一个字节
{
uchar i,ByteData;
SDA = 1; //SCL在ChkAck中已经置0了。注意SCL时序仍然由主机控制!24C02只能将SDA由高拉低,象橡皮筋松手又恢复高,而下面只是读SDA,没赋值
//其实程序中多处给SDA置1都可省,因为检查应答时为0就正常,无所谓,写字节时也无所谓,就是在读之前要保证SDA为1!
//因之前有WriteI2CByte(0xa1); 其实这句也可省略。
delay5us(); //24C02作为发送方在第9个时钟的negative edge clocks data out of each device,所以现在SDA上为新数据
for(i=0;i<8;i++)
{
SCL = 1; //置时钟线为高使数据线上数据有效
delay5us();
ByteData = (ByteData<<1)|SDA; //SDA上已是新数据了,读之。data不管以前多少,左移后最右边为0,和SDA“按位或”后MLB就是SDA
SCL = 0;
delay5us();
}
return ByteData;
}
//页写。输入两参数,一个为首字地址,另一个是指向待写入数据数组的指针(括号内第二个参数也可写作uchar ToSDAdataBuffer[],即数组名代表首地址)。
bit PageWrite(uchar WordAddress,uchar *ToSDAdataBuffer)
{
//下面的程序我用的if嵌套,网上有些程序是顺序结构,但因为遇到return就返回主程序不再往下执行,所以效果是一样的。
uchar i;
StartI2C();
WriteI2CByte(0xa0);//之所以没设DeviceAddress这个参数,是因为最后一位不属于地址。E2PROM一般前四位为1010,这里A2~A0接地,为0,最后一位0表示写
if(!ChkAck())//检查应答函数返回0说明从机应答0成功。
{
//写8-bit data word address,即写到哪个存储单元(24C02有2kbits,所以数据字有2048/8=256个,故地址线有8位)
WriteI2CByte(WordAddress);
if(!ChkAck())
{
for(i = 0; i < 16; i++)
{
WriteI2CByte(ToSDAdataBuffer[i]);
if(ChkAck())
{ //这里可添加错误处理代码。如用几个LED的亮灭组合表示此I2C器件有问题,类似主板错误提示。
return 1;//一般返回1表示异常,且遇到return就退出整个子程序。
}
}
StopI2C(); //写完发送结束信号。
return 0; //一般返回0表示程序正常
}
else return 1; //之前可添加错误处理代码。
}
else return 1;
}
//不能用Current Address Read,因为那是24C02数据字地址计数器上次操作后加1的值;
//而SEQUENTIAL_READ如果不给一个要读取的开始地址,会从头输出,
//所以需要Random Read的开始部分,但不要停止信号。
bit SequentialRead(uchar WordAddress)
{
uchar i;
StartI2C();
WriteI2CByte(0xa0);
if (!ChkAck())
{
WriteI2CByte(WordAddress);
if (!ChkAck())
{
StartI2C();
//Device Address后紧跟的那一位R/W^是1说明是读,24C02内部就是根据最后这位来判断是从SDA上读数,还是往SDA上送数
WriteI2CByte(0xa1);
//之所以设为1是读,是因为根据WriteI2CByte子程序,最后给SDA赋1,P3^4就维持1,这样24C02内部Dout为高就将SDA拉低;如果最后一位是0,24C02没能力拉高!
if (!ChkAck())
{
for(i = 0;i < 16;i++)
{
ReceivedData[i] = ReadI2CByte();
AckAsMaster(0); //51此时接收数据,调用应答的函数(置SDA为0)
}
AckAsMaster(1);
StopI2C();
return 0;
}
else return 1;//之前可添加错误处理代码。
}
else return 1;
}
else return 1;
}
void main(void)
{
TMOD = 0x01; //方式1的16位计数器
TH0 = (65536-50000)/256;
TL0 = (65536-50000)%256;
EA = 1;
ET0 = 1;
TR0 = 1; //启动定时器0工作
InitI2C();
if (PageWrite(0,ToSDAdataBuffer) == 0) //先执行页写操作,设从地址00开始,没问题就延迟一下再从同一地址读回来。
{
delay(100); //等待24C02页写操作完毕
//if(SequentialRead(0) == 0) //如果顺序读操作成功,则每隔1秒送P0口显示一个字节
//{}
SequentialRead(0);
}
while(1)
{
temp = ReceivedData[j];
a = temp / 10;
b = temp % 10;
display(0,0,0,a,b);
}
}
void timer0() interrupt 1
{
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
tt++;
if(tt == 5)
{
tt = 0;
LED = ~LED;
j++;
if(j == 16)j=0;
}
}
用状态机方式:每次写一页8字节(24C02),然后间歇5ms,再写一页……相当于连写25页(共200字节),约125ms完成200字节写入,OK
STC官网的参考代码
- /**************************************
- AT24C04测试程序
- 主芯片 : STC90C52RC (12T)
- 工作频率: 12.000MHz
- **************************************/
- #include "REG51.H"
- #include "INTRINS.H"
- typedef unsigned char BYTE;
- typedef unsigned short WORD;
- sbit SCL = P3^4; //AT24C04的时钟
- sbit SDA = P3^5; //AT24C04的数据
- BYTE BUF[16]; //数据缓存区
- BYTE code res[6] _at_ 0x23;
- BYTE code TESTDATA[] =
- {
- 0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,
- 0x88,0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF
- };
- void Delay5us();
- void Delay5ms();
- void AT24C04_Start();
- void AT24C04_Stop();
- void AT24C04_SendACK(bit ack);
- bit AT24C04_RecvACK();
- void AT24C04_SendByte(BYTE dat);
- BYTE AT24C04_RecvByte();
- void AT24C04_ReadPage();
- void AT24C04_WritePage();
- void main()
- {
- AT24C04_WritePage();
- Delay5ms();
- AT24C04_ReadPage();
- while (1);
- }
- /**************************************
- 向AT24C04写1页(16字节)数据
- 将TESTDATA开始的16个测试数据写如设备的00~0F地址中
- **************************************/
- void AT24C04_WritePage()
- {
- BYTE i;
- AT24C04_Start(); //起始信号
- AT24C04_SendByte(0xa0); //发送设备地址+写信号
- AT24C04_SendByte(0x00); //发送存储单元地址
- for (i=0; i<16; i++)
- {
- AT24C04_SendByte(TESTDATA[i]);
- }
- AT24C04_Stop(); //停止信号
- }
- /**************************************
- 从AT24C04读取1页(16字节)数据
- 将设备的00~0F地址中的数据读出存放在DATA区的BUF中
- **************************************/
- void AT24C04_ReadPage()
- {
- BYTE i;
- AT24C04_Start(); //起始信号
- AT24C04_SendByte(0xa0); //发送设备地址+写信号
- AT24C04_SendByte(0x00); //发送存储单元地址
- AT24C04_Start(); //起始信号
- AT24C04_SendByte(0xa1); //发送设备地址+读信号
- for (i=0; i<16; i++)
- {
- BUF[i] = AT24C04_RecvByte();
- if (i == 15)
- {
- AT24C04_SendACK(1); //最后一个数据需要会NAK
- }
- else
- {
- AT24C04_SendACK(0); //回应ACK
- }
- }
- AT24C04_Stop(); //停止信号
- }
- /**************************************
- 延时5微秒(STC90C52RC@12M)
- 不同的工作环境,需要调整此函数
- 当改用1T的MCU时,请调整此延时函数
- **************************************/
- void Delay5us()
- {
- _nop_();
- _nop_();
- }
- /**************************************
- 延时5毫秒(STC90C52RC@12M)
- 不同的工作环境,需要调整此函数
- 当改用1T的MCU时,请调整此延时函数
- **************************************/
- void Delay5ms()
- {
- WORD n = 560;
- while (n--);
- }
- /**************************************
- 起始信号
- **************************************/
- void AT24C04_Start()
- {
- SDA = 1; //拉高数据线
- SCL = 1; //拉高时钟线
- Delay5us(); //延时
- SDA = 0; //产生下降沿
- Delay5us(); //延时
- SCL = 0; //拉低时钟线
- }
- /**************************************
- 停止信号
- **************************************/
- void AT24C04_Stop()
- {
- SDA = 0; //拉低数据线
- SCL = 1; //拉高时钟线
- Delay5us(); //延时
- SDA = 1; //产生上升沿
- Delay5us(); //延时
- }
- /**************************************
- 发送应答信号
- 入口参数:ack (0:ACK 1:NAK)
- **************************************/
- void AT24C04_SendACK(bit ack)
- {
- SDA = ack; //写应答信号
- SCL = 1; //拉高时钟线
- Delay5us(); //延时
- SCL = 0; //拉低时钟线
- Delay5us(); //延时
- }
- /**************************************
- 接收应答信号
- **************************************/
- bit AT24C04_RecvACK()
- {
- SCL = 1; //拉高时钟线
- Delay5us(); //延时
- CY = SDA; //读应答信号
- SCL = 0; //拉低时钟线
- Delay5us(); //延时
- return CY;
- }
- /**************************************
- 向IIC总线发送一个字节数据
- **************************************/
- void AT24C04_SendByte(BYTE dat)
- {
- BYTE i;
- for (i=0; i<8; i++) //8位计数器
- {
- dat <<= 1; //移出数据的最高位
- SDA = CY; //送数据口
- SCL = 1; //拉高时钟线
- Delay5us(); //延时
- SCL = 0; //拉低时钟线
- Delay5us(); //延时
- }
- AT24C04_RecvACK();
- }
- /**************************************
- 从IIC总线接收一个字节数据
- **************************************/
- BYTE AT24C04_RecvByte()
- {
- BYTE i;
- BYTE dat = 0;
- SDA = 1; //使能内部上拉,准备读取数据
- for (i=0; i<8; i++) //8位计数器
- {
- dat <<= 1;
- SCL = 1; //拉高时钟线
- Delay5us(); //延时
- dat |= SDA; //读数据
- SCL = 0; //拉低时钟线
- Delay5us(); //延时
- }
- return dat;
- }
复制代码先定义一个数组:
on_off_cnt[200];
ISendStr(0xa0,0x00,on_off_cnt,8);//把数据on_off_cnt[0]-on_off_cnt[7]-写到0x00-0X07地址中
delayms(10);
ISendStr(0xa0,0x08,&on_off_cnt[8],8);//把数据on_off_cnt[8]-on_off_cnt[15]-写到0x08-0X0f地址中
delayms(10);
ISendStr(0xa0,0x10,&on_off_cnt[16],8);//把数据on_off_cnt[16]-on_off_cnt[23]-写到0x10-0X17地址中
delayms(10);
ISendStr(0xa0,0x18,&on_off_cnt[24],8);//把数据on_off_cnt[24]-on_off_cnt[31]-写到0x18-0X1f地址中
.................................
.................................
delayms(10);
ISendStr(0xa0,0xc0,&on_off_cnt[192],8);//把数据on_off_cnt[192]-on_off_cnt[199]-写到0x08-0X0f地址中
以此类推,写25次就可以把200字节写完。
读取的话就按地址:
IRcvStr(0xa0,0x00,on_off_cnt,1);//从地址0x00-0X07中读出数据到on_off_cnt[0]-on_off_cnt[7]
delayms(10);
IRcvStr(0xa0,0x08,&on_off_cnt[8],1);//从地址0x08-0X0f中读出数据到on_off_cnt[8]-on_off_cnt[16]
................................
...............................
一周热门 更多>