从今天早上开始看AT24C02的手册,凭着有些撮的英语水平,24页的资料,愣是啃了半天,上午的时光就过去了。
AT24C02是一款EEPROM芯片,IIC接口,就是两条线:SDA与SCL;不过对于单单操纵这款芯片而言,没有设计到IIC总线协议之中所谓的仲裁。因而,大体看下了芯片手册,心中就知道大概怎么操作了。
另外,24C02的2K是指2Kbit。一般的话,存储器都用字节来衡量,所以其实24c02只有256byte。是不是比较小?这256byte又分成了32pages,每页有8byte。
现在来看看该如何编写代码,首先得知道起始与停止条件
所谓起始条件:在SCL高电平期间,SDA的一个下降沿;
所谓停止条件:在SCL高电平期间,SDA的一个上升沿;
当然起始和停止条件与普通的位传送是不同的,位传送的时序如图所示
可以看到,在SCL的高电平期间,SDA是不允许变化的;而只有在SCL的电平期间,SDA才能够出现变化;
当传输完1字节之后,可能需要有一个应答信号。说到这儿,就需要明白几个概念:发送器、接受器、主器件、从器件。
发送器:在总线上发送数据的器件。
接受器:在总线上接受数据的器件。
主器件:控制信息交互的器件,产生SCL时钟信息,产生起始与停止条件。
从器件:受从器件控制的器件。
那这儿的应答信号,就是总线上的接受器每接受到一个字节后产生的应答。如图所示:
需要注意的是,在等待应答信号之前,需要将SDA置高,即释放掉总线。
24c02写的方式有两种:字节写、页写(就是连续写多个字节)。
掌握了字节写,对于页写就容易的多了。
字节写的时序如图所示:
字节写的时序:首先是起始条件(由MCU产生),接着单片机向总线上传送器件地址,总线上地址相同的器件会有一个ACK(即应答信息),然后向器件写入字地址(告诉24c02想把信息写在那个地址,24c02刚好有256个字节,8bit的字地址信息刚好表示),同样会有一个应答信息,紧接着需要写入需要传送的数据(8位),同理会有一个应答信息。最后,需要主器件产生一个停止条件。Ok,写字节就结束了,是不是很简单,页写只不过是在这一步没有发送停止条件,而是接着发数据,这个时候24c02会知道你要写下一个字节,它会自动的加地址。
再来看看如何读取,读取有三种方式:当前地址读、随机读(我得承认这个名字不是太好)、顺序读。
当前地址读取是指你可以读取到芯片内部地址计数器(最近一次操作留下的值)加上1的地址上的值。看清楚是加上1地址上的值,不过不太明白有何用处。
接下来的随机读取跟是让我迷惑(刚看手册时候),随机的意思不就是产生一个无脑的地址,然后去读取,这样的话,读取有什么意义?不过我想错了,你可以看看时序图:
看到了,先发写命令了,也传送了字地址,然后没有发写的内容。转而,重新开始发起始条件、器件地址(这次最后一位是读),然后再接受总线传送来的数据,此后主控器作为接受方,不用发应答信号,直接发一个停止条件,就ok了。
所以要想指定一个地址,就可以用随机读这个方法。不过随机读说法不太形象,改为指定地址读跟容易理解。
顺序读跟页写是对应的,只是在随机读或当前地址读后面接着接受数据。
写了三页,貌似不是很长。那就贴上代码,来充长。
个人喜欢写一个头文件,然后,再写一个c文件。
头文件部分:
#ifndef __hal_24c02_h__
#define __hal_24c02_h__
#include"reg52.h"
#include"hal.h"
#include"datatype.h"
#include"delay.h"
sbit scl=P3^7;//IIC时钟线
sbit sda=P3^6;//IIC数据线
#define SCL scl
#define SDA sda
//起始条件,SCL高电平时候,SDA的一个下降沿
#define HAL_24C02_START() {SDA=1;SCL=1;SDA=0;}
//停止条件,SCL高电平时候,SDA的一个上升沿
#define HAL_24C02_STOP() {SDA=0;SCL=1;SDA=1;}
#define HAL_24C02_BYTE_ADDR(addr) hal_24c02_send_char(addr)
#define HAL_24C02_ACK() hal_24c02_acknowledge(0)
void hal_24c02_init();
void hal_24c02_send_char(uchar val);
uchar hal_24c02_receive_char();
void hal_24c02_search_device(bit a2,bit a1,bit a0,bit rw);
void hal_24c02_acknowledge(bit mcu);
//学会使用以下两个函数即可
//需要注意写完之后,等待5ms之后,再去读取
void hal_24c02_byte_write(bit a2,bit a1,bit a0,uchar byte_addr,uchar byte_data);
uchar hal_24c02_byte_read(bit a2,bit a1,bit a0,uchar byte_addr);
#endif
C文件部分:
#include"hal_24c02.h"
//初始化,需将时钟线和数据线拉高
void hal_24c02_init()
{
SCL=1;
SDA=1;
}
//IIC协议要求从高位开始传送,传送1字节
void hal_24c02_send_char(uchar val)
{
uchar i;
SCL=0;//此时,SCL被拉低,才允许SDA变化
for(i=0;i<8;i++)
{
if(val&(0x80>>i))
SDA=1;
else
SDA=0;
SCL=1;//拉高SCL,不允许SDA变化
delay_ms(1);
SCL=0;//拉低SCL,开始下1bit传送
}
//不包括应答脉冲,此时,SCL被拉低,SDA未被释放
SDA=1;//释放数据线
//SCL处于低电平;SDA处于高电平
}
uchar hal_24c02_receive_char()
{
//SCL低电平 SDA高电平
uchar tmp=0,i;
for(i=0;i<8;i++)
{
SCL=1;
if(SDA)
tmp=tmp|(0x80>>i);
SCL=0;
}
return tmp;
//SCL低电平 SDA高电平
}
//a2、a1、a0:表示器件地址
//rw:1 代表读操作;0 代表写操作
void hal_24c02_search_device(bit a2,bit a1,bit a0,bit rw)
{
uchar tmp;
tmp=0xa0|(a2?0x08:0x00)|(a1?0x04:0x00)|(a0?0x02:0x00)|rw;
hal_24c02_send_char(tmp);
}
//参数mcu=1,表示由主机主动产生应答信号,此时还需要将SDA拉低
//参数mcu=0,表示非主机参数应答信号,此时主机只需要产生SCL脉冲
//返回1:表示有应答;0:无应答
void hal_24c02_acknowledge(bit mcu)
{
//SCL处于低电平;SDA处于高电平
if(mcu) SDA=0;//产生1应答信号
SCL=1;
delay_ms(1);
while(SDA);//等待应答
SCL=0;
if(mcu) SDA=1;//释放总线
//SCL处于低电平;SDA处于高电平
}
//参数a2,a1,a0:器件地址
//参数byte_addr:字节地址
//参数byte_data:需要写的数据
void hal_24c02_byte_write(bit a2,bit a1,bit a0,uchar byte_addr,uchar byte_data)
{
HAL_24C02_START()
hal_24c02_search_device(a2,a1,a0,0);
HAL_24C02_ACK();
HAL_24C02_BYTE_ADDR(byte_addr);
HAL_24C02_ACK();
hal_24c02_send_char(byte_data);
HAL_24C02_ACK();
HAL_24C02_STOP()
}
//参数a2 a1 a0为器件地址;
//参数byte_char为需要读取的字节地址
//返回一个uchar型
uchar hal_24c02_byte_read(bit a2,bit a1,bit a0,uchar byte_addr)
{
uchar val=0;
HAL_24C02_START()
hal_24c02_search_device(a2,a1,a0,0);//伪写
HAL_24C02_ACK();
HAL_24C02_BYTE_ADDR(byte_addr);//伪写的目的,就是为了传送地址
HAL_24C02_ACK();
HAL_24C02_START();
hal_24c02_search_device(a2,a1,a0,1);//真读
HAL_24C02_ACK();
val=hal_24c02_receive_char();
//NO ACK
HAL_24C02_STOP()
return val;
}