RC Car Using Raspberry Pi 5

m.youtube.com/watch?v=g_...

Here’s an image that captures the heart of your project: a Raspberry Pi–powered RC car—built with the Raspberry Pi 5 and ready for a full DIY build and test drive.


Project Overview: RC Car Using Raspberry Pi 5—Full DIY Build + Test Drive

What We Know So Far

A recent YouTube video titled “RC Car Using Raspberry Pi 5 | Full DIY Build + Test Drive” showcases a functional build using an 8 GB Pi 5. It highlights improved speed and responsiveness thanks to the newer board (YouTube).

Though the video provides a dynamic demonstration, you’ll likely need additional sources or breakdowns to guide through your own build—such as wiring diagrams, code walkthroughs, and parts lists. Let’s explore what’s typically involved in such a build:


Typical Components & Setup

  • Chassis & Drive System
    A standard RC car chassis with steering servo and ESC (Electronic Speed Controller) forms the foundation. You’ll need to interface these with the Pi’s GPIO pins—often using a PWM-capable HAT or driver board (forums.raspberrypi.com, element14 Community).
  • Motor Control
    L298N or PCA9685 motor driver modules are commonly used. The driver receives PWM signals from the Pi to control steering and throttle (Instructables, techwithsach.com).
  • Power Supply
    Use a battery pack suitable for the Pi and motors. Many builders opt for separate power sources—5 V for the Pi, and 12 V or higher for motors via the ESC or driver board (plainenglish.io, Instructables).
  • Camera & Control Interface
    A Pi Camera or USB webcam streams live video to a web interface. Control inputs can be handled via keyboard (WASD), game controllers, or custom scripts—often using frameworks like Flask or pygame (Instructables, Reddit, techwithsach.com).

Overview: Core Steps to Build

  1. Prepare the Chassis
    Secure the motors, wheels, servo, and mount points. Ensure the ESC and steering servo are accessible for wiring.
  2. Wire and Test Motor Drivers
    Connect the steering and throttle PWM signals to the Pi via a driver board. Test with basic PWM scripts to ensure proper movement.
  3. Set Up Power
    Supply stable power to both the Pi and motors, ensuring voltage compatibility and current capacity.
  4. Add the Camera & Stream Video
    Mount the camera to the chassis and set up a streaming server or lightweight web interface to view what the car sees.
  5. Control Logic & Code
    Implement control logic—whether manual via keyboard/controller or immersive via a web dashboard. Tools like Flask or pygame are popular options (techwithsach.com, Instructables).
  6. Test & Refine
    Conduct test drives, calibrate steering and throttle response, and iterate on control code and latency.

Inspiration & Extensions

  • Autonomous RC Car with Donkey Car
    Platforms like Donkey Car enable adding self-driving capabilities using machine learning. These use Raspberry Pi, camera modules, throttle/steering control, and a training workflow—often using Google Colab (techwithsach.com, Emlid Community Forum).
  • Internet-Controlled Builds
    Some creators use platforms like Surrogate.tv to host RC cars that anyone can drive remotely through the internet—great for interactive demos (Reddit).

How I Can Help You Now

Let me know if you’d like support with any of the following:

  • Component selection: Ideal motor drivers, power configurations, or camera modules.
  • Wiring diagrams: Visual layout of connections between Pi 5, ESC, servo, battery, and camera.
  • Control code templates: Python scripts for manual keyboard control, or Flask-based streaming + control.
  • Autonomous options: Guidance on integrating Donkey Car or similar ML-based control.
  • Remote access setup: How to control your car over Wi-Fi or the internet.

Awesome — let’s add an AI Autopilot that does camera-based obstacle avoidance, while keeping manual override from your gamepad and web sliders. If you touch the sticks or the web UI, manual instantly takes over; if you stop touching for a moment, autopilot resumes.

Below you’ll get:

  • Updated index.html (adds Autopilot toggle + status)
  • Single-file app.py (Flask + camera stream + PCA9685 + gamepad + autopilot thread)
  • Two autopilot modes:
    1. Lite (default): classical vision (edges + motion) — no extra downloads
    2. DNN (optional): MobileNet-SSD person/object awareness — drop in model files later

1) Install packages

sudo apt update
sudo apt install python3-flask python3-picamera2 python3-pip python3-opencv python3-pygame
pip3 install adafruit-circuitpython-pca9685

Optional (for DNN mode later — put files in models/):

  • models/MobileNetSSD_deploy.caffemodel
  • models/MobileNetSSD_deploy.prototxt

2) Web UI — static/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Pi RC Car</title>
  <style>
    body { font-family: Arial, sans-serif; max-width: 900px; margin: 0 auto; padding: 16px; }
    img { width: 100%; max-width: 860px; border: 1px solid #ddd; border-radius: 8px; }
    .row { display:flex; gap:16px; flex-wrap:wrap; margin-top:12px;}
    .card { flex:1; min-width:260px; border:1px solid #eee; border-radius:12px; padding:12px; box-shadow:0 2px 10px rgba(0,0,0,0.05);}
    .label { font-weight:bold; }
    .slider { width:100%; }
    .pill { display:inline-block; padding:4px 10px; border-radius:999px; background:#efefef; margin-left:6px;}
    button { padding:8px 12px; border-radius:10px; border:1px solid #ddd; cursor:pointer; }
    .on { background:#d9f8df; border-color:#9ae6a3; }
    .danger { background:#ffe3e3; border-color:#ffbdbd; }
  </style>
</head>
<body>
  <h1>Raspberry Pi 5 RC Car</h1>
  <img src="{{ url_for('video_feed') }}" />

  <div class="row">
    <div class="card">
      <div class="label">Steering</div>
      <input id="steering" class="slider" type="range" min="60" max="120" value="90">
      <div><span>Angle:</span> <span id="steerVal" class="pill">90</span></div>
    </div>
    <div class="card">
      <div class="label">Throttle</div>
      <input id="throttle" class="slider" type="range" min="60" max="120" value="90">
      <div><span>Angle:</span> <span id="throtVal" class="pill">90</span></div>
    </div>
    <div class="card">
      <div class="label">Actions</div>
      <button onclick="stopCar()" class="danger">Stop</button>
      <button id="apBtn" onclick="toggleAP()">Autopilot: OFF</button>
      <div style="margin-top:8px;">
        <span class="label">Mode:</span>
        <select id="apMode" onchange="setMode(this.value)">
          <option value="lite">Lite (Edges/Motion)</option>
          <option value="dnn">DNN (MobileNet-SSD)</option>
        </select>
      </div>
      <div style="margin-top:8px;">
        <span class="label">Status:</span>
        <span id="apStatus" class="pill">idle</span>
      </div>
    </div>
  </div>

  <script>
    const send = (p, v) => fetch(`/${p}/${v}`);
    const post = (p, body={}) => fetch(`/${p}`, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)});

    const s = document.getElementById('steering');
    const t = document.getElementById('throttle');
    const steerVal = document.getElementById('steerVal');
    const throtVal = document.getElementById('throtVal');
    const apBtn = document.getElementById('apBtn');
    const apStatus = document.getElementById('apStatus');
    const apMode = document.getElementById('apMode');

    s.oninput = e => { steerVal.textContent = e.target.value; send('steering', e.target.value); };
    t.oninput = e => { throtVal.textContent = e.target.value; send('throttle', e.target.value); };

    function stopCar() {
      send('throttle', 90); send('steering', 90);
      s.value = 90; t.value = 90; steerVal.textContent='90'; throtVal.textContent='90';
    }

    async function toggleAP() {
      const r = await post('autopilot/toggle');
      const j = await r.json();
      apBtn.textContent = `Autopilot: ${j.enabled ? 'ON' : 'OFF'}`;
      apBtn.className = j.enabled ? 'on' : '';
    }

    async function setMode(m) {
      const r = await post('autopilot/mode', {mode:m});
      const j = await r.json();
      apStatus.textContent = `mode: ${j.mode}`;
    }

    // Poll autopilot status every 1s
    async function poll() {
      try {
        const r = await fetch('/autopilot/status');
        const j = await r.json();
        apBtn.textContent = `Autopilot: ${j.enabled ? 'ON' : 'OFF'}`;
        apBtn.className = j.enabled ? 'on' : '';
        apMode.value = j.mode;
        apStatus.textContent = j.state;
      } catch(e) {}
      setTimeout(poll, 1000);
    }
    poll();
  </script>
</body>
</html>

3) Server — app.py

from flask import Flask, render_template, Response, request, jsonify
from adafruit_servokit import ServoKit
from picamera2 import Picamera2
import cv2, time, threading, pygame, numpy as np
import os

app = Flask(__name__, static_folder="static", template_folder="static")

# === Servo / ESC ===
kit = ServoKit(channels=16)
STEERING_CENTER = 90
THROTTLE_STOP   = 90
kit.servo[0].angle = STEERING_CENTER
kit.servo[1].angle = THROTTLE_STOP

# Limits
STEER_MIN, STEER_MAX = 60, 120
THROT_MIN, THROT_MAX = 60, 120

# Manual override logic
last_manual_time = time.time()
MANUAL_TIMEOUT_S = 1.0   # if no manual input for 1s, autopilot may act

def clamp(v, lo, hi): return max(lo, min(hi, v))

def set_steering(angle):
    global last_manual_time
    kit.servo[0].angle = clamp(int(angle), STEER_MIN, STEER_MAX)
    last_manual_time = time.time()

def set_throttle(angle):
    global last_manual_time
    kit.servo[1].angle = clamp(int(angle), THROT_MIN, THROT_MAX)
    last_manual_time = time.time()

# === Camera ===
camera = Picamera2()
camera.configure(camera.create_video_configuration(main={"size": (640, 480)}))
camera.start()

frame_lock = threading.Lock()
latest_frame = None

def cam_loop():
    global latest_frame
    while True:
        frame = camera.capture_array()
        with frame_lock:
            latest_frame = frame

threading.Thread(target=cam_loop, daemon=True).start()

def gen_frames():
    while True:
        with frame_lock:
            frame = latest_frame.copy() if latest_frame is not None else None
        if frame is None:
            time.sleep(0.01); continue
        _, buffer = cv2.imencode('.jpg', frame)
        yield (b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')

@app.route('/')
def index(): return render_template('index.html')

@app.route('/video_feed')
def video_feed(): return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/steering/<int:angle>')
def http_steer(angle): set_steering(angle); return ("", 204)

@app.route('/throttle/<int:angle>')
def http_throt(angle): set_throttle(angle); return ("", 204)

# === Autopilot state ===
ap_enabled = False
ap_mode    = "lite"   # "lite" or "dnn"
ap_state   = "idle"

@app.route('/autopilot/toggle', methods=['POST'])
def ap_toggle():
    global ap_enabled
    ap_enabled = not ap_enabled
    return jsonify({"enabled": ap_enabled})

@app.route('/autopilot/mode', methods=['POST'])
def ap_set_mode():
    global ap_mode
    ap_mode = request.json.get("mode","lite")
    return jsonify({"mode": ap_mode})

@app.route('/autopilot/status')
def ap_status():
    return jsonify({"enabled": ap_enabled, "mode": ap_mode, "state": ap_state})

# === Gamepad thread ===
def gamepad_loop():
    global last_manual_time
    pygame.init()
    pygame.joystick.init()
    if pygame.joystick.get_count() == 0:
        print("No gamepad found. (Autopilot and web control still work.)")
        return
    js = pygame.joystick.Joystick(0); js.init()
    print(f"Gamepad connected: {js.get_name()}")
    while True:
        pygame.event.pump()
        steer_axis = js.get_axis(0)      # left stick X
        throttle_axis = -js.get_axis(1)  # left stick Y (invert)
        if abs(steer_axis) > 0.05 or abs(throttle_axis) > 0.05:
            # Apply manual input and refresh override timer
            s_angle = STEERING_CENTER + steer_axis * 30
            t_angle = THROTTLE_STOP + throttle_axis * 30
            kit.servo[0].angle = clamp(int(s_angle), STEER_MIN, STEER_MAX)
            kit.servo[1].angle = clamp(int(t_angle), THROT_MIN, THROT_MAX)
            last_manual_time = time.time()
        time.sleep(0.03)

threading.Thread(target=gamepad_loop, daemon=True).start()

# === Autopilot helpers ===

# Lite mode: edge+motion occupancy left/center/right
def lite_nav(frame):
    """
    Returns (steer_delta_degrees, throttle_angle) where positive steer_delta turns right.
    Simple strategy:
      - Edge map + motion map to estimate obstacle density L/C/R.
      - Prefer region with least density; slow if center is crowded.
    """
    h, w = frame.shape[:2]
    roi = frame[int(h*0.45):h, :]  # use lower half
    gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 60, 150)

    # Simple motion (frame differencing) — keep a tiny ring buffer
    if not hasattr(lite_nav, "prev"):
        lite_nav.prev = gray
    diff = cv2.absdiff(gray, lite_nav.prev)
    lite_nav.prev = gray
    _, motion = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)

    # Combine signals
    occ = cv2.addWeighted(edges, 0.7, motion, 0.3, 0)
    thirds = np.array_split(occ, 3, axis=1)
    dens = [cv2.countNonZero(t) for t in thirds]  # [L, C, R]

    L, C, R = dens
    # Steering: aim for least dense side
    target = np.argmin(dens)  # 0=L,1=C,2=R
    steer_delta = {0: -20, 1: 0, 2: 20}[target]

    # Throttle: slow if center crowded
    crowd = C / (occ.shape[0]*occ.shape[1]/3)
    if crowd > 0.12:  # heuristic
        throttle = THROTTLE_STOP + 5   # creep
    else:
        throttle = THROTTLE_STOP + 15  # cruise

    return steer_delta, clamp(int(throttle), THROT_MIN, THROT_MAX)

# DNN mode: optional MobileNet-SSD person/obstacle awareness
net = None
def ensure_dnn():
    global net
    if net is not None: return True
    proto = "models/MobileNetSSD_deploy.prototxt"
    model = "models/MobileNetSSD_deploy.caffemodel"
    if not (os.path.exists(proto) and os.path.exists(model)):
        return False
    net = cv2.dnn.readNetFromCaffe(proto, model)
    return True

def dnn_nav(frame):
    """
    Detect common objects; if a large box is centered/near bottom, reduce speed and steer away.
    """
    h, w = frame.shape[:2]
    blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 0.007843, (300,300), 127.5)
    net.setInput(blob)
    dets = net.forward()

    Lpen = Cpen = Rpen = 0.0
    for i in range(dets.shape[2]):
        conf = dets[0,0,i,2]
        if conf < 0.5: continue
        box = dets[0,0,i,3:7] * np.array([w,h,w,h])
        x1,y1,x2,y2 = box.astype(int)
        area = (x2-x1)*(y2-y1)
        # weight near lower center more (potential path)
        ymid = (y1+y2)/2 / h
        weight = area / (w*h) * (0.5 + ymid)  # bigger/closer = higher weight
        xm = (x1+x2)/2
        if xm < w/3: Lpen += weight
        elif xm < 2*w/3: Cpen += weight
        else: Rpen += weight

    # steer to lowest penalty
    dens = [Lpen, Cpen, Rpen]
    target = int(np.argmin(dens))
    steer_delta = {0:-20, 1:0, 2:20}[target]

    # throttle: slower if center penalty high
    throttle = THROTTLE_STOP + (8 if Cpen > 0.015 else 18)
    return steer_delta, clamp(int(throttle), THROT_MIN, THROT_MAX)

# === Autopilot loop ===
def autopilot_loop():
    global ap_state, last_manual_time
    while True:
        time.sleep(0.03)
        if not ap_enabled:
            ap_state = "off"
            continue
        with frame_lock:
            frame = latest_frame.copy() if latest_frame is not None else None
        if frame is None:
            ap_state = "waiting_camera"; continue

        # Manual override check
        if time.time() - last_manual_time < MANUAL_TIMEOUT_S:
            ap_state = "manual_override"
            continue

        try:
            if ap_mode == "dnn" and ensure_dnn():
                ap_state = "autopilot_dnn"
                steer_delta, throttle = dnn_nav(frame)
            else:
                ap_state = "autopilot_lite"
                steer_delta, throttle = lite_nav(frame)

            # Apply command
            current_steer = kit.servo[0].angle or STEERING_CENTER
            new_steer = clamp(int(current_steer + steer_delta), STEER_MIN, STEER_MAX)
            kit.servo[0].angle = new_steer
            kit.servo[1].angle = throttle

        except Exception as e:
            ap_state = f"error:{type(e).__name__}"

threading.Thread(target=autopilot_loop, daemon=True).start()

if __name__ == '__main__':
    # TIP: for lower latency on LAN, consider threaded server like waitress or gunicorn
    app.run(host='0.0.0.0', port=5000, debug=False)

How the hybrid control works

  • Manual inputs (gamepad or web sliders) update servos immediately and set a timestamp.
  • Autopilot only acts if there’s been no manual input for 1 second (configurable via MANUAL_TIMEOUT_S).
  • Lite mode uses edges+motion on the road area to choose Left / Center / Right with least “crowding,” and adjusts speed.
  • DNN mode (optional) uses MobileNet-SSD to estimate obstacle density L/C/R (more aware of “things” like people/signs) and steers away.

Safety & tuning tips

  • Start with wheels off the ground; verify directions and neutral at 90.
  • Tune ranges:
    • Steering throw: change ±30 in gamepad_loop and steer_delta values.
    • Speed: tweak THROTTLE_STOP + {5..20} in nav functions.
  • If your ESC requires arming (neutral 1–2s), add a short delay at boot keeping throttle at 90.
  • For DNN mode, copy model files into models/ and switch the UI to DNN.

Want me to bundle this into a ready-to-run zip (folders + files) or add on-screen HUD overlays (FPS, mode, detections) to the video stream?

All packed up! Grab your ready-to-run project bundle:

Download the ZIP

What you’re getting

  • app.py — Flask server with web UI, gamepad control, autopilot, and HUD overlay (FPS + AP state/mode).
  • static/index.html — Web controls (sliders + Autopilot toggle + mode selector).
  • models/ — Placeholder folder for optional MobileNet-SSD files (for DNN mode).
  • requirements.txt & README.md — Install + run instructions.

Quick start

unzip rc_car_ai_bundle.zip
cd rc_car_ai
sudo apt update
sudo apt install -y python3-flask python3-picamera2 python3-opencv python3-pygame python3-pip
pip3 install -r requirements.txt
python3 app.py
# open http://<PI_IP>:5000

Want me to add a systemd service so it auto-starts on boot, or tailor the PWM ranges for your exact ESC/servo?

HTML Snippets Powered By : XYZScripts.com