Skip to content

TheDeveloperOps/ESP-RC-CAR

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

🚗 ESP32 WiFi RC Car

ESP32 Arduino WiFi License Platform

A fully wireless RC car controlled from any phone browser — no app install needed!

Built with ESP32 WROOM-32 · L298N Motor Driver · BO Motors · Touch Joystick Controller


📸 Project Overview

This project turns a simple BO motor chassis into a WiFi-controlled RC car using an ESP32 microcontroller. The ESP32 creates its own WiFi hotspot — anyone can connect and drive it straight from their phone browser using a touch joystick. No app installation, no Bluetooth pairing, no internet required.


🧰 Hardware Used

# Component Details
1 ESP32 WROOM-32 Main microcontroller, hosts WiFi hotspot + web server
2 L298N Motor Driver Module Dual H-bridge, controls 2 BO motors
3 BO Motors 2x Battery Operated gear motors
4 11.8V Battery Pack Powers L298N + ESP32 (via L298N 5V out)
5 Micro USB Cable For uploading code (data cable, not charge-only)
6 Jumper Wires Male-to-male for connections

⚡ Wiring Diagram

Power Wiring

[11.8V Battery]
      (+) ───────────────→ L298N  [ +12V terminal ]
      (−) ───────────────→ L298N  [ GND terminal  ]
                                         │
                              ┌──────────┘
                              │
                              ├──→ ESP32 GND pin

L298N [ 5V terminal ] ───────→ ESP32 VIN pin

💡 One battery powers everything! The L298N's onboard 7805 regulator steps 11.8V down to 5V for the ESP32. Make sure the 12V jumper on L298N is ON to enable the onboard regulator.


Signal Wiring (ESP32 → L298N)

ESP32 GPIO          L298N Pin        Function
──────────────────────────────────────────────────
GPIO 27     ───→    IN1              Motor A direction
GPIO 26     ───→    IN2              Motor A direction
GPIO 25     ───→    IN3              Motor B direction
GPIO 33     ───→    IN4              Motor B direction
GPIO 14     ───→    ENA              Motor A speed (PWM)
GPIO 12     ───→    ENB              Motor B speed (PWM)
GND         ───→    GND              Common ground ⚠️ REQUIRED

⚠️ Common Ground is critical! ESP32 signal levels are measured relative to its own GND. Without a shared GND between ESP32 and L298N, signals are unreadable and motors won't respond.


Motor Wiring

L298N OUT1 ───→ Motor 1  (+)
L298N OUT2 ───→ Motor 1  (−)

L298N OUT3 ───→ Motor 2  (+)
L298N OUT4 ───→ Motor 2  (−)

🔄 If a motor spins the wrong direction, simply swap its two wires on the OUT terminals.


🧠 How It Works

┌─────────────────────────────────────────────────┐
│                                                 │
│   Phone Browser  ──HTTP──→  ESP32 Web Server   │
│   (touch joystick)          192.168.4.1         │
│                                  │              │
│                             L298N Module        │
│                            /          \         │
│                       Motor 1       Motor 2     │
│                                                 │
└─────────────────────────────────────────────────┘
  1. ESP32 boots and creates a WiFi hotspot called RC_CAR
  2. Phone connects to the hotspot
  3. Browser opens 192.168.4.1
  4. ESP32 serves a web app with a touch joystick + speed slider
  5. Dragging the joystick sends HTTP commands (/forward, /left etc.)
  6. ESP32 receives commands and drives motors via L298N

🕹️ Controls

Joystick Direction Car Action
⬆️ Drag Up Forward
⬇️ Drag Down Backward
⬅️ Drag Left Turn Left
➡️ Drag Right Turn Right
🔵 Release / Center Stop
🎚️ Speed Slider Adjust motor speed (0–100%)

💻 Software Setup

Step 1 — Install Arduino IDE

Download from arduino.cc/en/software and install.


Step 2 — Add ESP32 Board Support

Go to File → Preferences → Additional Board Manager URLs and paste:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

Then go to Tools → Board → Board Manager, search esp32, and install esp32 by Espressif Systems.


Step 3 — Install USB Driver

Most ESP32 boards use one of these USB chips:

Chip Driver Download
CP2102 silabs.com/developers/usb-to-uart-bridge-vcp-drivers
CH340 wch-ic.com

After install, check Device Manager → Ports for your COM port (e.g. COM3, COM4).


Step 4 — Board Settings in Arduino IDE

Tools → Board        → ESP32 Dev Module
Tools → Port         → COM3  (your port)
Tools → Upload Speed → 115200

Step 5 — Upload the Code

  1. Open motortest.ino in Arduino IDE
  2. Click Verify (✓) — check for errors
  3. Hold the BOOT button on ESP32
  4. Click Upload (→)
  5. When Connecting.... appears — release BOOT
  6. Wait for Done uploading

💡 Some ESP32 boards auto-upload without holding BOOT — try without it first.


📄 Full Code

#include <WiFi.h>
#include <WebServer.h>

#define IN1 27
#define IN2 26
#define IN3 25
#define IN4 33
#define ENA 14
#define ENB 12

const char* ssid     = "RC_CAR";
const char* password = "12345678";

WebServer server(80);
int currentSpeed = 200;

void setupPWM() {
  ledcAttach(ENA, 1000, 8);
  ledcAttach(ENB, 1000, 8);
}

void setSpeed(int s) { ledcWrite(ENA, s); ledcWrite(ENB, s); }

void forward()   { digitalWrite(IN1,HIGH);digitalWrite(IN2,LOW);digitalWrite(IN3,HIGH);digitalWrite(IN4,LOW);setSpeed(currentSpeed); }
void backward()  { digitalWrite(IN1,LOW);digitalWrite(IN2,HIGH);digitalWrite(IN3,LOW);digitalWrite(IN4,HIGH);setSpeed(currentSpeed); }
void turnLeft()  { digitalWrite(IN1,LOW);digitalWrite(IN2,HIGH);digitalWrite(IN3,HIGH);digitalWrite(IN4,LOW);setSpeed(currentSpeed); }
void turnRight() { digitalWrite(IN1,HIGH);digitalWrite(IN2,LOW);digitalWrite(IN3,LOW);digitalWrite(IN4,HIGH);setSpeed(currentSpeed); }
void stopMotors(){ digitalWrite(IN1,LOW);digitalWrite(IN2,LOW);digitalWrite(IN3,LOW);digitalWrite(IN4,LOW);setSpeed(0); }

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>RC Car</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;}
body{background:#111;color:#fff;font-family:Arial,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;overflow:hidden;user-select:none;}
h1{color:#00e5ff;font-size:20px;letter-spacing:3px;margin-bottom:6px;}
#status{font-size:24px;font-weight:bold;color:#fff;margin-bottom:20px;letter-spacing:2px;min-height:32px;}
#zone{width:220px;height:220px;border-radius:50%;background:#1a1a2e;border:2px solid #00e5ff;position:relative;touch-action:none;}
#stick{width:66px;height:66px;border-radius:50%;background:#00e5ff;position:absolute;top:77px;left:77px;pointer-events:none;}
.arr{position:absolute;color:#00e5ff;font-size:20px;}
.up{top:6px;left:50%;transform:translateX(-50%);}
.dn{bottom:6px;left:50%;transform:translateX(-50%);}
.lt{left:6px;top:50%;transform:translateY(-50%);}
.rt{right:6px;top:50%;transform:translateY(-50%);}
#speed-wrap{width:80%;margin:20px 0 8px;text-align:center;}
#speed-label{font-size:13px;color:#aaa;margin-bottom:6px;}
input[type=range]{width:100%;accent-color:#00e5ff;}
</style>
</head>
<body>
<h1>RC CAR</h1>
<div id="status">STOPPED</div>
<div id="zone">
  <div id="stick"></div>
  <span class="arr up">&#9650;</span>
  <span class="arr dn">&#9660;</span>
  <span class="arr lt">&#9668;</span>
  <span class="arr rt">&#9658;</span>
</div>
<div id="speed-wrap">
  <div id="speed-label">Speed: 78%</div>
  <input type="range" min="0" max="255" value="200" id="slider">
</div>
<script>
const zone=document.getElementById('zone');
const stick=document.getElementById('stick');
const statusEl=document.getElementById('status');
const slider=document.getElementById('slider');
const speedLabel=document.getElementById('speed-label');
const R=110,SR=33;
let lastCmd='',dragging=false;

function getCmd(x,y){
  const d=Math.sqrt(x*x+y*y);
  if(d<28)return 'stop';
  if(Math.abs(x)>Math.abs(y))return x>0?'right':'left';
  return y>0?'backward':'forward';
}

const labels={forward:'FORWARD',backward:'BACKWARD',left:'LEFT',right:'RIGHT',stop:'STOPPED'};

function applyPos(cx,cy){
  const rect=zone.getBoundingClientRect();
  let x=cx-rect.left-R,y=cy-rect.top-R;
  const dist=Math.sqrt(x*x+y*y);
  if(dist>R-SR){x=x/dist*(R-SR);y=y/dist*(R-SR);}
  stick.style.left=(R+x-SR)+'px';
  stick.style.top=(R+y-SR)+'px';
  const cmd=getCmd(x,y);
  statusEl.innerText=labels[cmd];
  statusEl.style.color=cmd==='stop'?'#fff':'#00e5ff';
  if(cmd!==lastCmd){lastCmd=cmd;fetch('/'+cmd);}
}

function resetStick(){
  stick.style.left=(R-SR)+'px';
  stick.style.top=(R-SR)+'px';
  statusEl.innerText='STOPPED';
  statusEl.style.color='#fff';
  if(lastCmd!=='stop'){lastCmd='stop';fetch('/stop');}
}

zone.addEventListener('touchstart',e=>{e.preventDefault();dragging=true;applyPos(e.touches[0].clientX,e.touches[0].clientY);},{passive:false});
window.addEventListener('touchmove',e=>{if(dragging)applyPos(e.touches[0].clientX,e.touches[0].clientY);});
window.addEventListener('touchend',()=>{if(dragging){dragging=false;resetStick();}});
zone.addEventListener('mousedown',e=>{dragging=true;applyPos(e.clientX,e.clientY);});
window.addEventListener('mousemove',e=>{if(dragging)applyPos(e.clientX,e.clientY);});
window.addEventListener('mouseup',()=>{if(dragging){dragging=false;resetStick();}});

slider.addEventListener('input',function(){
  const pct=Math.round(this.value/255*100);
  speedLabel.innerText='Speed: '+pct+'%';
  fetch('/speed?val='+this.value);
});
</script>
</body>
</html>
)rawliteral";

void setup() {
  Serial.begin(115200);
  pinMode(IN1,OUTPUT);pinMode(IN2,OUTPUT);
  pinMode(IN3,OUTPUT);pinMode(IN4,OUTPUT);
  setupPWM();
  stopMotors();

  WiFi.softAP(ssid, password);
  Serial.print("Hotspot IP: ");
  Serial.println(WiFi.softAPIP());

  server.on("/",        [](){server.send(200,"text/html",index_html);});
  server.on("/forward", [](){forward();   server.send(200,"text/plain","ok");});
  server.on("/backward",[](){backward();  server.send(200,"text/plain","ok");});
  server.on("/left",    [](){turnLeft();  server.send(200,"text/plain","ok");});
  server.on("/right",   [](){turnRight(); server.send(200,"text/plain","ok");});
  server.on("/stop",    [](){stopMotors();server.send(200,"text/plain","ok");});
  server.on("/speed",   [](){
    if(server.hasArg("val"))currentSpeed=server.arg("val").toInt();
    server.send(200,"text/plain","ok");
  });
  server.begin();
  Serial.println("Server started!");
}

void loop(){ server.handleClient(); }

📡 Connecting & Driving

Step 1 → Power on the car (battery connected)
Step 2 → On your phone, go to WiFi Settings
Step 3 → Connect to  "RC_CAR"
Step 4 → Password:   "12345678"
Step 5 → Open browser → type  192.168.4.1
Step 6 → Drag the joystick and drive! 🚗

📶 WiFi Range: ~10–30 metres open space. Perfect for indoors, hallways, and garden use.


🐛 Troubleshooting

Problem Cause Fix
Port not showing in Arduino IDE Wrong cable or missing driver Use a data cable, install CP2102/CH340 driver
Failed to connect upload error ESP32 not in flash mode Hold BOOT button while clicking Upload
Motors don't move Missing common ground Connect L298N GND → ESP32 GND
Motors spin wrong direction Wires reversed Swap OUT1/OUT2 or OUT3/OUT4 wires
L298N gets very hot High voltage + load Add heatsink to L298N chip
Web page doesn't load Wrong IP Check Serial Monitor for actual IP
ledcSetup compile error Old code on new ESP32 core v3.x Use ledcAttach(pin, freq, res) instead

🔑 Key Concepts Learned

🔌 Why Common Ground Matters

Voltage is always relative — ESP32 signals are measured against its own GND. Without a shared GND, L298N has no reference point and misreads all signals. One wire between GND pins = everything works.

⚡ Why PWM for Speed Control

ledcWrite() sends a PWM signal (rapid on/off pulses) to ENA/ENB. The duty cycle (0–255) controls average voltage to motors, which translates directly to speed.

📶 Why No Gyroscope on Android + HTTP

Android Chrome blocks gyroscope access on plain HTTP pages for security. ESP32 hotspot serves HTTP, so gyroscope is unavailable on Android. The touch joystick works perfectly as an alternative with zero setup needed.

🔋 L298N as Power Supply

The L298N's onboard 7805 regulator converts battery voltage (6–12V) to a stable 5V output — enough to power the ESP32 via its VIN pin, making the whole build run off a single battery pack.


📁 File Structure

esp32-rc-car/
│
├── motortest/
│   └── motortest.ino       ← Main Arduino sketch
│
└── README.md               ← This file

🛠️ Built With


📜 License

MIT License — free to use, modify, and share!


Made with ❤️ and a lot of wire

If this helped you, give it a ⭐ on GitHub!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors