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+AT24C02实现易变参数存储_第1张图片


实现原理

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

STM32+AT24C02实现易变参数存储_第2张图片

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 be
      saved */
      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.

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

你可能感兴趣的:(MCU平台,stm32,单片机,嵌入式硬件)