clasa I2C – un protocol serial cu două fire

I2C este un protocol cu două fire pentru comunicarea între dispozitive. La nivel fizic constă din 2 fire: SCL și SDA, respectiv liniile de ceas și de date.

Obiectele I2C sunt create atașate la o magistrală specifică. Ele pot fi inițializate la creare sau inițializate mai târziu.

Afișarea obiectului I2C vă oferă informații despre configurația acestuia.

Există atât implementări hardware, cât și software ale I2C, prin clasele I2C și SoftI2C. I2C hardware folosește suportul hardware subiacent al sistemului pentru a efectua citirile/scrierile și este de obicei eficient și rapid, dar poate avea restricții privind pinii care pot fi utilizați. I2C software este implementat prin bit-banging și poate fi folosit pe orice pin, dar nu este la fel de eficient. Aceste clase au aceleași metode disponibile și diferă în principal prin modul în care sunt construite.

Notă

Magistrala I2C necesită un circuit de pull-up atât pe SDA, cât și pe SCL pentru funcționarea sa. De obicei, acestea sunt rezistoare în intervalul 1 - 10 kOhm, conectate de la fiecare SDA/SCL la Vcc. Fără acestea, comportamentul este nedefinit și poate varia de la blocare, resetare neașteptată a watchdog-ului, până la pur și simplu valori greșite. Adesea, acest circuit de pull-up este deja integrat în placa MCU sau în plăcile breakout pentru senzori, dar nu există o regulă în acest sens. Așadar, verificați în caz de probleme. Consultați și acest excelent ghid de învățare de la Adafruit despre cablarea I2C.

Exemplu de utilizare:

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

Constructori

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

Construiește și returnează un nou obiect I2C folosind următorii parametri:

  • id identifică un anumit periferic I2C. Valorile permise depind de portul/placa anume

  • scl trebuie să fie un obiect pin care specifică pinul de utilizat pentru SCL.

  • sda trebuie să fie un obiect pin care specifică pinul de utilizat pentru SDA.

  • freq trebuie să fie un întreg care setează frecvența maximă pentru SCL.

  • timeout este timpul maxim în microsecunde permis pentru tranzacțiile I2C. Acest parametru nu este permis pe unele porturi.

Rețineți că unele porturi/plăci vor avea valori implicite pentru scl și sda care pot fi modificate în acest constructor. Altele vor avea valori fixe pentru scl și sda care nu pot fi modificate.

Metode generale

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

Inițializează magistrala I2C cu argumentele date:

  • scl este un obiect pin pentru linia SCL

  • sda este un obiect pin pentru linia SDA

  • freq este rata de ceas SCL

În cazul I2C hardware, frecvența reală a ceasului poate fi mai mică decât frecvența solicitată. Aceasta depinde de hardware-ul platformei. Rata reală poate fi determinată prin afișarea obiectului I2C.

scan() List[int]

Scanează toate adresele I2C între 0x08 și 0x77 inclusiv și returnează o listă cu cele care răspund. Un dispozitiv răspunde dacă trage linia SDA la nivel jos după ce adresa sa (inclusiv un bit de scriere) este trimisă pe magistrală.

Operații I2C primitive

Următoarele metode implementează operațiile primitive de magistrală ale controllerului I2C și pot fi combinate pentru a realiza orice tranzacție I2C. Ele sunt furnizate dacă aveți nevoie de mai mult control asupra magistralei, altfel pot fi folosite metodele standard (vezi mai jos).

Aceste metode sunt disponibile doar pe clasa SoftI2C.

start() None

Generează o condiție START pe magistrală (SDA trece la nivel jos în timp ce SCL este la nivel înalt).

stop() None

Generează o condiție STOP pe magistrală (SDA trece la nivel înalt în timp ce SCL este la nivel înalt).

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

Citește octeți de pe magistrală și îi stochează în buf. Numărul de octeți citiți este lungimea lui buf. Un ACK va fi trimis pe magistrală după primirea tuturor octeților, cu excepția ultimului. După primirea ultimului octet, dacă nack este adevărat atunci va fi trimis un NACK, altfel va fi trimis un ACK (și în acest caz perifericul presupune că vor fi citiți mai mulți octeți într-un apel ulterior).

write(buf: bytes) int

Scrie octeții din buf pe magistrală. Verifică dacă se primește un ACK după fiecare octet și oprește transmiterea octeților rămași dacă se primește un NACK. Funcția returnează numărul de ACK-uri primite.

Operații standard de magistrală

Următoarele metode implementează operațiile standard de citire și scriere ale controllerului I2C care vizează un anumit dispozitiv periferic.

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

Citește nbytes de la perifericul specificat de addr. Dacă stop este adevărat, atunci se generează o condiție STOP la sfârșitul transferului. Returnează un obiect bytes cu datele citite.

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

Citește în buf de la perifericul specificat de addr. Numărul de octeți citiți va fi lungimea lui buf. Dacă stop este adevărat, atunci se generează o condiție STOP la sfârșitul transferului.

Metoda returnează None.

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

Scrie octeții din buf către perifericul specificat de addr. Dacă se primește un NACK după scrierea unui octet din buf, atunci octeții rămași nu sunt trimiși. Dacă stop este adevărat, atunci se generează o condiție STOP la sfârșitul transferului, chiar dacă se primește un NACK. Funcția returnează numărul de ACK-uri primite.

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

Scrie octeții conținuți în vector către perifericul specificat de addr. vector trebuie să fie un tuplu sau o listă de obiecte cu protocolul buffer. addr este trimis o singură dată, apoi octeții din fiecare obiect din vector sunt scriși secvențial. Obiectele din vector pot avea lungime zero octeți, caz în care nu contribuie la ieșire.

Dacă se primește un NACK după scrierea unui octet din unul dintre obiectele din vector, atunci octeții rămași și orice obiecte rămase nu sunt trimiși. Dacă stop este adevărat, atunci se generează o condiție STOP la sfârșitul transferului, chiar dacă se primește un NACK. Funcția returnează numărul de ACK-uri primite.

Operații de memorie

Unele dispozitive I2C acționează ca un dispozitiv de memorie (sau un set de registre) din care se poate citi și în care se poate scrie. În acest caz există două adrese asociate unei tranzacții I2C: adresa perifericului și adresa de memorie. Următoarele metode sunt funcții de conveniență pentru a comunica cu astfel de dispozitive.

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

Citește nbytes de la perifericul specificat de addr începând de la adresa de memorie specificată de memaddr. Argumentul addrsize specifică dimensiunea adresei în biți. Returnează un obiect bytes cu datele citite.

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

Citește în buf de la perifericul specificat de addr începând de la adresa de memorie specificată de memaddr. Numărul de octeți citiți este lungimea lui buf. Argumentul addrsize specifică dimensiunea adresei în biți.

Metoda returnează None.

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

Scrie buf către perifericul specificat de addr începând de la adresa de memorie specificată de memaddr. Argumentul addrsize specifică dimensiunea adresei în biți.

Metoda returnează None.

clasa SoftI2C – magistrală I2C emulată software

Clasa SoftI2C implementează I2C prin bit-banging pe pini GPIO arbitrari. Ea expune aceeași suprafață de metode ca I2C plus operațiile primitive de magistrală de nivel scăzut (start(), stop(), readinto(), write()) pentru apelanții care au nevoie să asambleze tranzacții non-standard. Folosiți-o atunci când pinii de care aveți nevoie nu sunt conectați la un bloc I2C hardware, când aveți nevoie de mai multe magistrale decât oferă hardware-ul sau pentru a comunica cu dispozitive care necesită secvențe neobișnuite (ceasuri suplimentare, START-uri repetate după scrieri etc.).

Constructori

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

Construiește o magistrală I2C software acționată de scl / sda.

freq este rata țintă de ceas SCL în Hz (rata reală este de obicei mai mică din cauza supraîncărcării buclei de bit-bang).

timeout este timpul maxim în microsecunde de așteptare pentru întinderea ceasului (SCL menținut la nivel jos de un alt dispozitiv de pe magistrală); la expirare se ridică o eroare OSError(ETIMEDOUT).

Metode generale

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

Reinițializează magistrala I2C software cu pinii și frecvența date. Echivalent cu construirea unui nou SoftI2C pe același obiect.

scan() List[int]

Scanează toate adresele I2C între 0x08 și 0x77 inclusiv și returnează o listă cu cele care au răspuns.

Operații I2C primitive

Următoarele metode implementează operațiile primitive de magistrală ale controllerului I2C și pot fi combinate pentru a realiza orice tranzacție I2C. Ele sunt disponibile doar pe SoftI2C – clasa hardware I2C nu le expune.

start() None

Generează o condiție START pe magistrală (SDA trece la nivel jos în timp ce SCL este la nivel înalt).

stop() None

Generează o condiție STOP pe magistrală (SDA trece la nivel înalt în timp ce SCL este la nivel înalt).

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

Citește octeți de pe magistrală în buf. Se citesc len(buf) octeți; un ACK este trimis după fiecare octet, cu excepția ultimului. După ultimul octet, nack=True (valoarea implicită) trimite un NACK pentru a încheia transferul; nack=False trimite un ACK astfel încât dispozitivul să rămână selectat pentru un readinto() ulterior.

write(buf: bytes) int

Scrie buf pe magistrală, verificând ACK după fiecare octet. Transmisia se oprește la primul NACK. Returnează numărul de ACK-uri primite.

Operații standard de magistrală

Următoarele metode implementează operațiile standard de citire și scriere ale controllerului I2C care vizează un anumit dispozitiv periferic.

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

Citește nbytes de la dispozitivul de la adresa pe 7 biți addr. Dacă stop este adevărat, se generează o condiție STOP la sfârșitul transferului.

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

Citește len(buf) octeți de la dispozitivul de la addr în buf. Dacă stop este adevărat, se generează o condiție STOP la sfârșitul transferului.

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

Scrie buf către dispozitivul de la addr. Transmisia se oprește la primul NACK. Dacă stop este adevărat, se generează întotdeauna o condiție STOP la sfârșitul transferului (chiar și la un NACK timpuriu). Returnează numărul de ACK-uri primite.

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

Scrie concatenarea tampoanelor din vector către dispozitivul de la addr ca o singură tranzacție. Tampoanele goale sunt ignorate. Se comportă ca writeto() în privința semanticii stop și a valorii returnate.

Operații de memorie

Unele dispozitive I2C acționează ca un dispozitiv de memorie (sau un set de registre) din care se poate citi și în care se poate scrie. În acest caz există două adrese asociate unei tranzacții I2C: adresa perifericului și adresa de memorie. Următoarele metode sunt funcții auxiliare de conveniență pentru comunicarea cu astfel de dispozitive.

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

Citește nbytes de la dispozitivul de la addr începând de la registrul memaddr. addrsize este lățimea adresei de registru în biți (de obicei 8 sau 16).

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

Citește în buf de la dispozitivul de la addr începând de la registrul memaddr.

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

Scrie buf către dispozitivul de la addr începând de la registrul memaddr.