DIY Mini RC Car: Xiao ESP32 , Web Controls and Wireless Wii Nunchuk

by iamrachelle in Circuits > Arduino

1060 Views, 22 Favorites, 0 Comments

DIY Mini RC Car: Xiao ESP32 , Web Controls and Wireless Wii Nunchuk

MINI RC Car.png
GOPR1457.JPG
Mini RC controlled with modified wireless nunchuck

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

GOPR1457.JPG

Here's what you'll need:

Tools


Gather these tools for the project:

  • 3D printer
  • Soldering iron and solder
  • Wire cutters and strippers
  • Screwdrivers
  • Multimeter
  • Computer with Arduino IDE


Assembly

4.png
3.png
5.png
Screenshot 2024-07-21 185009.png

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

Your paragraph text.png

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)

DIY Mini RC Car: Xiao ESP32 and Custom Web Controls

We'll control our RC car using a WebSocket server and the html file. Here's how:

  1. Connect to its WIFI with the SSID "MiniRC" and password "12345678".
  2. Open your browser and go to 192.168.4.1/index.html.
  3. Send commands: Use the interface to control the car.


Demo(Using Your Wireless Nunchuck)

Mini RC controlled with modified wireless nunchuck

Using the Nunchuck

To use the Nunchuck controller

  1. Connect the Nunchuck: Attach the Nunchuck controller made using this tutorial.
  2. Power Up: Turn on the power to your mini RC car.
  3. Test the Controller:Move the joystick on the Nunchuck to control the direction and speed of the RC car.
  4. 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.