TC
Troy’s Tech Corner
build tech2026-02-2812-18 min read

Build a Raspberry Pi Robot Car: Simple Remote Control Tutorial

Understand Tech

Understand Tech

About the author

Build a Raspberry Pi Robot Car: Simple Remote Control Tutorial

Learn how to build your own remote-controlled robot car using Raspberry Pi. Beginner-friendly tutorial covering chassis assembly, motor control, and web browser control from your phone or computer.

Keywords: raspberry pi robot car, diy robot car, raspberry pi robotics, remote control car raspberry pi, robot car tutorial, beginner robotics project

Build an impressive robot car you can control from your phone via web browser. Perfect introduction to robotics, programming, and electronics for beginners.

What You're Building

A simple robot car that:

  • Drives forward, backward, left, and right
  • Controlled via web browser from any device
  • Live camera feed for remote driving
  • Basic obstacle detection
  • Great learning platform for robotics

Difficulty: ⭐⭐⭐ Intermediate Time Required: 3-4 hours Cost: $100-150

Shopping List

Core Components:

Robot Parts:

  • Robot car chassis kit with 4 motors – $35-50
  • L298N motor driver board – $8-12
  • Raspberry Pi camera module – $25-30
  • HC-SR04 ultrasonic sensor – $5-8
  • Jumper wires and breadboard – $10-15
  • 18650 battery pack (2-cell) – $15-20

Total Cost: $106-158

Step-by-Step Build

Step 1: Assemble the Chassis

  1. Build the frame following kit instructions
  2. Mount the 4 motors in corners
  3. Attach wheels to motor shafts
  4. Install battery holder in center
  5. Mount Pi on top using standoffs

Step 2: Wire Everything Up

Motor Driver Connections:

L298N → Raspberry Pi
ENA   → GPIO 18
IN1   → GPIO 24
IN2   → GPIO 23
ENB   → GPIO 19
IN3   → GPIO 21
IN4   → GPIO 20
VCC   → 5V
GND   → GND

Motors to Driver:

Left Motors  → OUT1, OUT2
Right Motors → OUT3, OUT4

Ultrasonic Sensor:

VCC  → 5V
GND  → GND
Trig → GPIO 16
Echo → GPIO 12

Power:

  • Battery pack positive → L298N motor power input
  • Battery pack negative → Pi GND and L298N GND
  • Connect Pi to power via USB or GPIO

Step 3: Software Setup

Install Raspberry Pi OS:

  1. Use Raspberry Pi Imager
  2. Enable SSH and Wi-Fi in advanced options
  3. Flash to SD card and boot Pi

Install required packages:

sudo apt update && sudo apt upgrade -y
sudo apt install python3-pip python3-flask python3-opencv -y
pip3 install RPi.GPIO opencv-python flask --break-system-packages
sudo raspi-config  # Enable camera interface

Step 4: Create the Robot Code

Main robot control script (robot_car.py):

from flask import Flask, render_template_string
import RPi.GPIO as GPIO
import cv2
import time
import threading
from datetime import datetime

app = Flask(__name__)

# Motor pins
ENA, IN1, IN2 = 18, 24, 23  # Left motors
ENB, IN3, IN4 = 19, 21, 20  # Right motors

# Sensor pins
TRIG, ECHO = 16, 12

# Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup([ENA, IN1, IN2, ENB, IN3, IN4, TRIG], GPIO.OUT)
GPIO.setup(ECHO, GPIO.IN)

# PWM for speed control
left_motor = GPIO.PWM(ENA, 1000)
right_motor = GPIO.PWM(ENB, 1000)
left_motor.start(0)
right_motor.start(0)

# Camera setup
camera = cv2.VideoCapture(0)

class Robot:
    def __init__(self):
        self.speed = 50
        
    def stop(self):
        left_motor.ChangeDutyCycle(0)
        right_motor.ChangeDutyCycle(0)
        GPIO.output([IN1, IN2, IN3, IN4], GPIO.LOW)
    
    def forward(self):
        left_motor.ChangeDutyCycle(self.speed)
        right_motor.ChangeDutyCycle(self.speed)
        GPIO.output([IN1, IN3], GPIO.HIGH)
        GPIO.output([IN2, IN4], GPIO.LOW)
    
    def backward(self):
        left_motor.ChangeDutyCycle(self.speed)
        right_motor.ChangeDutyCycle(self.speed)
        GPIO.output([IN1, IN3], GPIO.LOW)
        GPIO.output([IN2, IN4], GPIO.HIGH)
    
    def left(self):
        left_motor.ChangeDutyCycle(self.speed)
        right_motor.ChangeDutyCycle(self.speed)
        GPIO.output(IN1, GPIO.LOW)
        GPIO.output(IN2, GPIO.HIGH)
        GPIO.output(IN3, GPIO.HIGH)
        GPIO.output(IN4, GPIO.LOW)
    
    def right(self):
        left_motor.ChangeDutyCycle(self.speed)
        right_motor.ChangeDutyCycle(self.speed)
        GPIO.output(IN1, GPIO.HIGH)
        GPIO.output(IN2, GPIO.LOW)
        GPIO.output(IN3, GPIO.LOW)
        GPIO.output(IN4, GPIO.HIGH)
    
    def get_distance(self):
        GPIO.output(TRIG, True)
        time.sleep(0.00001)
        GPIO.output(TRIG, False)
        
        start_time = time.time()
        while GPIO.input(ECHO) == 0:
            start_time = time.time()
        
        while GPIO.input(ECHO) == 1:
            end_time = time.time()
        
        distance = (end_time - start_time) * 17150
        return round(distance, 2)

robot = Robot()

# Web interface HTML
HTML_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>
    <title>Robot Car Control</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body { 
            font-family: Arial; 
            text-align: center; 
            background: #2c3e50; 
            color: white;
            margin: 0;
            padding: 20px;
        }
        .container { max-width: 600px; margin: 0 auto; }
        h1 { color: #3498db; margin-bottom: 30px; }
        .controls { 
            display: grid; 
            grid-template-columns: repeat(3, 1fr); 
            gap: 15px; 
            max-width: 300px; 
            margin: 30px auto;
        }
        .btn { 
            background: #3498db; 
            border: none; 
            color: white; 
            padding: 20px; 
            border-radius: 10px; 
            font-size: 18px;
            cursor: pointer;
            transition: background 0.3s;
        }
        .btn:hover { background: #2980b9; }
        .btn:active { background: #21618c; }
        .stop { background: #e74c3c !important; }
        .stop:hover { background: #c0392b !important; }
        .speed-control { 
            margin: 20px 0; 
            padding: 20px;
            background: rgba(255,255,255,0.1);
            border-radius: 10px;
        }
        .slider { 
            width: 100%; 
            margin: 10px 0;
            height: 6px;
            border-radius: 3px;
            background: #34495e;
            outline: none;
        }
        .status { 
            margin: 20px 0;
            padding: 15px;
            background: rgba(255,255,255,0.1);
            border-radius: 10px;
        }
        .camera-feed {
            margin: 20px 0;
            padding: 20px;
            background: rgba(255,255,255,0.1);
            border-radius: 10px;
        }
        #camera { 
            max-width: 100%; 
            border-radius: 10px;
            background: #34495e;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🤖 Robot Car Control</h1>
        
        <div class="camera-feed">
            <h3>📹 Camera Feed</h3>
            <img id="camera" src="/video" width="400" height="300" alt="Camera Loading...">
        </div>
        
        <div class="controls">
            <div></div>
            <button class="btn" onclick="move('forward')">↑</button>
            <div></div>
            
            <button class="btn" onclick="move('left')">←</button>
            <button class="btn stop" onclick="move('stop')">STOP</button>
            <button class="btn" onclick="move('right')">→</button>
            
            <div></div>
            <button class="btn" onclick="move('backward')">↓</button>
            <div></div>
        </div>
        
        <div class="speed-control">
            <h3>Speed Control</h3>
            <input type="range" class="slider" id="speed" min="20" max="100" value="50" 
                   onchange="setSpeed(this.value)">
            <p>Speed: <span id="speedValue">50</span>%</p>
        </div>
        
        <div class="status">
            <h3>Robot Status</h3>
            <p>Distance: <span id="distance">-- cm</span></p>
            <p>Last Update: <span id="time">--:--:--</span></p>
        </div>
    </div>

    <script>
        function move(direction) {
            fetch('/move/' + direction);
        }
        
        function setSpeed(speed) {
            document.getElementById('speedValue').textContent = speed;
            fetch('/speed/' + speed);
        }
        
        function updateStatus() {
            fetch('/status')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('distance').textContent = data.distance + ' cm';
                    document.getElementById('time').textContent = data.time;
                });
        }
        
        // Update status every 2 seconds
        setInterval(updateStatus, 2000);
        
        // Keyboard controls
        document.addEventListener('keydown', function(event) {
            switch(event.key) {
                case 'ArrowUp': case 'w': case 'W': move('forward'); break;
                case 'ArrowDown': case 's': case 'S': move('backward'); break;
                case 'ArrowLeft': case 'a': case 'A': move('left'); break;
                case 'ArrowRight': case 'd': case 'D': move('right'); break;
                case ' ': move('stop'); event.preventDefault(); break;
            }
        });
        
        document.addEventListener('keyup', function(event) {
            if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'W', 'a', 'A', 's', 'S', 'd', 'D'].includes(event.key)) {
                move('stop');
            }
        });
    </script>
</body>
</html>
'''

@app.route('/')
def index():
    return render_template_string(HTML_TEMPLATE)

@app.route('/move/<direction>')
def move(direction):
    if direction == 'forward':
        robot.forward()
    elif direction == 'backward':
        robot.backward()
    elif direction == 'left':
        robot.left()
    elif direction == 'right':
        robot.right()
    elif direction == 'stop':
        robot.stop()
    
    # Auto-stop after 0.5 seconds for safety
    if direction != 'stop':
        threading.Timer(0.5, robot.stop).start()
    
    return 'OK'

@app.route('/speed/<int:speed>')
def set_speed(speed):
    robot.speed = max(20, min(100, speed))
    return 'OK'

@app.route('/status')
def status():
    return {
        'distance': robot.get_distance(),
        'time': datetime.now().strftime('%H:%M:%S')
    }

@app.route('/video')
def video():
    def generate():
        while True:
            ret, frame = camera.read()
            if ret:
                _, jpeg = cv2.imencode('.jpg', frame)
                yield (b'--frame\r\n'
                       b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n')
            time.sleep(0.1)
    
    return app.response_class(generate(),
                            mimetype='multipart/x-mixed-replace; boundary=frame')

if __name__ == '__main__':
    try:
        print("Starting Robot Car Server...")
        print("Access at: http://robotcar.local:5000")
        app.run(host='0.0.0.0', port=5000, debug=False)
    except KeyboardInterrupt:
        print("Shutting down...")
    finally:
        robot.stop()
        GPIO.cleanup()
        camera.release()

Step 5: Test Your Robot

Run the robot:

python3 robot_car.py

Control your robot:

  1. Open web browser on phone or computer
  2. Go to: http://robotcar.local:5000
  3. Use arrow buttons or WASD keys to drive
  4. Watch live camera feed
  5. Adjust speed slider as needed

Basic Troubleshooting

Robot won't move:

  • Check battery connections
  • Verify GPIO wiring
  • Test motors directly with battery

Can't access web interface:

  • Check Pi IP address: hostname -I
  • Try IP address instead: http://192.168.1.50:5000
  • Ensure Pi is connected to Wi-Fi

Camera not working:

  • Enable camera: sudo raspi-config → Interface → Camera
  • Check camera connection
  • Restart Pi

Simple Upgrades

Auto-Start on Boot

Create service file:

sudo nano /etc/systemd/system/robotcar.service

Add:

[Unit]
Description=Robot Car
After=network.target

[Service]
ExecStart=/usr/bin/python3 /home/pi/robot_car.py
WorkingDirectory=/home/pi
Restart=always
User=pi

[Install]
WantedBy=multi-user.target

Enable service:

sudo systemctl enable robotcar.service
sudo systemctl start robotcar.service

Basic Obstacle Avoidance

Add this to your robot code:

def auto_drive(self):
    distance = self.get_distance()
    if distance > 20:  # 20cm threshold
        self.forward()
        time.sleep(0.5)
    else:
        self.backward()
        time.sleep(0.3)
        self.right()
        time.sleep(0.5)
    self.stop()

What's Next?

Once your basic robot works, you can:

  • Add more sensors (gyroscope, GPS)
  • Implement line following
  • Add voice control
  • Create autonomous missions
  • Build obstacle courses

Cost vs. Commercial Options

Your DIY Robot: $100-150 one-time Commercial RC cars: $50-200 (no camera/programming) Educational robot kits: $200-500+

Your advantages:

  • Learn programming and electronics
  • Completely customizable
  • Web-based control from any device
  • Foundation for advanced robotics

Frequently Asked Questions

Can I use a different Raspberry Pi model?

Yes! Pi 3 B+ works fine, Pi Zero 2 W works but slower camera streaming.

How long does the battery last?

Typically 1-2 hours depending on usage and battery capacity.

Can I control it from outside my home?

Yes, set up VPN or port forwarding (advanced topic).

What if I mess up the wiring?

No permanent damage likely - just double-check connections and try again.

Conclusion

You've built a working robot car! This project teaches:

  • Basic robotics and motor control
  • Web development and remote control
  • Electronics and circuit building
  • Problem-solving and debugging

Next steps: Experiment with the code, add new features, and most importantly - have fun driving your creation!


Ready to build your robot car? This beginner-friendly project is perfect for learning robotics fundamentals!

Enjoyed this guide?

Get more beginner-friendly tech explanations and guides sent to your inbox.

No spam. Unsubscribe at any time. We respect your privacy.

Related Guides