存储空间分配 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有自动地址溢出的功能