DIY Mini RC Car: Xiao ESP32 , Web Controls and Wireless Wii Nunchuk
by iamrachelle in Circuits > Arduino
1864 Views, 27 Favorites, 0 Comments
DIY Mini RC Car: Xiao ESP32 , Web Controls and Wireless Wii Nunchuk
In this project, we will create a mini RC car using a 3D printer and an ESP32 microcontroller. We’ll be using the ESP32 WiFi Robot design by Wingman94 from Thingiverse. This guide will walk you through the entire process, from gathering supplies to programming the car and demonstrating its functionality using a WebSocket server or a wireless Nunchuck controller.
Supplies
Here's what you'll need:
- xiao esp32c3
- 3D printed car chassis and parts: ESP32 WiFi Robot by Wingman94
- Motors(Micro Metal DC Gear Motor 6V 150RPM JGA12-N20)
- Motor driver module(Mini L298N Motor Driver)
- Battery Charger Module
- Battery (14500 Batteries [AA Lithium-Ion])
- Wires
Tools
Gather these tools for the project:
- 3D printer
- Soldering iron and solder
- Wire cutters and strippers
- Screwdrivers
- Multimeter
- Computer with Arduino IDE
Assembly
I’ve modified the design of the ESP32 WiFi Robot by Wingman94 and printed the car body using a 3D printer. To ensure a perfect fit, I used wire cutters to adjust some of the 3D-printed parts. You can find the files for the chassis and parts attached here. You can find the modified files for the chassis and parts attached here.
Wiring Process
For detailed wiring instructions, refer to the attached schematic. This schematic will guide you through connecting the ESP32, motors, and other electronics correctly.
Program Overview
This program transforms an ESP32 into a remote-controlled car using WebSocket communication and an HTML control interface. The car's movements are controlled based on input from a web interface or a Nunchuck controller, with the program handling motor speed and direction. Below is the structure of the program and its associated files:
MiniRC-Pink.ino: This is the main program file that you'll upload to your ESP32. It contains all the logic for setting up the WiFi, WebSocket server, and controlling the motors based on received commands.
data (Folder): This folder contains the index.html file, which is the control interface for the RC car.
HTML Control Interface
The program serves an HTML file stored in SPIFFS, which provides a user interface for controlling the car. The HTML file is loaded when the client accesses the ESP32's IP address. For more details about this, see my other tutorial for it.
Here's the code for index.html file:
<!DOCTYPE html>
<html>
<head>
<title>
Mousebot
</title>
<meta name="viewport" content="user-scalable=no">
</head>
<body style="position: fixed; font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif ;
color:rgb(128, 128, 128);height: 100%; overflow: hidden;overscroll-behavior: none;
font-size: xx-large;">
<h1 style="text-align:center">
MOUSEBOT </h1>
<p style="text-align: center;">
X: <span id="x_coordinate"> </span>
Y: <span id="y_coordinate"> </span>
Speed: <span id="speed"> </span> %
Angle: <span id="angle"> </span>
</p>
<canvas id="canvas" name="game"></canvas>
<script>
var connection = new WebSocket('ws://' + "192.168.4.1" + ':81/', ['arduino']);
connection.onopen = function () {
connection.send('Connect ' + new Date());
};
connection.onerror = function (error) {
console.log('WebSocket Error ', error);
alert('WebSocket Error ', error);
};
connection.onmessage = function (e) {
console.log('Server: ', e.data);
};
function send(x,y,speed,angle){
var data = {"x":x,"y":y,"speed":speed,"angle":angle};
data = JSON.stringify(data);
console.log(data);
connection.send(data);
}
var canvas, ctx;
window.addEventListener('load', () => {
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
resize();
document.addEventListener('mousedown', startDrawing);
document.addEventListener('mouseup', stopDrawing);
document.addEventListener('mousemove', Draw);
document.addEventListener('touchstart', startDrawing);
document.addEventListener('touchend', stopDrawing);
document.addEventListener('touchcancel', stopDrawing);
document.addEventListener('touchmove', Draw);
window.addEventListener('resize', resize);
document.getElementById("x_coordinate").innerText = 0;
document.getElementById("y_coordinate").innerText = 0;
document.getElementById("speed").innerText = 0;
document.getElementById("angle").innerText = 0;
});
var width, height, radius, x_orig, y_orig;
function resize() {
width = window.innerWidth;
radius = 100;
height = radius * 6.5;
ctx.canvas.width = width;
ctx.canvas.height = height;
background();
joystick(width / 2, height / 3);
}
function background() {
x_orig = width / 2;
y_orig = height / 3;
ctx.beginPath();
ctx.arc(x_orig, y_orig, radius + 20, 0, Math.PI * 2, true);
ctx.fillStyle = '#ECE5E5';
ctx.fill();
}
function joystick(width, height) {
ctx.beginPath();
ctx.arc(width, height, radius, 0, Math.PI * 2, true);
ctx.fillStyle = '#F08080';
ctx.fill();
ctx.strokeStyle = '#F6ABAB';
ctx.lineWidth = 8;
ctx.stroke();
}
let coord = { x: 0, y: 0 };
let paint = false;
function getPosition(event) {
var mouse_x = event.clientX || event.touches[0].clientX;
var mouse_y = event.clientY || event.touches[0].clientY;
coord.x = mouse_x - canvas.offsetLeft;
coord.y = mouse_y - canvas.offsetTop;
}
function is_it_in_the_circle() {
var current_radius = Math.sqrt(Math.pow(coord.x - x_orig, 2) + Math.pow(coord.y - y_orig, 2));
if (radius >= current_radius) return true
else return false
}
function startDrawing(event) {
paint = true;
getPosition(event);
if (is_it_in_the_circle()) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
background();
joystick(coord.x, coord.y);
Draw();
}
}
function stopDrawing() {
paint = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
background();
joystick(width / 2, height / 3);
document.getElementById("x_coordinate").innerText = 0;
document.getElementById("y_coordinate").innerText = 0;
document.getElementById("speed").innerText = 0;
document.getElementById("angle").innerText = 0;
send( 0,0,0,0);
}
function Draw(event) {
if (paint) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
background();
var angle_in_degrees,x, y, speed;
var angle = Math.atan2((coord.y - y_orig), (coord.x - x_orig));
if (Math.sign(angle) == -1) {
angle_in_degrees = Math.round(-angle * 180 / Math.PI);
}
else {
angle_in_degrees =Math.round( 360 - angle * 180 / Math.PI);
}
if (is_it_in_the_circle()) {
joystick(coord.x, coord.y);
x = coord.x;
y = coord.y;
}
else {
x = radius * Math.cos(angle) + x_orig;
y = radius * Math.sin(angle) + y_orig;
joystick(x, y);
}
getPosition(event);
var speed = Math.round(100 * Math.sqrt(Math.pow(x - x_orig, 2) + Math.pow(y - y_orig, 2)) / radius);
var x_relative = Math.round(x - x_orig);
var y_relative =- Math.round(y - y_orig);
document.getElementById("x_coordinate").innerText = x_relative;
document.getElementById("y_coordinate").innerText =y_relative ;
document.getElementById("speed").innerText = speed;
document.getElementById("angle").innerText = angle_in_degrees;
const scale = (number, [inMin, inMax], [outMin, outMax]) => {
// if you need an integer value use Math.floor or Math.ceil here
return (number - inMin) / (inMax - inMin) * (outMax - outMin) + outMin;
}
send( x_relative/200,y_relative/200,scale(speed, [0, 100], [180, 250]),angle_in_degrees);
}
}
</script>
</body>
</html><br>
Program Code
The complete program code is provided below for reference and each part is explained below.
Main Functions
- setup: Initializes serial communication, sets motor pins as outputs, starts the WiFi Access Point, initializes SPIFFS, starts the WebSocket server, and ensures all motors are off initially.
- loop: Continuously handles client requests to the web server and processes WebSocket communication.
Detailed Explanation:
- WiFi Setup
The ESP32 sets up a WiFi Access Point with the SSID "MiniRC" and password "12345678". This allows your device to connect directly to the ESP32 for controlling the car.
- WebSocket Server
A WebSocket server is initiated on port 81. This server manages real-time communication between the ESP32 and the client (web interface or Nunchuck).
- Motor Control
The program defines the motor pins and sets them as outputs. It uses the `analogWrite` function to control the speed and direction of the motors based on input received via the WebSocket.
- WebSocket Event Handling
The `webSocketEvent` function handles different WebSocket events:
- Connection: Logs the connection and sends a confirmation message to the client.
- Disconnection: Logs the disconnection.
- Message: Parses the received JSON data to determine the joystick position, speed, and angle. Based on these values, it computes the speed for each motor and adjusts the motor outputs accordingly.
Downloads
Demo(Using Your Phone/tablet)
We'll control our RC car using a WebSocket server and the html file. Here's how:
- Connect to its WIFI with the SSID "MiniRC" and password "12345678".
- Open your browser and go to 192.168.4.1/index.html.
- Send commands: Use the interface to control the car.
Demo(Using Your Wireless Nunchuck)
Using the Nunchuck
To use the Nunchuck controller
- Connect the Nunchuck: Attach the Nunchuck controller made using this tutorial.
- Power Up: Turn on the power to your mini RC car.
- Test the Controller:Move the joystick on the Nunchuck to control the direction and speed of the RC car.
- Observe Movement: The car should respond to the joystick movements and button presses, adjusting its speed and direction accordingly.
Additional Observation: I found that mopping the floor made the RC car move faster. The smoother surface likely reduces friction, allowing for quicker movement.