一、I2C接口读写EEPROM(AT24C02)

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

1、I 2C接口初始化

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

  1. void I2C_EE_Init(void)
  2. {
  3. I2C_GPIO_Config();
  4. I2C_Mode_Config();
  5.  
  6. /* 根据头文件 i2c_ee. 14 h 中的定义来选择 EEPROM 要写入的地址 */
  7. #ifdef EEPROM_Block0_ADDRESS /* 选择 EEPROM Block0 来写入 */
  8. EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
  9. #endif
  10. #ifdef EEPROM_Block1_ADDRESS /* 选择 EEPROM Block1 来写入 */
  11. EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
  12. #endif
  13. #ifdef EEPROM_Block2_ADDRESS /* 选择 EEPROM Block2 来写入 */
  14. EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
  15. #endif
  16. #ifdef EEPROM_Block3_ADDRESS /* 选择 EEPROM Block3 来写入 */
  17. EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
  18. #endif
  19. }

(1)EEPROM地址

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

2、GPIO端口初始化

  1. static void I2C_GPIO_Config(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStructure;
  4.  
  5. /* 使能与 I2C1 有关的时钟 */
  6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  7. RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
  8.  
  9. /* 配置SCL SDA引脚速率输出方式 */
  10. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  11. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  12. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
  13. GPIO_Init(GPIOB, &GPIO_InitStructure);
  14. }


3、I2C模式初始化

  1. typedef struct
  2. {
  3. uint32_t I2C_ClockSpeed;
  4. uint16_t I2C_Mode;
  5. uint16_t I2C_DutyCycle;
  6. uint16_t I2C_OwnAddress1;
  7. uint16_t I2C_Ack;
  8. uint16_t I2C_AcknowledgedAddress;
  9. } 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 外设。

  1. static void I2C_Mode_Configu(void)
  2. {
  3. I2C_InitTypeDef I2C_InitStructure;
  4.  
  5. I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; /* I2C 配置 */
  6.  
  7. I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; /* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
  8. I2C_InitStructure.I2C_OwnAddress1 = I2C1_OWN_ADDRESS7;
  9. I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
  10. I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /* I2C 的寻址模式 */
  11. I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; /* 通信速率 */
  12.  
  13. I2C_Init(I2C1, &I2C_InitStructure); /* I2C1 初始化 */
  14. I2C_Cmd(I2C1, ENABLE); /* 使能 I2C1 */
  15. }

二、对EEPROM的读写操作

  1. void I2C_Test(void)
  2. {
  3. u16 i;
  4.  
  5. printf("写入的数据\n\r");
  6.  
  7. for ( i = 0; i <= 255; i++ ) //填充缓冲
  8. {
  9. I2c_Buf_Write[i] = i;
  10. printf("0x%02X ", I2c_Buf_Write[i]);
  11. if (i % 16 == 15)
  12. {
  13. printf("\n\r");
  14. }
  15. }
  16.  
  17. I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256); //将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中
  18.  
  19. printf("\n\r 写成功\n\r");
  20. printf("\n\r 读出的数据\n\r");
  21.  
  22. I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256); //将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中
  23.  
  24. //将 I2c_Buf_Read 中的数据通过串口打印
  25. for (i = 0; i < 256; i++)
  26. {
  27. if (I2c_Buf_Read[i] != I2c_Buf_Write[i])
  28. {
  29. printf("0x%02X ", I2c_Buf_Read[i]);
  30. printf("错误:I2C EEPROM 写入与读出的数据不一致\n\r");
  31. return;
  32. }
  33. printf("0x%02X ", I2c_Buf_Read[i]);
  34. if (i % 16 == 15)
  35. {
  36. printf("\n\r");
  37. }
  38. }
  39. printf("I2C(AT24C02)读写测试成功\n\r");
  40. }

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

  1. void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
  2. {
  3. u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
  4.  
  5. Addr = WriteAddr % I2C_PageSize;
  6. count = I2C_PageSize - Addr;
  7. NumOfPage = NumByteToWrite / I2C_PageSize;
  8. NumOfSingle = NumByteToWrite % I2C_PageSize;
  9.  
  10. /* If WriteAddr is I2C_PageSize aligned */
  11. if (Addr == 0)
  12. {
  13. /* If NumByteToWrite < I2C_PageSize */
  14. if (NumOfPage == 0)
  15. {
  16. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  17. I2C_EE_WaitEepromStandbyState();
  18. }
  19. /* If NumByteToWrite > I2C_PageSize */
  20. else
  21. {
  22. while (NumOfPage--)
  23. {
  24. I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
  25. I2C_EE_WaitEepromStandbyState();
  26. WriteAddr += I2C_PageSize;
  27. pBuffer += I2C_PageSize;
  28. }
  29.  
  30. if (NumOfSingle != 0)
  31. {
  32. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  33. I2C_EE_WaitEepromStandbyState();
  34. }
  35. }
  36. }
  37. /* If WriteAddr is not I2C_PageSize aligned */
  38. else
  39. {
  40. /* If NumByteToWrite < I2C_PageSize */
  41. if (NumOfPage == 0)
  42. {
  43. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  44. I2C_EE_WaitEepromStandbyState();
  45. }
  46. /* If NumByteToWrite > I2C_PageSize */
  47. else
  48. {
  49. NumByteToWrite -= count;
  50. NumOfPage = NumByteToWrite / I2C_PageSize;
  51. NumOfSingle = NumByteToWrite % I2C_PageSize;
  52.  
  53. if (count != 0)
  54. {
  55. I2C_EE_PageWrite(pBuffer, WriteAddr, count);
  56. I2C_EE_WaitEepromStandbyState();
  57. WriteAddr += count;
  58. pBuffer += count;
  59. }
  60.  
  61. while (NumOfPage--)
  62. {
  63. I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
  64. I2C_EE_WaitEepromStandbyState();
  65. WriteAddr += I2C_PageSize;
  66. pBuffer += I2C_PageSize;
  67. }
  68. if (NumOfSingle != 0)
  69. {
  70. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  71. I2C_EE_WaitEepromStandbyState();
  72. }
  73. }
  74. }
  75. }

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

  1. void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
  2. {
  3. while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
  4.  
  5. I2C_GenerateSTART(I2C1, ENABLE); /* Send START condition */
  6. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /* Test on EV5 and clear it */
  7.  
  8.  
  9. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Send EEPROM address for write */
  10. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /* Test on EV6 and clear it */
  11.  
  12.  
  13. I2C_SendData(I2C1, WriteAddr); /* Send the EEPROM's internal address to write to */
  14. while (! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /* Test on EV8 and clear it */
  15.  
  16. while (NumByteToWrite--) /* While there is data to be written */
  17. {
  18. I2C_SendData(I2C1, *pBuffer); /* Send the current byte */
  19. pBuffer++; /* Point to the next byte to be written */
  20. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ); /* Test on EV8 and clear it */
  21. }
  22.  
  23. I2C_GenerateSTOP(I2C1, ENABLE); /* Send STOP condition */
  24. }

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内部写入完成

  1. void I2C_EE_WaitEepromStandbyState(void)
  2. {
  3. vu16 SR1_Tmp = 0;
  4. do
  5. {
  6. I2C_GenerateSTART(I2C1, ENABLE); /* Send START condition */
  7. SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1); /* Read I2C1 SR1 register */
  8. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Send EEPROM address for write */
  9. }
  10. while (!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));
  11.  
  12. I2C_ClearFlag(I2C1, I2C_FLAG_AF); /* Clear AF flag */
  13. I2C_GenerateSTOP(I2C1, ENABLE); /* STOP condition */
  14. }

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

三、EEPROM读

  1. void I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
  2. {
  3. //*((u8 *)0x4001080c) |=0x80;
  4. while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // Added by Najoua
  5.  
  6. /* Send START condition */
  7. I2C_GenerateSTART(I2C1, ENABLE);
  8. //*((u8 *)0x4001080c) &=~0x80;
  9.  
  10. /* Test on EV5 and clear it */
  11. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  12.  
  13. /* Send EEPROM address for write */
  14. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
  15.  
  16. /* Test on EV6 and clear it */
  17. while (!I2C_CheckEvent(I2C1,
  18. I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
  19.  
  20. /* Clear EV6 by setting again the PE bit */
  21. I2C_Cmd(I2C1, ENABLE);
  22.  
  23. /* Send the EEPROM's internal address to write to */
  24. I2C_SendData(I2C1, ReadAddr);
  25.  
  26. /* Test on EV8 and clear it */
  27. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  28.  
  29. /* Send STRAT condition a second time */
  30. I2C_GenerateSTART(I2C1, ENABLE);
  31.  
  32. /* Test on EV5 and clear it */
  33. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  34.  
  35. /* Send EEPROM address for read */
  36. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);
  37.  
  38. /* Test on EV6 and clear it */
  39. while (!I2C_CheckEvent(I2C1,
  40. I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
  41.  
  42. /* While there is data to be read */
  43. while (NumByteToRead)
  44. {
  45. if (NumByteToRead == 1)
  46. {
  47. /* Disable Acknowledgement */
  48. I2C_AcknowledgeConfig(I2C1, DISABLE);
  49.  
  50. /* Send STOP Condition */
  51. I2C_GenerateSTOP(I2C1, ENABLE);
  52. }
  53.  
  54. /* Test on EV7 and clear it */
  55. if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
  56. {
  57. /* Read a byte from the EEPROM */
  58. *pBuffer = I2C_ReceiveData(I2C1);
  59.  
  60. /* Point to the next location where the byte read will be
  61. saved */
  62. pBuffer++;
  63.  
  64. /* Decrement the read bytes counter */
  65. NumByteToRead--;
  66. }
  67. }
  68.  
  69. /* Enable Acknowledgement to be ready for another reception */
  70. I2C_AcknowledgeConfig(I2C1, ENABLE);
  71. }

四、使用 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 位器件地址和读模式。

⑦ 接收数据。

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

一、I2C接口读写EEPROM(AT24C02)

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

1、I 2C接口初始化

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

  1. void I2C_EE_Init(void)
  2. {
  3. I2C_GPIO_Config();
  4. I2C_Mode_Config();
  5.  
  6. /* 根据头文件 i2c_ee. 14 h 中的定义来选择 EEPROM 要写入的地址 */
  7. #ifdef EEPROM_Block0_ADDRESS /* 选择 EEPROM Block0 来写入 */
  8. EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
  9. #endif
  10. #ifdef EEPROM_Block1_ADDRESS /* 选择 EEPROM Block1 来写入 */
  11. EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
  12. #endif
  13. #ifdef EEPROM_Block2_ADDRESS /* 选择 EEPROM Block2 来写入 */
  14. EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
  15. #endif
  16. #ifdef EEPROM_Block3_ADDRESS /* 选择 EEPROM Block3 来写入 */
  17. EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
  18. #endif
  19. }

(1)EEPROM地址

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

2、GPIO端口初始化

  1. static void I2C_GPIO_Config(void)
  2. {
  3. GPIO_InitTypeDef GPIO_InitStructure;
  4.  
  5. /* 使能与 I2C1 有关的时钟 */
  6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  7. RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
  8.  
  9. /* 配置SCL SDA引脚速率输出方式 */
  10. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  11. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  12. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
  13. GPIO_Init(GPIOB, &GPIO_InitStructure);
  14. }


3、I2C模式初始化

  1. typedef struct
  2. {
  3. uint32_t I2C_ClockSpeed;
  4. uint16_t I2C_Mode;
  5. uint16_t I2C_DutyCycle;
  6. uint16_t I2C_OwnAddress1;
  7. uint16_t I2C_Ack;
  8. uint16_t I2C_AcknowledgedAddress;
  9. } 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 外设。

  1. static void I2C_Mode_Configu(void)
  2. {
  3. I2C_InitTypeDef I2C_InitStructure;
  4.  
  5. I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; /* I2C 配置 */
  6.  
  7. I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; /* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
  8. I2C_InitStructure.I2C_OwnAddress1 = I2C1_OWN_ADDRESS7;
  9. I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
  10. I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /* I2C 的寻址模式 */
  11. I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; /* 通信速率 */
  12.  
  13. I2C_Init(I2C1, &I2C_InitStructure); /* I2C1 初始化 */
  14. I2C_Cmd(I2C1, ENABLE); /* 使能 I2C1 */
  15. }

二、对EEPROM的读写操作

  1. void I2C_Test(void)
  2. {
  3. u16 i;
  4.  
  5. printf("写入的数据\n\r");
  6.  
  7. for ( i = 0; i <= 255; i++ ) //填充缓冲
  8. {
  9. I2c_Buf_Write[i] = i;
  10. printf("0x%02X ", I2c_Buf_Write[i]);
  11. if (i % 16 == 15)
  12. {
  13. printf("\n\r");
  14. }
  15. }
  16.  
  17. I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256); //将 I2c_Buf_Write 中顺序递增的数据写入 EERPOM 中
  18.  
  19. printf("\n\r 写成功\n\r");
  20. printf("\n\r 读出的数据\n\r");
  21.  
  22. I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256); //将 EEPROM 读出数据顺序保持到 I2c_Buf_Read 中
  23.  
  24. //将 I2c_Buf_Read 中的数据通过串口打印
  25. for (i = 0; i < 256; i++)
  26. {
  27. if (I2c_Buf_Read[i] != I2c_Buf_Write[i])
  28. {
  29. printf("0x%02X ", I2c_Buf_Read[i]);
  30. printf("错误:I2C EEPROM 写入与读出的数据不一致\n\r");
  31. return;
  32. }
  33. printf("0x%02X ", I2c_Buf_Read[i]);
  34. if (i % 16 == 15)
  35. {
  36. printf("\n\r");
  37. }
  38. }
  39. printf("I2C(AT24C02)读写测试成功\n\r");
  40. }

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

  1. void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
  2. {
  3. u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
  4.  
  5. Addr = WriteAddr % I2C_PageSize;
  6. count = I2C_PageSize - Addr;
  7. NumOfPage = NumByteToWrite / I2C_PageSize;
  8. NumOfSingle = NumByteToWrite % I2C_PageSize;
  9.  
  10. /* If WriteAddr is I2C_PageSize aligned */
  11. if (Addr == 0)
  12. {
  13. /* If NumByteToWrite < I2C_PageSize */
  14. if (NumOfPage == 0)
  15. {
  16. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  17. I2C_EE_WaitEepromStandbyState();
  18. }
  19. /* If NumByteToWrite > I2C_PageSize */
  20. else
  21. {
  22. while (NumOfPage--)
  23. {
  24. I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
  25. I2C_EE_WaitEepromStandbyState();
  26. WriteAddr += I2C_PageSize;
  27. pBuffer += I2C_PageSize;
  28. }
  29.  
  30. if (NumOfSingle != 0)
  31. {
  32. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  33. I2C_EE_WaitEepromStandbyState();
  34. }
  35. }
  36. }
  37. /* If WriteAddr is not I2C_PageSize aligned */
  38. else
  39. {
  40. /* If NumByteToWrite < I2C_PageSize */
  41. if (NumOfPage == 0)
  42. {
  43. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  44. I2C_EE_WaitEepromStandbyState();
  45. }
  46. /* If NumByteToWrite > I2C_PageSize */
  47. else
  48. {
  49. NumByteToWrite -= count;
  50. NumOfPage = NumByteToWrite / I2C_PageSize;
  51. NumOfSingle = NumByteToWrite % I2C_PageSize;
  52.  
  53. if (count != 0)
  54. {
  55. I2C_EE_PageWrite(pBuffer, WriteAddr, count);
  56. I2C_EE_WaitEepromStandbyState();
  57. WriteAddr += count;
  58. pBuffer += count;
  59. }
  60.  
  61. while (NumOfPage--)
  62. {
  63. I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
  64. I2C_EE_WaitEepromStandbyState();
  65. WriteAddr += I2C_PageSize;
  66. pBuffer += I2C_PageSize;
  67. }
  68. if (NumOfSingle != 0)
  69. {
  70. I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
  71. I2C_EE_WaitEepromStandbyState();
  72. }
  73. }
  74. }
  75. }

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

  1. void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
  2. {
  3. while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
  4.  
  5. I2C_GenerateSTART(I2C1, ENABLE); /* Send START condition */
  6. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /* Test on EV5 and clear it */
  7.  
  8.  
  9. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Send EEPROM address for write */
  10. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /* Test on EV6 and clear it */
  11.  
  12.  
  13. I2C_SendData(I2C1, WriteAddr); /* Send the EEPROM's internal address to write to */
  14. while (! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /* Test on EV8 and clear it */
  15.  
  16. while (NumByteToWrite--) /* While there is data to be written */
  17. {
  18. I2C_SendData(I2C1, *pBuffer); /* Send the current byte */
  19. pBuffer++; /* Point to the next byte to be written */
  20. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ); /* Test on EV8 and clear it */
  21. }
  22.  
  23. I2C_GenerateSTOP(I2C1, ENABLE); /* Send STOP condition */
  24. }

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内部写入完成

  1. void I2C_EE_WaitEepromStandbyState(void)
  2. {
  3. vu16 SR1_Tmp = 0;
  4. do
  5. {
  6. I2C_GenerateSTART(I2C1, ENABLE); /* Send START condition */
  7. SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1); /* Read I2C1 SR1 register */
  8. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Send EEPROM address for write */
  9. }
  10. while (!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));
  11.  
  12. I2C_ClearFlag(I2C1, I2C_FLAG_AF); /* Clear AF flag */
  13. I2C_GenerateSTOP(I2C1, ENABLE); /* STOP condition */
  14. }

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

三、EEPROM读

  1. void I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
  2. {
  3. //*((u8 *)0x4001080c) |=0x80;
  4. while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // Added by Najoua
  5.  
  6. /* Send START condition */
  7. I2C_GenerateSTART(I2C1, ENABLE);
  8. //*((u8 *)0x4001080c) &=~0x80;
  9.  
  10. /* Test on EV5 and clear it */
  11. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  12.  
  13. /* Send EEPROM address for write */
  14. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
  15.  
  16. /* Test on EV6 and clear it */
  17. while (!I2C_CheckEvent(I2C1,
  18. I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
  19.  
  20. /* Clear EV6 by setting again the PE bit */
  21. I2C_Cmd(I2C1, ENABLE);
  22.  
  23. /* Send the EEPROM's internal address to write to */
  24. I2C_SendData(I2C1, ReadAddr);
  25.  
  26. /* Test on EV8 and clear it */
  27. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  28.  
  29. /* Send STRAT condition a second time */
  30. I2C_GenerateSTART(I2C1, ENABLE);
  31.  
  32. /* Test on EV5 and clear it */
  33. while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  34.  
  35. /* Send EEPROM address for read */
  36. I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);
  37.  
  38. /* Test on EV6 and clear it */
  39. while (!I2C_CheckEvent(I2C1,
  40. I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
  41.  
  42. /* While there is data to be read */
  43. while (NumByteToRead)
  44. {
  45. if (NumByteToRead == 1)
  46. {
  47. /* Disable Acknowledgement */
  48. I2C_AcknowledgeConfig(I2C1, DISABLE);
  49.  
  50. /* Send STOP Condition */
  51. I2C_GenerateSTOP(I2C1, ENABLE);
  52. }
  53.  
  54. /* Test on EV7 and clear it */
  55. if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
  56. {
  57. /* Read a byte from the EEPROM */
  58. *pBuffer = I2C_ReceiveData(I2C1);
  59.  
  60. /* Point to the next location where the byte read will be
  61. saved */
  62. pBuffer++;
  63.  
  64. /* Decrement the read bytes counter */
  65. NumByteToRead--;
  66. }
  67. }
  68.  
  69. /* Enable Acknowledgement to be ready for another reception */
  70. I2C_AcknowledgeConfig(I2C1, ENABLE);
  71. }

四、使用 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 位器件地址和读模式。

⑦ 接收数据。

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

转载连接:https://blog.csdn.net/wqx521/article/details/51027393

STM32——EEPROM使用——(转载)的更多相关文章

  1. Keil5创建基于RTX的STM32工程(转载+自己的体会)

    转载自:https://blog.csdn.net/u011976086/article/details/54342447#commentBox 之前使用过ucos,freertos,但是这个keil ...

  2. STM32应用笔记转载

    stm32 外部中断嵌套[操作寄存器+库函数] stm32 NVIC中断管理实现[直接操作寄存器] stm32 SPI通信[操作寄存器+库函数] stm32 i2c通信 [操作寄存器+库函数] stm ...

  3. 转载:关于STM32硬件I2C读写EEPROM代码实现原理的理解与总结

    http://home.eeworld.com.cn/my/space-uid-716241-blogid-655190.html 一.I2C协议简介 I2C是两线式串行总线,用于连接微控制器及其外围 ...

  4. 【STM32】IIC的基本原理(实例:普通IO口模拟IIC时序读取24C02)(转载)

     版权声明:本文为博主原创文章,允许转载,但希望标注转载来源. https://blog.csdn.net/qq_38410730/article/details/80312357 IIC的基本介绍 ...

  5. [转载]:STM32为什么必须先配置时钟再配置GPIO

    转载来源 :http://blog.csdn.net/fushiqianxun/article/details/7926442 [原创]:我来添两句,就是很多同学(包括我)之前搞低端单片机,到了stm ...

  6. 【转载】 stm32之PWM

    发现这位博主的博客被大量的转发,我也转载一篇,谁叫人家写的好呢. 原文地址:http://blog.sina.com.cn/s/blog_49cb42490100s6uh.html 脉冲宽度调制(PW ...

  7. 【转载】MDK环境下让STM32用上FreeRTOS v8.1.2和FreeRTOS+Trace v2.6.0全过程

    [转载]https://www.amobbs.com/thread-5601460-1-2.html?_dsign=6a59067b   本人选择使用FreeRTOS的最大原因就是想使用FreeRTO ...

  8. ARM 架构、ARM7、ARM9、STM32、Cortex M3 M4 、51、AVR 之间有什么区别和联系?(转载自知乎)

    ARM架构:  由英国ARM公司设计的一系列32位的RISC微处理器架构总称,现有ARMv1~ARMv8种类. ARM7:       一类采用ARMv3或ARMv4架构的,使用冯诺依曼结构的内核. ...

  9. STM32 中 BIT_BAND(位段/位带)和别名区使用入门(转载)

    一. 什么是位段和别名区 是这样的,记得MCS51吗? MCS51就是有位操作,以一位(BIT)为数据对象的操作,MCS51可以简单的将P1口的第2位独立操作: P1.2=0;P1.2=1 :这样就把 ...

随机推荐

  1. BUAA_OO_第二单元

    BUAA_OO_2020_UNIT2 一.程序结构分析 第五次作业 UML & Mertrics ​ 电梯的调度问题,实质上就是任务的请求与分配问题,笔者在第五次作业中采用简单的"生 ...

  2. UnitOneSummary

    目录 一.程序结构分析 第一次作业 第二次作业 第三次作业 二.Test & Bugs 三.设计模式 四.总结与反思 一.程序结构分析 第一次作业 思路: 1.输入预处理: 去除空格和\t 替 ...

  3. Facebook资深工程师带你学Python核心技术

    人工智能时代下,Python毫无疑问是最热的编程语言.在推开Python的大门后却发现,Python入门容易但精通却不易. 想要精通这门语言,必须真正理解知识概念,比如适当从源码层面深化认知,然后熟悉 ...

  4. ES6转ES5(Babel转码器)

    ES6转ES5(Babel转码器) 前提:必须在VScode中已经安装了Node.js 官网:https://nodejs.org/en/ 一.安装命令行转码工具 npm install --glob ...

  5. 【CTF】WDCTF-finals-2017 3-11 writeup

    题目来源:WDCTF-finals-2017 题目链接:https://adworld.xctf.org.cn/task/answer?type=misc&number=1&grade ...

  6. 弦图及其在 OI 中的现代应用

    八月份的时候得知要填综评表格,综评表格里面又需要一个研究性学习报告,而我连研究性学习课的老师长啥样都不知道.于是我把两份 OI 笔记拼拼凑凑成了这篇文章充当两份研究性学习报告之一(另一份可能更有趣一些 ...

  7. Vue学习笔记(三)

    1 监听 在Vue.js中可以通过watch来监听数据的变化,比如通过watch实现的简单计数器: <div id="app"> <p>计数器:{{coun ...

  8. Django 入门范例

    1. Django 介绍 2. Django 环境搭建 3. 模型(Model) 4. 站点管理 5. 视图(View) 6. 模板(Template) 1. Django 介绍 MVC 模型 大部分 ...

  9. 1025 PAT Ranking

    Programming Ability Test (PAT) is organized by the College of Computer Science and Technology of Zhe ...

  10. 用vue-cli3搭建vue项目

    1.在nodejs官网下载node安装包,并进行安装:http://nodejs.cn/download/,在环境变量进行配置,并添加node_global和node_cache路径. 2.在D盘新建 ...