The raspi-config utility is the gateway between the Raspberry Pi OS software stack and the physical 40-pin GPIO header. While it is famous for setting locale and hostname, its true power for embedded engineers lies in the Interface Options menu. Without properly configuring these interfaces via raspi-config, the underlying device tree overlays will not load, leaving your I2C, SPI, and UART hardware buses completely invisible to the Linux kernel.

This guide targets the Raspberry Pi 5 (8GB variant) running the 64-bit Raspberry Pi OS (Bookworm). We will map the configuration matrix, build a dual-bus environmental and GPS logger, and debug the exact kernel errors that occur when interface configurations fail.

The Interface Options Matrix

Before wiring any sensors, you must understand what raspi-config is actually doing under the hood. When you toggle an interface, the tool modifies /boot/firmware/config.txt (note the firmware subdirectory introduced in Bookworm) to inject specific dtparam and dtoverlay directives. Here is the data-dense breakdown of the primary hardware buses.

raspi-config Menu Path Default State Primary GPIO Pins (Physical) Max Clock / Speed Kernel Overlay Injected Common Modules
Interface Options > I2C Disabled Pin 3 (SDA), Pin 5 (SCL) 400 kHz (Fast Mode) dtparam=i2c_arm=on BME280, SSD1306, MPU6050
Interface Options > SPI Disabled 19 (MISO), 21 (MOSI), 23 (SCLK), 24 (CE0) 125 MHz (Theoretical) dtparam=spi=on RFID-RC522, MAX7219, ILI9341
Interface Options > Serial Port Disabled 8 (TXD), 10 (RXD) 115,200 baud (Standard) enable_uart=1 NEO-6M GPS, HC-05 Bluetooth
Interface Options > 1-Wire Disabled Pin 7 (GPIO 4) 15 kbps dtoverlay=w1-gpio DS18B20 Temp Sensor
Pro-Tip: Non-Interactive Mode
For headless deployments or automated fleet provisioning, skip the blue TUI screen. Use the non-interactive CLI flags directly in your bash setup scripts:
sudo raspi-config nonint do_i2c 0 (0 = Enable, 1 = Disable)
sudo raspi-config nonint do_serial 2 (2 = Disable login shell, Enable hardware)

Project Build: Serial GPS & I2C Sensor Logger

To demonstrate the necessity of raspi-config, we will build a data logger that reads ambient environmental data over I2C and parses NMEA GPS sentences over the hardware UART. The Raspberry Pi 5 utilizes the RP1 southbridge chip, which handles the GPIO multiplexing differently than the legacy BCM2711, making proper device tree configuration via raspi-config mandatory.

Parts List

  • Microcontroller: Raspberry Pi 5 (8GB RAM)
  • Environmental Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652)
  • GPS Module: Adafruit Ultimate GPS Breakout (Product ID: 746)
  • Wiring: 24 AWG silicone jumper wires, 4.7kΩ pull-up resistors (if not using Adafruit breakouts with onboard pull-ups)

Pin Mapping Table

Module Module Pin Pi 5 Physical Pin Pi 5 GPIO / Function
BME280 VIN 1 3.3V Power
BME280 GND 6 Ground
BME280 SCK (SCL) 5 GPIO 3 (SCL)
BME280 SDI (SDA) 3 GPIO 2 (SDA)
GPS VIN 17 3.3V Power
GPS GND 20 Ground
GPS TX 10 GPIO 15 (RXD / UART RX)
GPS RX 8 GPIO 14 (TXD / UART TX)

Configuration Steps

  1. Open the terminal and launch the tool: sudo raspi-config
  2. Navigate to 3 Interface Options.
  3. Select I4 I2C, choose Yes to enable the ARM I2C interface.
  4. Return to the menu, select I5 Serial Port.
  5. When asked "Would you like a login shell to be accessible over serial?", select No. (Crucial: leaving this Yes will corrupt your GPS data with Linux boot logs).
  6. When asked "Would you like the serial port hardware to be enabled?", select Yes.
  7. Exit and reboot the Pi: sudo reboot.

Python Code & Error Handling

Before running this script, install the required libraries: sudo apt install python3-smbus2 python3-serial. This script includes explicit pin definitions and robust error handling for common bus failures.

#!/usr/bin/env python3
"""
Dual-Bus Logger: BME280 (I2C) + GPS (UART)
Target: Raspberry Pi 5 (8GB) / Bookworm 64-bit
"""

import time
import sys
from smbus2 import SMBus, i2c_msg
import serial

# ==========================================
# PIN & INTERFACE DEFINITIONS
# ==========================================
I2C_BUS_ID = 1                  # /dev/i2c-1 (Physical pins 3 & 5)
BME280_I2C_ADDR = 0x77          # Default Adafruit BME280 address
GPS_SERIAL_PORT = '/dev/ttyAMA0' # Pi 5 primary hardware UART (Physical pins 8 & 10)
GPS_BAUD_RATE = 9600            # Standard NMEA baud rate

def read_bme280_raw_data(bus, address):
    """Reads raw compensation data from BME280 registers."""
    try:
        # Request 8 bytes starting from register 0xF7 (pressure, temp, humidity)
        msg = i2c_msg.read(address, 8)
        bus.i2c_rdwr(msg)
        data = list(msg)
        
        # Simplified parsing for demonstration (raw ADC values)
        raw_temp = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
        return raw_temp
    except OSError as e:
        print(f"[I2C ERROR] Hardware communication failed: {e}")
        return None

def read_gps_sentence(ser):
    """Reads and filters NMEA sentences from the UART stream."""
    try:
        if ser.in_waiting > 0:
            line = ser.readline().decode('ascii', errors='replace').strip()
            if line.startswith('$GPGGA') or line.startswith('$GNGGA'):
                return line
    except serial.SerialException as e:
        print(f"[UART ERROR] Serial port disconnected or locked: {e}")
    return None

def main():
    print("Initializing Hardware Interfaces...")
    
    # Initialize I2C Bus
    try:
        i2c_bus = SMBus(I2C_BUS_ID)
        print(f"[OK] I2C Bus {I2C_BUS_ID} opened.")
    except FileNotFoundError:
        print("[FATAL] /dev/i2c-1 not found. Did you enable I2C in raspi-config and reboot?")
        sys.exit(1)
    except PermissionError:
        print("[FATAL] Permission denied. Run with sudo or add user to i2c group.")
        sys.exit(1)

    # Initialize UART
    try:
        gps_serial = serial.Serial(
            port=GPS_SERIAL_PORT,
            baudrate=GPS_BAUD_RATE,
            timeout=1
        )
        print(f"[OK] UART {GPS_SERIAL_PORT} opened.")
    except serial.SerialException as e:
        print(f"[FATAL] Could not open {GPS_SERIAL_PORT}: {e}")
        sys.exit(1)

    try:
        while True:
            temp_raw = read_bme280_raw_data(i2c_bus, BME280_I2C_ADDR)
            gps_nmea = read_gps_sentence(gps_serial)
            
            if temp_raw is not None:
                print(f"BME280 Raw Temp ADC: {temp_raw}")
            if gps_nmea:
                print(f"GPS NMEA: {gps_nmea}")
                
            time.sleep(1.0)
            
    except KeyboardInterrupt:
        print("\nShutting down gracefully...")
    finally:
        i2c_bus.close()
        gps_serial.close()

if __name__ == '__main__':
    main()

Debugging: When raspi-config Settings Fail to Apply

Even after running raspi-config, you may encounter kernel-level rejections when your Python script attempts to poll the hardware. Here are the exact error strings and their ranked causes.

1. FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'

  • Cause A (Most Likely): You forgot to reboot. raspi-config modifies the boot configuration, but the kernel device tree is only parsed during the bootloader sequence.
  • Cause B: A manual override exists in /boot/firmware/config.txt. Look for dtparam=i2c_arm=off or a commented-out #dtparam=i2c_arm=on and fix it.

2. PermissionError: [Errno 13] Permission denied: '/dev/ttyAMA0'

  • Cause A (Most Likely): Your user lacks the dialout group permissions required to access serial TTY devices.
  • Fix: Run sudo usermod -a -G dialout $USER, then log out and log back in to refresh the group token.
  • Cause B: The Bluetooth module has claimed the primary UART. On the Pi 5, you may need to add dtoverlay=disable-bt to config.txt to free up /dev/ttyAMA0 for the GPIO header.

3. OSError: [Errno 121] Remote I/O error

  • Cause A (Most Likely): Missing pull-up resistors on the I2C SDA/SCL lines. The RP1 chip requires clean logic edges.
  • Cause B: Incorrect I2C address. Run i2cdetect -y 1 in the terminal to scan the bus and verify the physical hex address of your sensor.
The First Three Things to Check When Interfaces Fail:
  1. Verify the Device Node: Run ls -l /dev/i2c* and ls -l /dev/ttyAMA*. If the files don't exist, the kernel overlay failed to load.
  2. Check Group Memberships: Run groups. You must see i2c, spi, and dialout in the output.
  3. Inspect the Boot Log: Run dmesg | grep -i i2c or dmesg | grep -i uart to see if the kernel threw a pin-muxing conflict error during boot.

Extending and Simplifying the Build

Once you have mastered the raspi-config TUI, you can manipulate the deployment pipeline to suit your specific project constraints.

How to Extend: Adding SPI Displays

If you want to add an SPI-based OLED (like the SSD1351) to this logger, return to raspi-config and enable the SPI interface. Because SPI supports multiple Chip Select (CS) lines, you can map the OLED to Physical Pin 24 (CE0) and an RFID reader to Physical Pin 26 (CE1) on the same MOSI/MISO/SCLK bus. Ensure you enable the SPI device tree overlay by verifying dtparam=spi=on is present in your boot config.

How to Simplify: Headless Fleet Provisioning

If you are building 50 of these loggers for a commercial greenhouse deployment, using the interactive raspi-config menu is unscalable. Instead, use the Raspberry Pi Imager's advanced settings (the gear icon) before flashing the SD card. You can pre-configure the hostname, SSH keys, and WiFi.

For the hardware interfaces, create a custom config.txt file on your host machine and inject it into the /boot/firmware/ partition of the flashed SD card before the Pi ever boots. By pre-populating dtparam=i2c_arm=on and enable_uart=1, the Pi will boot with the correct device tree overlays already active, entirely bypassing the need to run raspi-config on the target device. For comprehensive details on device tree parameters, consult the official Raspberry Pi configuration documentation and the Adafruit BME280 wiring guide.