STM32——EEPROM

STM32——EEPROM

宗旨:技术的学习是有限的,分享的精神的无限的。

一、I2C接口读写EEPROM(AT24C02)

——主模式,分别用作主发送器和主接收器。通过查询事件的方式来确保正常通信。

1、I 2C接口初始化

与其他对GPIO 复用的外设一样,它先调用了用户函数I2C_GPIO_Confi g() 配置好 I 2 C 所用的 I/O端口,然后再调用用户函数 I2C_Mode_Confi gu() 设置 I 2 C 的工作模式,并使能相关外设的时钟。

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
}

(1)EEPROM地址

AT24C02:256字节,高四位硬性规定,最低位是R/W(传输方向选择位),在制作硬件时,我们可以根据需要改变的是地址位中的 A2、A1、A0 位。原理图上面全接地,所以它的地址为 :0xA0 或 0xA1。

2、GPIO端口初始化

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);
}

3、I2C模式初始化

typedef struct
{
  uint32_t I2C_ClockSpeed;
  uint16_t I2C_Mode;
  uint16_t I2C_DutyCycle;
  uint16_t I2C_OwnAddress1;
  uint16_t I2C_Ack;
  uint16_t I2C_AcknowledgedAddress;
} I2C_InitTypeDef;

(1)I2C_Mode:本成员是选择 I 2 C 的使用方式,有 I 2 C 模式(I2C_Mode_I2C)和SMBus
模式。(I2C_Mode_SMBusDevice、I2C_Mode_SMBusHost)

(2)I2C_DutyCycle:设置的是 I 2 C 的 SCL 线时钟的占空比。在 STM32 的 I 2 C 占空比配置中有两个选择,分别为高电平时间和低电平时间之比为16
:9 (I2C_DutyCycle_16_9)和 2 :1( I2C_DutyCycle_2)。

(3)I2C_OwnAddress1:本 成 员 配 置 的 是 STM32 的 I 2 C 设 备 自 己 的 地 址,
每个 连 接 到 I 2 C 总线上的设备都要有一个自己的地址,作为主机也不例外。这个地址可以被配置为 7 位和 10 位地址。我们把这个地址设置为 0x0A (自定义宏I2C1_OWN_ADDRESS7 的值)。

(4)I2C_Ack_Enable:本成员关于 I 2 C 应答设置,设置为使能则每接收到一个字节就返回一个应答信号。配置为允许应答(I2C_Ack_Enable),这是绝大多数遵循
I 2 C标准的设备通信的要求,改为禁止应答 (I2C_Ack_Disable)往往会导致通信错误。

(5)I2C_AcknowledgeAddress:本成员选择 I 2 C 的寻址模式是 7 位还是 10 位地址。这需要根据实际连接到
I 2C 总线上设备的地址进行选择。与 EEPROM 进行通信,使用的为 7 位寻址模式(I2C_AcknowledgedAddress_7bit)。

(6)I2C_ClockSpeed:本成员设置的是 I 2 C 的传输速率,在调用初始化函数时,函数会根据我们输入的数值经过运算后把分频值写入到
I 2 C 的时钟控制寄存器。而我们写入的这个参数值不得高于 400 kHz。——400000

对结构体成员赋值完成后,我们调用库函数 I2C_Init() 根据我们的配置对 I 2 C 进行初始化, 并调用库函数 I2C_Cmd() 使能I 2 C 外设。

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 */
}

二、对EEPROM的读写操作

void I2C_Test(void)
{
  u16 i;

  printf("写入的数据\n\r");

  for ( i = 0; i <= 255; i++ ) //填充缓冲
  {
    I2c_Buf_Write[i] = i;
    printf("0x%02X ", I2c_Buf_Write[i]);
    if (i % 16 == 15)
    {
      printf("\n\r");
    }
  }

  I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256);  //将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中

  printf("\n\r 写成功\n\r");
  printf("\n\r 读出的数据\n\r");

  I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256);  //将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中

//将 I2c_Buf_Read 中的数据通过串口打印
  for (i = 0; i < 256; i++)
  {
    if (I2c_Buf_Read[i] != I2c_Buf_Write[i])
    {
      printf("0x%02X ", I2c_Buf_Read[i]);
      printf("错误:I2C EEPROM 写入与读出的数据不一致\n\r");
      return;
    }
    printf("0x%02X ", I2c_Buf_Read[i]);
    if (i % 16 == 15)
    {
      printf("\n\r");
    }
  }
  printf("I2C(AT24C02)读写测试成功\n\r");
}

功能是把数值 0 ~ 255 按顺序填入缓冲区数组,并通过串口打印到终端,接着通过用户函数I2C_EE_BufferWrite()把缓冲区的数据写入EEPROM。写入成功之后,利用用户函数 I2C_EE_BufferRead() 把数据读取出来,进行校验,判断数据是否被正确写入。

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();
      }
    }
  }
}

AT24C02 的 EEPROM 分为 32 页,每页可存储8个字节的数据,若在同一页写入超过 8 字节,则超过的部分会被写在该页的起始地址,这样部分数据会被覆盖。为了把连续的缓冲区数组按页写入 EEPROM,就需要对缓冲区进入分页处理。I2C_EE_BufferWrite() 函数根据我们输入的缓冲区大小参数 NumByteToWrite,计算出我们需要写入多少页,并计算写入位置。分页处理好之后,调用 I2C_EE_PageWrite() 函数,这个函数是与 EEPROM 进行 I 2
C通信的最底层函数,它与 STM32 的 I 2 C 库函数使用密切相关。

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 */
}

1、EEPROM页写入时序

这个页写入的函数是根据 EEPROM 的页写入时序来编写的。

调用库函数I2C_Generate START() 产生 I 2 C 的通信起始信号 S。

调用库函数I2C_Send7bitAddress() 把前面条件编译中赋值的变量EEPROM_ADDRESS 地 址 通 过 I 2 C1接口发送出去,数据传输方向为STM32的I2 C发送数据(I2C_Direction_Transmitter)。

调 用 库 函 数I2C_SendData() , 请 注 意 这 个 库 函 数 的 输 入 参 数 为WriteAddr,根据 EEPROM 的页写入时序,发送完 I 2 C 的地址后的第一个数据并不就是要写入 EEPROM 的数据, EEPROM 对这个数据解释为将要对存储矩阵写入的地址,这个参数 WriteAddr 是在我们调用 I2C_EE_PageWrite() 函数时作为参数输入的。这个库函数实际上是把数据传输到数据寄存器,再由 I 2 C 模块根据 I 2 C 协议发送出

去。

调用I2C_SendData() 函数,向 EEPROM 发送要写入的数据,根据EEPROM 的页写入时序,这些数据将会被写入到前面发送的页地址中,若连续写入超过一页的最大字节数(8个),则多出来的数据会重新从该页的起始地址连续写入,覆盖前面的数据。

调用库函数I2C_Generate STOP() 产生 I 2 C 传输结束信号,完成一次 I2 C 通信。

2、I2C事件检测

在 I 2 C的通信过程中,会产生一系列的事件,出现事件后在相应的寄存器中会产生标志位。

若发出了起始信号,会产生事件 5(EV5),即 STM32 的 I 2 C成为主机模式;继续发送完 I 2C 设备寻址并得到应答后,会产生 EV6,即 STM32 的 I 2C 成为数据发送端;之后发送数据完成会产生 EV8 等。我们在做出 I 2 C 通信操作时,可以通过循环调用库函数I2C_CheckEvent()进行事件查询,以确保上一操作完成后才进行下一操作。

3、等到EEPROM内部写入完成

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 */
}

利用了 EEPROM 在接收完数据后,启动内部周期写入数据的时间内不会对主机的请求做出应答的特性。所以利用这个函数循环发送起始信号,若检测到 EEPROM 的应答,则说明 EEPROM 已经完成上一步的数据写入,进入 Standby 状态,可以进行下一步的操作了。

三、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);
}

四、使用 I2 C读写EEPROM
流程总结

(1)配置 I/O 端口,确定并配置 I 2 C 的模式,使能 GPIO 和 I 2 C 时钟。

(2)写 :

① 检测 SDA 是否空闲。

② 按 I 2 C 协议发出起始信号。

③ 发出 7 位器件地址和写模式。

④ 要写入的存储区首地址。

⑤ 用页写入方式或字节写入方式写入数据。

⑥ 发送 I 2 C 通信结束信号。

每个操作之后要检测“事件”是否成功。写完后检测 EEPROM 是否进入Standby状态。

(3)读 :

① 检测 SDA 是否空闲。

② 按 I 2 C 协议发出起始信号。

③ 发出 7 位器件地址和写模式(伪写)。

④ 发出要读取的存储区首地址。

⑤ 重发起始信号。

⑥ 发出 7 位器件地址和读模式。

⑦ 接收数据。

类似写操作,每个操作之后要检测“事件”是否成功。

时间: 2024-10-11 12:27:48

STM32——EEPROM的相关文章

STM32 程序所占用空间计算 &amp;&amp; FLASH存储的起始地址计算

程序编译完成,会乘车program size .. 对STM32容量选型或者 计算FLASH 充当EEPROM起始地址时会用到此参数. 按照下面截图  程序空间 = (16700+732+4580)/1024 = 21.5K 但需要注意的是  程序的起始地址 为0x08000000,所以 flash的 起始地址 必须是 0x08000000 + 0x55FC(22012的16进制)  = 0x080055FC之后 其余的空间都可以作为 其他功能使用.

STM32——SPI接口

STM32--SPI接口 宗旨:技术的学习是有限的,分享的精神的无限的. 一.SPI协议[SerialPeripheral Interface] 串行外围设备接口,是一种高速全双工的通信总线.在ADC/LCD等与MCU间通信. 1.SPI信号线 SPI 包含 4 条总线,SPI 总线包含 4 条总线,分别为SS .SCK.MOSI.MISO. (1)SS(SlaveSelect):片选信号线,当有多个 SPI 设备与 MCU 相连时,每个设备的这个片选信号线是与 MCU 单独的引脚相连的,而其他

基于STM32的触摸屏学习笔记

本文共有三个内容:一.电阻触摸屏的原理:二.XPT2046的控制字与数字接口:三.程序源码讲解(参考正点原子的代码) 一.电阻触摸屏的原理,上图: 图上的文字介绍了触摸的原理,下面给总结一下触摸的原理: 触摸屏工作主要是两个电阻屏(上下两层)在工作,如上图,当某一层电级加上电压时,会在该网络上形成电压梯度.如果有外力使得上下两层在某一点接触,则在未加电压的那一层可以测得接触点的电压,从而得出接触点的坐标(X或Y).举个例子:当我们在上层的电极间(Y+和Y-)加上电压,则会在上层形成电压梯度(这里

STM32 I2C 總線佔用問題解析

这几天解决 STM32 MCU的I2C 总线占用(bus BUSY) 问题,觉得是不错的学习,从文中可得知I2C问题的思考逻辑逻,文末并指出经常出错的问题点,在此分享给大家. 问题描述 STM32F207 MCU有三组I2C,I2C1~I2C3,此项目的初版硬件使用I2C1接三颗chip (DSP/Codec/EEPROM),运作良好. 但新硬件把Codec/EEPROM移到I2C3,DSP还是留在I2C1,发现I2C3有机会传输失败,失败時log打总线占用(Bus BUSY) 查MCU dat

STM32 SPI 通信

SPI  是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口.是 Motorola首先在其 MC68HCXX 系列处理器上定义的. SPI 接口主要应用在  EEPROM, FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间.SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信

STM32 对内部FLASH读写接口函数

因为要用内部FLASH代替外部EEPROM,把参数放在STM32的0x08000000+320K处,其中20K是bootloader,300K是应用程序. 原理:先要把整页FLASH的内容搬到RAM中,然后在RAM中改动,然后擦除整页FLASH,再把改动后的内容写入原Flash页.下面程序调试通过. /******************************************************************************** Function Name  :

单片机(simple chip microcomputer)概论、51单片机、MCS-51单片机、STM32、ARM

catalogue 1. 单片机概论 2. 51单片机 3. MCS-51单片机 4. QX-MINI51开发板(STC89C52芯片) 5. STM32单片机 6. stm32f103zet6开发板实验 1. 单片机概论 0x1: 仿真器 单片机应用系统的软硬件调试需要专门的开发工具,称为"单片机开发系统"或"仿真机".常用的开发方式是把开发系统(例如PC)中的CPU和RAM暂时出借给用户系统,利用开发系统对用户系统的软硬件进行调试(又称仿真),然后将调试好的程序

STM32的flash数据页转存过程分析!

stm32模拟eeprom要实现flash数据页转存,实现函数为 1 /** 2 * @brief Transfers last updated variables data from the full Page to 3 * an empty one. 4 * @param VirtAddress: 16 bit virtual address of the variable 5 * @param Data: 16 bit data to be written as variable valu

STM32 硬件I2C 到底是不是个坑?

/** ****************************************************************************** * @author    Maoxiao Hu * @version   V1.0.0 * @date       May-2015 ****************************************************************************** * < COPYRIGHT 2015 IS