The Short Answer: Python for Prototyping, C/C++ for Strict Timing

If you are asking raspberry pi what programming language is best for hardware projects, the direct answer is Python for 95% of use cases, and C/C++ for the remaining 5% requiring microsecond precision. Python dominates the Pi ecosystem because of libraries like gpiozero and Adafruit's CircuitPython, which abstract away complex register configurations. However, because the Raspberry Pi runs a full Linux OS (not a real-time RTOS), Python's garbage collection can introduce timing jitter. If you are bit-banging high-frequency protocols or driving high-speed stepper motors, C++ with the pigpio or lgpio C libraries is mandatory.

CriterionPython (gpiozero / Blinka)C/C++ (lgpio / pigpio)
Setup TimeMinutes (pip install)Hours (Makefiles, compiling)
Timing JitterHigh (milliseconds)Low (microseconds)
Library EcosystemMassive (sensors, displays)Limited (mostly raw GPIO/I2C)
Pi 5 CompatibilityExcellent (via lgpio backend)Good (requires lgpio C API)

Project Build: Temperature-Triggered Relay on Raspberry Pi 5

To demonstrate why Python is the default choice, we will build a hardware project that reads an I2C temperature sensor and triggers a relay when the room gets too warm. This targets the Raspberry Pi 5 (8GB variant). The Pi 5 uses the new RP1 southbridge chip, meaning legacy libraries like RPi.GPIO are deprecated; we must use gpiozero v2.0+ which leverages the lgpio backend under the hood.

Parts List

  • Board: Raspberry Pi 5 (8GB) with active cooler (~$85)
  • Sensor: Adafruit MCP9808 High Accuracy I2C Temperature Sensor (Product ID: 1782, ~$10)
  • Actuator: Adafruit Power Relay FeatherWing (Product ID: 2935, accepts 3.3V logic, ~$12)
  • Wiring: Silicone female-to-female jumper wires (26 AWG)
  • Power: 27W USB-C PD Power Supply (official Pi 5 supply)

Pin Mapping Table

Pi 5 Physical PinBCM / FunctionConnects To
Pin 13.3V PowerMCP9808 VIN
Pin 3GPIO 2 (SDA1)MCP9808 SDA
Pin 5GPIO 3 (SCL1)MCP9808 SCL
Pin 6GroundMCP9808 GND
Pin 11GPIO 17Relay FeatherWing 'IN' Pin
Pin 25V PowerRelay FeatherWing 5V Pin
Pin 9GroundRelay FeatherWing GND Pin
Bench Tip: Never connect a standard 5V relay module directly to a Pi 5 GPIO pin. The RP1 chip operates strictly at 3.3V and has lower current sourcing limits than the Pi 4. The Adafruit FeatherWing specified above includes an onboard NPN transistor to safely isolate the 3.3V logic from the 5V relay coil.

Numbered Build Steps

  1. De-energize the Pi: Unplug the USB-C power supply before touching the GPIO header.
  2. Enable I2C: Boot the Pi, open a terminal, run sudo raspi-config, navigate to Interface Options -> I2C, and enable it. Reboot.
  3. Wire the Sensor: Connect the MCP9808 to Pins 1, 3, 5, and 6 as mapped above.
  4. Wire the Relay: Connect the Relay FeatherWing to Pins 11, 2, and 9.
  5. Verify I2C Address: Run i2cdetect -y 1 in the terminal. You should see 18 in the grid, confirming the MCP9808 is at address 0x18.

The Code: Python GPIO Zero with I2C Error Handling

Install the required system and Python packages before running the script:

sudo apt update
sudo apt install python3-gpiozero python3-smbus2
pip3 install --break-system-packages smbus2 gpiozero

Save the following code as temp_relay.py. This script includes complete bitwise math to parse the raw I2C registers and robust error handling for hardware disconnects.

import time
import sys
import signal
from gpiozero import OutputDevice
from smbus2 import SMBus

# --- PIN & I2C DEFINITIONS ---
RELAY_PIN = 17          # BCM GPIO 17 (Physical Pin 11)
I2C_BUS_ID = 1          # /dev/i2c-1
MCP9808_ADDR = 0x18     # Default I2C address for MCP9808
TEMP_REG = 0x05         # Ambient Temperature Register
TEMP_THRESHOLD_C = 24.5 # Trigger relay above this temp

# Initialize Relay (Active High for FeatherWing)
relay = OutputDevice(RELAY_PIN, active_high=True, initial_value=False)

def read_temp_celsius(bus):
    """Reads raw 16-bit register and applies MCP9808 bitwise math."""
    raw_data = bus.read_i2c_block_data(MCP9808_ADDR, TEMP_REG, 2)
    
    # Combine bytes into 16-bit integer
    raw_temp = (raw_data[0] << 8) | raw_data[1]
    
    # Clear flag bits (bits 15, 14, 13)
    raw_temp &= 0x1FFF
    
    # Calculate Celsius
    temp_c = raw_temp / 16.0
    
    # Handle negative temperatures
    if raw_temp & 0x1000:
        temp_c -= 256.0
        
    return temp_c

def graceful_exit(signum, frame):
    print("\n[INFO] Shutting down safely. Turning off relay.")
    relay.off()
    relay.close()
    sys.exit(0)

signal.signal(signal.SIGINT, graceful_exit)
signal.signal(signal.SIGTERM, graceful_exit)

def main():
    print(f"[START] Monitoring I2C Bus {I2C_BUS_ID}, Relay on GPIO {RELAY_PIN}")
    
    try:
        with SMBus(I2C_BUS_ID) as bus:
            while True:
                try:
                    current_temp = read_temp_celsius(bus)
                    print(f"[READ] Temperature: {current_temp:.2f} C", end="")
                    
                    if current_temp > TEMP_THRESHOLD_C:
                        if not relay.value:
                            print(" | [ACTION] Threshold exceeded. Relay ON.")
                            relay.on()
                        else:
                            print(" | [STATE] Relay already ON.")
                    else:
                        if relay.value:
                            print(" | [ACTION] Temp dropped. Relay OFF.")
                            relay.off()
                        else:
                            print(" | [STATE] Relay OFF.")
                            
                    time.sleep(2.0)
                    
                except OSError as e:
                    if e.errno == 121:
                        print("\n[ERROR] Remote I/O error. Sensor disconnected or wiring loose.")
                    elif e.errno == 12:
                        print("\n[ERROR] Cannot allocate memory. I2C bus locked.")
                    else:
                        print(f"\n[ERROR] I2C Read failed: {e}")
                    relay.off() # Fail-safe: turn off relay on sensor loss
                    time.sleep(5.0) # Wait before retrying
                    
    except FileNotFoundError as e:
        print(f"[FATAL] {e}")
        print("[FIX] I2C is not enabled. Run 'sudo raspi-config' and enable I2C interface.")
        sys.exit(1)
    except Exception as e:
        print(f"[FATAL] Unexpected error: {e}")
        relay.off()
        sys.exit(1)

if __name__ == "__main__":
    main()

Debugging: When the Sensor Fails to Read

Hardware debugging on Linux is rarely as clean as bare-metal microcontrollers. When your script crashes, it usually throws one of two specific errors. Here is how to handle them.

The Exact Error: OSError: [Errno 121] Remote I/O error

This is the most common I2C error on the Raspberry Pi. It means the Linux kernel attempted to clock the SCL line, but the sensor did not acknowledge (ACK) the address.

Ranked Causes:

  1. Loose Dupont Wires: Female-to-female jumper wires lose tension over time. The SDA line is floating.
  2. Missing Pull-up Resistors: The MCP9808 breakout has internal pull-ups, but if you are using a raw I2C chip on a breadboard, you need 4.7kΩ resistors to 3.3V.
  3. Address Mismatch: You hardcoded 0x18 but the A0/A1/A2 pads on the sensor board are bridged, shifting the address to 0x19 or higher.

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

Ranked Causes:

  1. I2C Disabled in OS: The device tree overlay for I2C is not loaded.
  2. Wrong Bus ID: On some older Pi Compute Modules, the primary bus is /dev/i2c-0. On the Pi 5, it remains /dev/i2c-1 for the 40-pin header.
The First Three Things to Check When I2C Fails:
  1. Run i2cdetect -y 1. If the grid is entirely empty or filled with UU, your wiring is wrong or the bus is locked.
  2. Measure the voltage between the Sensor VCC pin and GND with a multimeter. It must read between 3.2V and 3.4V. If it reads 0V, your 3.3V rail is blown or disconnected.
  3. Verify the dtparam=i2c_arm=on line exists in your /boot/firmware/config.txt file.

Extending and Simplifying the Build

How to Extend: To turn this into an IoT node, add the paho-mqtt Python library. Wrap the current_temp variable in a JSON payload and publish it to a Mosquitto broker over WiFi. You can then integrate this with Home Assistant without writing any additional polling scripts.

How to Simplify: If bitwise math and raw smbus2 registers feel like overkill, simplify the build by using the adafruit-circuitpython-mcp9808 library via Adafruit Blinka. This abstracts the I2C bus into simple sensor.temperature property calls, though it adds roughly 40MB of Python dependencies to your Pi's SD card.

FAQ: Raspberry Pi Programming Language Questions

Is C++ better than Python for Raspberry Pi robotics?

For high-level robotics (like ROS 2 navigation, computer vision, and path planning), Python is standard and preferred. However, for low-level motor commutation, PID loop tuning, and reading high-resolution quadrature encoders without dropping pulses, C++ is strictly better. The Linux kernel's scheduling latency can cause Python to miss encoder counts at high RPMs, whereas C++ utilizing hardware interrupts via the lgpio C API will capture every pulse.

Can I use JavaScript (Node.js) for Raspberry Pi GPIO?

Yes, using libraries like onoff or pigpio for Node.js. JavaScript is an excellent choice if your project is heavily web-focused (e.g., a Pi serving a local dashboard via Express.js while toggling relays). However, Node.js suffers from the same garbage-collection timing jitter as Python, making it unsuitable for strict real-time hardware protocols like bit-banged WS2812B LED strips.

What programming language does the Raspberry Pi Pico use?

The Raspberry Pi Pico (RP2040/RP2350) is a microcontroller, not a Linux computer. The primary languages are C/C++ (using the official Pico SDK) and MicroPython. CircuitPython is also widely supported. Because the Pico runs bare-metal or on a lightweight RTOS, C++ on a Pico offers true microsecond real-time performance, unlike C++ on a full Raspberry Pi 5.

Do I need to learn Linux bash scripting for Pi projects?

While not strictly required to write hardware logic, bash scripting is essential for deployment. You will need bash to write systemd service files (so your Python script runs automatically on boot), manage cron jobs, and write udev rules to ensure your USB serial devices always map to the same /dev/ttyUSB* path. A basic grasp of bash will save you hours of debugging deployment issues.