3.24. I2C в коде#

machine.I2C оборачивает аппаратный контроллер I2C. Создайте его, указав идентификатор шины и необязательную тактовую частоту:

from machine import I2C

i2c = I2C(2, freq=400_000)

id выбирает, какой аппаратный блок I2C использовать; доступные номера и выводы SDA / SCL, на которые они отображаются, зависят от платы (см. Платы OpenMV). freq – это частота тактового сигнала SCL: 100_000 – стандартный режим, 400_000 – распространённая «быстрая» скорость, которую поддерживает большинство датчиков.

3.24.1. Сканирование шины#

scan() проходит по 7-битному адресному пространству и возвращает список всех устройств, подтвердивших свой адрес:

>>> i2c.scan()
[0x40, 0x68]

Это первое, что стоит попробовать после подключения новой шины. Если устройство есть в списке, то проводка и подтягивающие резисторы в порядке. Если список пуст (или короче ожидаемого), подозревайте подтягивающие резисторы, проводку или адресные выводы устройства, прежде чем подозревать код.

3.24.2. Шаблон отображения в память#

Большинство датчиков I2C представляют своё состояние в виде набора внутренних регистров: флаги состояния по одному адресу, байты измерений по другому, конфигурация по третьему. В библиотеке есть два удобных метода, которые напрямую соответствуют шаблону «адресовать устройство, указать регистр, затем прочитать или записать данные».

Чтение регистра:

addr = 0x68
reg  = 0x3B
data = i2c.readfrom_mem(addr, reg, 6)
# `data` is 6 bytes starting at register 0x3B

Этот единственный вызов выдаёт START, отправляет адрес устройства с битом записи, отправляет адрес регистра (повторный старт), повторно отправляет адрес с битом чтения, читает 6 байт, выдаёт NACK на последний и выполняет STOP. Устройство видит стандартную последовательность «чтение из регистра»; скрипт видит один вызов Python.

Запись в регистр:

i2c.writeto_mem(addr, 0x6B, b"\x00")

Эта последовательность является зеркальным отражением: START, адрес + запись, регистр, байт полезной нагрузки, STOP.

Для устройств, у которых адрес регистра имеет ширину два байта (некоторые EEPROM и память с 16-битной адресацией), передайте addrsize=16:

data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)

3.24.3. Чтение датчика#

Короткий цикл, читающий идентификатор WHO_AM_I и ось X акселерометра типичного MEMS-датчика:

import time
import struct
from machine import I2C

i2c         = I2C(2, freq=400_000)
ADDR        = 0x68
WHO_AM_I    = 0x75
ACCEL_X_HI  = 0x3B

print("WHO_AM_I:", i2c.readfrom_mem(ADDR, WHO_AM_I, 1)[0])

while True:
    raw = i2c.readfrom_mem(ADDR, ACCEL_X_HI, 2)
    x   = struct.unpack(">h", raw)[0]
    print("accel X:", x)
    time.sleep_ms(100)

readfrom_mem() возвращает объект bytes, поэтому чтение WHO_AM_I индексирует первый (и единственный) байт через [0]. Чтение акселерометра – это два сырых байта, которые надо собрать в одно знаковое 16-битное значение; формат ">h" из struct делает это как знаковое значение в порядке big-endian – порядок байтов, который использует большинство датчиков I2C, противоположный соглашению little-endian, распространённому на линиях UART. struct.unpack() всегда возвращает кортеж (по одному элементу на символ формата), поэтому [0] извлекает единственное значение.

3.24.4. Доступ более низкого уровня#

Для устройств, которые не следуют шаблону карты регистров, два метода более низкого уровня дают прямой контроль над фазой адреса и направлением данных:

i2c.writeto(addr, b"\x01\x02\x03")    # write 3 bytes to addr
data = i2c.readfrom(addr, 4)          # read 4 bytes from addr

Они полезны для устройств с интерфейсами потока команд (дисплеи, некоторые часы реального времени), где удобные вспомогательные методы карты регистров не вполне подходят.

3.24.5. Программная эмуляция, когда аппаратный блок недоступен#

machine.SoftI2C предоставляет тот же API на произвольных выводах GPIO, программно эмулируя протокол:

from machine import SoftI2C, Pin

i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)

Используйте его, когда устройство должно быть на выводах, не подключённых к аппаратному блоку I2C, или когда все аппаратные блоки заняты. Цикл программной эмуляции медленнее аппаратного I2C, и ЦП остаётся занят на протяжении всей транзакции, но API идентичен I2C, поэтому существующий код переключается заменой только конструктора.