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