跟其他外设一样, STM32提供了I2C的初始化结构体,详情如下:

typedef struct{
    uint32_t I2C_ClockSpeed;    //设置SCL时钟频率,此值要低于400khz
    uint16_t I2C_Mode;          //指定工作模式,可选I2C模式或者SMBUS模式
    uint16_t I2C_DutyCycle;     //指定时钟占空比,可选2/1或者16/9
    uint16_t I2C_OwnAddress1;  //指定自身的I2C设备地址,即STM32主机地址
    uint16_t I2C_Ack;           //使能或者关闭响应(一般都要使能)
    uint16_t I2C_AcknowledgeAddress; //指定地址的长度,可以是7位地址或者10位地址
}I2C_InitTypeDef;

软件编程要点:
(1)配置通讯使用的目标引脚为开漏模式

(2)使能I2C外设的时钟

(3)配置I2C外设的模式,地址、速率等参数并使能I2C外设

(4)编写基本的I2C按字节收发的函数

(5)编写EEPROM存储内容的函数

(6)编写测试程序,对读写数据进行校验。

首先为了提高代码的移植性,对用到的GPIO和外设使用宏定义

#define     EEPROM_I2Cx                        I2C1
#define     EEPROM_I2C_APBxClock_FUN           RCC_APB1PeriphClockCmd
#define     EEPROM_I2C_CLK                     RCC_APB1Periph_I2C1
#define     EEPROM_I2C_GPIO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define     EEPROM_I2C_GPIO_CLK                RCC_APB2Periph_GPIOB
#define     EEPROM_I2C_SCL_PORT                GPIOB
#define     EEPROM_I2C_SCL_PIN                 GPIO_Pin_6
#define     EEPROM_I2C_SDA_PORT                GPIOB
#define     EEPROM_I2C_SDA_PIN                 GPIO_Pin_7

#define     I2C_Speed                          400000
#define     I2Cx_OWN_ADDRESS7                  0x0A

#define     I2C_PageSize                         8
#define     EEPROM_ADDR                         0xA0

初始化I2C的GPIO引脚

static void I2C_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    EEPROM_I2C_APBxClock_FUN(EEPROM_I2C_CLK,ENABLE);
    EEPROM_I2C_GPIO_APBxClock_FUN(EEPROM_I2C_GPIO_CLK,ENABLE);

    GPIO_InitStruct.GPIO_Pin    =    EEPROM_I2C_SCL_PIN;
    GPIO_InitStruct.GPIO_Speed    =    GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode      =    GPIO_Mode_AF_OD;
    GPIO_Init(EEPROM_I2C_SCL_PORT,&GPIO_InitStruct);

    GPIO_InitStruct.GPIO_Pin    =    EEPROM_I2C_SDA_PIN;
    GPIO_InitStruct.GPIO_Speed    =    GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Mode      =    GPIO_Mode_AF_OD;
    GPIO_Init(EEPROM_I2C_SDA_PORT,&GPIO_InitStruct);

}

配置I2C的工作模式

1 /**
2 * @brief I2C 工作模式配置
3 * @param 无
4 * @retval 无
5 */
6 static void I2C_Mode_Configu(void)
7 {
8 I2C_InitTypeDef I2C_InitStructure;
9 
10 /* I2C 配置 */
11 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
12 
13 /* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
14 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
15 
16 I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;
17 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
18 
19 /* I2C 的寻址模式 */
20 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
21 
22 /* 通信速率 */
23 I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
24 
25 /* I2C 初始化 */
26 I2C_Init(EEPROM_I2Cx, &I2C_InitStructure);
27 
28 /* 使能 I2C */
29 I2C_Cmd(EEPROM_I2Cx, ENABLE);
30 }
31 
32 
33 /**
34 * @brief I2C 外设(EEPROM)初始化
35 * @param 无
36 * @retval 无
37 */
38 void I2C_EE_Init(void)
39 {
40 I2C_GPIO_Config();
41 
42 I2C_Mode_Configu();
43 
44 /* 根据头文件 i2c_ee.h 中的定义来选择 EEPROM 要写入的设备地址 */
45 /* 选择 EEPROM Block0 来写入 */
46 EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
47 }

这段程序很好理解,其实就是把I2C外设通讯时钟引脚占空比设置为2/1,使能响应功能,使用7位地址I2C_OWN_ADDRESS7以及速率配置I2C_Speed。最后,调用库函数I2C_Init把这些配置写到相应的寄存器里面,并调用I2C_Cmd函数使能I2C外设。

向EEPROM写入一个字节的数据

初始化后,使用I2C外设,向EEPROM写入一个字节

1 
2 /***************************************************************/
3 /*通讯等待超时时间*/
4 #define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
5 #define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
6 
7 /**
8 * @brief I2C 等待事件超时的情况下会调用这个函数来处理
9 * @param errorCode:错误代码,可以用来定位是哪个环节出错.
10 * @retval 返回 0,表示 IIC 读取失败.
11 */
12 static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
13 {
14 /* 使用串口 printf 输出错误信息,方便调试 */
15 EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode);
16 return 0;
17 }
18 /**
19 * @brief 写一个字节到 I2C EEPROM 中
20 * @param pBuffer:缓冲区指针
21 * @param WriteAddr:写地址
22 * @retval 正常返回 1,异常返回 0
23 */
24 uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
25 {
26 /* 产生 I2C 起始信号 */
27 I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
28 
29 /*设置超时等待时间*/
30 I2CTimeout = I2CT_FLAG_TIMEOUT;
31 /* 检测 EV5 事件并清除标志*/
32 while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
33 {
34 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
35 }
36 
37 /* 发送 EEPROM 设备地址 */
38 I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,
39 I2C_Direction_Transmitter);
40 
41 I2CTimeout = I2CT_FLAG_TIMEOUT;
42 /* 检测 EV6 事件并清除标志*/
43 while (!I2C_CheckEvent(EEPROM_I2Cx,
44 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
45 {
46 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
47 }
48 
49 /* 发送要写入的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */
50 I2C_SendData(EEPROM_I2Cx, WriteAddr);
51 
52 I2CTimeout = I2CT_FLAG_TIMEOUT;
53 /* 检测 EV8 事件并清除标志*/
54 while (!I2C_CheckEvent(EEPROM_I2Cx,
55 I2C_EVENT_MASTER_BYTE_TRANSMITTED))
56 {
57 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
58 }
59 /* 发送一字节要写入的数据 */
60 I2C_SendData(EEPROM_I2Cx, *pBuffer);
61 
62 I2CTimeout = I2CT_FLAG_TIMEOUT;
63 /* 检测 EV8 事件并清除标志*/
64 while (!I2C_CheckEvent(EEPROM_I2Cx,
65 I2C_EVENT_MASTER_BYTE_TRANSMITTED))
66 {
67 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
68 }
69 
70 /* 发送停止信号 */
71 I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
72 
73 return 1;
74 }

这里由于一些时间问题,我找到一篇很详细的博客说明I2C读写EEPROM。

STM32系统学习——I2C (读写EEPROM)_Yuk丶的博客-CSDN博客_i2c读写eeprom

个人认为I2C还是比较简单的,只需要根据对应的时序图,完全可以写出代码,其中最为关键的一点就是STM32的速度相较于EEPROM来说是非常快的,写入数据到EEPROM还需要等待一段时间,作为EEPROM的内部时序完成写操作,AT24C02里面其实介绍了这个时序叫做Acknowledge Polling(应答轮询)。