3.24. I2C w kodzie

machine.I2C opakowuje sprzętowy kontroler I2C. Utwórz instancję, podając identyfikator magistrali i opcjonalną częstotliwość zegara:

from machine import I2C

i2c = I2C(2, freq=400_000)

id wybiera, który sprzętowy blok I2C ma zostać użyty; dostępne numery oraz piny SDA / SCL, do których są przypisane, zależą od płytki (zobacz Płytki OpenMV). freq to częstotliwość zegara SCL – 100_000 to tryb standardowy, 400_000 to powszechna prędkość „szybka”, którą obsługuje większość sensorów.

3.24.1. Skanowanie magistrali

scan() przeszukuje 7-bitową przestrzeń adresową i zwraca listę każdego urządzenia, które potwierdziło swój adres:

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

To pierwsza rzecz, którą warto wypróbować po podłączeniu nowej magistrali. Jeśli urządzenie jest na liście, połączenia i rezystory podciągające są w porządku. Jeśli lista jest pusta (lub krótsza niż oczekiwano), podejrzewaj rezystory podciągające, połączenia lub piny adresowe urządzenia, zanim zaczniesz podejrzewać kod.

3.24.2. Wzorzec mapowania w pamięci

Większość sensorów I2C udostępnia swój stan jako zestaw rejestrów wewnętrznych: flagi statusu pod jednym adresem, bajty pomiarów pod innym, konfigurację pod trzecim. Biblioteka oferuje dwie wygodne metody, które bezpośrednio odwzorowują wzorzec „zaadresuj urządzenie, wskaż rejestr, a następnie odczytaj lub zapisz dane”.

Odczyt rejestru:

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

To pojedyncze wywołanie wystawia START, wysyła adres urządzenia z bitem zapisu, wysyła adres rejestru (powtórzony start), ponownie wysyła adres z bitem odczytu, odczytuje 6 bajtów, odpowiada NACK na ostatni i wystawia STOP. Urządzenie widzi standardową sekwencję „odczyt z rejestru”; skrypt widzi jedno wywołanie w Pythonie.

Zapis do rejestru:

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

Ta sekwencja jest tym samym w lustrzanym odbiciu: START, adres + zapis, rejestr, bajt danych, STOP.

Dla urządzeń, których adres rejestru ma szerokość dwóch bajtów (niektóre pamięci EEPROM i pamięci o 16-bitowym adresowaniu), przekaż addrsize=16:

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

3.24.3. Odczyt z sensora

Krótka pętla odczytująca identyfikator WHO_AM_I oraz oś X akcelerometru z typowego sensora 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() zwraca obiekt bytes, więc odczyt WHO_AM_I indeksuje pierwszy (i jedyny) bajt za pomocą [0]. Odczyt z akcelerometru to dwa surowe bajty, które trzeba złożyć w jedną liczbę 16-bitową ze znakiem; format ">h" z modułu struct robi to jako big-endian ze znakiem – jest to kolejność bajtów stosowana przez większość sensorów I2C, przeciwna do konwencji little-endian powszechnej na łączach UART. struct.unpack() zawsze zwraca krotkę (po jednym elemencie na każdy znak formatu), więc [0] wyciąga pojedynczą wartość.

3.24.4. Dostęp niższego poziomu

Dla urządzeń, które nie stosują wzorca mapy rejestrów, dwie metody niższego poziomu dają bezpośrednią kontrolę nad fazą adresowania i kierunkiem przepływu danych:

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

Są przydatne dla urządzeń z interfejsami opartymi na strumieniu poleceń (wyświetlacze, niektóre zegary czasu rzeczywistego), gdzie wygodne metody pomocnicze oparte na mapie rejestrów nie do końca pasują.

3.24.5. Bit-banging, gdy brak dostępnego bloku sprzętowego

machine.SoftI2C udostępnia to samo API na dowolnych pinach GPIO, realizując protokół programowo metodą bit-bangingu:

from machine import SoftI2C, Pin

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

Użyj go, gdy urządzenie musi znaleźć się na pinach, które nie są podłączone do sprzętowego bloku I2C, lub gdy wszystkie bloki sprzętowe są zajęte. Pętla bit-bangingu jest wolniejsza niż sprzętowe I2C, a procesor pozostaje zajęty przez całą transakcję, ale API jest identyczne jak w I2C, więc istniejący kod przechodzi na nią, zmieniając jedynie konstruktor.