Acceder a UART y I2C desde RaspberryOS: guía completa

  • UART e I2C son complementarios: serie asíncrona punto a punto frente a bus síncrono de dos hilos con direcciones.
  • Raspberry Pi expone UART e I2C en GPIO14/15 y SDA1/SCL1; activa con raspi-config y evita la consola serie si los usas.
  • Herramientas clave: i2c-tools y smbus2 para I2C; pyserial o miniterm para UART; vigila pull-ups, GND común y baud rate.
  • Evita errores típicos: en Pi 3 fija core_freq o libera PL011, no mezcles pines, y diagnostica el Errno 121 con i2cdetect.

Interfaces UART e I2C en RaspberryOS

Controlar hardware desde una Raspberry Pi es de lo más entretenido y útil cuando dominas sus buses de comunicaciones. En el día a día, los reyes del mambo son UART e I2C: uno es serie asíncrono de toda la vida; el otro, un bus síncrono de dos hilos que permite colgar varios dispositivos a la vez. Aquí tienes una guía para manejarlos en RaspberryOS de forma clara y sin rodeos.

Además de explicar qué son y cómo activarlos, verás configuraciones reales en Raspberry Pi (consola serie, pines, herramientas), ejemplos prácticos en Python, detalles finos sobre Bluetooth y mini-UART en ciertos modelos, y una sección de diagnóstico para errores habituales como el temido Remote I/O error al mezclar I2C y UART. También incluimos el enfoque de Windows 10/IoT con RhProxy y ACPI, por si te mueves en ese mundo.

Qué son UART e I2C y por qué te interesan

Qué es UART e I2C

UART (Universal Asynchronous Receiver-Transmitter) es una interfaz serie asíncrona que transmite y recibe datos sin reloj compartido. Usa dos líneas dedicadas: TX (envío) y RX (recepción). Al no haber reloj, ambos extremos deben acordar la misma velocidad, es decir, el baud rate (por ejemplo 9600, 115200 bps). Frente a SPI o I2C, es más simple de cablear, aunque suele ser más lento y punto a punto.

En el otro lado tenemos I2C (Inter-Integrated Circuit), un protocolo serie síncrono que funciona con solo dos hilos: SDA (datos) y SCL (reloj). Cada dispositivo del bus tiene dirección única de 7 o 10 bits, lo que permite colgar varios sensores, pantallas o memorias sobre las mismas líneas. Soporta diferentes velocidades (100 kHz estándar, 400 kHz rápido, y variantes superiores como 3.4 Mbps) y multi-maestro bajo ciertas condiciones.

En la práctica, UART es ideal para consolas, GPS, Bluetooth o microcontroladores que hablen serie clásica; I2C brilla para leer varios sensores con poco cableado y buena integración en proyectos de IoT, robótica y automatización. Ambas conviven de maravilla en Raspberry Pi, siempre que configures bien sus pines y evites solapamientos.

UART en Raspberry Pi: pines, consola y configuraciones clave

UART en Raspberry Pi

En la Raspberry Pi, la UART principal se expone en GPIO14 (TXD) y GPIO15 (RXD) del conector de 40 pines. Por defecto, muchas imágenes de sistema activan la consola serie sobre estos pines, muy útil para depurar sin monitor ni red. Históricamente el SoC Broadcom incluye dos UART: UART0 (PL011, completa) y UART1 (mini-UART, recortada).

En modelos como Raspberry Pi 3, la UART «buena» (PL011) se reserva para Bluetooth y la consola se mueve a la mini-UART. Esto tiene consecuencias: la mini-UART depende de la frecuencia del core, y para que sea estable se suele fijar core_freq=250. Si no, puedes sufrir variaciones de baud con el governor de frecuencia y perder caracteres a lo loco.

Si quieres usar UART para tu proyecto en lugar de la consola, lo habitual es deshabilitar la consola serie. Tienes varias opciones: mediante raspi-config en Interfaces > Serial, desactivar el login por serie (pero mantener el hardware activado), o de forma manual editando /boot/cmdline.txt para eliminar console=serial0,115200 y reiniciar. Con eso, los pines GPIO14/15 quedan libres para tu aplicación.

Otra opción muy socorrida es emplear un adaptador USB–UART. Muchos cables traen cuatro hilos: rojo (5 V), negro (GND), blanco y verde (TX/RX). En uso típico con la Raspberry Pi, no conectes el rojo (la Pi ya se alimenta sola), enlaza GND con GND, y cruza TX con RX (el TX del adaptador hacia RX de la Pi y viceversa). Conecta el extremo USB a la Pi para hablar con otra placa o con otra Pi a través de /dev/ttyUSB0 o /dev/ttyACM0.

Para probar la consola en serie desde la propia Raspberry Pi con el adaptador, puedes usar miniterm.py u otra terminal serie: miniterm.py /dev/ttyUSB0 115200. Verás el prompt de login (usuario pi, contraseña raspberry en sistemas clásicos). Para salir de miniterm en el ejemplo del taller, usa Ctrl + AltGr + ]. Si prefieres screen: screen /dev/ttyUSB0 115200 y sales con Ctrl-A seguido de \.

I2C en Raspberry Pi: habilitar, cablear y herramientas imprescindibles

I2C en Raspberry Pi está expuesto en los pines físicos 3 y 5 del encabezado (SDA1 y SCL1, que corresponden a GPIO2 y GPIO3). Lo primero es habilitarlo. En RaspberryOS entra en sudo raspi-config y, en «Advanced Options» o «Interface Options», activa I2C. El asistente te ofrece cargar el módulo al arranque.

En configuraciones clásicas se añadían a /etc/modules las líneas i2c-bcm2708 e i2c-dev; hoy día las imágenes recientes ya gestionan los overlays, pero i2c-dev sigue siendo clave para el espacio de usuario. Instala las utilidades con: sudo apt update y sudo apt install i2c-tools python-smbus (o python3-smbus según tu versión).

Para comprobar que está todo en marcha, ejecuta lsmod | grep i2c y verás los módulos cargados. Luego, identifica el bus y explóralo con i2cdetect -y 1 (en modelos muy antiguos, era -y 0). Si hay dispositivos correctamente conectados y con pull-ups adecuados en SDA y SCL, el escaneo mostrará direcciones como 0x48, 0x20, etc.

Recuerda que el bus I2C requiere resistencias pull-up en SDA y SCL (a 3.3 V en la Pi) y que todas las masas deben estar unidas (GND común). La ausencia de pull-ups o un cableado largo/ruidoso puede dar problemas intermitentes o NACK, sobre todo a 400 kHz y superiores.

Hablar I2C desde Python (smbus/smbus2)

Para interactuar con dispositivos en I2C desde Python, puedes usar smbus o smbus2. Instálalo si hace falta con pip install smbus2. Un ejemplo básico de escritura y lectura a la dirección 0x48 sería el siguiente:

from smbus2 import SMBus

DEVICE_ADDRESS = 0x48  # Dirección I2C del dispositivo

bus = SMBus(1)         # Bus I2C 1 en Raspberry Pi

# Escribir un byte (por ejemplo, 0x01) al dispositivo
bus.write_byte(DEVICE_ADDRESS, 0x01)

# Leer un byte del dispositivo
data = bus.read_byte(DEVICE_ADDRESS)
print(f"Dato leído: {data}")

bus.close()

Este patrón de write/read te sirve para validar que el dispositivo responde. Si te topas con un Remote I/O error, normalmente el esclavo no está reconociendo la dirección o el registro/operación enviada, o bien las líneas SDA/SCL no están en buen estado (pull-ups, cableado, GND o alimentación).

Comunicación UART con Arduino: USB o GPIO, tú eliges

Para que Raspberry Pi y Arduino hablen por serie, lo más directo es el cable USB: conectas el Arduino a la Pi, y aparecerá un puerto del estilo /dev/ttyACM0 o /dev/ttyUSB0. Es cómodo y evita líos de niveles y consola. Alternativamente, puedes usar los pines GPIO (UART hardware) si deshabilitas el login por serie con raspi-config y dejas activo el HW.

Instala la librería de Python para serie con sudo apt install python3-serial y localiza el puerto disponible con ls /dev/tty*. En Arduino sube un boceto simple que envíe texto:

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println("Hello from Arduino!");
  delay(1000);
}

En la Raspberry Pi, un lector mínimo con pyserial quedaría tal que así:

import serial
import time

ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)

# Espera breve para que Arduino reinicie al abrir el puerto
time.sleep(2)

try:
    while True:
        if ser.in_waiting > 0:
            data = ser.readline().decode('utf-8', errors='ignore').strip()
            print(f"Received: {data}")
except KeyboardInterrupt:
    ser.close()

Para enviar desde la Pi hacia Arduino, basta con ser.write(b»Hola desde Raspberry Pi!\n») y en el sketch leer con Serial.readStringUntil(‘\n’). Es vital que ambos usen la misma velocidad (baud), y conviene esperar un par de segundos al abrir el puerto para que el ATmega reinicie y evites «basura» inicial.

El enfoque Windows 10/IoT: APIs de usuario para GPIO, I2C, SPI y UART con RhProxy y ACPI

Si trabajas con Windows 10/IoT Core o Windows Enterprise, existe un mecanismo para exponer GPIO, I2C, SPI y UART al modo usuario mediante un controlador llamado RhProxy. La idea es declarar, en las tablas ACPI (ASL/AML), los recursos SPB/GPIO que se permiten al usuario, y RhProxy los hace accesibles a través de las APIs de Windows.Devices.*. Esta aproximación es la que usan placas como Raspberry Pi 2/3 en ese ecosistema.

El punto de partida es crear un nodo ACPI como Device(RHPX) con _HID/_CID «MSFT8000» y un _UID, y dentro definir recursos de tipo SPISerialBus, I2CSerialBus, UARTSerialBus y los GPIO permitidos al usuario. En el _DSD se asocian propiedades como bus-SPI-SPI0, SPI0-MinClockInHz, SPI0-MaxClockInHz, SPI0-SupportedDataBitLengths o bus-I2C-I2C1, de modo que la API pueda devolver un «controlador por defecto» para cada bus.

Para I2C, un ejemplo de descriptor sería algo como I2CSerialBus(… «\_SB.I2C1» …), mientras que para UART tienes UARTSerialBus con campos de baud inicial, paridad, bufferes y el ResourceSource del controlador. En SPI, cada CE (chip select) se declara como recurso separado y luego se agrupan por bus en el DSD con índices de recurso.

Un concepto potente en este entorno es la multiplexación de pines en tiempo de ejecución. A través de recursos ACPI especiales MsftFunctionConfig(), los frameworks GpioClx, SpbCx y SerCx piden al controlador GPIO que conmute la función de los pines cuando un cliente abre el dispositivo (p. ej., FromIdAsync() en UWP). Si los pines ya los tenía otra función, la apertura falla; al cerrar el descriptor, se revierte la multiplexación.

Del lado del desarrollador de plataforma, la validación pasa por comprobar con devcon que los drivers SpbCx/GpioClx/SerCx cargan, que rhproxy existe en el sistema (clave de registro de servicio), compilar el ASL a ACPITABL.dat con asl.exe (modo /MsftInternal para MsftFunctionConfig) y activar testsigning. Después, puedes listar dispositivos de usuario con herramientas como I2cTestTool.exe -list, SpiTestTool.exe -list, GpioTestTool.exe -list o MinComm.exe -list, y ejercitarlos con lecturas/escrituras de ejemplo. Para certificación, ejecuta las pruebas de HLK (I2C WinRT, GPIO WinRT, SPI WinRT).

Mezclar I2C y UART sin errores: diagnóstico del Remote I/O y otros sustos

Un caso típico: ejecutas un programa que habla por I2C con un sensor IMU (ITG/MPU) y otro que usa UART con una controladora (p. ej., SSC-32), y de repente el proceso de I2C revienta con Remote I/O error. Este error indica que el maestro no recibe ACK en el bus: el dispositivo no contesta o las líneas no están en estado correcto.

Para salir del atasco, revisa lo siguiente: 1) Consola serie desactivada si usas los pines GPIO14/15 para tu propio UART; 2) En Pi 3/derivadas, considera deshabilitar Bluetooth si necesitas la UART PL011 estable, o al menos fija core_freq=250 si tiras de mini-UART; 3) Verifica GND común entre todos los equipos (Pi, IMU, controladora), y una alimentación limpia (las IMU son sensibles a bajones).

4) Comprueba el direccionamiento I2C: escanea con i2cdetect -y 1 y asegúrate de que la dirección (p. ej., 0x68/0x69 en muchas IMU) aparece. Si no sale, puede que uses el bus incorrecto, haya un cable mal, o falten las pull-ups. 5) Revisa la velocidad del bus (si vas a 400 kHz y el montaje es de protoboard con cables largos, baja a 100 kHz y prueba). 6) Evita colisiones de acceso: aunque Linux permite múltiples procesos sobre /dev/i2c-1, si dos hilos acceden en paralelo sin coordinarse puedes provocar condiciones extrañas; usa bloqueos o serializa operaciones.

7) Si el error aparece justo cuando el programa UART carga o eleva carga de CPU, sospecha de la mini-UART desincronizada por el escalado de frecuencia (síntoma en Pi 3). Fija core_freq=250 en /boot/config.txt o libera la PL011 para tu UART desactivando Bluetooth. 8) Verifica que no compartes pines entre funciones: en Windows/IoT la apertura fallaría por la multiplexación; en RaspberryOS, un overlay o servicio podría estar manteniendo ocupados ciertos GPIO. 9) Finalmente, valida el cableado físico: SDA con SDA, SCL con SCL, sin cruzar, y TX–RX cruzados en serie.

Pruebas rápidas y utilidades que te salvan el día

Para I2C, el tridente básico es: i2cdetect -y 1 para ver dispositivos, i2cget/i2cset para lecturas y escrituras de registros simples, y un script de smbus2 para validar el flujo real. Si i2cdetect no ve la IMU, no pierdas tiempo con el código: hay un problema físico o de overlay.

Para UART, usa miniterm.py o screen como terminal serie y verifica que puedes enviar/recibir a la velocidad elegida sin caracteres extraños. En Python, pyserial con timeout y pequeña pausa inicial suele evitar lecturas vacías. Y si te mueves en Windows IoT, devcon, ACPITABL.dat con asl.exe y las herramientas de ejemplo I2cTestTool/SpiTestTool/GpioTestTool/MinComm te dan visibilidad total.

Ya tienes el mapa completo para trabajar I2C y UART en RaspberryOS (y también en Windows/IoT si te aplica): desde qué son y cómo se activan, a su uso en Python, manejo de consola, particularidades de la UART en Pi 3 y la multiplexación de pines, hasta la resolución del Errno 121 cuando combinas ambos buses. Bien configurados y con un cableado limpio, son herramientas robustas para cualquier proyecto de sensores, robótica o control.

especificaciones técnicas Raspberry Pi CM5-4
Artículo relacionado:
Todo sobre el Raspberry Pi Compute Module 5: Rendimiento y flexibilidad