Robo Race Bot
Robotics competitions like Robo Race challenge students to design and build fast, stable, and efficient racing robots. In this project, I built a Wireless Robo Race Bot using an ESP32 microcontroller, FlySky transmitter and receiver, and Cytron MDD10A motor driver.
This robot is designed to move at high speed while maintaining stability and smooth control on racing tracks with sharp turns, zig-zag paths, and obstacles.
Supplies
Electronic Components
- ESP32 Dev Kit
- Cytron MDD10A Motor Driver
- FlySky Transmitter
- FlySky Receiver
- 4 × 1000 RPM Johnson Motors
- 4 × 80mm Wheels
- 12V Lithium Battery
- Connecting Wires
- XT60 Connector
Mechanical Components
- Acrylic Chassis (Laser Cut)
- Motor Mounts
- Nuts and Bolts
- Wheel Couplers
Tools Required
- Screwdriver Set
- Soldering Iron
- Wire Cutter
- Laptop with Arduino IDE
- USB Cable for ESP32
Software Used
- Arduino IDE
- Onshape (for chassis design)
Chassis Design
First, I designed the robot chassis using Onshape.
The chassis is designed to hold all components properly:
- Motors
- Battery
- ESP32
- Motor Driver
- FlySky Receiver
I used an acrylic sheet because it is:
- Lightweight
- Strong
- Easy to laser cut
The robot uses a 4-wheel chassis design for better balance and smooth movement.
This design helps the robot stay stable during racing
Assembling Components
After making the chassis, I assembled all main components.
Components assembled:
- 4 Johnson motors
- 4 wheels
- Cytron MDD10A motor driver
Assembly process:
- Fix all motors to chassis
- Attach wheels properly
- Mount Cytron motor driver
- Place battery section at center
This setup helps the robot move fast and stay balanced.
Soldering ESP32 and Buck Converter
In this step, I soldered the electronics on PCB.
Components soldered:
- ESP32
- PCB board
- Buck converter
The buck converter is used to reduce voltage and give proper power supply to ESP32.
After soldering:
- Check all connections
- Make sure no loose wires are present
This makes the circuit clean and stable.
Circuit Connections and Code Upload
Now connect all components together.
Connections made:
- PCB to Cytron motor driver
- Motors to motor driver
- ESP32 to motor driver
After connections:
- Open Arduino IDE
- Select ESP32 board
- Upload code
The uploaded code helps:
- Control motors
- Read FlySky signals
- Create WiFi dashboard
After uploading, check if ESP32 powers ON correctly.
Code : Complete Code
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
// --- PIN DEFINITIONS ---
const int CH1_PIN = 2;
const int CH2_PIN = 4;
const int M1_PWM = 25; const int M1_DIR = 26;
const int M2_PWM = 27; const int M2_DIR = 33;
// PWM Config
const int M1_CH = 0;
const int M2_CH = 1;
// Variables
float speedMultiplier;
bool invertSteering;
bool swapMotors;
long lastPulse1 = 0, lastPulse2 = 0;
WebServer server(80);
Preferences preferences;
void handleRoot() {
if (server.hasArg("speed")) {
speedMultiplier = server.arg("speed").toFloat() / 100.0;
preferences.putFloat("speed", speedMultiplier);
}
if (server.hasArg("invert")) {
invertSteering = (Server.arg("invert") == "true");
preferences.putBool("invert", invertsteering);
}
if (server.hasArg("swap")) {
swapMotors = (server.arg("swap") == "true");
preferences.putBool("swap", swapMotors);
}
String html = "<html><head><meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<style>body{font-family:'Segoe UI',sans-serif; background:#1a1a1a; color:white; text-align:center; padding:20px;}";
html += ".card{background:#2d2d2d; padding:25px; border-radius:20px; box-shadow:0 10px 20px rgba(0,0,0,0.5); max-width:450px; margin:auto;}";
html += "h1{color:#00d4ff; margin-bottom:10px;} .stat-grid{display:grid; grid-template-columns:1-fr 1fr; gap:10px; margin:20px 0;}";
html += ".stat-box{background:#3d3d3d; padding:15px; border-radius:12px; border:1px solid #444;}";
html += ".slider{width:100%; height:15px; border-radius:10px; background:#444; outline:none; -webkit-appearance:none; margin:20px 0;}";
html += ".slider::-webkit-slider-thumb{-webkit-appearance:none; width:25px; height:25px; background:#00d4ff; border-radius:50%; cursor:pointer;}";
html += ".btn{display:block; width:100%; padding:15px; margin:10px 0; border-radius:10px; border:none; color:white; font-weight:bold; cursor:pointer; text-decoration:none;}";
html += ".blue{background:#007bff;} .grey{background:#555;} .green{background:#28a745;}</style>";
html += "<script>setInterval(function(){location.reload();}, 3000);</script></head><body>"; // Auto-refresh diagnostics
html += "<div class='card'><h1>ROBOT DASHBOARD</h1>";
// Diagnostics Section
html += "<div class='stat-grid'>";
html += "<div class='stat-box'><b>CH1 Pulse</b><br><span style='color:#00d4ff'>" + String(lastPulse1) + "us</span></div>";
html += "<div class='stat-box'><b>CH2 Pulse</b><br><span style='color:#00d4ff'>" + String(lastPulse2) + "us</span></div>";
html += "</div>";
// Speed Control Slider
html += "<h3>Power Limit: " + String(speedMultiplier * 100, 0) + "%</h3>";
html += "<form action='/'> <input name='speed' type='range' min='10' max='100' value='" + String(speedMultiplier * 100) + "' class='slider' onchange='this.form.submit()'>";
html += "<input type='submit' value='SET SPEED' class='btn green'></form>";
// Settings Buttons
html += "<a href='/?invert=" + String(!invertSteering ? "true" : "false") + "' class='btn blue'>STEERING: " + String(invertSteering ? "INVERSED" : "NORMAL") + "</a>";
html += "<a href='/?swap=" + String(!swapMotors ? "true" : "false") + "' class='btn grey'>MAP: " + String(swapMotors ? "SWAPPED" : "STANDARD") + "</a>";
html += "<p style='font-size:12px; color:#888; margin-top:20px;'>Connected to: " + WiFi.softAPIP().toString() + "</p>";
html += "</div></body></html>";
server.send(200, "text/html", html);
}
void setup() {
Serial.begin(115200);
preferences.begin("robot-ui", false);
speedMultiplier = preferences.getFloat("speed", 0.8);
invertSteering = preferences.getBool("invert", false);
swapMotors = preferences.getBool("swap", false);
pinMode(CH1_PIN, INPUT);
pinMode(CH2_PIN, INPUT);
pinMode(M1_DIR, OUTPUT);
pinMode(M2_DIR, OUTPUT);
ledcSetup(M1_CH, 5000, 8);
ledcSetup(M2_CH, 5000, 8);
ledcAttachPin(M1_PWM, M1_CH);
ledcAttachPin(M2_PWM, M2_CH);
WiFi.softAP("ESP32-Commander", "12345678");
Serial.print("Web UI at: "); Serial.println(WiFi.softAPIP());
server.on("/", handleRoot);
server.begin();
}
void loop() {
server.handleClient();
long rawSteer = pulseIn(CH1_PIN, HIGH, 30000);
long rawThrottle = pulseIn(CH2_PIN, HIGH, 30000);
// Store for UI display
if(rawSteer > 0) lastPulse1 = rawSteer;
if(rawThrottle > 0) lastPulse2 = rawThrottle;
if (rawThrottle > 900 && rawSteer > 900) {
int moveVal = map(rawThrottle, 1000, 2000, -255, 255);
int turnVal = map(rawSteer, 1000, 2000, -255, 255);
int leftSpeed, rightSpeed;
if (!invertSteering) {
leftSpeed = moveVal + turnVal;
rightSpeed = moveVal - turnVal;
} else {
leftSpeed = moveVal - turnVal;
rightSpeed = moveVal + turnVal;
}
leftSpeed *= speedMultiplier;
rightSpeed *= speedMultiplier;
if (swapMotors) {
controlMotor(M1_CH, M1_DIR, rightSpeed);
controlMotor(M2_CH, M2_DIR, leftSpeed);
} else {
controlMotor(M1_CH, M1_DIR, leftSpeed);
controlMotor(M2_CH, M2_DIR, rightSpeed);
}
} else {
ledcWrite(M1_CH, 0);
ledcWrite(M2_CH, 0);
}
}
void controlMotor(int channel, int dirPin, int speed) {
speed = constrain(speed, -255, 255);
if (abs(speed) < 20) {
ledcWrite(channel, 0);
} else {
digitalWrite(dirPin, speed > 0 ? HIGH : LOW);
ledcWrite(channel, abs(speed));
}
}
Connect FlySky Receiver and Run the Bot
Final step is connecting the receiver and battery.
Connections:
- FlySky receiver to ESP32
- Battery to motor driver
After connecting:
- Turn ON battery
- Turn ON FlySky transmitter
- Check receiver connection
- Test movement
Now the robot is ready to:
- Move forward
- Move backward
- Turn left
- Turn right
The Robo Race Bot is now ready for competition.