跳到主要内容

I2C 通信

本节介绍 I2C 通信协议的基本概念,并演示如何使用 MicroPython 的 machine.I2C 类来扫描 I2C 设备以及驱动 OLED 显示屏。

I2C(Inter-Integrated Circuit),也称为 I²CIIC,是一种广泛使用的两线制串行通信协议。I2C 协议允许设备间通过两根信号线进行通信,常用于连接传感器、显示器、存储器等外设。

I2C 具有以下特点:

  • 两线通信:只需要 SDA(数据线)和 SCL(时钟线)两根信号线。

  • 主从架构:支持多个主设备(控制器)和从设备(目标)在同一总线上。

    信息

    I²C 规范的第七次修订 已将传统的 “主/从 (Master/Slave)” 术语更新为 “控制器/目标 (Controller/Target)”。为确保与现有代码和文档的兼容性,本教程可能会根据上下文混用这两种表述。

  • 地址寻址:每个设备都有唯一的 7 位或 10 位地址。

  • 同步通信:通过时钟线进行同步,数据传输更可靠。


I2C 总线包含以下信号线:

  • SDA (Serial Data Line):串行数据线,用于传输数据
  • SCL (Serial Clock Line):串行时钟线,由主设备提供时钟信号

实际硬件连接时所有 I2C 设备还需连接地线(GND),以保证电路共地

I2C 连接

关于上拉电阻

I2C 协议规范要求 SDA 和 SCL 两条线路上必须有上拉电阻。这是因为 I2C 总线采用开漏(Open-Drain)电路结构,设备只能将信号线拉到低电平,而不能主动输出高电平。上拉电阻的作用就是在总线空闲时,将信号线拉回到高电平,确保通信正常。

添加外部上拉电阻的情况:

  • 实际连线时(特别是外接模块或多板通信),建议在 SDA 和 SCL 各连接一个 4.7kΩ 上拉电阻至 3.3V,提升通信可靠性。
  • 总线较长、设备较多或通信不稳定时,必须使用外部上拉电阻。

可以省略外部上拉电阻的情况:

  • 许多 I2C 模块(如本教程使用的 微雪 1.5 寸 OLED 模块)内部已集成上拉电阻。使用此类模块时通常可直接连接,无需额外添加上拉电阻。
  • ESP32 GPIO 支持内部弱上拉,简单应用中可能够用。但最佳做法仍是添加外部上拉电阻以确保通信稳定。

如不确定模块是否包含上拉电阻,建议查看模块原理图或数据手册。

1. ESP32 上的 I2C

在 MicroPython 中,I2C 功能通过 machine 模块中的 I2C 类(硬件 I2C)和 SoftI2C 类(软件 I2C)来实现。

  • Hardware I2C (machine.I2C): 使用 ESP32 芯片内部专用的 I2C 硬件控制器。它的速度更快,CPU 占用率更低。ESP32 的硬件 I2C 支持映射到任意 GPIO 引脚
  • Software I2C (machine.SoftI2C): 通过软件模拟 I2C 时序(Bit-banging)。通常仅在硬件 I2C 资源不足时使用。

建议优先使用 Hardware I2C。由于 ESP32 支持引脚矩阵映射,通常不需要使用 Software I2C。

2. 示例 1:I2C 扫描器 (I2C Scanner)

连接新 I2C 模块时,获取其地址是首要步骤。很多模块并不标明地址,或地址可通过跳线更改。I2C 扫描仪程序可以快速检测并报告总线上所有设备的地址,是进行 I2C 开发与调试的重要工具。

2.1 搭建电路

需要使用的器件有:

  • 微雪 1.5 寸 OLED 模块 * 1(也可以替换为其他 I2C 模块)
  • 4.7kΩ 电阻 * 2(可选,若 I2C 模块内置上拉电阻可省略)
  • 面包板 * 1
  • 导线
  • ESP32 开发板

按照下面接线图连接电路:

ESP32-S3-Zero 引脚图

ESP32-S3-Zero-Pinout

接线图
电路图说明

电路图中的 4.7kΩ 上拉电阻是 I2C 的标准接法。由于本教程使用中的 OLED 模块已内置上拉电阻,不连接这两个电阻,电路依然可以正常工作。

OLED 模块需切换到 I2C 接口

微雪 1.5 寸 OLED 模块出厂默认为 4 线 SPI(BS1、BS2 接 GND),此时 I2C 扫描不到设备。需按 产品 Wiki 把模块背面的 BS1、BS2 电阻焊位改接到 VCC,切换后 DIN 即 SDA、CLK 即 SCL,CS 和 DC 无需连接,默认地址为 0x3D。


开发板引脚OLED 模块说明
GPIO 1DIN(SDA)I2C 数据线。按需外接 4.7kΩ 上拉电阻至 3.3V
GPIO 2CLK(SCL)I2C 时钟线。按需外接 4.7kΩ 上拉电阻至 3.3V
3.3VVCC电源正极
GNDGND电源负极

2.2 代码

from machine import Pin, I2C

# I2C 引脚定义
SDA_PIN = 1
SCL_PIN = 2

# 初始化 I2C
# id=0 使用第一个硬件 I2C 控制器
# scl=Pin(SCL_PIN), sda=Pin(SDA_PIN) 指定引脚
# freq=100000 设置 I2C 频率为 100kHz (标准模式)
i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=100000)

print("Scanning I2C bus...")
devices = i2c.scan() # 扫描总线上的设备

if len(devices) == 0:
print("No I2C devices found")
else:
print("I2C devices found:", len(devices))
for device in devices:
# 打印十进制和十六进制地址
print("Decimal address: ", device, " | Hex address: ", hex(device))

2.3 代码解析

  1. I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=100000):

    • 0: 指定使用 I2C0 硬件控制器。
    • scl, sda: 指定连接的 GPIO 引脚。
    • freq: 设置通信频率,通常为 100000 (100kHz) 或 400000 (400kHz)。
  2. i2c.scan():

    • 扫描 I2C 总线上所有可能的 7 位地址(0x08 到 0x77)。
    • 返回一个包含所有响应设备地址的列表(List)。
  3. hex(device):

    • 将十进制地址转换为十六进制字符串(例如 0x3d),这是 I2C 地址最常用的表示方式。

2.4 运行结果

运行代码后,REPL 将输出扫描到的设备地址。例如,对于微雪 1.5 寸 OLED 模块,通常会看到地址 0x3d

Scanning I2C bus...
I2C devices found: 1
Decimal address: 61 | Hex address: 0x3d

3. 示例 2:驱动 OLED 显示屏 (SSD1327)

实际应用中,通常无需自行编写底层的 I2C 数据收发代码,而是直接使用针对特定硬件的库(由社区或者制造商提供)。

提示

此代码示例依赖 ssd1327.py 驱动库。该库基于社区开发者 mcauser 的 micropython-ssd1327 项目。

下载链接:micropython-ssd1327-master.zip

请将该库中的 ssd1327.py 文件上传到开发板的根目录中。

3.1 准备驱动文件

MicroPython 固件通常不内置特定显示屏的驱动库。为了驱动使用 SSD1327 芯片的 OLED 屏幕,我们需要手动添加驱动文件。

将下载的库文件中的 ssd1327.py 上传到 ESP 设备中。

注意:这个文件必须保存在 ESP32 的文件系统中的根目录下。

3.2 代码

确保 ssd1327.py 已上传到开发板后,运行以下代码:

from machine import I2C, Pin
import ssd1327

# 引脚定义
SDA_PIN = 1
SCL_PIN = 2
I2C_ADDR = 0x3d

# 初始化 I2C
i2c = I2C(0, scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=400000)

# 初始化 OLED
oled = ssd1327.SSD1327_I2C(128, 128, i2c, I2C_ADDR)

# 清屏 (填充黑色)
oled.fill(0)

# 显示文字
# framebuf.text(s, x, y, c)
# c 是颜色值,对于 4-bit 灰度,范围是 0-15。15 为最亮,0 为全黑。
oled.text("Hello,", 10, 10, 15)
oled.text("MicroPython!", 10, 25, 8)
oled.text("ESP32", 10, 40, 1) # 亮度较低的文字

# 使用 framebuf 绘制图像
# 绘制矩形框
oled.framebuf.rect(0, 0, 128, 128, 15)
# 绘制圆或椭圆
oled.framebuf.ellipse(0, 0, 128, 128, 15)

# 刷新显示
oled.show()

3.3 代码解析

  1. import ssd1327: 导入驱动库。
  2. oled = ssd1327.SSD1327_I2C(128, 128, i2c, 0x3d): 创建 OLED 对象。需要传入屏幕的宽、高、I2C 对象以及 I2C 地址。
  3. oled.fill(0): 清除屏幕缓冲区。0 代表黑色。
  4. oled.text(...): 在缓冲区中写入文本。注意 framebuf 模块内置一种默认字体(8x8 像素)。
    • 第一个参数为要显示的文本内容。
    • 第二个参数为文本的 x 坐标。
    • 第三个参数为文本的 y 坐标。
    • 第四个参数是颜色值。SSD1327 支持 16 级灰度(4-bit),因此可传入 0-15 之间的整数来控制亮度。15 为最亮,0 为全黑。
  5. 关于 framebuf 模块:
    • ssd1327 驱动库基于 MicroPython 内置的 framebuf(Frame Buffer,帧缓冲)模块构建。framebuf 提供了一套标准的图形绘制 API,包括绘制文本、线条、矩形、圆形等基本图形。
    • 在代码中,oled.framebuf.rect()oled.framebuf.ellipse() 等方法直接调用了 framebuf 的绘图功能。
    • framebuf 支持多种颜色格式和绘图操作,是 MicroPython 中进行图形显示的标准工具。更多绘图方法和详细用法,请参考 MicroPython framebuf 官方文档
  6. oled.show(): 将缓冲区的数据发送到 OLED 屏幕,更新显示内容。在调用此方法之前,屏幕内容不会改变。

3.4 运行结果

OLED 屏幕将被点亮,显示 "Hello, MicroPython!" 和 "ESP32",周围有一个矩形边框。

示例 2 运行结果

4. 常见问题与注意事项

4.1 Hardware I2C vs Software I2C

注意: ESP32 的 I2C 引脚支持通过 GPIO 矩阵路由到任意可用引脚,因此通常情况下不需要使用 SoftI2C,直接使用 I2C (硬件 I2C) 即可。

from machine import Pin, SoftI2C

# SoftI2C 由 CPU 模拟
i2c = SoftI2C(scl=Pin(SCL_PIN), sda=Pin(SDA_PIN), freq=100000)

SoftI2C 的用法与 I2C 完全相同,但它是由 CPU 模拟的,因此在高速通信或高负载下可能不如硬件 I2C 稳定。

4.2 关于 I2C 从机模式

MicroPython 的标准 machine.I2Cmachine.SoftI2C 类目前仅支持主机(控制器)模式,不支持从机(目标)模式。

5. 相关链接