存储空间分配
AT24C02A,2K 串行 EEPROM:共 32 页,每页 8 字节,寻址字需 8 位。
AT24C04A,4K 串行 EEPROM:共 32 页,每页 16 字节,寻址字需 9 位。
AT24C08A,8K 串行 EEPROM:共 64 页,每页 16 字节,寻址字需 10 位。
AT24C16A,16K 串行 EEPROM:共 128 页,每页 16 字节,寻址字需 11 位。
图为 几类不同容量的芯片的存储空间结构,24C16以下空间的大于8位后的寻址高位地址在片选地址中选择,详细看芯片手册。另外要注意的就是字节页,一次连续写入的数据量不能超过一页的数据量。有些老款的芯片甚至不支持跨页写入。为了适用也参照不跨页写入的方法写这个程序。而读取数据没有这个限制,只要单片机开辟的缓存够大,可以一直连续读下去。
/*****24Cxx Seriel EEPROM*************************/#define EEPROM 8
/********
01 -> 24C01; 02 -> 24C02; 04 -> 24C04; 08 -> 24C08;
16 -> 24C16; 32 -> 24C32; 64 -> 24C64; 128 -> 24C128;
256-> 24C256; 512 -> 24C512;
*****/
#if EEPROM==1
#define PAGE_SIZE 8
#define EE_SIZE 0x007F
#elif EEPROM==2
#define PAGE_SIZE 16
#define EE_SIZE 0x00FF
#elif EEPROM==4
#define PAGE_SIZE 16
#define EE_SIZE 0x01FF
#elif EEPROM==8
#define PAGE_SIZE 16
#define EE_SIZE 0x03FF
#elif EEPROM==16
#define PAGE_SIZE 16
#define EE_SIZE 0x07FF
#elif EEPROM==32
#define PAGE_SIZE 32
#define EE_SIZE 0x0FFF
#elif EEPROM==64
#define PAGE_SIZE 32
#define EE_SIZE 0x1FFF
#elif EEPROM==128
#define PAGE_SIZE 64
#define EE_SIZE 0x3FFF
#elif EEPROM==256
#define PAGE_SIZE 64
#define EE_SIZE 0x7FFF
#elif EEPROM==512
#define PAGE_SIZE 128
#define EE_SIZE 0xFFFF #endif
头文件可以写成预编译模式,方便移植后修改,PAGE_SIZE为一页的存储量,EE_SIZE为芯片的存储量,而后一些程序的判断也根据选择的存储芯片来判断。
顶层用于外部程序调用的函数只有两个,读和写两个情况而已
unsigned char EEPROM_Write(unsigned char* pBuffer,unsigned int WriteAddress,unsigned char NumbyteToWrite);
unsigned char EEPROM_Read(unsigned char* pBuffer,unsigned int ReadAddress,unsigned char NumbyteToWrite);
传递函数有三个:
pBuffer:要存储或读出来的数据所在的变量存储区字符串头指针;
WriteAddress/ReadAddress:要存入EEPROM所在的存储空间的第一个存储空间地址;
NumbyteToWrite:数据写入或读出的字节个数;
这样的应用比较简单 例如在头文件中分配了两类数据的存储空间起始地址DATA1、DATA2
#define DATA1 0x0010
#define DATA2 0x0050
存储数据所在缓存 EE_Buffer[20],应用程序如下写法:
EEPROM_Write(EE_Buffer,DATA1,20);
这样EE_Buffer内的数据便被写入EEPROM中 0x10~0x30 的数据存储空间中了。
合理的分配陪EEPROM 的存储空间对数据管理非常重要。甚至于可以作为一个小型黑匣子一样。
unsigned char EEPROM_Write(unsigned char* pBuffer,unsigned int WriteAddress,unsigned char NumbyteToWrite)
{
unsigned char TempBuffer,Temp2Buffer;
if((WriteAddress+NumbyteToWrite) > EE_SIZE) //判断是否超出存储空间
{
return 0;
}
else
{// 连续写入两次避免因EMC等因素造成的写入失败情况
IIC_WriteBuffer(pBuffer,WriteAddress,NumbyteToWrite);
IIC_WriteBuffer(pBuffer,WriteAddress,NumbyteToWrite);
//读取eeprom 的数据与缓存中的数据对比 相同为确认写入成功
IIC_ReadBuffer(&TempBuffer,WriteAddress+NumbyteToWrite-1,1);
Temp2Buffer=*(pBuffer+NumbyteToWrite-1);
if(TempBuffer==Temp2Buffer)
return 1;
else
return 0;
}
}
unsigned char EEPROM_Read(unsigned char* pBuffer,unsigned int ReadAddress,unsigned char NumbyteToWrite)
{
if((ReadAddress+NumbyteToWrite) > EE_SIZE)
{
return 0;
}
else
{
IIC_ReadBuffer(pBuffer,ReadAddress,NumbyteToWrite);
IIC_ReadBuffer(pBuffer,ReadAddress,NumbyteToWrite);
return 1;
}
}
接下来的是是IIC_ReadBuffer、IIC_WriteBuffer,两个函数主要是对存入数据的处理,如页面内写入,超过页面数量的数据处理等,我这里把函数定义为static 函数只能对内部应用,外部只能调用上面的两个函数,累似于API函数一样,这也可以避免不必要的程序调用书写。IIC_ReadBuffer函数相对简单,因为读取没有对页面的限制,可以无限制的读下去。读取缓存的函数只对地址做一下判断即可。写入函数较为复杂,需判断数据起始存储地址 和页等关系
static void IIC_ReadBuffer(unsigned char* pBuffer,unsigned int ReadAddress,unsigned char NumbyteToWrite);
static void IIC_WriteBuffer(unsigned char* pBuffer,unsigned int WriteAddress,unsigned char NumByteToWrite);
void IIC_ReadBuffer(unsigned char* pBuffer,unsigned int ReadAddress,unsigned char NumbyteToWrite)
{
u8 PageAddress=0;
/******pageAddress is over 8bit***********/
/******判断存储地址是否超过8位************/
#if EEPROM < 32
PageAddress=(u8)(ReadAddress>>7)&0x0E|ReadAddress_EEPROM;
#else
PageAddress=WriteAddress_EEPROM;
#endif
IIC_ReadPage(pBuffer,PageAddress,ReadAddress,NumbyteToWrite);
}
void IIC_WriteBuffer(unsigned char* pBuffer,unsigned int WriteAddress,unsigned char NumByteToWrite)
{
u8 NumOfPage=0,NumOfSingle=0; //
u16 Part=0;//
u8 PageAddress=0;
/******pageAddress is over 8bit***********/
/******判断存储地址是否超过8位************/
#if EEPROM < 32
PageAddress=(u8)(WriteAddress>>7)&0x0E|WriteAddress_EEPROM;
#else
PageAddress=WriteAddress_EEPROM;
#endif
/*********判断起始地址与跨页地址的字节个数********/
Part=WriteAddress/PAGE_SIZE;
if(Part!=0)
{
Part=PAGE_SIZE*(Part+1)-WriteAddress;
}
else
{
Part=PAGE_SIZE-WriteAddress;
}
/******/
if(Part >= NumByteToWrite)
{
/***写入的数据个数小于跨页剩余的个数可直接写入 ***/
IIC_WritePage(pBuffer,PageAddress,WriteAddress,NumByteToWrite);
}
else
{
NumOfPage = (NumByteToWrite-Part)/PAGE_SIZE;
NumOfSingle = (NumByteToWrite-Part)%PAGE_SIZE;
pBuffer = IIC_WritePage(pBuffer,PageAddress,WriteAddress,Part);
/***1.写入的数据个数大于跨页剩余的个数先把剩余的跨页个数填充满 ***/
NumByteToWrite -= Part;
WriteAddress += Part;
while(NumOfPage--)
{
pBuffer = IIC_WritePage(pBuffer,PageAddress,WriteAddress,PAGE_SIZE);
/***2.按计算的数据量占页面数,连续写入页面*******/
WriteAddress += PAGE_SIZE;
}
if(NumOfSingle!=0)
{
IIC_WritePage(pBuffer,PageAddress,WriteAddress,NumOfSingle);
/***3.补充页面写完后超出的不足一页数据量的数据***/
}
}
}
剩下的是IIC_WritePage()、IIC_ReadPage()两个函数是芯片的IIC通讯底层函数,根据单片机的IIC通讯模式写入即可。以上的程序可通用到任何24Cxx 系列所应用的程序。
在实际应用中要注意的便是存储空间的分配,已经空间长度的输入,这样EEPROM的使用便能得心应手
void I2C_EE_Init()
{
/* depending on the EEPROM Address selected in the i2c_ee.h file */
#ifdef EEPROM_Block0_ADDRESS
/* Select the EEPROM Block0 to write on */
EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
#endif
#ifdef EEPROM_Block1_ADDRESS
/* Select the EEPROM Block1 to write on */
EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
#endif
#ifdef EEPROM_Block2_ADDRESS
/* Select the EEPROM Block2 to write on */
EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
#endif
#ifdef EEPROM_Block3_ADDRESS
/* Select the EEPROM Block3 to write on */
EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
#endif
}
void I2C_ByteWrite(u8* pBuffer, u8 WriteAddr)//дһ¸ö×Ö½Ú
{
I2C_GenerateSTART(I2C1, ENABLE); //ÆðʼÐźÅ
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//Çå³ýEV5
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);//·¢ËÍ´ÓÆ÷¼þµØÖ·
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//Çå³ýEV6
I2C_SendData(I2C1, WriteAddr);//дµÄµØÖ·00~FF
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//EV8-1
I2C_SendData(I2C1, *pBuffer); //·¢ËÍÊý¾Ý
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Çå³ýEv8
I2C_GenerateSTOP(I2C1, ENABLE);//²úÉúÍ£Ö¹ÐźÅ
}
void I2C_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)//Á¬Ðøд¶à¸ö×Ö½Ú
{
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // ÅжÏ×ÜÏßÊÇ·ñ·±Ã¦
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//EV5
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);//·¢ËÍ´ÓÆ÷¼þµØÖ·
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//Çå³ýEV6
I2C_SendData(I2C1, WriteAddr);//дµÄµØÖ·00~FF
while(! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//EV8-1
while(NumByteToWrite--)
{
I2C_SendData(I2C1, *pBuffer);
pBuffer++;
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//Çå³ýEv8
}
I2C_GenerateSTOP(I2C1, ENABLE);//²úÉúÍ£Ö¹ÐźÅ
}
void I2C_WaitEepromStandbyState(void)//µÈ´ý²Ù×÷Íê³É
{
vu16 SR1_Tmp = 0;
do
{
I2C_GenerateSTART(I2C1, ENABLE);//ÆðʼÐźÅ
SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);//¶ÁÈ¡¼Ä´æÆ÷±ê־λ
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
}
while(!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));
I2C_ClearFlag(I2C1, I2C_FLAG_AF);//Çå³ý
I2C_GenerateSTOP(I2C1, ENABLE);
}
void I2C_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)//ºÍ25X16Ò»Ñù
{
u8 NumOfPage = 0;
u8 NumOfSingle = 0;
u8 Addr = 0;
u8 count = 0;
Addr = WriteAddr % I2C_PageSize;//¿´¿´²»ÂúÒ»Ò³µÄÓÃÁ˶àÉÙ
count =I2C_PageSize-Addr;//²»ÂúһҳûÓõIJ¿·Ö
NumOfPage = NumByteToWrite / I2C_PageSize;//Ò»¸ö¶àÉÙÒ³
NumOfSingle = NumByteToWrite % I2C_PageSize;//²»ÂúÒ»Ò³×Ö½ÚÊý
if(Addr == 0)//ҳͷ¿ªÊ¼Ð´
{
if(NumOfPage == 0)//²»ÂúÒ»Ò³
{
I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_WaitEepromStandbyState();
}
else//³¬¹ýÒ»Ò³
{
while(NumOfPage--)//Õûҳд
{
I2C_PageWrite(pBuffer, WriteAddr,I2C_PageSize);
I2C_WaitEepromStandbyState();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
if(NumOfSingle!=0)//²»ÂúÕûÒ³µÄ
{
I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_WaitEepromStandbyState();
}
}
}
else//ÆðʼµØÖ·²»ÊÇҳͷ
{
if(NumOfPage== 0)
{
I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_WaitEepromStandbyState();
}
else
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / I2C_PageSize;
NumOfSingle = NumByteToWrite % I2C_PageSize;
if(count != 0)
{
I2C_PageWrite(pBuffer, WriteAddr, count);
I2C_WaitEepromStandbyState( );
WriteAddr += count;
pBuffer += count;
}
while(NumOfPage--)
{
I2C_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
I2C_WaitEepromStandbyState();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
if(NumOfSingle != 0)
{
I2C_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_WaitEepromStandbyState();
}
}
}
}
void I2C1_GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void I2C1_Config()
{
I2C_InitTypeDef I2C_InitStructure;
I2C_DeInit(I2C1);
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_OwnAddress1 =0XA0;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_ClockSpeed = 400000;
I2C_Init(I2C1, &I2C_InitStructure);//
I2C_Cmd(I2C1, ENABLE);//´ò¿ªI2C1
I2C_AcknowledgeConfig(I2C1, ENABLE);//ÔÊÐíÓ¦´ð
}
void I2C_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)//¶ÁÒ»´®Êý
{
while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));// ÅжÏ×ÜÏßÊÇ·ñ·±Ã¦
I2C_GenerateSTART(I2C1, ENABLE); //ÆðʼÐźÅ
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//EV5
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);//·¢ËÍ´ÓÆ÷¼þµØÖ·
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//Çå³ýEV6
I2C_Cmd(I2C1, ENABLE);
I2C_SendData(I2C1, ReadAddr);//Òª¶ÁÊý¾ÝµÄµØÖ·
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));//EV8-1
I2C_GenerateSTART(I2C1, ENABLE);//ÆðʼÐźÅ
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));//Çå³ýEV5
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);//¸æËßÆ÷¼þ ÎÒÒª¶ÁÊý¾Ý
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));//EV6
while(NumByteToRead)
{
if(NumByteToRead == 1)//Ê£×îºóÒ»¸öÊý¾Ýʱ
{
I2C_AcknowledgeConfig(I2C1, DISABLE);//¹Ø±ÕÓ¦´ð
I2C_GenerateSTOP(I2C1, ENABLE);// ʹÄÜÍ£Ö¹
}
if(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))//EV7 ÓнÓÊÕʱ
{
*pBuffer = I2C_ReceiveData(I2C1);//µôÓÿ⺯Êý
pBuffer++; //
NumByteToRead--;//
}
}
I2C_AcknowledgeConfig(I2C1, ENABLE);//´ò¿ªÓ¦´ð ΪÏÂÒ»´Î×¼±¸
}
1. 注意24C16的管脚7是写保护,需要把它拉低
2.如果没有级联。最好A2 A1 A0 最好都接地
2. 24C16只能挂1个
3. 24C16的A2,A1,A0都是扇区地址
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
EEPROM_ADDRESS就是八个扇区代表 0XA0 0XA2 0XA4 0XA6 0XA8 0XAA 0XAC 0XAE
Send7bit意思是前7位自己决定最后一位方向为由I2C_Direction_Transmitter决定,看源码就知道了
I2C_SendData(I2C1, WriteAddr1);决定每个扇区内256个字节的具体地址
4 24C16有8个扇区(block) 128个页 2048字节 16k位
1扇区有16页 1页有16字节
4. 24C16的IIC函数第二个地址是从0开始,每256byte进行加一次地址,也就是说每次读写最大就是256byte,超过了的,要重新写地址,第二次地址= 0+256开始,继续最大写256byte ,第三次地址是0+256+256这样循环下去写 好像24C16有自动地址溢出的功能