3.24. I2C nel codice¶
machine.I2C incapsula un controller I2C hardware. Costruiscine uno con l’id del bus e una frequenza di clock opzionale:
from machine import I2C
i2c = I2C(2, freq=400_000)
id seleziona quale blocco I2C hardware usare; i numeri disponibili e i pin SDA / SCL a cui sono mappati dipendono dalla scheda (vedi Schede OpenMV). freq è la frequenza di clock di SCL – 100_000 è la modalità standard, 400_000 è la comune velocità «veloce» supportata dalla maggior parte dei sensori.
3.24.1. Scansione del bus¶
scan() percorre lo spazio di indirizzamento a 7 bit e restituisce un elenco di ogni dispositivo che ha confermato il proprio indirizzo:
>>> i2c.scan()
[0x40, 0x68]
Questa è la prima cosa da provare dopo aver cablato un nuovo bus. Se un dispositivo è nell’elenco, il cablaggio e i pull-up sono a posto. Se l’elenco è vuoto (o più corto del previsto), sospetta dei pull-up, del cablaggio o dei pin di indirizzo del dispositivo prima di sospettare del codice.
3.24.2. Lo schema con mappatura in memoria¶
La maggior parte dei sensori I2C espone il proprio stato come un insieme di registri interni: flag di stato a un indirizzo, byte di misura a un altro, configurazione a un terzo. La libreria ha due metodi di comodità che si mappano direttamente sullo schema «indirizza il dispositivo, punta a un registro, poi leggi o scrivi dati».
Lettura di un registro:
addr = 0x68
reg = 0x3B
data = i2c.readfrom_mem(addr, reg, 6)
# `data` is 6 bytes starting at register 0x3B
Quella singola chiamata emette uno START, invia l’indirizzo del dispositivo con il bit di scrittura, invia l’indirizzo del registro (un repeated start), reinvia l’indirizzo con il bit di lettura, legge 6 byte, dà il NACK all’ultimo ed emette uno STOP. Il dispositivo vede la sequenza standard «lettura da registro»; lo script vede una sola chiamata Python.
Scrittura su un registro:
i2c.writeto_mem(addr, 0x6B, b"\x00")
Quella sequenza è la stessa, in immagine speculare: START, indirizzo + scrittura, registro, byte di payload, STOP.
Per i dispositivi il cui indirizzo di registro è largo due byte (alcune EEPROM e memorie con indirizzamento a 16 bit) passa addrsize=16:
data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)
3.24.3. Lettura di un sensore¶
Un breve ciclo che legge l’identificatore WHO_AM_I e l’asse X dell’accelerometro su un tipico sensore 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() restituisce un oggetto bytes, quindi la lettura di WHO_AM_I indicizza il primo (e unico) byte con [0]. La lettura dell’accelerometro è di due byte grezzi che devono essere assemblati in un unico valore con segno a 16 bit; il formato ">h" di struct lo fa come big-endian con segno – l’ordine dei byte usato dalla maggior parte dei sensori I2C, opposto alla convenzione little-endian comune sui collegamenti UART. struct.unpack() restituisce sempre una tupla (un elemento per ogni carattere di formato), quindi [0] estrae il singolo valore.
3.24.4. Accesso di livello più basso¶
Per i dispositivi che non seguono lo schema della mappa di registri, due metodi di livello più basso danno il controllo diretto sulla fase di indirizzo e sulla direzione dei dati:
i2c.writeto(addr, b"\x01\x02\x03") # write 3 bytes to addr
data = i2c.readfrom(addr, 4) # read 4 bytes from addr
Sono utili per i dispositivi con interfacce a flusso di comandi (display, alcuni orologi in tempo reale) in cui gli helper di comodità della mappa di registri non si adattano del tutto.
3.24.5. Bit-banging quando non è disponibile alcun blocco hardware¶
machine.SoftI2C fornisce la stessa API su pin GPIO arbitrari, eseguendo il bit-banging del protocollo via software:
from machine import SoftI2C, Pin
i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)
Usalo quando il dispositivo deve stare su pin non cablati a un blocco I2C hardware, oppure quando ogni blocco hardware è già in uso. Il ciclo di bit-banging è più lento dell’I2C hardware e la CPU rimane occupata per l’intera transazione, ma l’API è identica a I2C quindi il codice esistente passa al nuovo metodo modificando solo il costruttore.