3.24. I2C en código¶
machine.I2C envuelve un controlador I2C por hardware. Construye uno con el id del bus y una frecuencia de reloj opcional:
from machine import I2C
i2c = I2C(2, freq=400_000)
id selecciona qué bloque I2C de hardware usar; los números disponibles y los pines SDA / SCL a los que se asignan dependen de la placa (consulta la Placas OpenMV). freq es la velocidad de reloj de SCL – 100_000 es el modo estándar, 400_000 es la habitual velocidad «rápida» que admiten la mayoría de los sensores.
3.24.1. Escanear el bus¶
scan() recorre el espacio de direcciones de 7 bits y devuelve una lista de todos los dispositivos que reconocieron su dirección:
>>> i2c.scan()
[0x40, 0x68]
Esto es lo primero que conviene probar tras cablear un bus nuevo. Si un dispositivo aparece en la lista, el cableado y los pull-ups están bien. Si la lista está vacía (o es más corta de lo esperado), sospecha de los pull-ups, el cableado o los pines de dirección del dispositivo antes de sospechar del código.
3.24.2. El patrón mapeado en memoria¶
La mayoría de los sensores I2C exponen su estado como un conjunto de registros internos: indicadores de estado en una dirección, bytes de medición en otra, configuración en una tercera. La biblioteca tiene dos métodos de conveniencia que se corresponden directamente con el patrón «direccionar el dispositivo, apuntar a un registro y luego leer o escribir datos».
Leer un registro:
addr = 0x68
reg = 0x3B
data = i2c.readfrom_mem(addr, reg, 6)
# `data` is 6 bytes starting at register 0x3B
Esa única llamada emite un START, envía la dirección del dispositivo con el bit de escritura, envía la dirección del registro (un start repetido), reenvía la dirección con el bit de lectura, lee 6 bytes, hace NACK del último y emite STOP. El dispositivo ve la secuencia estándar de «lectura desde registro»; el script ve una sola llamada de Python.
Escribir en un registro:
i2c.writeto_mem(addr, 0x6B, b"\x00")
Esa secuencia es la misma en imagen especular: START, dirección + escritura, registro, byte de carga útil, STOP.
Para dispositivos cuya dirección de registro tiene dos bytes de ancho (algunas EEPROM y memorias direccionadas a 16 bits) pasa addrsize=16:
data = i2c.readfrom_mem(addr, 0x0100, 32, addrsize=16)
3.24.3. Leer un sensor¶
Un bucle corto que lee el identificador WHO_AM_I y el eje X del acelerómetro en un sensor MEMS típico:
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() devuelve un objeto bytes, así que la lectura de WHO_AM_I indexa el primer (y único) byte con [0]. La lectura del acelerómetro son dos bytes crudos que hay que ensamblar en un único valor con signo de 16 bits; el formato ">h" de struct lo hace como big-endian con signo – el orden de bytes que usan la mayoría de los sensores I2C, opuesto al convenio little-endian habitual en los enlaces UART. struct.unpack() siempre devuelve una tupla (un elemento por carácter de formato), así que [0] extrae el valor único.
3.24.4. Acceso de bajo nivel¶
Para dispositivos que no siguen el patrón de mapa de registros, dos métodos de más bajo nivel dan control directo sobre la fase de dirección y la dirección de los datos:
i2c.writeto(addr, b"\x01\x02\x03") # write 3 bytes to addr
data = i2c.readfrom(addr, 4) # read 4 bytes from addr
Son útiles para dispositivos con interfaces de flujo de comandos (pantallas, algunos relojes de tiempo real) donde los métodos de conveniencia de mapa de registros no encajan del todo.
3.24.5. Bit-banging cuando no hay un bloque de hardware disponible¶
machine.SoftI2C ofrece la misma API en pines GPIO arbitrarios, implementando el protocolo por software mediante bit-banging:
from machine import SoftI2C, Pin
i2c = SoftI2C(scl=Pin("P5"), sda=Pin("P4"), freq=100_000)
Úsalo cuando el dispositivo deba estar en pines que no están cableados a un bloque I2C de hardware, o cuando todos los bloques de hardware estén en uso. El bucle de bit-banging es más lento que el I2C por hardware y la CPU permanece ocupada durante toda la transacción, pero la API es idéntica a la de I2C, así que el código existente se migra cambiando únicamente el constructor.