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, поэтому существующий код переключается заменой только конструктора.