class I2C —— 一种双线串行协议

I2C 是一种用于设备间通信的双线协议。在物理层面上,它由 2 根线组成:SCL 和 SDA,分别是时钟线和数据线。

I2C 对象在创建时会绑定到特定的总线。它们可以在创建时初始化,也可以稍后再初始化。

打印 I2C 对象可以查看其配置信息。

硬件和软件两种 I2C 实现分别通过 I2CSoftI2C 类提供。硬件 I2C 利用系统底层的硬件支持来执行读写操作,通常高效快速,但可能对可使用的引脚有所限制。软件 I2C 通过位操作(bit-banging)实现,可用于任意引脚,但效率不如硬件 I2C。这两个类提供相同的方法,主要区别在于构造方式不同。

备注

I2C 总线在工作时需要在 SDA 和 SCL 上都接有上拉电路。这些通常是阻值在 1 - 10 kOhm 范围内的电阻,从每条 SDA/SCL 线连接到 Vcc。若没有这些上拉,行为将是未定义的,可能表现为阻塞、意外的看门狗复位,乃至仅仅是读到错误的数值。这种上拉电路往往已经内置在 MCU 板或传感器扩展板上,但这并非定规。所以遇到问题时请务必检查。另请参阅 Adafruit 关于 I2C 接线的这篇出色的 学习指南

用法示例:

from machine import I2C

i2c = I2C(freq=400000)          # create I2C peripheral at frequency of 400kHz
                                # depending on the port, extra parameters may be required
                                # to select the peripheral and/or pins to use

i2c.scan()                      # scan for peripherals, returning a list of 7-bit addresses

i2c.writeto(42, b'123')         # write 3 bytes to peripheral with 7-bit address 42
i2c.readfrom(42, 4)             # read 4 bytes from peripheral with 7-bit address 42

i2c.readfrom_mem(42, 8, 3)      # read 3 bytes from memory of peripheral 42,
                                #   starting at memory-address 8 in the peripheral
i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42
                                #   starting at address 2 in the peripheral

构造函数

class machine.I2C(id: int, *, scl: Pin | None = None, sda: Pin | None = None, freq: int = 400000, timeout: int = 50000)

使用以下参数构造并返回一个新的 I2C 对象:

  • id 标识特定的 I2C 外设。允许的取值取决于具体的移植版本/开发板

  • scl 应为指定用于 SCL 的引脚对象。

  • sda 应为指定用于 SDA 的引脚对象。

  • freq 应为一个整数,用于设置 SCL 的最大频率。

  • timeout 是 I2C 事务所允许的最长时间(以微秒为单位)。某些移植版本不允许使用此参数。

请注意,某些移植版本/开发板会为 sclsda 提供默认值,可在此构造函数中更改。另一些则会为 sclsda 提供固定值,无法更改。

通用方法

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

使用给定参数初始化 I2C 总线:

  • scl 是 SCL 线的引脚对象

  • sda 是 SDA 线的引脚对象

  • freq 是 SCL 时钟速率

对于硬件 I2C,实际的时钟频率可能低于请求的频率。这取决于平台硬件。实际速率可通过打印 I2C 对象来确定。

scan() List[int]

扫描 0x08 到 0x77(含)之间的所有 I2C 地址,并返回有响应的地址列表。如果设备在其地址(包括写位)被发送到总线后将 SDA 线拉低,则视为该设备有响应。

原始 I2C 操作

以下方法实现了原始的 I2C 控制器总线操作,可以组合起来完成任意 I2C 事务。它们用于在你需要对总线进行更精细控制时使用,否则使用标准方法(见下文)即可。

这些方法仅在 SoftI2C 类上可用。

start() None

在总线上生成一个 START 条件(SCL 为高时 SDA 由高变低)。

stop() None

在总线上生成一个 STOP 条件(SCL 为高时 SDA 由低变高)。

readinto(buf: bytearray, nack: bool = True, /) None

从总线读取字节并存入 buf。读取的字节数为 buf 的长度。在接收除最后一个字节以外的所有字节后,会在总线上发送 ACK。在接收最后一个字节后,如果 nack 为真,则发送 NACK,否则发送 ACK(在这种情况下,外设会认为后续调用中还将读取更多字节)。

write(buf: bytes) int

buf 中的字节写入总线。每写入一个字节后都会检查是否收到 ACK,若收到 NACK 则停止发送剩余字节。该函数返回收到的 ACK 数量。

标准总线操作

以下方法实现了针对指定外设设备的标准 I2C 控制器读写操作。

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

从由 addr 指定的外设读取 nbytes 个字节。如果 stop 为真,则在传输结束时生成一个 STOP 条件。返回包含所读数据的 bytes 对象。

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

从由 addr 指定的外设读取数据到 buf 中。读取的字节数为 buf 的长度。如果 stop 为真,则在传输结束时生成一个 STOP 条件。

该方法返回 None

writeto(addr: int, buf: bytes, stop: bool = True, /) int

buf 中的字节写入由 addr 指定的外设。如果在写入 buf 中某个字节后收到 NACK,则不再发送剩余字节。如果 stop 为真,则在传输结束时生成一个 STOP 条件,即使收到了 NACK 也是如此。该函数返回收到的 ACK 数量。

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

vector 中包含的字节写入由 addr 指定的外设。vector 应为一个由支持缓冲区协议的对象组成的元组或列表。addr 只发送一次,随后 vector 中每个对象的字节依次写出。vector 中的对象长度可以为零字节,在这种情况下它们不会对输出产生贡献。

如果在写入 vector 中某个对象的某个字节后收到 NACK,则不再发送剩余字节以及任何剩余对象。如果 stop 为真,则在传输结束时生成一个 STOP 条件,即使收到了 NACK 也是如此。该函数返回收到的 ACK 数量。

内存操作

某些 I2C 设备充当可读写的内存设备(或一组寄存器)。在这种情况下,一次 I2C 事务关联着两个地址:外设地址和内存地址。以下方法是用于与此类设备通信的便捷函数。

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

从由 addr 指定的外设、起始于由 memaddr 指定的内存地址,读取 nbytes 个字节。参数 addrsize 以位为单位指定地址大小。返回包含所读数据的 bytes 对象。

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

从由 addr 指定的外设、起始于由 memaddr 指定的内存地址,读取数据到 buf 中。读取的字节数为 buf 的长度。参数 addrsize 以位为单位指定地址大小。

该方法返回 None

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

buf 写入由 addr 指定的外设,起始于由 memaddr 指定的内存地址。参数 addrsize 以位为单位指定地址大小。

该方法返回 None

class SoftI2C —— 软件模拟的 I2C 总线

SoftI2C 类通过对任意 GPIO 引脚进行位操作(bit-banging)来实现 I2C。它对外提供与 I2C 相同的方法集合,外加底层的原始总线操作(start()stop()readinto()write()),供需要组装非标准事务的调用者使用。当你所需的引脚未连接到硬件 I2C 模块、当你需要的总线数量超过硬件所能提供的数量,或者需要与要求特殊时序(额外时钟、写后重复启动等)的设备通信时,可使用它。

构造函数

class machine.SoftI2C(scl: Pin, sda: Pin, *, freq: int = 400000, timeout: int = 50000)

构造一个由 scl / sda 驱动的软件 I2C 总线。

freq 是目标 SCL 时钟速率(以 Hz 为单位)(由于位操作循环的开销,实际速率通常会更低)。

timeout 是等待时钟拉伸(总线上另一设备将 SCL 保持为低)的最长时间(以微秒为单位);超时后会引发 OSError(ETIMEDOUT)

通用方法

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

使用给定的引脚和频率重新初始化软件 I2C 总线。等同于在同一对象上构造一个新的 SoftI2C

scan() List[int]

扫描 0x08 到 0x77(含)之间的所有 I2C 地址,并返回有响应的地址列表。

原始 I2C 操作

以下方法实现了原始的 I2C 控制器总线操作,可以组合起来完成任意 I2C 事务。它们仅 SoftI2C 可用——硬件 I2C 类并不对外提供这些方法。

start() None

在总线上生成一个 START 条件(SCL 为高时 SDA 由高变低)。

stop() None

在总线上生成一个 STOP 条件(SCL 为高时 SDA 由低变高)。

readinto(buf: bytearray, nack: bool = True, /) None

从总线读取字节到 buf 中。读取 len(buf) 个字节;除最后一个字节外,每个字节之后都会发送 ACK。在最后一个字节之后,nack=True(默认值)会发送 NACK 以结束传输;nack=False 则发送 ACK,使设备保持选中状态,以便后续的 readinto()

write(buf: bytes) int

buf 写入总线,每写一个字节后检查 ACK。一旦收到 NACK 即停止传输。返回收到的 ACK 数量。

标准总线操作

以下方法实现了针对指定外设设备的标准 I2C 控制器读写操作。

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

从 7 位地址 addr 处的设备读取 nbytes 个字节。如果 stop 为真,则在传输结束时生成一个 STOP 条件。

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

addr 处的设备读取 len(buf) 个字节到 buf 中。如果 stop 为真,则在传输结束时生成一个 STOP 条件。

writeto(addr: int, buf: bytes, stop: bool = True, /) int

buf 写入 addr 处的设备。一旦收到 NACK 即停止传输。如果 stop 为真,则总会在传输结束时生成一个 STOP 条件(即使提前收到 NACK 也是如此)。返回收到的 ACK 数量。

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

vector 中各缓冲区的连接结果作为一次事务写入 addr 处的设备。空缓冲区会被忽略。在 stop 语义和返回值方面,其行为与 writeto() 相同。

内存操作

某些 I2C 设备充当可读写的内存设备(或一组寄存器)。在这种情况下,一次 I2C 事务关联着两个地址:外设地址和内存地址。以下方法是用于与此类设备通信的便捷辅助函数。

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

addr 处的设备、起始于寄存器 memaddr,读取 nbytes 个字节。addrsize 是以位为单位的寄存器地址宽度(通常为 816)。

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

addr 处的设备、起始于寄存器 memaddr,读取数据到 buf 中。

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

buf 写入 addr 处的设备,起始于寄存器 memaddr