class I2C – un protocollo seriale a due fili

I2C è un protocollo a due fili per la comunicazione tra dispositivi. A livello fisico è costituito da 2 fili: SCL e SDA, rispettivamente le linee di clock e dei dati.

Gli oggetti I2C vengono creati collegati a un bus specifico. Possono essere inizializzati al momento della creazione, oppure successivamente.

La stampa dell’oggetto I2C fornisce informazioni sulla sua configurazione.

Esistono implementazioni I2C sia hardware che software, tramite le classi I2C e SoftI2C. L’I2C hardware utilizza il supporto hardware sottostante del sistema per eseguire le letture/scritture ed è solitamente efficiente e veloce, ma può avere restrizioni su quali pin possono essere utilizzati. L’I2C software è implementato tramite bit-banging e può essere utilizzato su qualsiasi pin, ma non è altrettanto efficiente. Queste classi mettono a disposizione gli stessi metodi e differiscono principalmente per il modo in cui vengono costruite.

Nota

Il bus I2C richiede una circuiteria di pull-up sia su SDA che su SCL per il suo funzionamento. Di solito si tratta di resistori nell’intervallo di 1 - 10 kOhm, collegati da ciascuna linea SDA/SCL a Vcc. Senza di essi, il comportamento è indefinito e può variare dal blocco, a un reset inatteso del watchdog, fino a valori semplicemente errati. Spesso questa circuiteria di pull-up è già integrata nella scheda MCU o nelle schede breakout dei sensori, ma non è una regola assoluta. Quindi controlla in caso di problemi. Vedi anche questa eccellente guida di apprendimento di Adafruit sul cablaggio I2C.

Esempio di utilizzo:

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

Costruttori

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

Costruisce e restituisce un nuovo oggetto I2C utilizzando i seguenti parametri:

  • id identifica una particolare periferica I2C. I valori consentiti dipendono dalla particolare porta/scheda

  • scl deve essere un oggetto pin che specifica il pin da utilizzare per SCL.

  • sda deve essere un oggetto pin che specifica il pin da utilizzare per SDA.

  • freq deve essere un intero che imposta la frequenza massima per SCL.

  • timeout è il tempo massimo in microsecondi consentito per le transazioni I2C. Questo parametro non è ammesso su alcune porte.

Nota che alcune porte/schede avranno valori predefiniti di scl e sda che possono essere modificati in questo costruttore. Altre avranno valori fissi di scl e sda che non possono essere modificati.

Metodi generali

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

Inizializza il bus I2C con gli argomenti specificati:

  • scl è un oggetto pin per la linea SCL

  • sda è un oggetto pin per la linea SDA

  • freq è la frequenza di clock di SCL

Nel caso dell’I2C hardware, la frequenza di clock effettiva può essere inferiore a quella richiesta. Ciò dipende dall’hardware della piattaforma. La frequenza effettiva può essere determinata stampando l’oggetto I2C.

scan() List[int]

Scansiona tutti gli indirizzi I2C compresi tra 0x08 e 0x77 inclusi e restituisce un elenco di quelli che rispondono. Un dispositivo risponde se porta la linea SDA a livello basso dopo che il suo indirizzo (incluso un bit di scrittura) è stato inviato sul bus.

Operazioni I2C primitive

I seguenti metodi implementano le operazioni primitive del bus controller I2C e possono essere combinati per realizzare qualsiasi transazione I2C. Sono forniti nel caso in cui sia necessario un maggior controllo sul bus, altrimenti possono essere usati i metodi standard (vedi sotto).

Questi metodi sono disponibili solo nella classe SoftI2C.

start() None

Genera una condizione di START sul bus (SDA passa a livello basso mentre SCL è alto).

stop() None

Genera una condizione di STOP sul bus (SDA passa a livello alto mentre SCL è alto).

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

Legge byte dal bus e li memorizza in buf. Il numero di byte letti è la lunghezza di buf. Dopo aver ricevuto tutti i byte tranne l’ultimo verrà inviato un ACK sul bus. Dopo che l’ultimo byte è stato ricevuto, se nack è true verrà inviato un NACK, altrimenti verrà inviato un ACK (e in questo caso la periferica presuppone che altri byte verranno letti in una chiamata successiva).

write(buf: bytes) int

Scrive i byte da buf sul bus. Verifica che venga ricevuto un ACK dopo ogni byte e interrompe la trasmissione dei byte rimanenti se viene ricevuto un NACK. La funzione restituisce il numero di ACK ricevuti.

Operazioni standard del bus

I seguenti metodi implementano le operazioni standard di lettura e scrittura del controller I2C che si rivolgono a un determinato dispositivo periferico.

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

Legge nbytes dalla periferica specificata da addr. Se stop è true viene generata una condizione di STOP al termine del trasferimento. Restituisce un oggetto bytes con i dati letti.

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

Legge in buf dalla periferica specificata da addr. Il numero di byte letti sarà la lunghezza di buf. Se stop è true viene generata una condizione di STOP al termine del trasferimento.

Il metodo restituisce None.

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

Scrive i byte da buf sulla periferica specificata da addr. Se viene ricevuto un NACK dopo la scrittura di un byte da buf, i byte rimanenti non vengono inviati. Se stop è true viene generata una condizione di STOP al termine del trasferimento, anche se viene ricevuto un NACK. La funzione restituisce il numero di ACK ricevuti.

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

Scrive i byte contenuti in vector sulla periferica specificata da addr. vector deve essere una tupla o una lista di oggetti che supportano il buffer protocol. L”addr viene inviato una sola volta e poi i byte di ciascun oggetto in vector vengono scritti in sequenza. Gli oggetti in vector possono avere lunghezza zero byte, nel qual caso non contribuiscono all’output.

Se viene ricevuto un NACK dopo la scrittura di un byte di uno degli oggetti in vector, i byte rimanenti, e qualsiasi oggetto rimanente, non vengono inviati. Se stop è true viene generata una condizione di STOP al termine del trasferimento, anche se viene ricevuto un NACK. La funzione restituisce il numero di ACK ricevuti.

Operazioni di memoria

Alcuni dispositivi I2C si comportano come un dispositivo di memoria (o un insieme di registri) da cui è possibile leggere e su cui è possibile scrivere. In questo caso ci sono due indirizzi associati a una transazione I2C: l’indirizzo della periferica e l’indirizzo di memoria. I seguenti metodi sono funzioni di comodità per comunicare con tali dispositivi.

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

Legge nbytes dalla periferica specificata da addr a partire dall’indirizzo di memoria specificato da memaddr. L’argomento addrsize specifica la dimensione dell’indirizzo in bit. Restituisce un oggetto bytes con i dati letti.

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

Legge in buf dalla periferica specificata da addr a partire dall’indirizzo di memoria specificato da memaddr. Il numero di byte letti è la lunghezza di buf. L’argomento addrsize specifica la dimensione dell’indirizzo in bit.

Il metodo restituisce None.

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

Scrive buf sulla periferica specificata da addr a partire dall’indirizzo di memoria specificato da memaddr. L’argomento addrsize specifica la dimensione dell’indirizzo in bit.

Il metodo restituisce None.

class SoftI2C – bus I2C emulato via software

La classe SoftI2C implementa l’I2C tramite bit-banging di pin GPIO arbitrari. Espone la stessa interfaccia di metodi di I2C più le operazioni primitive di basso livello del bus (start(), stop(), readinto(), write()) per i chiamanti che devono assemblare transazioni non standard. Usala quando i pin di cui hai bisogno non sono collegati a un blocco I2C hardware, quando hai bisogno di più bus di quelli forniti dall’hardware, oppure per dialogare con dispositivi che richiedono sequenze insolite (clock aggiuntivi, start ripetuti dopo le scritture, ecc.).

Costruttori

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

Costruisce un bus I2C software pilotato da scl / sda.

freq è la frequenza di clock SCL desiderata in Hz (la frequenza effettiva è tipicamente inferiore a causa dell’overhead del ciclo di bit-bang).

timeout è il tempo massimo in microsecondi di attesa per il clock stretching (SCL tenuto basso da un altro dispositivo sul bus); alla scadenza viene sollevato un OSError(ETIMEDOUT).

Metodi generali

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

Re-inizializza il bus I2C software con i pin e la frequenza specificati. Equivale a costruire un nuovo SoftI2C sullo stesso oggetto.

scan() List[int]

Scansiona tutti gli indirizzi I2C compresi tra 0x08 e 0x77 inclusi e restituisce un elenco di quelli che hanno risposto.

Operazioni I2C primitive

I seguenti metodi implementano le operazioni primitive del bus controller I2C e possono essere combinati per realizzare qualsiasi transazione I2C. Sono esclusivi di SoftI2C – la classe hardware I2C non li espone.

start() None

Genera una condizione di START sul bus (SDA passa a livello basso mentre SCL è alto).

stop() None

Genera una condizione di STOP sul bus (SDA passa a livello alto mentre SCL è alto).

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

Legge byte dal bus in buf. Vengono letti len(buf) byte; dopo ogni byte tranne l’ultimo viene inviato un ACK. Dopo l’ultimo byte, nack=True (il valore predefinito) invia un NACK per terminare il trasferimento; nack=False invia un ACK in modo che il dispositivo resti selezionato per una successiva readinto().

write(buf: bytes) int

Scrive buf sul bus, verificando l’ACK dopo ogni byte. La trasmissione si interrompe al primo NACK. Restituisce il numero di ACK ricevuti.

Operazioni standard del bus

I seguenti metodi implementano le operazioni standard di lettura e scrittura del controller I2C che si rivolgono a un determinato dispositivo periferico.

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

Legge nbytes dal dispositivo all’indirizzo a 7 bit addr. Se stop è true viene generata una condizione di STOP al termine del trasferimento.

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

Legge len(buf) byte dal dispositivo all’indirizzo addr in buf. Se stop è true viene generata una condizione di STOP al termine del trasferimento.

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

Scrive buf sul dispositivo all’indirizzo addr. La trasmissione si interrompe al primo NACK. Se stop è true viene sempre generata una condizione di STOP al termine del trasferimento (anche in caso di NACK anticipato). Restituisce il numero di ACK ricevuti.

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

Scrive la concatenazione dei buffer in vector sul dispositivo all’indirizzo addr come una singola transazione. I buffer vuoti vengono ignorati. Si comporta come writeto() per quanto riguarda la semantica di stop e il valore di ritorno.

Operazioni di memoria

Alcuni dispositivi I2C si comportano come un dispositivo di memoria (o un insieme di registri) da cui è possibile leggere e su cui è possibile scrivere. In questo caso ci sono due indirizzi associati a una transazione I2C: l’indirizzo della periferica e l’indirizzo di memoria. I seguenti metodi sono ausili di comodità per dialogare con tali dispositivi.

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

Legge nbytes dal dispositivo all’indirizzo addr a partire dal registro memaddr. addrsize è la larghezza dell’indirizzo del registro in bit (tipicamente 8 o 16).

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

Legge in buf dal dispositivo all’indirizzo addr a partire dal registro memaddr.

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

Scrive buf sul dispositivo all’indirizzo addr a partire dal registro memaddr.