klasa I2C – dwuprzewodowy protokół szeregowy

I2C to dwuprzewodowy protokół do komunikacji między urządzeniami. Na poziomie fizycznym składa się z 2 przewodów: SCL i SDA, odpowiednio linii zegara i danych.

Obiekty I2C są tworzone jako przypisane do określonej magistrali. Mogą zostać zainicjalizowane podczas tworzenia lub później.

Wydrukowanie obiektu I2C dostarcza informacji o jego konfiguracji.

Istnieją implementacje I2C zarówno sprzętowe, jak i programowe, dostępne za pośrednictwem klas I2C i SoftI2C. Sprzętowe I2C wykorzystuje wbudowane wsparcie sprzętowe systemu do wykonywania odczytów/zapisów i jest zwykle wydajne i szybkie, ale może mieć ograniczenia co do tego, których pinów można używać. Programowe I2C jest realizowane techniką bit-banging i może być używane na dowolnym pinie, lecz nie jest tak wydajne. Klasy te udostępniają te same metody i różnią się głównie sposobem konstruowania.

Informacja

Do działania magistrala I2C wymaga obwodu podciągającego (pull-up) na obu liniach SDA i SCL. Zazwyczaj są to rezystory w zakresie 1 - 10 kOhm, podłączone od każdej z linii SDA/SCL do Vcc. Bez nich zachowanie jest nieokreślone i może obejmować zarówno blokowanie, nieoczekiwany reset watchdoga, jak i po prostu błędne wartości. Często ten obwód podciągający jest już wbudowany w płytkę MCU lub moduły rozszerzające z sensorami, ale nie jest to regułą. Dlatego w razie problemów należy to sprawdzić. Zobacz także ten doskonały przewodnik firmy Adafruit dotyczący okablowania I2C.

Przykład użycia:

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

Konstruktory

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

Konstruuje i zwraca nowy obiekt I2C, używając następujących parametrów:

  • id identyfikuje konkretne urządzenie peryferyjne I2C. Dozwolone wartości zależą od konkretnego portu/płytki

  • scl powinien być obiektem pinu określającym pin używany dla SCL.

  • sda powinien być obiektem pinu określającym pin używany dla SDA.

  • freq powinno być liczbą całkowitą ustawiającą maksymalną częstotliwość dla SCL.

  • timeout to maksymalny czas w mikrosekundach dozwolony dla transakcji I2C. Ten parametr nie jest dozwolony na niektórych portach.

Należy zauważyć, że niektóre porty/płytki będą miały domyślne wartości scl i sda, które można zmienić w tym konstruktorze. Inne będą miały stałe wartości scl i sda, których nie można zmienić.

Metody ogólne

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

Inicjalizuje magistralę I2C z podanymi argumentami:

  • scl to obiekt pinu dla linii SCL

  • sda to obiekt pinu dla linii SDA

  • freq to częstotliwość zegara SCL

W przypadku sprzętowego I2C rzeczywista częstotliwość zegara może być niższa niż żądana. Zależy to od sprzętu platformy. Rzeczywistą szybkość można ustalić, drukując obiekt I2C.

scan() List[int]

Skanuje wszystkie adresy I2C w zakresie od 0x08 do 0x77 włącznie i zwraca listę tych, które odpowiadają. Urządzenie odpowiada, jeśli ściąga linię SDA do stanu niskiego po wysłaniu na magistralę jego adresu (wraz z bitem zapisu).

Podstawowe operacje I2C

Poniższe metody implementują podstawowe operacje magistrali kontrolera I2C i można je łączyć, aby zrealizować dowolną transakcję I2C. Są one dostarczane na wypadek, gdy potrzebujesz większej kontroli nad magistralą; w przeciwnym razie można używać metod standardowych (patrz niżej).

Te metody są dostępne wyłącznie w klasie SoftI2C.

start() None

Generuje na magistrali warunek START (SDA przechodzi w stan niski, gdy SCL jest w stanie wysokim).

stop() None

Generuje na magistrali warunek STOP (SDA przechodzi w stan wysoki, gdy SCL jest w stanie wysokim).

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

Odczytuje bajty z magistrali i zapisuje je do buf. Liczba odczytanych bajtów to długość buf. Po odebraniu wszystkich bajtów oprócz ostatniego na magistralę zostanie wysłany ACK. Po odebraniu ostatniego bajtu, jeśli nack jest prawdą, zostanie wysłany NACK, w przeciwnym razie zostanie wysłany ACK (w tym przypadku urządzenie peryferyjne zakłada, że kolejne bajty zostaną odczytane w późniejszym wywołaniu).

write(buf: bytes) int

Zapisuje bajty z buf na magistralę. Sprawdza, czy po każdym bajcie odebrano ACK, i przerywa transmisję pozostałych bajtów w przypadku odebrania NACK. Funkcja zwraca liczbę odebranych ACK.

Standardowe operacje na magistrali

Poniższe metody implementują standardowe operacje odczytu i zapisu kontrolera I2C skierowane do danego urządzenia peryferyjnego.

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

Odczytuje nbytes z urządzenia peryferyjnego określonego przez addr. Jeśli stop jest prawdą, na końcu transferu generowany jest warunek STOP. Zwraca obiekt bytes z odczytanymi danymi.

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

Odczytuje do buf z urządzenia peryferyjnego określonego przez addr. Liczba odczytanych bajtów będzie równa długości buf. Jeśli stop jest prawdą, na końcu transferu generowany jest warunek STOP.

Metoda zwraca None.

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

Zapisuje bajty z buf do urządzenia peryferyjnego określonego przez addr. Jeśli po zapisie bajtu z buf odebrany zostanie NACK, pozostałe bajty nie są wysyłane. Jeśli stop jest prawdą, na końcu transferu generowany jest warunek STOP, nawet jeśli odebrano NACK. Funkcja zwraca liczbę odebranych ACK.

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

Zapisuje bajty zawarte w vector do urządzenia peryferyjnego określonego przez addr. vector powinien być krotką lub listą obiektów obsługujących protokół buforów. addr jest wysyłany raz, a następnie bajty z każdego obiektu w vector są zapisywane kolejno. Obiekty w vector mogą mieć zerową długość, w którym to przypadku nie wnoszą nic do wyjścia.

Jeśli po zapisie bajtu z jednego z obiektów w vector odebrany zostanie NACK, pozostałe bajty oraz wszelkie pozostałe obiekty nie są wysyłane. Jeśli stop jest prawdą, na końcu transferu generowany jest warunek STOP, nawet jeśli odebrano NACK. Funkcja zwraca liczbę odebranych ACK.

Operacje na pamięci

Niektóre urządzenia I2C działają jak urządzenia pamięciowe (lub zestawy rejestrów), z których można odczytywać i do których można zapisywać. W takim przypadku z transakcją I2C powiązane są dwa adresy: adres urządzenia peryferyjnego oraz adres pamięci. Poniższe metody to funkcje pomocnicze do komunikacji z takimi urządzeniami.

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

Odczytuje nbytes z urządzenia peryferyjnego określonego przez addr, zaczynając od adresu pamięci określonego przez memaddr. Argument addrsize określa rozmiar adresu w bitach. Zwraca obiekt bytes z odczytanymi danymi.

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

Odczytuje do buf z urządzenia peryferyjnego określonego przez addr, zaczynając od adresu pamięci określonego przez memaddr. Liczba odczytanych bajtów to długość buf. Argument addrsize określa rozmiar adresu w bitach.

Metoda zwraca None.

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

Zapisuje buf do urządzenia peryferyjnego określonego przez addr, zaczynając od adresu pamięci określonego przez memaddr. Argument addrsize określa rozmiar adresu w bitach.

Metoda zwraca None.

klasa SoftI2C – programowo emulowana magistrala I2C

Klasa SoftI2C implementuje I2C poprzez bit-banging dowolnych pinów GPIO. Udostępnia ten sam zestaw metod co I2C oraz dodatkowo niskopoziomowe podstawowe operacje magistrali (start(), stop(), readinto(), write()) dla wywołujących, którzy muszą złożyć niestandardowe transakcje. Używaj jej, gdy potrzebne piny nie są podłączone do sprzętowego bloku I2C, gdy potrzebujesz większej liczby magistral niż zapewnia sprzęt, lub aby komunikować się z urządzeniami wymagającymi nietypowych sekwencji (dodatkowe takty zegara, powtarzane starty po zapisach itp.).

Konstruktory

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

Konstruuje programową magistralę I2C sterowaną przez scl / sda.

freq to docelowa szybkość zegara SCL w Hz (rzeczywista szybkość jest zazwyczaj niższa ze względu na narzut pętli bit-bang).

timeout to maksymalny czas w mikrosekundach oczekiwania na rozciąganie zegara (clock stretching) (SCL przytrzymane w stanie niskim przez inne urządzenie na magistrali); po jego upływie zgłaszany jest OSError(ETIMEDOUT).

Metody ogólne

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

Ponownie inicjalizuje programową magistralę I2C z podanymi pinami i częstotliwością. Równoważne skonstruowaniu nowego obiektu SoftI2C na tym samym obiekcie.

scan() List[int]

Skanuje wszystkie adresy I2C w zakresie od 0x08 do 0x77 włącznie i zwraca listę tych, które odpowiedziały.

Podstawowe operacje I2C

Poniższe metody implementują podstawowe operacje magistrali kontrolera I2C i można je łączyć, aby zrealizować dowolną transakcję I2C. Są one dostępne wyłącznie w SoftI2C – sprzętowa klasa I2C ich nie udostępnia.

start() None

Generuje na magistrali warunek START (SDA przechodzi w stan niski, gdy SCL jest w stanie wysokim).

stop() None

Generuje na magistrali warunek STOP (SDA przechodzi w stan wysoki, gdy SCL jest w stanie wysokim).

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

Odczytuje bajty z magistrali do buf. Odczytywane jest len(buf) bajtów; po każdym bajcie oprócz ostatniego wysyłany jest ACK. Po ostatnim bajcie nack=True (wartość domyślna) wysyła NACK kończący transfer; nack=False wysyła ACK, dzięki czemu urządzenie pozostaje wybrane na potrzeby kolejnego readinto().

write(buf: bytes) int

Zapisuje buf na magistralę, sprawdzając ACK po każdym bajcie. Transmisja zatrzymuje się przy pierwszym NACK. Zwraca liczbę odebranych ACK.

Standardowe operacje na magistrali

Poniższe metody implementują standardowe operacje odczytu i zapisu kontrolera I2C skierowane do danego urządzenia peryferyjnego.

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

Odczytuje nbytes z urządzenia o 7-bitowym adresie addr. Jeśli stop jest prawdą, na końcu transferu generowany jest warunek STOP.

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

Odczytuje len(buf) bajtów z urządzenia o adresie addr do buf. Jeśli stop jest prawdą, na końcu transferu generowany jest warunek STOP.

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

Zapisuje buf do urządzenia o adresie addr. Transmisja zatrzymuje się przy pierwszym NACK. Jeśli stop jest prawdą, na końcu transferu zawsze generowany jest warunek STOP (nawet przy wczesnym NACK). Zwraca liczbę odebranych ACK.

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

Zapisuje konkatenację buforów z vector do urządzenia o adresie addr jako pojedynczą transakcję. Puste bufory są ignorowane. Zachowuje się jak writeto() pod względem semantyki stop i wartości zwracanej.

Operacje na pamięci

Niektóre urządzenia I2C działają jak urządzenia pamięciowe (lub zestawy rejestrów), z których można odczytywać i do których można zapisywać. W takim przypadku z transakcją I2C powiązane są dwa adresy: adres urządzenia peryferyjnego oraz adres pamięci. Poniższe metody to pomocnicze funkcje ułatwiające komunikację z takimi urządzeniami.

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

Odczytuje nbytes z urządzenia o adresie addr, zaczynając od rejestru memaddr. addrsize to szerokość adresu rejestru w bitach (zazwyczaj 8 lub 16).

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

Odczytuje do buf z urządzenia o adresie addr, zaczynając od rejestru memaddr.

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

Zapisuje buf do urządzenia o adresie addr, zaczynając od rejestru memaddr.