一、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. 基于gitlab的项目管理流程

    框架 背景 个人是不太愿意使用用户体验差的软件来做项目管理,行业内,要找到这么一款软件,又要符合自己的需求,着实不容易.要免费,易用性要好,要安全,要有数据统计.而程序员的世界,SVN 之后,可能没有 ...

  2. Java性能调优实战,覆盖80%以上调优场景

    Java 性能调优对于每一个奋战在开发一线的技术人来说,随着系统访问量的增加.代码的臃肿,各种性能问题便会层出不穷. 日渐复杂的系统,错综复杂的性能调优,都对Java工程师的技术广度和技术深度提出了更 ...

  3. C#与Python交互方式

    前言: 在平时工作中,需求有多种实现方式:根据不同的需求可以采用不同的编程语言来实现.发挥各种语言的强项 如:Python的强项是:数据分析.人工智能等 .NET 开发桌面程序界面比Python更简单 ...

  4. RocketMq(一)初识

    消息中间件基本上是互联网公司必用的一个中间件,为什么要使用MQ,当然是因为能给我们的系统带来很多好处. 消息队列简单来说是一种先进先出的数据结构,先简单认识下. 一.应用场景 消息中间件主要应用场景主 ...

  5. JVM学习笔记(二):JVM基本结构

    1 来源 来源:<Java虚拟机 JVM故障诊断与性能优化>--葛一鸣 章节:第二章 本文是第二章的一些笔记整理. 2 JVM基本参数-Xmx java命令的一般形式如下: java [- ...

  6. linux下Mysql 8.0.19 编译安装

    1 前言 linux下安装MySQL的方式有很多种,包括以仓库的方式安装(yum,apt,zypper),以包的方式安装(rpm,deb),以docker方式安装,从压缩包解压安装,从源码编译安装,这 ...

  7. Day14_81_反射机制获取Class属性

    反射机制获取Class属性 获取属性 方法一: Class对象 . getFields();只能用来获取公开的属性,不能获取有私有的或者受保护的属性 获取属性 方法二: Class对象 . getDe ...

  8. Alpine镜像

    Alpine Linux 是一个面向安全,轻量级的基于musl libc与busybox项目的Linux发行版. Alpine 提供了自己的包管理工具 apk,可以通过 https://pkgs.al ...

  9. lumen Rest API 起步

    lumen Rest API 起步 修改项目文件 .env DB_DATABASE=<数据库名> DB_USERNAME=<数据库用户名> DB_PASSWORD=<数据 ...

  10. Laravel 定时任务 任务调度 可手动执行

    1.创建一个命令 php artisan make:command TestCommand 执行成功后会提示: Console command created successfully. 生成了一个新 ...