To build a reliable Raspberry Pi RC car, skip the ancient L298N H-bridge and use a TB6612FNG dual motor driver. The L298N uses bipolar junction transistors (BJTs) that drop up to 2V across the chip, robbing your motors of torque and wasting battery as heat. The TB6612FNG uses MOSFETs, dropping that loss to roughly 0.5V and giving you precise speed control via hardware PWM.
This guide targets the Raspberry Pi 4 Model B (4GB) running Raspberry Pi OS (Bookworm). While the Pi 5 is faster, its higher idle power draw (2.7W vs 1.2W on the Pi 4) and strict 5V/5A USB-C PD requirement make it a poor fit for lightweight, battery-powered RC platforms unless you are willing to carry heavy buck converters. We will use a low-latency UDP socket architecture for control, bypassing the lag inherent in HTTP-based web interfaces.
Estimated Time: 3-4 hours
Estimated Cost: $85 - $110 USD
Spec Sheet & Parts List
The biggest mistake in Raspberry Pi RC builds is under-gearing the motors or using unprotected lithium cells. Below is the exact bill of materials needed for a balanced, 2WD platform that can handle outdoor terrain without browning out the Pi.
| Component | Exact Model / Variant | Key Specification | Est. Cost (2026) |
|---|---|---|---|
| Compute | Raspberry Pi 4 Model B (4GB) | 1.5GHz Quad-core, 1.2W idle draw | $55.00 |
| Motor Driver | Pololu TB6612FNG Breakout | 1.2A continuous, 3.2A peak, MOSFET | $12.50 |
| Motors (x2) | Pololu Micro Metal Gearmotor MP (30:1) | 6V nominal, 1000 RPM no-load, 1.5A stall | $18.00 |
| Power Supply | 2S 18650 Holder + DW01 BMS Module | 7.4V nominal, Over-discharge protection | $15.00 |
| Voltage Regulator | Pololu D24V50F5 Step-Down (5V, 5A) | Switching regulator, low dropout | $14.00 |
Note on Motors: Avoid the cheap yellow "TT" gearmotors (1:48 ratio). They spin too fast with almost zero stall torque, making RC control impossible. The 30:1 Micro Metal Gearmotors provide the low-end torque needed for steering and hill climbing.
Pin Mapping & Wiring the TB6612FNG
The Raspberry Pi 4 has four hardware PWM pins: GPIO 12, 13, 18, and 19. We will use GPIO 18 and 19 for the motor speed control to ensure smooth, jitter-free acceleration. The TB6612FNG requires a logic HIGH on the STBY (Standby) pin to operate; if you forget to wire this, the motors will never spin.
| TB6612FNG Pin | Raspberry Pi 4 GPIO | Function |
|---|---|---|
| PWMA | GPIO 18 (Pin 12) | Hardware PWM0 for Motor A speed |
| AIN1 | GPIO 23 (Pin 16) | Motor A Direction Logic 1 |
| AIN2 | GPIO 24 (Pin 18) | Motor A Direction Logic 2 |
| STBY | GPIO 25 (Pin 22) | Chip Enable (Must be HIGH) |
| BIN1 | GPIO 17 (Pin 11) | Motor B Direction Logic 1 |
| BIN2 | GPIO 27 (Pin 13) | Motor B Direction Logic 2 |
| PWMB | GPIO 19 (Pin 35) | Hardware PWM1 for Motor B speed |
| GND | Any GND Pin | Common Ground (Critical) |
Python Control Code (UDP Socket Server)
For an RC car, HTTP requests (Flask/Django) introduce 50-150ms of latency per command, making the car feel sluggish. We use a raw UDP socket server. The Pi listens for single-byte characters ('F' for forward, 'B' for back, 'L' for left, 'R' for right, 'S' for stop) sent from a client script on your phone or laptop.
This code targets the gpiozero library using the lgpio pin factory, which is the default in Raspberry Pi OS Bookworm. It includes explicit pin definitions and error handling for socket binding and GPIO cleanup.
import socket
import sys
from gpiozero import PWMOutputDevice, DigitalOutputDevice
from gpiozero.exc import PinFactoryFallback
# --- PIN DEFINITIONS ---
# Motor A (Left)
PWMA = PWMOutputDevice(18, frequency=1000)
AIN1 = DigitalOutputDevice(23)
AIN2 = DigitalOutputDevice(24)
# Motor B (Right)
PWMB = PWMOutputDevice(19, frequency=1000)
BIN1 = DigitalOutputDevice(17)
BIN2 = DigitalOutputDevice(27)
# Standby Pin (Must be HIGH to enable the TB6612FNG)
STBY = DigitalOutputDevice(25)
UDP_IP = '0.0.0.0'
UDP_PORT = 5005
BUFFER_SIZE = 1024
def stop_motors():
PWMA.value = 0
PWMB.value = 0
AIN1.off(); AIN2.off()
BIN1.off(); BIN2.off()
def move_forward(speed=1.0):
STBY.on()
AIN1.on(); AIN2.off()
BIN1.on(); BIN2.off()
PWMA.value = speed
PWMB.value = speed
def move_backward(speed=1.0):
STBY.on()
AIN1.off(); AIN2.on()
BIN1.off(); BIN2.on()
PWMA.value = speed
PWMB.value = speed
def turn_left(speed=0.5):
STBY.on()
AIN1.off(); AIN2.on() # Left motor backward
BIN1.on(); BIN2.off() # Right motor forward
PWMA.value = speed
PWMB.value = speed
def turn_right(speed=0.5):
STBY.on()
AIN1.on(); AIN2.off() # Left motor forward
BIN1.off(); BIN2.on() # Right motor backward
PWMA.value = speed
PWMB.value = speed
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Allow rapid restart without 'Address already in use' errors
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((UDP_IP, UDP_PORT))
print(f'UDP server listening on {UDP_IP}:{UDP_PORT}')
STBY.on() # Wake up the motor driver
while True:
data, addr = sock.recvfrom(BUFFER_SIZE)
cmd = data.decode('utf-8').strip().upper()
if cmd == 'F': move_forward(1.0)
elif cmd == 'B': move_backward(1.0)
elif cmd == 'L': turn_left(0.6)
elif cmd == 'R': turn_right(0.6)
elif cmd == 'S': stop_motors()
else: stop_motors() # Failsafe for garbage data
except KeyboardInterrupt:
print('\nShutting down...')
except Exception as e:
print(f'Server error: {e}')
finally:
stop_motors()
STBY.off()
sock.close()
sys.exit(0)
if __name__ == '__main__':
main()
Debugging: When the Motors Won't Spin
Embedded hardware rarely works on the first boot. If your Raspberry Pi RC car is unresponsive, do not blindly rewrite your code. Follow this diagnostic path.
The First Three Things to Check When It Fails
- STBY Pin Logic Level: Use a multimeter to measure voltage between the STBY pin on the TB6612FNG and GND. It must read ~3.3V when the script is running. If it reads 0V, the chip is in sleep mode. Check your GPIO 25 wiring.
- Common Ground: The Pi's GND, the motor driver's GND, and the battery pack's negative terminal must share a common ground bus. Without this, the 3.3V logic signals from the Pi have no reference point and the MOSFETs will not switch.
- VM Voltage Under Load: Measure the voltage at the VM (Motor Power) pins on the driver while the motors are commanded to spin. If it drops below 4.5V, your battery pack's internal resistance is too high, or your BMS is tripping the over-current protection.
Ranked Causes for Specific Error Strings
Error 1: lgpio.error: 'gpiochip0' or RuntimeError: Failed to allocate GPIO
- Cause: Raspberry Pi OS Bookworm switched from
rpigpiotolgpiofor GPIO access. Your user account lacks permissions to access the character device, or another process is holding the pin. - Fix: Ensure your user is in the
gpiogroup (sudo usermod -aG gpio $USER), reboot, and ensure no background scripts (like a previous crashed instance of this code) are holding the pins. Usekillall python3to clear ghosts.
Error 2: OSError: [Errno 98] Address already in use
- Cause: The UDP socket from a previous run didn't close cleanly, leaving the port bound in a TIME_WAIT state.
- Fix: The provided code includes
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)which prevents this. If you stripped that line out, put it back. Alternatively, wait 60 seconds for the OS to release the port.
Error 3: Motors whine or click at low speeds but don't turn
- Cause: The PWM frequency is too low, or the duty cycle is below the motor's stall threshold. DC motors require a minimum voltage to overcome static friction.
- Fix: In the code,
frequency=1000(1kHz) is set. If you changed this to 50Hz, the motor will whine audibly. Keep it at 1kHz. If it still won't move at 0.2 duty cycle, implement a "kickstart" in your code: send 1.0 duty cycle for 50ms, then drop to your target low speed.
Extending vs. Simplifying the Build
Depending on your budget and use case, you may want to alter the complexity of this Raspberry Pi RC platform.
How to Simplify
- Drop WiFi for Tethered Control: If you are building an indoor rover and don't want to deal with network latency or battery weight, strip the 18650 pack and power the Pi via a long, 18 AWG tethered USB-C cable. Use a standard USB gamepad and the
evdevPython library to read joystick inputs directly. This eliminates the need for a client script entirely. - Use a Pi Zero 2 W: If weight is your primary constraint, swap the Pi 4 for a Pi Zero 2 W. It draws roughly 0.7W at idle and costs around $15. The Python code above will run unmodified, though you will need to remap the GPIO pins to match the Zero's 40-pin header layout.
How to Extend
- Add Traction Control (IMU): Wire an MPU6050 accelerometer/gyroscope via I2C (SDA to GPIO 2, SCL to GPIO 3). By reading the Z-axis acceleration, you can detect when a wheel is slipping (high RPM, low acceleration) and dynamically reduce the PWM duty cycle to that specific motor until grip is restored.
- Implement Video Telemetry: Mount a Raspberry Pi Camera Module 3. Use
libcamera-vidto stream an RTSP feed over the same WiFi network. To prevent video lag from starving your UDP control packets, ensure your router supports QoS (Quality of Service) and prioritize UDP port 5005 traffic over the video stream.
- gpiozero Official Documentation (Pin factory configurations and PWM devices)
- Pololu TB6612FNG Motor Driver Carrier (Datasheet and wiring diagrams)
- Raspberry Pi 4 Datasheet (Power consumption and GPIO current limits)






