浮云落笔

浮云落笔

首页
瞬间
反馈
浮云落笔

浮云落笔

首页 瞬间 反馈
  1. 首页
  2. stm32
  3. I2C 实验记录与原理复习

I2C 实验记录与原理复习

  • stm32
  • 发布于 2026-11-13
  • 11 次阅读
flor
flor

核心结论

I2C 学习可以归纳为三层:

电气层:SCL、SDA、开漏输出、上拉电阻
协议层:START、设备地址、数据、ACK、STOP
器件层:寄存器地址、寄存器数据、芯片手册定义

最重要的一句话:

I2C 只负责传输字节;设备地址决定访问哪个芯片,寄存器地址决定访问芯片内部哪个位置,芯片手册决定读回来的字节代表什么。


一、I2C 的硬件结构

I2C 使用两根线:

信号英文全称作用
SCLSerial Clock,串行时钟主机产生时钟
SDASerial Data,串行数据传输地址和数据

本次实验连接:

项目配置
STM32 SCLPB6
STM32 SDAPB7
电平3.3V
OLEDSSD1306,7 位地址 0x3C
传感器MPU6050,AD0 接地,7 位地址 0x68
必要连接电源正极、电源地、SCL、SDA

STM32、OLED 和 MPU6050 必须共地。否则即使模块电源灯亮,也不代表通信线路真正连通。


二、为什么需要开漏输出和上拉电阻

I2C 的 SCL、SDA 都是开漏结构:

  • 设备可以主动拉低总线
  • 设备不能主动输出高电平
  • 设备释放总线后,由上拉电阻把线拉高

结构可以表示为:

3.3V
  │
上拉电阻
  │
SCL 或 SDA ───── 多个设备的开漏引脚共同连接

这样允许多个设备共享总线。任何一个设备拉低,总线就为低;所有设备都释放,总线才为高。

当前软件 I2C 将 PB6、PB7 配置为开漏输出,通过 GPIO 控制电平。


三、SCL 和 SDA 的时序规则

最核心的规则:

SDA 只能在 SCL 低电平期间改变,在 SCL 高电平期间必须保持稳定。

主机在 SCL 高电平期间读取 SDA:

SCL       __/‾‾\__/‾‾\__/‾‾\__
             ↑     ↑     ↑
采样        读取   读取   读取

SDA       ‾‾‾‾‾‾‾‾‾\________
          稳定保持    在 SCL 低电平期间变化

每条 I2C 消息都由 START 开始、STOP 结束:

条件动作
STARTSCL 为高时,SDA 从高变低
STOPSCL 为高时,SDA 从低变高

普通数据位不能在高电平期间随意变化,否则可能被误认为 START 或 STOP。


四、设备地址与读写方向

I2C 芯片通常使用 7 位地址。实际发送 8 位:前 7 位选择设备,最低位表示方向。

bit7~bit1:7 位设备地址
bit0:0 = 写,1 = 读

MPU6050 的 7 位地址是 0x68:

0x68 = 1101000

写地址:1101000 0 = 0xD0
读地址:1101000 1 = 0xD1

这里需要准确理解:

  • 0xD0 不是单独的“写命令”
  • 0xD0 表示“访问 0x68 这个设备,本次方向为写”
  • 0xD1 表示“访问 0x68 这个设备,本次方向为读”

OLED 的 7 位地址是 0x3C:

0x3C << 1 = 0x78

所以串口打印的 0x78 与扫描得到的 0x3C 是同一个 OLED 地址,只是一个使用 8 位写法,一个使用 7 位写法。


五、I2C 怎样传输一个字节

每个数据字节包含 8 位,采用高位先发:

bit7 → bit6 → bit5 → bit4 → bit3 → bit2 → bit1 → bit0

发送完 8 位后,还有第 9 个时钟,用于 ACK:

ACK 状态SDA 电平含义
ACK接收方拉低收到,可以继续
NACKSDA 保持高不再接收,结束读取

读取多个字节时:

  • 非最后一个数据字节:主机发送 ACK,表示继续读
  • 最后一个数据字节:主机发送 NACK,表示不再继续

因此“读取一个字节”实际上也需要 9 个时钟:8 个数据时钟加 1 个 ACK 时钟。

0x6B = 0110 1011 只是教学示例。I2C 协议只规定如何传输字节,不规定固定字节内容。


六、写寄存器的完整流程

以 MPU6050 将 PWR_MGMT_1 写成 0x01 为例:

START
  → 0xD0 + ACK       设备地址,方向为写
  → 0x6B + ACK       选择 PWR_MGMT_1 寄存器
  → 0x01 + ACK       写入新值
STOP

对应关系:

总线内容意义
0xD0选择 MPU6050,并声明准备写
0x6B选择芯片内部寄存器
0x01写入该寄存器的新值

普通配置寄存器写入新值后,会覆盖原来的值:

原值:PWR_MGMT_1 = 0x40
写入:PWR_MGMT_1 ← 0x01
结果:PWR_MGMT_1 = 0x01

芯片随后根据新值改变工作方式。特殊命令寄存器也可能采用“写 1 触发动作”等规则,必须查看具体芯片手册。


七、读寄存器的完整流程

读取 MPU6050 的 WHO_AM_I:

START
  → 0xD0 + ACK       设备地址,方向为写
  → 0x75 + ACK       选择 WHO_AM_I 寄存器
  → 重复 START
  → 0xD1 + ACK       设备地址,方向改为读
  → 芯片返回一个字节
  → 主机 NACK
STOP

这里最容易混淆的是:“读取为什么一开始用了写地址?”

原因是读取分为两个阶段:

第一阶段:先选中芯片,并设置内部寄存器指针
第二阶段:重新以读方向访问同一芯片,读取指针所指寄存器的内容

第一阶段并不写入新的配置值,只发送寄存器地址。但从总线层面看,寄存器地址本身也是一个数据字节,因此此时 SDA 上当然有数据。

可以对比为:

写配置:设备写地址 + 寄存器地址 + 新值
读数据:设备写地址 + 寄存器地址 + 重复START + 设备读地址 + 返回数据

不能把“读操作”理解成只发送 0xD1。必须先告诉芯片从哪个寄存器开始读取。


八、为什么使用重复 START

读取过程中:

D0 → 寄存器地址 → 重复START → D1

重复 START 的作用是:

  • 不发送 STOP
  • 不释放总线
  • 直接改变设备访问方向
  • 保持之前设置的寄存器指针

如果中间先发送 STOP,部分器件可能退出当前访问状态,后续读地址就不一定从刚才指定的寄存器开始。


九、寄存器地址、寄存器和数据的关系

可以把芯片内部想成一排小存储盒:

概念例子类比
设备地址0x68哪栋楼
读写方向0xD0/0xD1来写东西还是取东西
寄存器地址0x75楼里的房间号
寄存器芯片内部存储单元房间
寄存器值0x68房间里当前保存的内容

重要区别:

  • 寄存器地址:选择操作哪个存储位置
  • 寄存器值:该位置里面保存的数据
  • 写入普通配置寄存器时,新值会覆盖旧值

十、MPU6050 初始化实验

程序先读取 WHO_AM_I:

寄存器地址:0x75
返回内容:0x68

确认芯片身份后,写入五个配置寄存器:

寄存器写入值控制效果
PWR_MGMT_10x01退出睡眠,选择时钟源
CONFIG0x03开启约 44Hz 低通滤波
SMPLRT_DIV0x04内部 1kHz 分频后约 200Hz
GYRO_CONFIG0x00陀螺仪量程 ±250°/s
ACCEL_CONFIG0x00加速度计量程 ±2g

这说明“写数据控制芯片”的本质:

向控制寄存器写入不同值,芯片会改变采样率、量程、滤波、电源状态等行为。


十一、MPU6050 连续读取 14 字节

程序设置起始寄存器 0x3B,连续读取 14 字节。MPU6050 读取一个寄存器后,内部地址会自动递增。

数据顺序固定为:

字节位置寄存器含义
1~20x3B/0x3CX 轴加速度高/低字节
3~40x3D/0x3EY 轴加速度高/低字节
5~60x3F/0x40Z 轴加速度高/低字节
7~80x41/0x42温度高/低字节
9~100x43/0x44X 轴角速度高/低字节
11~120x45/0x46Y 轴角速度高/低字节
13~140x47/0x48Z 轴角速度高/低字节

MPU6050 使用大端格式,高字节在前:

高字节:0x45
低字节:0x16
合并:0x45 << 8 | 0x16 = 0x4516

不能把 0x45 和 0x16 直接相加。

传感器数据是有符号 16 位数。例如:

0xF2B0 → -3408

物理量换算公式:

数据类型换算公式
加速度原始值 ÷ 16384,单位 g
陀螺仪原始值 ÷ 131,单位 °/s
温度原始值 ÷ 340 + 36.53,单位 ℃

例如:

0x4516 = 17686
17686 ÷ 16384 ≈ 1.079g

当前实际读取帧示例:

[RAW] 45 16 F2 B0 F3 90 F4 10 FF 00 00 5B 00 7C

拆分后:

AX   = 45 16
AY   = F2 B0
AZ   = F3 90
TEMP = F4 10
GX   = FF 00
GY   = 00 5B
GZ   = 00 7C

十二、SSD1306 OLED 的控制方式

OLED 同样使用“设备地址加后续字节”的结构,但 SSD1306 的第二个字节是控制字节,而不是普通寄存器地址。

控制字节后续字节含义
0x00OLED 命令
0x40OLED 显存数据

发送命令:

START + 0x78 + 0x00 + 命令字节 + STOP

发送像素数据:

START + 0x78 + 0x40 + 显存数据 + STOP

因此,不同器件虽然都使用 I2C,设备地址后面的内容需要按各自手册解释。

SSD1306 中:

  • 命令控制屏幕参数和显示模式
  • 显存数据决定哪些像素点亮
  • 程序先修改本地显存缓冲区,再整屏发送给 OLED

十三、本次实验完成的内容

实验工程:i2c_oled

完成内容:

  1. 使用 GPIO 实现软件 I2C。
  2. 扫描总线并识别 OLED 和 MPU6050。
  3. 读取 MPU6050 的 WHO_AM_I。
  4. 写入配置寄存器并启动 MPU6050。
  5. 连续读取 14 字节传感器原始数据。
  6. 将数据换算成加速度、温度和角速度。
  7. 通过串口输出原始帧和物理量。
  8. 在 OLED 中显示 MPU6050 温度。
  9. 温度页固定显示,每秒更新一次。
  10. 验证 OLED 与 MPU6050 可以同时挂在一条 I2C 总线上。

上电后的关键串口输出:

ACK: 0x3C
ACK: 0x68
WHO_AM_I=0x68
TEMP 27.7 C
[RAW] 45 16 F2 B0 F3 90 F4 10 FF 00 00 5B 00 7C

该结果说明:

  • 地址扫描成功
  • 两个设备都能回应 ACK
  • MPU6050 身份识别正确
  • 寄存器读写成功
  • 14 字节数据读取成功
  • OLED 与传感器并联通信正常

十四、本次实验遇到的问题

并联 OLED 和 MPU6050 后,曾经只扫描到 OLED,MPU6050 没有 ACK。检查软件时序、降低速度和尝试交换逻辑引脚都没有解决,最终重新连接模块线路后恢复。

这个故障提供了几个重要经验:

  1. 模块电源灯亮不代表 SCL/SDA 已经真正接通。
  2. 扫描不到地址时,应优先检查接线、共地、供电和上拉。
  3. 0x68 没有 ACK,不一定是程序错误。
  4. 总线通信失败应先验证最基础的地址 ACK,再分析寄存器内容。
  5. 两设备并联后,要保证每个模块的 SCL 都接总线 SCL,SDA 都接总线 SDA。

十五、软件 I2C 与硬件 I2C

项目软件 I2C硬件 I2C
时序产生GPIO 手动翻转STM32 I2C 外设自动产生
CPU 占用较高较低
引脚灵活度高受外设和复用引脚限制
时序理解最直观部分过程被硬件封装
适合场景学习协议、故障诊断、低速设备产品开发、高速或多设备通信

当前实验采用软件 I2C,速度设置为保守低速,优先保证时序容易观察和模块兼容。

软件 I2C 的核心实现包括:

  • START:SCL 高时拉低 SDA
  • 发送字节:每次先准备 SDA,再产生 SCL 高电平
  • 第 9 拍释放 SDA,读取 ACK
  • 接收字节:主机释放 SDA,从机驱动 8 位数据
  • 最后一字节发送 NACK
  • 读寄存器使用重复 START
  • STOP:SCL 高时释放 SDA

代码中还包含总线恢复逻辑:如果初始化时发现 SDA 被某个从机持续拉低,则最多发送 9 个 SCL 脉冲,再发送 STOP,让设备释放总线。


十六、I2C、SPI 与场景选择

总线线数特点常见设备
I2C2 根公共线节省引脚,多设备共享,需要地址和上拉OLED、传感器、EEPROM
SPI通常 4 根速度高,片选线随设备增加Flash、显示屏、无线模块

简单选择原则:

  • 设备多、速度要求一般、希望节省引脚:优先考虑 I2C
  • 数据量大、刷新速度快:优先考虑 SPI
  • 协议学习和时序诊断:软件 I2C 很直观
  • 正式产品中追求稳定和降低 CPU 占用:通常使用硬件 I2C

十七、学习新 I2C 芯片的检查表

拿到新器件后依次确认:

  1. 7 位设备地址是多少?
  2. 地址引脚是否会改变设备地址?
  3. SCL/SDA 应接在哪里,是否需要上拉?
  4. 控制寄存器有哪些?
  5. 每个控制值代表什么功能?
  6. 数据寄存器从哪个地址开始?
  7. 一次需要读取几个字节?
  8. 连续读取时地址是否自动递增?
  9. 数据是高字节在前还是低字节在前?
  10. 是否是有符号数,需要什么换算公式?
  11. 写操作后能否回读同一寄存器确认配置?

十八、最终必须记牢的四个模型

1. 一个设备地址

7 位设备地址 + 读写方向位

2. 一次写操作

设备写地址 → 寄存器地址 → 新值

3. 一次读操作

设备写地址 → 寄存器地址 → 重复START → 设备读地址 → 返回数据

读操作前半段不是写入配置值,而是设置内部寄存器指针。

4. 数据含义

寄存器地址 + 芯片手册 + 配置量程 → 字节含义和物理量

复习时只需回答这五个问题

  1. SDA 为什么只能在 SCL 低电平期间变化?
  2. 为什么 I2C 总线需要上拉电阻?
  3. 0xD0 和 0xD1 分别表示什么?
  4. 读取寄存器时为什么先发写地址,再发读地址?
  5. 读回一组字节后,怎样判断每个字节代表什么数据?

如果这五个问题都能用自己的话回答,就说明 I2C 的协议、寄存器模型和实验流程已经形成完整理解。

目录
  1. 核心结论
  2. 一、I2C 的硬件结构
  3. 二、为什么需要开漏输出和上拉电阻
  4. 三、SCL 和 SDA 的时序规则
  5. 四、设备地址与读写方向
  6. 五、I2C 怎样传输一个字节
  7. 六、写寄存器的完整流程
  8. 七、读寄存器的完整流程
  9. 八、为什么使用重复 START
  10. 九、寄存器地址、寄存器和数据的关系
  11. 十、MPU6050 初始化实验
  12. 十一、MPU6050 连续读取 14 字节
  13. 十二、SSD1306 OLED 的控制方式
  14. 十三、本次实验完成的内容
  15. 十四、本次实验遇到的问题
  16. 十五、软件 I2C 与硬件 I2C
  17. 十六、I2C、SPI 与场景选择
  18. 十七、学习新 I2C 芯片的检查表
  19. 十八、最终必须记牢的四个模型
    1. 1. 一个设备地址
    2. 2. 一次写操作
    3. 3. 一次读操作
    4. 4. 数据含义
  20. 复习时只需回答这五个问题
湘ICP备2025147565号-1
gongan beian 湘公网安备43102602000213号