CircuitPython, Adafruit Feather RP2040 e I2C

Como probablemente habrás adivinado por el título, este artículo trata sobre CircuitPython, el Adafruit Feather RP2040, y el protocolo de comunicación I2C.

El RP2040 tiene dos controladores I2C - bueno, por ejemplo, cuando se desea ejecutar dos dispositivos I2C con la misma dirección I2C.

En mi configuración de prueba, tengo una placa de microcontrolador Adafruit Feather RP2040, y he conectado dos de nuestros Placas de conexión BME688 - uno utilizando los pines SCL + SDA y otro utilizando A1 (para SCL) + A0 (para SDA).

Estoy usando CircuitPython en la versión 7.0.0, que puede descargar desde aquí.

Además, he instalado todas las bibliotecas de Adafruit en el lib en la Feather RP2040. (El Feather RP2040 tiene suficiente espacio en su Flash para permitir esto)

Puede descargar estas bibliotecas en el Adafruit CircuitPython Bundle aquí. (He descargado adafruit-circuitpython-bundle-7.x-mpy-20211123.zip)

NotaPara instalar estas librerías, basta con copiarlas en la carpeta lib de la "unidad" CIRCUITPY que está montada en su ordenador. Por supuesto, tendrás que copiar sólo las librerías, no los ejemplos y otras cosas.

La gran ventaja de usar el material de Adafruit es que obtienes una tonelada de ejemplos que cubren muchos chips populares, y puedes empezar muy fácilmente con ellos. Hay cosas como la conducción de una tarjeta microSD utilizando SPI, la lectura de un RTC, y la lectura del sensor BME680.

Probando I2C en CircuitPython

Tengo la siguiente configuración, ya que quiero manejar dos dispositivos independientemente uno del otro (que casualmente tienen las mismas direcciones en este caso):

Feather RP2040 y dos Placas de conexión BME688

Ten en cuenta que nuestras placas breakout BME688 ya incluyen pullups para SDA y SCL. (Necesita pullups en SDA y SCL).

Nota 2: Nuestra placa breakout BME688 tiene la opción de cambiar la dirección, por lo que este escenario está pensado para fines de demostración.

Para conducir a través de ambos conjuntos de pines secuencialmente (para descubrir los dispositivos), estoy utilizando el siguiente código:

print("Scanning SCL / SDA")
i2c = busio.I2C(board.SCL, board.SDA)
# a scan
i2c.try_lock()
print(i2c.scan())
i2c.unlock()
i2c.deinit()

print("Scanning A0 / A1")
si2c = busio.I2C(board.A1, board.A0)
# a scan
si2c.try_lock()
print(si2c.scan())
si2c.unlock()

Nota: el i2c.deinit() ¡es la clave para que este ejemplo en particular funcione! (porque SCL / SDA y A0 / A1 tienen ambos el mismo periférico hardware I2C - ver más abajo).

Esto debería dar como resultado lo siguiente:

Scanning SCL / SDA
[119]
Scanning A0 / A1
[119]

Aquí, el 119 es decimal para el hexágono 0x77 - que es la dirección de nuestra placa breakout BME688 en el estado por defecto.

Ambos tableros se ven, secuencialmente en las exploraciones individuales.

El problema es que queremos utilizarlos simultáneamente.

Ejecutando dos buses I2C simultáneamente en el Adafruit Feather RP2040

CircuitPython es compatible con ambos controladores de hardware (SDA0/SCL0 y SDA1/SCL1). No necesitas establecer ninguna configuración (qué controlador quieres usar, o cómo muxar los pines) - de esto se encarga busio para ti.

Sin embargo, hay que prestar atención a las clavijas que se utilizan, ya que las clavijas sólo proporcionan un de estos buses en cada caso, y si por casualidad elige pines conflictivos, obtendrá ValueError: Periférico I2C en uso .

Si quieres usar pines "conflictivos", por ejemplo SCL / SDA (que tienen SCL1 y SDA1) y A0 / A1 (que también tienen SCL1 y SDA1), tendrás que hacer un bitbang en uno de los puertos:

A continuación se explica cómo explorar esta configuración de pines sin llamar a deinit():

import board
import busio
import bitbangio
# https://circuitpython.readthedocs.io/en/latest/shared-bindings/bitbangio/index.html

print("Scanning SCL / SDA - main I2C")
i2c = busio.I2C(board.SCL, board.SDA)
# a scan
i2c.try_lock()
print(i2c.scan())
i2c.unlock()
# no need to call deinit here!
#i2c.deinit()

print("Scanning A0 / A1 - secondary I2C [bitbang!]")
si2c = bitbangio.I2C(board.A1, board.A0)
# a scan
si2c.try_lock()
print(si2c.scan())
si2c.unlock()

# we also do not need to call deinit here
#i2c.deinit()

Estamos utilizando bitbangio para manejar un I2C secundario. En mi caso el I2C secundario se utiliza para un propósito interno (para un expansor de puertos), y muy probablemente puede hacer con una velocidad de interfaz más baja.

Nota: no puedes elegir el periférico I2C que se enruta a los pines en el software - si necesitas un periférico diferente, tienes que usar pines diferentes.

Resolución de errores I2C

ValueError: Periférico I2C en uso

Si estás usando busio.I2C para ambos puertos: Compruebe si ya está utilizando el mismo periférico hardware I2C - y si necesita reasignar sus pines.

Por ejemplo, tanto SCL y SDA , como A0 y A1 comparten el mismo periférico I2C de hardware (SCL1 / SDA1 - ver la imagen del pinout del Adafruit Feather RP2040 en este artículo).

En caso de que quieras usar la misma configuración de pines, puedes usar bitbangio para "crear" un puerto I2C adicional controlado por software. La desventaja de esto es la menor velocidad de este puerto I2C por software, y una mayor carga de la CPU.

RuntimeError: No se ha encontrado pull up en SDA o SCL; compruebe su cableado

Si obtiene el siguiente error

RuntimeError: No se ha encontrado pull up en SDA o SCL; compruebe su cableado

entonces debe poner resistencias pullup entre 3V3 en la placa (el pin de 3.3V) y respectivamente sus pines SDA y SCL. Estos son necesarios para el funcionamiento normal de I2C (los dispositivos tiran de los pines I2C para comunicarse, el estado por defecto / inactivo en el bus es alto) - y no se incluyen en el Adafruit Feather RP2040. Están incluidos en muchos periféricos de Adafruit, y en periféricos de otras compañías (como, de nuevo, nuestro propio Placa de conexión BME688).

Si no sabes lo que es un pullup: se trata esencialmente de una resistencia entre el pin en cuestión (por ejemplo, SDA) y el pin de alimentación de 3,3 V. No es necesario que sea muy preciso. Deberías empezar con resistencias de 10 kOhm, si eso no funciona, posiblemente prueba con una resistencia de 1 kOhm para un pullup más "rígido".

TimeoutError: El tiempo de espera es demasiado largo

Comprueba si el chip con el que quieres hablar está bien alimentado.

Notas varias

Referencias / Recursos / Enlaces / Lecturas complementarias

¿Quieres saber más sobre I2C? Consulte nuestro artículo al respecto aquí.

3 Comentarios

  1. Rob el enero 23, 2023 a las 4:45 pm

    Gracias por un artículo tan útil. Tengo dos sugerencias:

    1) Una de las causas del error "no pull up found" (que es lo que me ha traído hasta aquí) también puede deberse a un error tonto: ¡tener un cable suelto! Esto me ocurre con demasiada frecuencia porque estoy usando conectores Stemma que son un poco finniky.

    2) Creo que tu código de ejemplo es un poco confuso, en el sentido de que creo que podría/debería haber empezado por el caso más sencillo en el que se utilizan los dos controladores I2C disponibles, como por ejemplo

    i2c = busio.I2C(board.SCL, board.SDA)

    si2c = busio.I2C(placa.A9, placa.A6)

    Por supuesto, los métodos que utilice después seguirán siendo válidos y útiles.

    /rob

    • raspi berry el febrero 4, 2023 a las 11:47 am

      ¡Gracias por tu atento comentario, Rob! Los cables sueltos son como el "¿tiene corriente?" del mundo del soporte informático 🙂 Mejor comprobarlo que pasarse horas depurando

  2. Edward M Johnstone el junio 27, 2023 a las 2:43 pm

    Gracias por este artículo.
    ¡Estoy migrando de Raspberry Pi Pico y he estado buscando algo como esto!
    ¡Impresionante!

Deja un comentario