上次简单介绍了一下12864的原理,这次就把实验中用到的另一个24c04芯片简单说一说。相比于12864,24c04就简单一点,它的操作原理相对于显示图像来说更容易理解,没有那么绕的地方。我会尽量将它的原理讲的通俗易懂一些,以便于大家理解和使用。
24c04的原理(含IIC简单介绍)
24c04属于AT24c系列存储芯片的一种,其芯片容量为512Byte,在官方手册上写为了4Kbit,其实是一个意思(1Byte=8bit,以1024为单位转换值)。除此之外,24c04与其他的系列还是存在比较大差别的。
在我对24c04编程的过程中,总结出了两个关键的问题,相信大家只要搞懂了这两个问题,24c04的使用就不在话下:①如何使用IIC进行读写?时序怎样分配?②24c04的组成与操作特性是什么样的?响应机制又是怎样的?该如何根据其特点进行程序编写?
下面先来讲讲第一个问题。24c04本质上是一种存储器件,因此我们必然需要一种操作方法来对其进行读取和写入,这种操作方法就是IIC。这里没用过IIC的话问题也不大,我就先开个小标题简单说一说IIC,已经懂的朋友也可以直接跳到下面的24c04介绍部分。
IIC的操作原理
因为本人不喜欢所谓的设计背景之类的套话,这里就直接介绍IIC原理。
简单来说,IIC其实就是两根线,一根负责时序(SCL),也就是类似于我们所熟悉的时钟;一根负责数据(SDA),也就是传输对应的数据。所有的设备都挂在这两根线上,其中,有一台设备为主机,剩余设备为从机,通常我们用单片机做主机。这些设备根据接收到的已经规定过信号的含义(两根线发送的不同的高低电平的变化)来表示不同的操作。
例如,IIC的起始信号表达方法就是:SCL(时钟总线)保持高电平期间,SDA(数据总线)由高电平向低电平变化产生的一个下降沿。只要挂在IIC上的设备接收到了上述这个状态,就认为IIC开始工作了。所以,IIC的使用实际上就是赋值语句与适当的nop()延时函数的组合。具体的高低电平变化则需要根据官方给出的时序图来决定,这里给出起始和停止的时序图,给大家熟悉熟悉:
可以看到,停止的表达方法为:SCL(时钟总线)保持高电平期间,SDA(数据总线)由低电平向高电平变化产生的一个上升沿。也就是说,当我的数据发送完毕后想要停止IIC,就需要先把SDA拉为低电平,SCL拉为高电平,然后再把SDA从低电平拉为高电平。
当然,IIC的操作远不止这两种,大家可以百度“IIC时序图详解”,查看相关的操作,这里不再做赘述。
24c04的原理介绍
关于24c04的基本原理方面,我提取了一下官方数据手册上比较有用的一些信息并加以整合,方便大家观看,如下:
大致翻译一下,意思是24c04分2大页,每大页16小页,每小页16字节,即总容量为512字节,每个储存空间单位都有自己的地址编号。然后这个芯片的A0口是不接的,发送的device addres(设备地址,可以认为具有标识芯片、携带操作行为与地址设定功能)的前四位固定为1010,之后接上A2、A1(这里直接取0),A0位被取消并用P0取代,用于辨别当前操作的是在哪个大页。也就是说,24c04每次最多支持连续16字节的写入,即一小页。后来实测时感觉有点问题,写满了8字节后指针似乎就翻转到开始处了,后8个字节会覆盖掉前8个字节,所以程序里还是只连续写了8个字节。
了解了这些,就可以阅读下面有关24c04的使用方法了。
24c04的使用方法
按例,我会把自己的代码放出来,然后在比较重要的地方做一下注释。这里的代码是经过本人实测可以使用的,所以请大家放心阅读。在这个C文件中,因为涉及到了IIC的知识,所以大家需要了解一下写入的原理,但是并不难,大家只要理解了上面我举的例子就很容易用起来:
#include <reg51.h>
#include <intrins.h>
#define DeviceAdress_WriteMode_Page1 0xA0
#define DeviceAdress_WriteMode_Page2 0xA2
#define DeviceAdress_ReadMode_Page1 0xA1
#define DeviceAdress_ReadMode_Page2 0xA3
#define I2Cdelay() {_nop_();_nop_();_nop_();_nop_();}
sbit SDA = P1^0;
sbit SCL = P1^1;//这里的端口可以根据实际需要修改
void I2CStart(void)//IIC启动,不明白的话可以结合上面给出的时序图分析
{
SDA=1;
SCL=1;
I2Cdelay();
SDA=0;
I2Cdelay();
SCL=0;
}
void I2CStop(void)//IIC停止
{
SCL=0;
SDA=0;
I2Cdelay();
SCL=1;
I2Cdelay();
SDA=1;
I2Cdelay();
}
bit I2CWrite(unsigned char dat)//IIC总线写操作,返回一个从机应答值(用于判断写入是否成功)
{
bit ack;
unsigned char mask;//用来探测某一位的掩码变量
for(mask=0x80;mask!=0;mask>>=1)
{
if((mask&dat)==0)//该位的值输出到SDA上
SDA = 0;
else
SDA = 1;
I2Cdelay();
SCL = 1;
I2Cdelay();
SCL = 0;//完成一个位的周期
}
SDA = 1;//一个字节发完后,释放SDA,用于检测从机应答
I2Cdelay();
SCL = 1;
ack = SDA;//此时的SDA即为从机应答值
I2Cdelay();
SCL = 0;
return(~ack);
}
unsigned char I2CReadnak()//IIC总线读操作,发送非应答信号,大致原理与写操作类似
{
unsigned char mask,dat;
SDA = 1;
for(mask=0x80;mask!=0;mask>>=1)
{
I2Cdelay();
SCL = 1;
if(SDA == 0)
dat &= ~mask;
else
dat |= mask;
I2Cdelay();
SCL = 0;
}
SDA = 1;
I2Cdelay();
SCL = 1;
I2Cdelay();
SCL = 0;
return dat;
}
unsigned char I2CReadack()//IIC总线读操作,发送应答信号
{
unsigned char mask,dat;
SDA = 1;
for(mask=0x80;mask!=0;mask>>=1)
{
I2Cdelay();
SCL = 1;
if(SDA == 0)
dat &= ~mask;
else
dat |= mask;
I2Cdelay();
SCL = 0;
}
SDA = 0;
I2Cdelay();
SCL = 1;
I2Cdelay();
SCL = 0;
return dat;
}
void Read(unsigned char *buf,unsigned char addr,unsigned char len)//buf-数据接收指针,addr-C04中写入数据的起始地址,len-读取长度
{
do{//查询当前是否能够进行读写操作
I2CStart();
if(I2CWrite(DeviceAdress_WriteMode_Page1))//应答则跳出循环,否则进行下一次查询
break;
I2CStop();
}while(1);
I2CWrite(addr);
I2CStart();
I2CWrite(DeviceAdress_ReadMode_Page1);
while(len>1)
{
*buf++ = I2CReadack();
len--;
}
*buf = I2CReadnak();
I2CStop();
}
void Write(unsigned char *buf,unsigned char addr,unsigned char len)//buf-源数据指针,addr-C04中的起始地址,len-写入长度
{
while(len--)
{
do{
I2CStart();
if(I2CWrite(DeviceAdress_WriteMode_Page1))
{
break;
}
I2CStop();
}while(1);
I2CWrite(addr++);
I2CWrite(*buf++);
I2CStop();
}
}
可以看到,在操作函数中,首先是IIC的起始信号,接着不管是读还是写操作,都要先将device adress的0位设为写,这样是为了写入要发送数据的存储位置或读取的数据地址,这之后,如果是写操作,就可以直接写数据了,不用更改模式;如果是读操作,则需要再IIC启动一次,并在这次中将device adress的0位设为读,之后再进行读取。操作完毕后,IIC停止即可。
因为这一次所提供的函数可以直接调取使用,没有什么特殊的注意事项或者难理解的地方,所以就不再给出实际应用的例子了。希望这篇文章能够帮助到有困扰的朋友。