STM32速成笔记:EEPROM(AT24C02)使用指南

文章目录

  • 一、AT24C02简介
  • 二、AT24C02引脚
  • 三、AT24C02寻址
  • 四、AT24C02读/写操作
  • 4.1 AT24C02写操作
  • 4.2 AT24C02读操作
  • 五、AT24C02程序
  • 六、应用实例
  • 七、拓展应用
  • 一、AT24C02简介

    AT24C01/02/04/08/16…是一个1K/2K/4K/8K/16K位电可擦除PROM,内部含有128/256/512/1024/2048个8位字节,AT24C01有一个8字节页写缓冲器,AT24C02/04/08/16有一个16字节页写缓冲器。电压可允许低至1.8V,待机电流和工作电流分别为1uA和1mA。该器件通过I2C总线接口进行操作,这里就不再对IIC做详细介绍了,具体可见外设系列OLED篇。

    二、AT24C02引脚

    AT24C02引脚定义

    三、AT24C02寻址

    使能芯片读写操作后,EEPROM都要求有8位的器件地址信息。

    AT24C02地址信息

    器件地址信息由"1"、"0"序列组成,前4位对于所有串行EEPROM都是一样的。对于24C02/32/64,随后3位A2、A1和A0为器件地址位,必须与硬件输入引脚保持一致。

    四、AT24C02读/写操作

    4.1 AT24C02写操作

    写操作要求主设备发送器件地址,收到应答信号后,先接收8位的字地址。接收到这个地址后EEPROM应答"0"(ACK),然后再是一个8位数据。在接收8位数据后,EEPROM应答"0"(ACK),接着必须由主器件发送停止条件来终止写序列。时序图如下

    写操作时序图

    24C02器件按8字节/页执行页写,24C04/08/16器件按16字节/页执行页写,24C32/64器件按32字节/页执行页写。页写初始化与字节写相同,只是主器件不会在第一个数据后发送停止条件,而是在EEPROMEEPROM收到每个数据后都应答“0”。最后仍需由主器件发送停止条件,终止写序列。

    接收到每个数据后,字地址的低3位 (24C02) 或4位(24C04/08/16) 或5位(24C32/64)内部自动加1,高位地址位不变,维持在当前页内。当内部产生的字地址达到该页边界地址时,随后的数据将写入该页的页首。如果超过8个 (24C02) 或16个 (24C04/08/16) 或32个(24C32/64) 数据传送给了EEPROM,字地址将回转到该页的首字节,先前的字节将会被覆盖。

    4.2 AT24C02读操作

    AT24C02的读操作有三种,分别是当前地址读,随机读和顺序读。

  • 当前地址读
    内部地址计数器保存着上次访问时最后一个地址加1的值。只要芯片有电,该地址就一直保存当读到最后页的最后字节,地址会回转到0。当写到某页尾的最后一个字节,地址会回转到该页的首字节。接收器件地址(读/写选择位为"1") 且EEPROM应答ACK后,当前地址的数据就随时钟送出。主器件无需应答"0",但需发送停止条件。当前地址读操作时序图如下
  • 读当前地址时序图

  • 随机读
    随机读需先写一个目标字地址,一旦EEPROM接收器件地址和字地址并应答了ACK,主器件就产生一个重复的起始条件。然后,主器件发送器件地址(读/写选择位为"1") ,EEPROM应答ACK,并随时钟送出数据。主器件无需应答"0",但需发送停止条件。这里的随机读就是读取任意一个字地址的数据,并不是随即返回一个数据的意思。随机读时序图如下
  • 随机读时序图

  • 顺序读
    顺序读可以通过“当前地址读”或“随机读”启动。主器件接收到一个数据后,应答ACK。只要EEPROM接收到ACK,将自动增加字地址并继续随时钟发送后面的数据。若达到存储器地址末尾,地址自动回转到0,仍可继续顺序读取数据。主器件不应答"0",而发送停止条件,即可结束顺序读操作。顺序读时序图如下
  • 顺序读时序图

    五、AT24C02程序

    这里给出一个AT24C02的程序,仅供参考

    /*******************************************************************************
    * 函 数 名         : IIC_Init
    * 函数功能		   : IIC初始化
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    void IIC_Init(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC,ENABLE);
    	
    	GPIO_InitStructure.GPIO_Pin=IIC_SCL_PIN;
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
    	GPIO_Init(IIC_SCL_PORT,&GPIO_InitStructure);
    	
    	GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
    	GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
    	
    	IIC_SCL=1;
    	IIC_SDA=1;	
    }
    /*******************************************************************************
    * 函 数 名         : SDA_OUT
    * 函数功能		   : SDA输出配置	   
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    void SDA_OUT(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	
    	GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
    	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
    	GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
    }
    
    /*******************************************************************************
    * 函 数 名         : SDA_IN
    * 函数功能		   : SDA输入配置	   
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    void SDA_IN(void)
    {
    	GPIO_InitTypeDef  GPIO_InitStructure;
    	
    	GPIO_InitStructure.GPIO_Pin=IIC_SDA_PIN;
    	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
    	GPIO_Init(IIC_SDA_PORT,&GPIO_InitStructure);
    }
    
    /*******************************************************************************
    * 函 数 名         : IIC_Start
    * 函数功能		   : 产生IIC起始信号   
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    void IIC_Start(void)
    {
    	SDA_OUT();     //sda线输出
    	IIC_SDA=1;	  	  
    	IIC_SCL=1;
    	delay_us(5);
     	IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
    	delay_us(6);
    	IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
    }	
    
    /*******************************************************************************
    * 函 数 名         : IIC_Stop
    * 函数功能		   : 产生IIC停止信号   
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    void IIC_Stop(void)
    {
    	SDA_OUT();//sda线输出
    	IIC_SCL=0;
    	IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
     	IIC_SCL=1; 
    	delay_us(6); 
    	IIC_SDA=1;//发送I2C总线结束信号
    	delay_us(6);							   	
    }
    
    /*******************************************************************************
    * 函 数 名         : IIC_Wait_Ack
    * 函数功能		   : 等待应答信号到来   
    * 输    入         : 无
    * 输    出         : 1,接收应答失败
            			 0,接收应答成功
    *******************************************************************************/
    u8 IIC_Wait_Ack(void)
    {
    	u8 tempTime=0;
    	SDA_IN();      //SDA设置为输入  
    	IIC_SDA=1;
    	delay_us(1);	   
    	IIC_SCL=1;
    	delay_us(1);	 
    	while(READ_SDA)
    	{
    		tempTime++;
    		if(tempTime>250)
    		{
    			IIC_Stop();
    			return 1;
    		}
    	}
    	IIC_SCL=0;//时钟输出0 	   
    	return 0;  
    } 
    
    /*******************************************************************************
    * 函 数 名         : IIC_Ack
    * 函数功能		   : 产生ACK应答  
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    void IIC_Ack(void)
    {
    	IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=0;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(5);
    	IIC_SCL=0;
    }
    
    /*******************************************************************************
    * 函 数 名         : IIC_NAck
    * 函数功能		   : 产生NACK非应答  
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/		    
    void IIC_NAck(void)
    {
    	IIC_SCL=0;
    	SDA_OUT();
    	IIC_SDA=1;
    	delay_us(2);
    	IIC_SCL=1;
    	delay_us(5);
    	IIC_SCL=0;
    }	
    
    /*******************************************************************************
    * 函 数 名         : IIC_Send_Byte
    * 函数功能		   : IIC发送一个字节 
    * 输    入         : txd:发送一个字节
    * 输    出         : 无
    *******************************************************************************/		  
    void IIC_Send_Byte(u8 txd)
    {                        
        u8 t;   
    	SDA_OUT(); 	    
        IIC_SCL=0;//拉低时钟开始数据传输
        for(t=0;t<8;t++)
        {              
            if((txd&0x80)>0) //0x80  1000 0000
    			IIC_SDA=1;
    		else
    			IIC_SDA=0;
            txd<<=1; 	  
    		delay_us(2);   //对TEA5767这三个延时都是必须的
    		IIC_SCL=1;
    		delay_us(2); 
    		IIC_SCL=0;	
    		delay_us(2);
        }	 
    } 
    
    /*******************************************************************************
    * 函 数 名         : IIC_Read_Byte
    * 函数功能		   : IIC读一个字节 
    * 输    入         : ack=1时,发送ACK,ack=0,发送nACK 
    * 输    出         : 应答或非应答
    *******************************************************************************/  
    u8 IIC_Read_Byte(u8 ack)
    {
    	u8 i,receive=0;
    	SDA_IN();//SDA设置为输入
        for(i=0;i<8;i++ )
    	{
            IIC_SCL=0; 
            delay_us(2);
    		IIC_SCL=1;
            receive<<=1;
            if(READ_SDA)receive++;   
    		delay_us(1); 
        }					 
        if (!ack)
            IIC_NAck();//发送nACK
        else
            IIC_Ack(); //发送ACK   
        return receive;
    }
    /*******************************************************************************
    * 函 数 名         : AT24CXX_Init
    * 函数功能		   : AT24CXX初始化
    * 输    入         : 无
    * 输    出         : 无
    *******************************************************************************/
    void AT24CXX_Init(void)
    {
    	IIC_Init();//IIC初始化
    }
    
    /*******************************************************************************
    * 函 数 名         : AT24CXX_ReadOneByte
    * 函数功能		   : 在AT24CXX指定地址读出一个数据
    * 输    入         : ReadAddr:开始读数的地址 
    * 输    出         : 读到的数据
    *******************************************************************************/
    u8 AT24CXX_ReadOneByte(u16 ReadAddr)
    {				  
    	u8 temp=0;		  	    																 
        IIC_Start();  
    	if(EE_TYPE>AT24C16)
    	{
    		IIC_Send_Byte(0XA0);	   //发送写命令
    		IIC_Wait_Ack();
    		IIC_Send_Byte(ReadAddr>>8);//发送高地址	    
    	}
    	else 
    	{
    		IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));   //发送器件地址0XA0,写数据
    	} 	   
    	IIC_Wait_Ack(); 
        IIC_Send_Byte(ReadAddr%256);   //发送低地址
    	IIC_Wait_Ack();	    
    	IIC_Start();  	 	   
    	IIC_Send_Byte(0XA1);           //进入接收模式			   
    	IIC_Wait_Ack();	 
        temp=IIC_Read_Byte(0);		   
        IIC_Stop();//产生一个停止条件	    
    	return temp;
    }
    
    /*******************************************************************************
    * 函 数 名         : AT24CXX_WriteOneByte
    * 函数功能		   : 在AT24CXX指定地址写入一个数据
    * 输    入         : WriteAddr  :写入数据的目的地址 
    					 DataToWrite:要写入的数据
    * 输    出         : 无
    *******************************************************************************/
    void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
    {				   	  	    																 
        IIC_Start();  
    	if(EE_TYPE>AT24C16)
    	{
    		IIC_Send_Byte(0XA0);	    //发送写命令
    		IIC_Wait_Ack();
    		IIC_Send_Byte(WriteAddr>>8);//发送高地址	  
    	}
    	else 
    	{
    		IIC_Send_Byte(0XA0+((WriteAddr/256)<<1));   //发送器件地址0XA0,写数据
    	} 	 
    	IIC_Wait_Ack();	   
        IIC_Send_Byte(WriteAddr%256);   //发送低地址
    	IIC_Wait_Ack(); 	 										  		   
    	IIC_Send_Byte(DataToWrite);     //发送字节							   
    	IIC_Wait_Ack();  		    	   
        IIC_Stop();//产生一个停止条件 
    	delay_ms(10);	 
    }
    
    /*******************************************************************************
    * 函 数 名         : AT24CXX_WriteLenByte
    * 函数功能		   : 在AT24CXX里面的指定地址开始写入长度为Len的数据
    					 用于写入16bit或者32bit的数据
    * 输    入         : WriteAddr  :写入数据的目的地址 
    					 DataToWrite:要写入的数据
    					 Len        :要写入数据的长度2,4
    * 输    出         : 无
    *******************************************************************************/
    void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
    {  	
    	u8 t;
    	for(t=0;t<Len;t++)
    	{
    		AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
    	}												    
    }
    
    /*******************************************************************************
    * 函 数 名         : AT24CXX_ReadLenByte
    * 函数功能		   : 在AT24CXX里面的指定地址开始读出长度为Len的数据
    					 用于读出16bit或者32bit的数据
    * 输    入         : ReadAddr   :开始读出的地址 
    					 Len        :要读出数据的长度2,4
    * 输    出         : 读取的数据
    *******************************************************************************/
    u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
    {  	
    	u8 t;
    	u32 temp=0;
    	for(t=0;t<Len;t++)
    	{
    		temp<<=8;
    		temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 	 				   
    	}
    	return temp;												    
    }
    
    /*******************************************************************************
    * 函 数 名         : AT24CXX_Check
    * 函数功能		   : 检查AT24CXX是否正常
    * 输    入         : 无
    * 输    出         : 1:检测失败,0:检测成功
    *******************************************************************************/
    u8 AT24CXX_Check(void)
    {
    	u8 temp;
    	temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX			   
    	if(temp==0x36)return 0;		   
    	else//排除第一次初始化的情况
    	{
    		AT24CXX_WriteOneByte(255,0X36);
    	    temp=AT24CXX_ReadOneByte(255);	  
    		if(temp==0X36)return 0;
    	}
    	return 1;											  
    }
    
    /*******************************************************************************
    * 函 数 名         : AT24CXX_Read
    * 函数功能		   : 在AT24CXX里面的指定地址开始读出指定个数的数据
    * 输    入         : ReadAddr :开始读出的地址 对24c02为0~255
    					 pBuffer  :数据数组首地址
    					 NumToRead:要读出数据的个数
    * 输    出         : 无
    *******************************************************************************/
    void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
    {
    	while(NumToRead)
    	{
    		*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);	
    		NumToRead--;
    	}
    } 
    
    /*******************************************************************************
    * 函 数 名         : AT24CXX_Write
    * 函数功能		   : 在AT24CXX里面的指定地址开始写入指定个数的数据
    * 输    入         : WriteAddr :开始写入的地址 对24c02为0~255
    					 pBuffer  :数据数组首地址
    					 NumToRead:要读出数据的个数
    * 输    出         : 无
    *******************************************************************************/
    void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
    {
    	while(NumToWrite--)
    	{
    		AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
    		WriteAddr++;
    		pBuffer++;
    	}
    }
    

    .h文件如下

    // 核心板使用的是24c02,所以定义EE_TYPE为AT24C02
    // 可修改成AT24CXX系列中的任意一个
    #define EE_TYPE   AT24C02
    
    // IIC函数
    void IIC_Init(void);   // 初始化IIC的IO口				 
    void IIC_Start(void);   // 发送IIC开始信号
    void IIC_Stop(void);   // 发送IIC停止信号
    void IIC_Send_Byte(u8 txd);   // IIC发送一个字节
    u8 IIC_Read_Byte(u8 ack);   // IIC读取一个字节
    u8 IIC_Wait_Ack(void);   // IIC等待ACK信号
    void IIC_Ack(void);   // IIC发送ACK信号
    void IIC_NAck(void);   // IIC不发送ACK信号
    
    u8 AT24CXX_ReadOneByte(u16 ReadAddr);   //指定地址读取一个字节
    void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite);   // 指定地址写入一个字节
    void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len);   // 指定地址开始写入指定长度的数据
    u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len);   // 指定地址开始读取指定长度数据
    void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);   // 从指定地址开始写入指定长度的数据
    void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);   // 从指定地址开始读出指定长度的数据
    
    u8 AT24CXX_Check(void);   // 检查器件
    void AT24CXX_Init(void);   // 初始化IIC
    

    六、应用实例

    给AT24C02写入一个数据,读取一次确认写入正常。注释掉写入程序,拔掉电源。一段时间后,插上电源再次读取之前写入时的地址的值,串口打印结果。AT24C02的初始化程序如下

    	AT24CXX_Init();   // AT24C02初始化
    	while(AT24CXX_Check())  //检测AT24C02是否正常
    	{
    		printf("AT24C02检测不正常!\r\n");
    		delay_ms(500);
    	}
    	printf("AT24C02检测正常!\r\n");
    

    main函数如下

    u8 gWData = 0xaa;   // 准备要写入的数据
    u8 gRData = 0xaa;   // 存储读出的数据
    
    int main(void)
    {
    	Med_Mcu_Iint();   // 系统初始化
    	
    	AT24CXX_WriteOneByte(0,gWData);
    	printf("写入的数据是:%d\r\n",gWData);
    	
    	gRData = AT24CXX_ReadOneByte(0);
    	printf("读取的数据是:%d\r\n",gRData);
    	
    	while(1)
      {
    	}
    }
    
    

    串口打印结果如下

    串口打印结果

    七、拓展应用

    AT24C02这种掉电数据不丢失的特性,使得它可以存储一些重要数据。比如将一些校准数据写入AT24C02中,再次上电之后就不会丢失。或者用AT24C02记录开机次数等。这些原理与应用实例中的例子原理相同,这里就不再赘述了。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32速成笔记:EEPROM(AT24C02)使用指南

    发表评论