Accessing UART and I2C from Raspberry OS: a complete guide

  • UART and I2C are complementary: asynchronous point-to-point serial versus two-wire synchronous bus with addresses.
  • Raspberry Pi exposes UART and I2C on GPIO14/15 and SDA1/SCL1; enable them with raspi-config and avoid the serial console if you use them.
  • Key tools: i2c-tools and smbus2 for I2C; pyserial or miniterm for UART; monitor pull-ups, common GND and baud rate.
  • Avoid typical errors: on Pi 3, fix core_freq or release PL011, don't mix pins, and diagnose Errno 121 with i2cdetect.

UART and I2C interfaces in RaspberryOS

Controlling hardware from a Raspberry Pi It's incredibly fun and useful once you master its communication buses. In everyday use, the key players are UART and I2C: one is the traditional asynchronous serial bus; the other, a two-wire synchronous bus that allows multiple devices to be connected simultaneously. Here's a clear and straightforward guide to using them in Raspberry Pi.

In addition to explaining what they are and how to activate them, You'll see real-world configurations on Raspberry Pi. (serial console, pins, tools), practical examples in Python, fine details about Bluetooth and mini-UART in certain models, and a diagnostic section For common errors like the dreaded Remote I/O error when mixing I2C and UART, we've also included the Windows 10/IoT approach with RhProxy and ACPI, in case you're working in that field.

What are UART and I2C and why should you care?

What are UART and I2C?

UART (Universal Asynchronous Receiver-Transmitter) It is an asynchronous serial interface that transmits and receives data without a shared clock. It uses two dedicated lines: TX (shipping) y RX (reception)Since there is no clock, both ends must agree on the same speed, that is, the baud rate (for example, 9600, 115200 bps). Compared to SPI or I2C, it is simpler to wire, although it is usually slower and point-to-point.

On the other side we have I2C (Inter-Integrated Circuit), a synchronous serial protocol that works with only two wires: SDA (data) and SCL (clock). Each device on the bus has 7 or 10 bit unique addressThis allows multiple sensors, displays, or memory devices to be connected to the same lines. It supports different speeds (100 kHz standard, 400 kHz fast, and higher variants such as 3.4 Mbps) and multi-master Under some conditions.

In practice, UART is ideal for consoles, GPS, Bluetooth or microcontrollers that speak classic serial; I2C shines for reading multiple sensors with minimal wiring and good integration into projects IoT, robotics and automationBoth work wonderfully together on Raspberry Pi, as long as you configure their pins correctly and avoid overlaps.

UART on Raspberry Pi: Pins, Console, and Key Settings

UART on Raspberry Pi

On the Raspberry Pi, The main UART is exposed on GPIO14 (TXD) and GPIO15 (RXD) of the 40-pin connector. By default, many system images activate the console series These pins are very useful for debugging without a monitor or network. Historically, the Broadcom SoC includes two UARTs: UART0 (PL011, complete) y UART1 (mini-UART, truncated).

In models like Raspberry Pi 3, The "good" UART (PL011) is reserved for Bluetooth and the console moves to the mini-UART. This has consequences: the mini-UART depends on the core frequency, and for it to be stable, it is usually set core_freq=250Otherwise, you may experience baud rate variations with the frequency governor and lose characters like crazy.

If you want to use UART for your project instead of the console, the usual thing to do is disable the serial consoleYou have several options: using raspi-config in Interfaces > Serial, disable serial login (but keep the hardware enabled), or manually by editing /boot/cmdline.txt to remove console=serial0,115200 y ReiniciarWith that, GPIO pins 14/15 are free for your application.

Another very useful option is to use a USB-UART adapterMany cables have four wires: red (5V), black (GND), white, and green (TX/RX). In typical use with the Raspberry Pi, Don't connect the red wire. (The Pi is already self-powered.) Connect GND to GND, and cross TX to RX (the adapter's TX to the Pi's RX and vice versa). Connect the USB end to the Pi to communicate with another board or another Pi via... / dev / ttyUSB0 o /dev/ttyACM0.

To test the serial console from the Raspberry Pi itself using the adapter, You can use miniterm.py or other serial terminal: miniterm.py /dev/ttyUSB0 115200You will see the login prompt (username) pi, password r (in classic systems). To exit miniterm in the workshop example, use Ctrl + AltGr + ]If you prefer a screen: screen /dev/ttyUSB0 115200 and you go out with Ctrl-A followed by \.

I2C on Raspberry Pi: enabling, wiring, and essential tools

I2C on Raspberry Pi It's exposed on physical pins 3 and 5 of the header (SDA1 and SCL1, which correspond to GPIO2 and GPIO3). The first step is to enable it. In Raspberry OS, go to sudo raspi-config and, in “Advanced Options” or “Interface Options”, activate I2CThe wizard offers to load the module at startup.

In classic configurations, they were added to / etc / modules the lines i2c-bcm2708 e i2c-devToday's recent images already handle overlays, but i2c-dev It remains key to the user space. Install the utilities with: sudo apt update y sudo apt install i2c-tools python-smbus (o python3-smbus (according to your version).

To verify that everything is working, run lsmod | grep i2c and you'll see the loaded modules. Then, identify the bus and explore it with i2cdetect -y 1 (in very old models, it was -and 0If there are devices properly connected and with pull-ups suitable for SDA and SCL, the scan will show addresses such as 0 x 48, 0 x 20, etc.

Remember that the I2C bus requires pull-up resistors in SDA and SCL (at 3.3 V on the Pi) and that all the masses must be united (common GND). The absence of pull-ups or long/noisy cabling can cause intermittent problems or NACK, especially at 400 kHz and above.

Speaking I2C from Python (smbus/smbus2)

To interact with I2C devices from Python, you can use smus o smbus2Install it if necessary with pip install smus2A basic example of writing and reading to the address 0 x 48 would be the following:

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()

This pattern of write/read It helps you verify that the device is responding. If you encounter a Remote I/O error, normally the slave It is not recognizing the address or the record/transaction sent, or The SDA/SCL lines are not in good condition (pull-ups, wiring, GND or power supply).

UART communication with Arduino: USB or GPIO, you choose

For Raspberry Pi and Arduino to communicate serially, the most direct method is to USB cableYou connect the Arduino to the Pi, and a port like this will appear. /dev/ttyACM0 o / dev / ttyUSB0It's convenient and avoids level and console hassles. Alternatively, you can use GPIO pins (hardware UART) if you disable serial login with raspi config and you leave the HW active.

Install the Python serial library with sudo apt install python3-serial and locate the available port with ls /dev/tty*In Arduino, upload a simple sketch that sends text:

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

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

On the Raspberry Pi, a minimal reader with pyserial It would look something like this:

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()

To send data from the Pi to the Arduino, simply... ser.write(b"Hello from Raspberry Pi!\n") and in the sketch read with Serial.readStringUntil('\n')It is vital that both use the same speed (baud), and it's advisable to wait a couple of seconds when opening the port so that the ATmega can reboot and avoid initial "garbage".

The Windows 10/IoT approach: User APIs for GPIO, I2C, SPI, and UART with RhProxy and ACPI

If you work with Windows 10/IoT Core or Windows Enterprise, there is a mechanism to Expose GPIO, I2C, SPI, and UART to user mode through a controller called RhProxyThe idea is to declare, in the ACPI (ASL/AML) tables, the SPB/GPIO resources that are allowed to the user, and RhProxy makes them accessible through the Windows.Devices.* APIs. This is the approach used by boards such as Raspberry Pi 2/3 in that ecosystem.

The starting point is to create an ACPI node as Device(RHPX) _HID/_CID «MSFT8000» or with a _UIDand within define resources of type SPISerialBus, I2CSerialBus, UARTSerialBus and GPIO allowed to the user. In the _DSD properties are associated with such as bus-SPI-SPI0, SPI0-MinClockInHz, SPI0-MaxClockInHz, SPI0-SupportedDataBitLengths o bus-I2C-I2C1so that the API can return a "default controller" for each bus.

For I2C, an example of a descriptor would be something like I2CSerialBus(… «\_SB.I2C1» …), while for UART you have UARTSerialBus with fields of initial baud, parity, buffers and the ResourceSource of the controller. In SPI, each CE (chip select) It is declared as a separate resource and then grouped by bus in the DSD with resource indices.

A powerful concept in this environment is the runtime pin multiplexingThrough special ACPI resources MsftFunctionConfig(), the frameworks GpioClx, SpbCx y SerCx They instruct the GPIO controller to switch the function of the pins when a client unlocks the device (e.g., FromIdAsync() (in UWP). If the pins were already in use by another function, the opening fails; when closing the descriptor, it reverts multiplexing.

From the platform developer's perspective, the validation It involves checking with devcon that the SpbCx/GpioClx/SerCx drivers load, that rhproxy exists in the system (service registration key), compile the ASL to ACPITABL.dat asl.exe (mode /MsftInternal for MsftFunctionConfig) and activate testsigningThen you can list user devices with tools like I2cTestTool.exe -list, SpiTestTool.exe -list, GpioTestTool.exe -list o MinComm.exe -listand practice them with sample reading/writing exercises. For certification, run the tests of H.L.K. (I2C WinRT, GPIO WinRT, SPI WinRT).

Mixing I2C and UART without errors: Remote I/O diagnostics and other surprises

A typical case: you run a program that speaks through I2C with an IMU sensor (ITG/MPU) and another one that uses UART with a controller (e.g., SSC-32), and suddenly the I2C process explodes with Remote I/O errorThis error indicates that the teacher is not receiving ACK On the bus: the device is not responding or the lines are not in the correct state.

To get out of the traffic jam, check the following: 1) Serial console deactivated If you use GPIO pins 14/15 for your own UART; 2) On Pi 3/derivatives, consider Disable Bluetooth If you need the PL011 UART to be stable, or at least fixed. core_freq=250 if you pull mini-UART; 3) Check common GND among all the equipment (Pi, IMU, controller), and a clean eating (IMUs are sensitive to power drops).

4) Check the I2C addressing: scan with i2cdetect -y 1 and make sure the address (e.g., 0x68/0x69 on many IMUs) appears. If it doesn't, you may need to use the wrong bus, there's a faulty cable, or the pull-ups5) Check the bus speed (If you're using 400 kHz and the setup is on a breadboard with long wires, lower the frequency to 100 kHz and test.) 6) Avoid access collisions: although Linux allows multiple processes on /dev/i2c-1If two threads access in parallel without coordinating, you can cause strange conditions; use locks or serializes operations.

7) If the error appears just as the UART program loads or increases CPU load, suspect the mini-UART out of sync due to frequency scaling (symptom in Pi 3). Fixed core_freq=250 en /boot/config. txt or releases the PL011 for your UART 8) Verify that You don't share pins Among other things: in Windows/IoT, the opening would fail due to multiplexing; in Raspberry OS, an overlay or service might be keeping certain GPIO pins busy. 9) Finally, validate the physical cabling: SDA with SDA, SCL with SCL, without crossing, and TX–RX crossed in series.

Quick tests and utilities that save the day

For I2C, the basic trident is: i2cdetect -y 1 to view devices, i2cget/i2cset for simple register reads and writes, and a script of smbus2 to validate the actual flow. If i2cdetect It doesn't see the IMU, don't waste your time with the code: There is a physical or overlay problem..

For UART, use miniterm.py o screen as a serial terminal and verify that you can send/receive at the chosen speed without any strange characters. In Python, pyserial timeout and short initial pause It usually avoids empty readings. And if you're working with Windows IoT, devcon, ACPITABL.dat asl.exe and the example tools I2cTestTool/SpiTestTool/GpioTestTool/MinComm They give you total visibility.

You now have the complete map for working with I2C and UART. on Raspberry OS (and also on Windows/IoT if applicable): from what they are and how to activate them, to their use in Python, console handling, UART features on the Pi 3 and pin multiplexing, to the resolution of Errno 121 When you combine both buses, properly configured and with clean wiring, they are robust tools for any sensor, robotics, or control project.

Raspberry Pi CM5-4 technical specifications
Related article:
All about the Raspberry Pi Compute Module 5: Performance and flexibility