clase I2C – un protocolo serie de dos hilos

I2C es un protocolo de dos hilos para la comunicación entre dispositivos. A nivel físico consta de 2 hilos: SCL y SDA, las líneas de reloj y de datos respectivamente.

Los objetos I2C se crean asociados a un bus específico. Pueden inicializarse al crearse, o inicializarse más tarde.

Al imprimir el objeto I2C se obtiene información sobre su configuración.

Existen implementaciones de I2C tanto por hardware como por software a través de las clases I2C y SoftI2C. El I2C por hardware utiliza el soporte de hardware subyacente del sistema para realizar las lecturas/escrituras y suele ser eficiente y rápido, pero puede tener restricciones sobre qué pines pueden usarse. El I2C por software se implementa mediante bit-banging y puede usarse en cualquier pin, pero no es tan eficiente. Estas clases tienen los mismos métodos disponibles y se diferencian principalmente en la forma en que se construyen.

Nota

El bus I2C requiere circuitería de pull-up tanto en SDA como en SCL para su funcionamiento. Normalmente se trata de resistencias en el rango de 1 a 10 kOhm, conectadas desde cada SDA/SCL a Vcc. Sin ellas, el comportamiento es indefinido y puede ir desde el bloqueo o un reinicio inesperado del watchdog hasta simplemente valores incorrectos. A menudo, esta circuitería de pull-up ya viene integrada en la placa del MCU o en las placas de conexión del sensor, pero no hay una regla para ello. Así que, por favor, compruébelo en caso de problemas. Consulte también esta excelente guía de aprendizaje de Adafruit sobre el cableado de I2C.

Ejemplo de uso:

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

Constructores

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

Construye y devuelve un nuevo objeto I2C usando los siguientes parámetros:

  • id identifica un periférico I2C en particular. Los valores permitidos dependen del puerto/placa concreto

  • scl debe ser un objeto pin que especifique el pin a usar para SCL.

  • sda debe ser un objeto pin que especifique el pin a usar para SDA.

  • freq debe ser un entero que establezca la frecuencia máxima para SCL.

  • timeout es el tiempo máximo en microsegundos permitido para las transacciones I2C. Este parámetro no se permite en algunos puertos.

Tenga en cuenta que algunos puertos/placas tendrán valores predeterminados de scl y sda que pueden cambiarse en este constructor. Otros tendrán valores fijos de scl y sda que no pueden cambiarse.

Métodos generales

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

Inicializa el bus I2C con los argumentos dados:

  • scl es un objeto pin para la línea SCL

  • sda es un objeto pin para la línea SDA

  • freq es la velocidad de reloj de SCL

En el caso del I2C por hardware, la frecuencia de reloj real puede ser inferior a la frecuencia solicitada. Esto depende del hardware de la plataforma. La velocidad real puede determinarse imprimiendo el objeto I2C.

scan() List[int]

Escanea todas las direcciones I2C entre 0x08 y 0x77 inclusive y devuelve una lista de las que responden. Un dispositivo responde si lleva la línea SDA a nivel bajo después de que su dirección (incluyendo un bit de escritura) se envía en el bus.

Operaciones primitivas de I2C

Los siguientes métodos implementan las operaciones primitivas del bus del controlador I2C y pueden combinarse para realizar cualquier transacción I2C. Se proporcionan por si necesita más control sobre el bus; de lo contrario, pueden usarse los métodos estándar (véase más abajo).

Estos métodos solo están disponibles en la clase SoftI2C.

start() None

Genera una condición de START en el bus (SDA pasa a nivel bajo mientras SCL está en alto).

stop() None

Genera una condición de STOP en el bus (SDA pasa a nivel alto mientras SCL está en alto).

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

Lee bytes del bus y los almacena en buf. El número de bytes leídos es la longitud de buf. Se enviará un ACK en el bus tras recibir todos los bytes menos el último. Después de recibir el último byte, si nack es verdadero se enviará un NACK; de lo contrario se enviará un ACK (y en este caso el periférico asume que se van a leer más bytes en una llamada posterior).

write(buf: bytes) int

Escribe los bytes de buf en el bus. Comprueba que se recibe un ACK después de cada byte y deja de transmitir los bytes restantes si se recibe un NACK. La función devuelve el número de ACK recibidos.

Operaciones estándar del bus

Los siguientes métodos implementan las operaciones estándar de lectura y escritura del controlador I2C dirigidas a un dispositivo periférico dado.

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

Lee nbytes del periférico especificado por addr. Si stop es verdadero, se genera una condición de STOP al final de la transferencia. Devuelve un objeto bytes con los datos leídos.

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

Lee en buf desde el periférico especificado por addr. El número de bytes leídos será la longitud de buf. Si stop es verdadero, se genera una condición de STOP al final de la transferencia.

El método devuelve None.

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

Escribe los bytes de buf en el periférico especificado por addr. Si se recibe un NACK tras la escritura de un byte de buf, los bytes restantes no se envían. Si stop es verdadero, se genera una condición de STOP al final de la transferencia, incluso si se recibe un NACK. La función devuelve el número de ACK recibidos.

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

Escribe los bytes contenidos en vector en el periférico especificado por addr. vector debe ser una tupla o lista de objetos con el protocolo de búfer. La addr se envía una vez y luego los bytes de cada objeto de vector se escriben secuencialmente. Los objetos de vector pueden tener longitud cero, en cuyo caso no contribuyen a la salida.

Si se recibe un NACK tras la escritura de un byte de uno de los objetos de vector, los bytes restantes, y cualquier objeto restante, no se envían. Si stop es verdadero, se genera una condición de STOP al final de la transferencia, incluso si se recibe un NACK. La función devuelve el número de ACK recibidos.

Operaciones de memoria

Algunos dispositivos I2C actúan como un dispositivo de memoria (o conjunto de registros) que puede leerse y escribirse. En este caso hay dos direcciones asociadas a una transacción I2C: la dirección del periférico y la dirección de memoria. Los siguientes métodos son funciones de conveniencia para comunicarse con tales dispositivos.

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

Lee nbytes del periférico especificado por addr comenzando desde la dirección de memoria especificada por memaddr. El argumento addrsize especifica el tamaño de la dirección en bits. Devuelve un objeto bytes con los datos leídos.

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

Lee en buf desde el periférico especificado por addr comenzando desde la dirección de memoria especificada por memaddr. El número de bytes leídos es la longitud de buf. El argumento addrsize especifica el tamaño de la dirección en bits.

El método devuelve None.

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

Escribe buf en el periférico especificado por addr comenzando desde la dirección de memoria especificada por memaddr. El argumento addrsize especifica el tamaño de la dirección en bits.

El método devuelve None.

clase SoftI2C – bus I2C emulado por software

La clase SoftI2C implementa I2C mediante bit-banging de pines GPIO arbitrarios. Expone la misma superficie de métodos que I2C más las operaciones primitivas de bus de bajo nivel (start(), stop(), readinto(), write()) para quienes necesiten ensamblar transacciones no estándar. Úsela cuando los pines que necesita no estén conectados a un bloque I2C de hardware, cuando necesite más buses de los que proporciona el hardware, o para comunicarse con dispositivos que requieren secuencias inusuales (pulsos de reloj adicionales, starts repetidos después de escrituras, etc.).

Constructores

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

Construye un bus I2C por software gobernado por scl / sda.

freq es la velocidad de reloj de SCL objetivo en Hz (la velocidad real suele ser inferior debido a la sobrecarga del bucle de bit-banging).

timeout es el tiempo máximo en microsegundos a esperar por el estiramiento del reloj (clock stretching, SCL mantenido en bajo por otro dispositivo del bus); al expirar se lanza un OSError(ETIMEDOUT).

Métodos generales

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

Reinicializa el bus I2C por software con los pines y la frecuencia dados. Equivale a construir un nuevo SoftI2C sobre el mismo objeto.

scan() List[int]

Escanea todas las direcciones I2C entre 0x08 y 0x77 inclusive y devuelve una lista de las que respondieron.

Operaciones primitivas de I2C

Los siguientes métodos implementan las operaciones primitivas del bus del controlador I2C y pueden combinarse para realizar cualquier transacción I2C. Son exclusivos de SoftI2C: la clase I2C por hardware no los expone.

start() None

Genera una condición de START en el bus (SDA pasa a nivel bajo mientras SCL está en alto).

stop() None

Genera una condición de STOP en el bus (SDA pasa a nivel alto mientras SCL está en alto).

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

Lee bytes del bus en buf. Se leen len(buf) bytes; se envía un ACK después de cada byte excepto el último. Tras el último byte, nack=True (el valor predeterminado) envía un NACK para terminar la transferencia; nack=False envía un ACK para que el dispositivo permanezca seleccionado para una readinto() posterior.

write(buf: bytes) int

Escribe buf en el bus, comprobando el ACK después de cada byte. La transmisión se detiene en el primer NACK. Devuelve el número de ACK recibidos.

Operaciones estándar del bus

Los siguientes métodos implementan las operaciones estándar de lectura y escritura del controlador I2C dirigidas a un dispositivo periférico dado.

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

Lee nbytes del dispositivo en la dirección de 7 bits addr. Si stop es verdadero, se genera una condición de STOP al final de la transferencia.

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

Lee len(buf) bytes del dispositivo en addr hacia buf. Si stop es verdadero, se genera una condición de STOP al final de la transferencia.

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

Escribe buf en el dispositivo en addr. La transmisión se detiene en el primer NACK. Si stop es verdadero, siempre se genera una condición de STOP al final de la transferencia (incluso ante un NACK temprano). Devuelve el número de ACK recibidos.

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

Escribe la concatenación de los búferes de vector en el dispositivo en addr como una sola transacción. Los búferes vacíos se ignoran. Se comporta como writeto() en cuanto a la semántica de stop y el valor de retorno.

Operaciones de memoria

Algunos dispositivos I2C actúan como un dispositivo de memoria (o conjunto de registros) que puede leerse y escribirse. En este caso hay dos direcciones asociadas a una transacción I2C: la dirección del periférico y la dirección de memoria. Los siguientes métodos son ayudantes de conveniencia para comunicarse con tales dispositivos.

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

Lee nbytes del dispositivo en addr comenzando en el registro memaddr. addrsize es el ancho de la dirección de registro en bits (normalmente 8 o 16).

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

Lee en buf desde el dispositivo en addr comenzando en el registro memaddr.

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

Escribe buf en el dispositivo en addr comenzando en el registro memaddr.