Build Spec Sheet
Target Board: Raspberry Pi 5 (8GB) running Raspberry Pi OS Bookworm
Difficulty: Intermediate (Requires 3.3V logic level shifting)
Time to Build: 2-3 hours
Primary Gotcha: Pi 5 GPIO is strictly 3.3V. Direct-wiring a 5V HC-SR04 Echo pin will fry the RP1 chip.

When exploring raspberry pi robotics projects, the leap from a blinking LED to a moving rover introduces three new variables: power management, motor driver logic, and sensor voltage translation. Most online tutorials still reference the Raspberry Pi 4 and the outdated L298N motor driver. In 2026, the standard is the Raspberry Pi 5 paired with a MOSFET-based TB6612FNG driver and the gpiozero library running on the lgpio backend.

This guide walks through building a 2-wheel differential drive rover with ultrasonic obstacle avoidance, specifically addressing the hardware and software debugging hurdles unique to the Pi 5 architecture.

The Core Build: Parts and Pin Mapping

Exact Bill of Materials

  • Compute: Raspberry Pi 5 (8GB variant recommended for future ROS 2 expansion)
  • Motor Driver: TB6612FNG Dual Motor Driver Breakout (Pololu #713 or SparkFun equivalent). Why not L298N? The L298N uses BJT transistors, dropping ~2V across the bridge. The TB6612FNG uses MOSFETs, dropping only ~0.5V, delivering significantly more torque to your motors.
  • Sensor: HC-SR04 Ultrasonic Distance Sensor (5V logic output)
  • Motors: 2x TT Gearmotors (3-6V DC) with matching 60mm wheels
  • Power: 2S LiPo Battery (7.4V nominal, 8.4V fully charged) with integrated BMS
  • Step-Down Converter: DROK or Pololu D24V50F5 Buck Converter (Must be rated for 5V / 5A output to prevent Pi 5 brownouts under motor stall loads)
  • Logic Level Shifting: 1x 1kΩ resistor, 1x 2kΩ resistor (for HC-SR04 voltage divider)

GPIO Pin Mapping Table

The following mapping uses BCM (Broadcom) pin numbering, which is mandatory for gpiozero on the Pi 5.

ComponentModule PinPi 5 BCM GPIONotes
Right MotorAIN1 / AIN217 / 27Direction control
Right MotorPWMA12Hardware PWM capable
Left MotorBIN1 / BIN222 / 23Direction control
Left MotorPWMB13Hardware PWM capable
Driver LogicSTBY24Must be HIGH to enable
SensorTRIG5Pi 5V output to sensor
SensorECHO6Via voltage divider!

Step-by-Step Wiring (Mind the 3.3V Logic!)

  1. Power Distribution: Wire the 2S LiPo positive lead to the buck converter VIN and the TB6612FNG VM (Motor Voltage) pin. Connect the LiPo ground to the common ground rail. Set the buck converter output to exactly 5.1V to compensate for cable drop, then wire it to the Pi 5 GPIO 5V and GND pins (pins 2 and 6).
  2. Motor Driver Logic: Connect TB6612FNG VCC to the Pi 5 3.3V pin (pin 1). This sets the logic threshold for the driver. Wire STBY to GPIO 24.
  3. Motor Outputs: Connect AIN1/AIN2 to GPIO 17/27, and PWMA to GPIO 12. Repeat for the B channels. Connect AO1/AO2 and BO1/BO2 to your TT motors.
  4. The Critical Voltage Divider: The HC-SR04 requires 5V to operate reliably, meaning its ECHO pin outputs a 5V HIGH signal. The Pi 5 RP1 chip GPIO pins are strictly 3.3V tolerant.
    ⚠️ Warning: Wiring the HC-SR04 ECHO pin directly to Pi 5 GPIO 6 will backfeed 5V into the RP1 silicon. Best case, the pin clamps and reads max distance; worst case, you permanently destroy the GPIO bank.
    Wire the 1kΩ resistor in series with the ECHO pin, and the 2kΩ resistor from the junction to GND. This drops the 5V pulse down to a safe ~3.33V.

Complete Python Control Code

This script targets the Raspberry Pi 5 (8GB) running Raspberry Pi OS Bookworm. It uses the modern gpiozero library, which automatically routes through the lgpio backend on Pi 5. Unlike generic tutorials that abuse the Motor class (designed for L298N enable pins), this code explicitly controls the TB6612FNG's separate direction and PWM pins for precise speed mapping.

from gpiozero import DigitalOutputDevice, PWMOutputDevice, DistanceSensor, OutputDevice
import time

# Pin Definitions (BCM)
AIN1, AIN2, PWMA = 17, 27, 12
BIN1, BIN2, PWMB = 22, 23, 13
STBY = 24
TRIG, ECHO = 5, 6

try:
    # TB6612FNG requires STBY HIGH to operate
    standby = OutputDevice(STBY, active_high=True, initial_value=True)
    
    # Initialize Right Motor (A)
    ain1 = DigitalOutputDevice(AIN1)
    ain2 = DigitalOutputDevice(AIN2)
    pwma = PWMOutputDevice(PWMA, frequency=1000)
    
    # Initialize Left Motor (B)
    bin1 = DigitalOutputDevice(BIN1)
    bin2 = DigitalOutputDevice(BIN2)
    pwmb = PWMOutputDevice(PWMB, frequency=1000)
    
    # Initialize Sensor (max_distance in meters)
    sensor = DistanceSensor(echo=ECHO, trigger=TRIG, max_distance=2.0)
    
    def set_motor(dir1, dir2, pwm, speed):
        """speed: -1.0 (full reverse) to 1.0 (full forward)"""
        if speed > 0:
            dir1.on(); dir2.off()
        elif speed < 0:
            dir1.off(); dir2.on()
        else:
            dir1.off(); dir2.off()
        pwm.value = abs(speed)
        
    print('Rover initialized. Press Ctrl+C to stop.')
    
    while True:
        dist_cm = sensor.distance * 100
        print(f'Distance: {dist_cm:.1f} cm')
        
        if dist_cm < 20 and dist_cm > 0:
            # Obstacle: Stop, reverse, pivot
            set_motor(ain1, ain2, pwma, -0.5)
            set_motor(bin1, bin2, pwmb, -0.5)
            time.sleep(0.5)
            set_motor(ain1, ain2, pwma, 0.6)
            set_motor(bin1, bin2, pwmb, -0.6)
            time.sleep(0.8)
        else:
            # Path clear: Drive forward
            set_motor(ain1, ain2, pwma, 0.7)
            set_motor(bin1, bin2, pwmb, 0.7)
            
        time.sleep(0.1)
        
except KeyboardInterrupt:
    print('\nStopping rover...')
except Exception as e:
    print(f'Critical Error: {e}')
finally:
    # Safe cleanup: disable PWM and engage standby
    pwma.off(); pwmb.off()
    standby.off()
    print('Motors disabled and standby engaged.')

Debugging: First Three Things to Check

When your rover fails to move or the sensor throws an exception, do not rewrite the code immediately. Hardware and OS-level locks are the culprits 90% of the time. Here are the first three things to check:

  1. The STBY Pin Logic: The TB6612FNG has a standby mode. If GPIO 24 is floating or LOW, the H-bridges are disabled, and the motors will coast freely regardless of PWM signals. Measure GPIO 24 with a multimeter; it must read 3.3V when the script is running.
  2. Voltage Divider Continuity: If the rover drives but ignores obstacles, the Pi 5 is likely clamping the 5V Echo signal. Verify the 1kΩ/2kΩ divider with your meter. You should read ~3.3V on the Pi-side of the 1kΩ resistor when the sensor is triggered.
  3. Power Rail Sag (Brownouts): TT motors can draw 1.5A+ each at stall. If your buck converter is rated for only 2A or 3A, the voltage will sag below 4.8V when the rover starts moving, causing the Pi 5 to reboot or drop USB/GPIO peripherals. Ensure your converter is rated for at least 5A continuous.

Fixing the "lgpio.error: 'GPIO X is already in use'"

If you run the script and immediately get the exact error string "lgpio.error: 'GPIO 6 is already in use'" (or any other pin number), your code is fine. The Pi 5's lgpio backend enforces strict hardware pin locking to prevent multiple processes from toggling the same GPIO simultaneously.

Ranked Causes and Fixes:

  1. Ghost Python Process (Most Likely): You pressed Ctrl+Z instead of Ctrl+C in a previous run, backgrounding the script while it still held the pin lock. Fix: Run sudo killall python3 in the terminal.
  2. pigpiod Daemon Running: If you installed the legacy pigpio library and enabled its daemon, it claims all GPIO pins at boot. Fix: Run sudo systemctl stop pigpiod and sudo systemctl disable pigpiod.
  3. I2C/SPI Interface Overlap: If you enabled I2C in raspi-config, it reserves GPIO 2 and 3. If your pin mapping accidentally overlaps with reserved hardware interfaces, lgpio will block it. Fix: Verify your pins against the Raspberry Pi 5 GPIO pinout.

Extending vs. Simplifying the Build

Once the base rover is navigating, you will hit the limits of ultrasonic sensing and basic differential steering. Here is how to scale the project based on your goals.

How to Extend (Advanced): Swap the HC-SR04 for a Slamtec RPLiDAR A1M8 and install ROS 2 Humble. The Pi 5 8GB variant has the RAM overhead to run SLAM (Simultaneous Localization and Mapping) nodes. You will need to migrate from gpiozero to ROS 2 Python nodes using the gpiozero documentation as a bridge for low-level hardware control.

How to Simplify (Beginner): If the 3.3V logic level shifting and PWM tuning are causing too much friction, remove the HC-SR04 entirely. Replace it with two mechanical limit switches wired to GPIO pins with internal pull-up resistors. This eliminates timing-critical pulse measurements and turns the project into a pure digital-logic bumper car.

FAQ: Raspberry Pi Robotics Projects

What are the best raspberry pi robotics projects for beginners?

The 2WD obstacle-avoiding rover detailed above is the gold standard for beginners because it teaches power distribution, logic-level shifting, and basic control loops without requiring complex kinematics. The next best step is a Pan-Tilt Camera Rover, which introduces servo control and OpenCV computer vision over WiFi, leveraging the Pi's processing power over a microcontroller.

How do I power raspberry pi robotics projects without a USB cable?

You must use a DC-DC buck converter to step down a battery pack (like a 2S or 3S LiPo) to 5V. The critical mistake beginners make is using cheap LM2596 modules rated for 2A. The Pi 5 requires up to 5A under load. Use a high-frequency synchronous buck converter (like the Pololu D24V50F5) wired directly to the GPIO 5V and GND pins to bypass the USB-C power path limitations.

Why is my raspberry pi robotics project motor stuttering?

Motor stuttering or 'cogging' at low speeds is usually caused by a PWM frequency mismatch or software timing jitter. The gpiozero software PWM defaults to 100Hz, which is audible and jerky for small TT motors. In the code provided above, we explicitly set frequency=1000 on the PWMOutputDevice to push the switching frequency above the audible range, resulting in smooth torque delivery. If stutter persists, check for voltage drop across your Dupont connectors.