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.
| Criterion | Python (gpiozero / Blinka) | C/C++ (lgpio / pigpio) |
|---|---|---|
| Setup Time | Minutes (pip install) | Hours (Makefiles, compiling) |
| Timing Jitter | High (milliseconds) | Low (microseconds) |
| Library Ecosystem | Massive (sensors, displays) | Limited (mostly raw GPIO/I2C) |
| Pi 5 Compatibility | Excellent (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 Pin | BCM / Function | Connects To |
|---|---|---|
| Pin 1 | 3.3V Power | MCP9808 VIN |
| Pin 3 | GPIO 2 (SDA1) | MCP9808 SDA |
| Pin 5 | GPIO 3 (SCL1) | MCP9808 SCL |
| Pin 6 | Ground | MCP9808 GND |
| Pin 11 | GPIO 17 | Relay FeatherWing 'IN' Pin |
| Pin 2 | 5V Power | Relay FeatherWing 5V Pin |
| Pin 9 | Ground | Relay FeatherWing GND Pin |
Numbered Build Steps
- De-energize the Pi: Unplug the USB-C power supply before touching the GPIO header.
- Enable I2C: Boot the Pi, open a terminal, run
sudo raspi-config, navigate to Interface Options -> I2C, and enable it. Reboot. - Wire the Sensor: Connect the MCP9808 to Pins 1, 3, 5, and 6 as mapped above.
- Wire the Relay: Connect the Relay FeatherWing to Pins 11, 2, and 9.
- Verify I2C Address: Run
i2cdetect -y 1in the terminal. You should see18in 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:
- Loose Dupont Wires: Female-to-female jumper wires lose tension over time. The SDA line is floating.
- 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.
- Address Mismatch: You hardcoded
0x18but the A0/A1/A2 pads on the sensor board are bridged, shifting the address to0x19or higher.
The Exact Error: FileNotFoundError: [Errno 2] No such file or directory: '/dev/i2c-1'
Ranked Causes:
- I2C Disabled in OS: The device tree overlay for I2C is not loaded.
- Wrong Bus ID: On some older Pi Compute Modules, the primary bus is
/dev/i2c-0. On the Pi 5, it remains/dev/i2c-1for the 40-pin header.
- Run
i2cdetect -y 1. If the grid is entirely empty or filled withUU, your wiring is wrong or the bus is locked. - 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.
- Verify the
dtparam=i2c_arm=online exists in your/boot/firmware/config.txtfile.
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.






