目录
一:I2C Init 字段逐个解析
① Instance — 用哪个 I2C 硬件
② AddressingMode — 7 位还是 10 位地址
③ ClockSpeed — I2C 总线速率
④ DualAddressMode — 双地址模式
⑤ DutyCycle — 时钟占空比
⑥ GeneralCallMode — 广播模式
⑦ NoStretchMode — 时钟拉伸
⑧ OwnAddress1 — STM32 自己作为从设备时的地址
⑨ OwnAddress2 — 第二从设备地址
跟 UART Init 对比
二、HAL_I2C_Mem_Write / HAL_I2C_Mem_Read
1: HAL_I2C_Mem_Write — 向设备的某个寄存器写入数据
2: HAL_I2C_Mem_Read — 从设备的某个寄存器读取数据
3: 跟 MPU6050 对比
4: AT24C02 vs MPU6050 vs W25Q64
三、轮询读写流程(阻塞模式)
四、I2C 中断读写
1: 轮询 vs 中断 新增内容对比
2: 中断架构:EV 管正常,ER 管故障
3: 中断写完整跳转链
4: 中断读完整跳转链
5: 错误路径
6: 跟 UART IT 对比
五、I2C DMA 读写
1: F429 I2C1 的 DMA 映射
2: 三轮模式新增内容对比
3: DMA 完成后的中断链:两波中断,硬件链式触发
4: IT 版 vs DMA 版对比
5: 三种模式共用一个 main 框架
六、实战踩坑:I2C 回调函数名(两个 bug)
Bug 1:多了一个下划线
Bug 2:Master 和 Mem 是两个家族
一:I2C Init 字段逐个解析
① Instance — 用哪个 I2C 硬件
.Instance = I2C1;
STM32F429 有三个 I2C 外设:I2C1、I2C2、I2C3。这块板子 AT24C02 接在 I2C1 上(PB6=SCL,PB7=SDA)。
跟huart1.Instance = USART1一样——选了硬件编号。
② AddressingMode — 7 位还是 10 位地址
.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
| 模式 | 地址范围 | 场景 |
|---|---|---|
| 7 位 | 0~127 | 绝大多数芯片,AT24C02 就是 0x50(7 位) |
| 10 位 | 0~1023 | 极少见,地址不够用时才用 |
器件地址 0xA0 是加上读写位之后的值。纯 7 位地址是 0x50,左移一位最低位放 R/W → 变成 0xA0(写)/ 0xA1(读)。
③ ClockSpeed — I2C 总线速率
.ClockSpeed = 400000; // 400 KHz
| 模式 | 速率 | 场景 |
|---|---|---|
| 标准模式 (Standard) | 100 KHz | 低速、长距离 |
| 快速模式 (Fast) | 400 KHz | 最常用 |
| 快速+ (Fast Plus) | 1 MHz | 手册说有,实际不常用 |
400KHz 对应一个时钟周期 2.5us。
④ DualAddressMode — 双地址模式
.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 关闭
STM32 自己是 I2C 主设备,也可以被别的设备呼叫。这个字段的意思是"要不要同时响应两个I2C 地址"。
DISABLE→ 只响应 OwnAddress1(0x0A)ENABLE→ 同时响应 OwnAddress1 和 OwnAddress2
做主设备去读 EEPROM,开着没用,关掉就行。只有 STM32 被当从设备时才需要。
⑤ DutyCycle — 时钟占空比
.DutyCycle = I2C_DUTYCYCLE_2;
只对快速模式(400KHz)有意义:
| 选项 | 时钟低:高 | 说明 |
|---|---|---|
DUTYCYCLE_2 | 2:1 | 低电平 2 份,高电平 1 份 →标准选择 |
DUTYCYCLE_16_9 | 16:9 | 低电平更长,给慢速设备更多反应时间 |
AT24C02 用DUTYCYCLE_2就行。
⑥ GeneralCallMode — 广播模式
.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 关闭
广播 = 主设备向地址0x00发消息,总线上所有从设备一起响应。工程只有 EEPROM 一个从设备,不需要广播,关掉。
⑦ NoStretchMode — 时钟拉伸
.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 允许从设备拉慢时钟
时钟拉伸(Clock Stretching):从设备来不及处理数据时,可以把SCL 线拉低,让主设备等着。处理完了释放 SCL,主设备才能继续发下一个字节。
主设备: [发数据]→[想发下一个]→咦 SCL 被从设备拉低?→等着→SCL 释放→继续 从设备: 收到数据 → 来不及处理 → 拉低 SCL → 处理完了 → 释放 SCL
DISABLE = 允许拉伸(等待),ENABLE = 禁止拉伸(不等人)。AT24C02 写一个字节后要等 5ms 才能再写——在这个间隙它会拉低 SCL。如果关了拉伸,主设备不等就发,写入失败。所以必须 DISABLE。
⑧ OwnAddress1 — STM32 自己作为从设备时的地址
.OwnAddress1 = I2C_OWN_ADDRESS7; // 0x0A
这个在 EEPROM 读写场景中根本用不到——你是主设备去读别人。但如果哪天真有一块板子要当 I2C 从设备给别人读,那别人就用 0x0A 这个地址来找它。现在随便填个值就行。
⑨ OwnAddress2 — 第二从设备地址
.OwnAddress2 = 0; // 没用,填 0
双地址模式关了,这个值无效。
跟 UART Init 对比
UART Init I2C Init ─────── ──────── .Instance = USART1 .Instance = I2C1 ← 同一套路 .BaudRate = 115200 .ClockSpeed = 400000 ← 波特率 vs 时钟速率 .WordLength = 8B .AddressingMode = 7BIT ← 数据位 vs 地址位 .StopBits = 1 .DutyCycle = 2 ← 都不复杂 .Parity = NONE .GeneralCall = DISABLE ← 都用不到的关掉 .Mode = TX_RX .DualAddress = DISABLE ← 都用不到的关掉
还是老规矩——填结构体 → 调HAL_I2C_Init→ 完事。跟 UART、DMA 一个模子刻出来的。
二、HAL_I2C_Mem_Write / HAL_I2C_Mem_Read
1: HAL_I2C_Mem_Write — 向设备的某个寄存器写入数据
HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRE