> 文章列表 > STM32+AT24C02实现易变参数存储

STM32+AT24C02实现易变参数存储

STM32+AT24C02实现易变参数存储

AT24C02是一个2K位串行CMOS E2PROM, 内部含有256个8位字节存储单元,该器件通过IIC总线接口进行操作, AT24C02把存储空间分为 32 页,每页可存储8个字节的数据,具有硬件数据写保护功能,100万次擦写,数据保持100年。

当你的产品有存储一些关键且易变的参数时,内置的Flash频繁在程序运行时擦写会有一些风险,而片外挂接一些Flash数据量小又没有必要,所以片外挂一个E2PROM是一种常见的解决办法,本例介绍在STM32平台上利用其提供的I2C控制器来实现对E2PROM的读写操作。

文章目录

概念说明

实现原理

嵌入式程序


概念说明

  • 页:页(Page)、扇区(Sector)、块(Block)是存储器空间的单位,定义来源于早期的软盘、硬盘等存储器发展而来,单位大小:页>扇区>块。不同厂家的、不同类型存储器的页大小不同,在AT24C02中一页等于8字节数据。
  • IIC:IIC(Inter-Integrated Circuit)是一种通用的总线协议,总线上只有一个Master,通信全由Master发起。对于硬件设计人员来说,只需要2个管脚(时钟SCL与数据SDA),极少的连接线和面积,就可以实现芯片间的通讯,对于软件开发者来说,可以使用同一个I2C驱动库,来实现实现不同器件的驱动,大大减少了软件的开发时间。I2C协议包含四种信号:起始、停止、应答和非应答信号。
    • 起始:I2C协议规定,SCL处于高电平时,SDA由高到低变化,这种信号是起始信号。表示要开始传输数据。
    • 停止:I2C协议规定,SCL处于高电平,SDA由低到高变化,这种信号是停止信号。
    • ACK与NACK:应答信号出现在1个字节传输完成之后,即第9个SCL时钟周期内,此时Master需要释放SDA总线,把总线控制权交给Slave,由于上拉电阻的作用,此时总线为高电平,如果从机正确的收到了主机发来的数据,会把SDA拉低,表示应答响应(ACK)。如果没有拉低,则无应答(NACK),下图是I2C总线电平时序示意图:


实现原理

嵌入式程序跑在STM32平台,封装正确的初始化I2C控制器以及读写接口供上层按地址读写EEPROM即可。原理示意图如下:

SCL和SDK两条线都接了10K的上拉电阻,MCU需要把这两个引脚设置为开漏输出(低电平,高阻态)而不是推挽输出,这样才能实现Master与Slave的ACK握手机制。


嵌入式程序

嵌入式代码构成也比较简单:

  • I2C控制器初始化,包括IO口配置
  • E2PROM写函数
  • E2PROM读函数

首先是初始化部分代码:

//i2c IO口初始化
static void I2C_GPIO_Config(void)
{GPIO_InitTypeDef GPIO_InitStructure;/* 使能与 I2C1 有关的时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);/*  配置SCL SDA引脚速率输出方式 */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);
}
//i2c 控制器配置
static void I2C_Mode_Configu(void)
{I2C_InitTypeDef I2C_InitStructure;I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;  /* I2C 配置 */I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;  /* 高电平数据稳定,低电平数据变化 SCL  时钟线的占空比 */I2C_InitStructure.I2C_OwnAddress1 = I2C1_OWN_ADDRESS7;I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;  /* I2C 的寻址模式 */I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;  /* 通信速率 */I2C_Init(I2C1, &I2C_InitStructure);  /* I2C1 初始化 */I2C_Cmd(I2C1, ENABLE);  /* 使能 I2C1 */
}//AT24C02 驱动初始化
void I2C_EE_Init(void)
{I2C_GPIO_Config();I2C_Mode_Config();/* 根据头文件 i2c_ee.  14 h 中的定义来选择 EEPROM 要写入的地址 */
#ifdef EEPROM_Block0_ADDRESS /* 选择 EEPROM Block0 来写入 */EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
#endif
#ifdef EEPROM_Block1_ADDRESS  /* 选择 EEPROM Block1 来写入 */EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
#endif
#ifdef EEPROM_Block2_ADDRESS  /* 选择 EEPROM Block2 来写入 */EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
#endif
#ifdef EEPROM_Block3_ADDRESS  /* 选择 EEPROM Block3 来写入 */EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
#endif
}

然后是写EEPROM实现代码:

//AT24C02页写操作
void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));I2C_GenerateSTART(I2C1, ENABLE);  /* Send START condition */while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));  /* Test on EV5 and clear it */I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);  /* Send EEPROM address for write */while (!I2C_CheckEvent(I2C1,  I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));  /* Test on EV6 and clear it */I2C_SendData(I2C1, WriteAddr);  /* Send the EEPROM's internal address to write to */while (! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));  /* Test on EV8 and clear it */while (NumByteToWrite--)  /* While there is data to be written */{I2C_SendData(I2C1, *pBuffer);    /* Send the current byte */pBuffer++;    /* Point to the next byte to be written */while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) );    /* Test on EV8 and clear it */}I2C_GenerateSTOP(I2C1, ENABLE);  /* Send STOP condition */
}
//AT24C02等待页写完毕
void I2C_EE_WaitEepromStandbyState(void)
{vu16 SR1_Tmp = 0;do{I2C_GenerateSTART(I2C1, ENABLE);    /* Send START condition */SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);    /* Read I2C1 SR1 register */I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS,  I2C_Direction_Transmitter);    /* Send EEPROM address for write */}while (!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));I2C_ClearFlag(I2C1, I2C_FLAG_AF);  /* Clear AF flag */I2C_GenerateSTOP(I2C1, ENABLE);  /* STOP condition */
}//指定起始地址写入数据
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
{u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;Addr = WriteAddr % I2C_PageSize;count = I2C_PageSize - Addr;NumOfPage = NumByteToWrite / I2C_PageSize;NumOfSingle = NumByteToWrite % I2C_PageSize;/* If WriteAddr is I2C_PageSize aligned */if (Addr == 0){/* If NumByteToWrite < I2C_PageSize */if (NumOfPage == 0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}/* If NumByteToWrite > I2C_PageSize */else{while (NumOfPage--){I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);I2C_EE_WaitEepromStandbyState();WriteAddr += I2C_PageSize;pBuffer += I2C_PageSize;}if (NumOfSingle != 0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}}}/* If WriteAddr is not I2C_PageSize aligned */else{/* If NumByteToWrite < I2C_PageSize */if (NumOfPage == 0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}/* If NumByteToWrite > I2C_PageSize */else{NumByteToWrite -= count;NumOfPage = NumByteToWrite / I2C_PageSize;NumOfSingle = NumByteToWrite % I2C_PageSize;if (count != 0){I2C_EE_PageWrite(pBuffer, WriteAddr, count);I2C_EE_WaitEepromStandbyState();WriteAddr += count;pBuffer += count;}while (NumOfPage--){I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);I2C_EE_WaitEepromStandbyState();WriteAddr += I2C_PageSize;pBuffer += I2C_PageSize;}if (NumOfSingle != 0){I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}}}
}

最后是读EEPROM的实现:

//EEprom读取操作
void I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{//*((u8 *)0x4001080c) |=0x80;while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // Added by Najoua/* Send START condition */I2C_GenerateSTART(I2C1, ENABLE);//*((u8 *)0x4001080c) &=~0x80;/* Test on EV5 and clear it */while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/* Send EEPROM address for write */I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);/* Test on EV6 and clear it */while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));/* Clear EV6 by setting again the PE bit */I2C_Cmd(I2C1, ENABLE);/* Send the EEPROM's internal address to write to */I2C_SendData(I2C1, ReadAddr);/* Test on EV8 and clear it */while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));/* Send STRAT condition a second time */I2C_GenerateSTART(I2C1, ENABLE);/* Test on EV5 and clear it */while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/* Send EEPROM address for read */I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);/* Test on EV6 and clear it */while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));/* While there is data to be read */while (NumByteToRead){if (NumByteToRead == 1){/* Disable Acknowledgement */I2C_AcknowledgeConfig(I2C1, DISABLE);/* Send STOP Condition */I2C_GenerateSTOP(I2C1, ENABLE);}/* Test on EV7 and clear it */if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)){/* Read a byte from the EEPROM */*pBuffer = I2C_ReceiveData(I2C1);/* Point to the next location where the byte read will besaved */pBuffer++;/* Decrement the read bytes counter */NumByteToRead--;}}/* Enable Acknowledgement to be ready for another reception */I2C_AcknowledgeConfig(I2C1, ENABLE);
}

十六宿舍 原创作品,转载必须标注原文链接。

©2023 Yang Li. All rights reserved.

欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。