ESP32 Remote-Controlled Wi-Fi Car Robot (Arduino IDE)

In this project, you’ll learn how to build an ESP32 Wi-Fi remote controlled car robot step by step. You’ll control the robot using a web server to make the robot move forward, backward, right, left, and stop. There’s also an option to control the speed of the robot. The ESP32 will be programmed using Arduino IDE.

ESP32 Remote-Controlled Wi-Fi Car Robot

Prerequisites

Before proceeding, make sure to check the following prerequisites.

1. Arduino IDE

The ESP32 will be programmed using Arduino IDE. So, make sure you have the ESP32 boards installed. You can follow the next tutorial as a reference:

2. Parts required:

Here’s a list of the parts required to build the ESP32 Wi-Fi remote-controlled car robot:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

3. DC Motors and the LN298 Motor Driver

To build the robot, we’ll use two DC motors controlled via a LN298 motor driver. Get familiar with controlling the speed and direction of a DC motor with the ESP32:

4. The Robot Chassis Kit

We’ll use the Smart Robot Chassis Kit. For instructions on how to assemble it, check the following tutorial:

Project Overview

Before starting the project, we’ll highlight the most important features and components to create the project.

Wi-Fi

The robot will be controlled via Wi-Fi using your ESP32. We’ll create a web-based interface to control the robot that can be accessed on any device inside your local network.

Robot Controls

The web server has five control options: forward, reverse, right, left, and stop. There’s also a slider to control the speed.

Smart Robot Chassis Kit

We’re going to use the robot chassis kit shown in the figure below. That is the Smart Robot Chassis Kit. You can find it in most online stores. The kit costs around $10, and it’s easy to assemble. You can use any other chassis kit as long as it comes with two DC motors.

L298N Motor Driver 

There are many ways to control DC motors. We’ll use the L298N motor driver that provides an easy way to control the speed and direction of 2 DC motors.

298N Motor Driver

Power

The motors draw a lot of current, so you need to use an external power supply. This means you need two different power sources. One will power the DC motors, and the other will power the ESP32. We’ll power the ESP32 using a small portable charger (like the ones used to charge your smartphone).

portable charger

The motors will be powered using 4 AA 1.5V batteries. You might consider using rechargeable batteries or any other suitable power supply.

Control DC motors with the L298N

To get familiar with the L298N motor driver, we recommend reading the following tutorial:

Let’s take a quick look at how to use the L298N motor driver to control the robot’s speed and direction.

The Enable Pins

The enable pins are like an ON and OFF switch for your motors. For example:

If you send a HIGH signal to enable 1 pin, motor A is ready to be controlled and at the maximum speed. If you send a LOW signal to the enable 1 pin, motor A turns off.

If you send a PWM signal, you can control the speed of the motor. The motor speed is proportional to the duty cycle. However, note that for small duty cycles, the motors might not spin, and make a continuous buzz sound.

Signal Sent to the Enable PinMotor State
HIGHMotor not enabled
LOWMotor enabled
PWMMotor enabled: speed proportional to the duty cycle

The Input Pins

The input pins control the direction the motors are spinning. Input 1 and input 2 control motor A, and input 3 and 4 control motor B.

If you apply LOW to input1 and HIGH to input 2, the motor will spin forward. If you apply power the other way around: HIGH to input 1 and LOW to input 2, the motor will rotate backwards. Motor B is controlled using the same method.

So, if you want your robot to move forward, both motors should be rotating forward. To make it go backward, both should be rotating backward.

To turn the robot in one direction, you need to spin the opposite motor faster. For example, to make the robot turn right, we’ll enable the motor at the left, and disable the motor at the right.

DIRECTIONINPUT 1INPUT 2INPUT 3INPUT 4
Forward0101
Backward1010
Right 0100
Left0001
Stop0000

Wiring the Circuit

After assembling the robot chassis, you can wire the circuit by following the next schematic diagram.

ESP32 Circuit to Control Two DC Motors Robot
L298N MOTOR DRIVERESP32
IN1GPIO 27
IN2GPIO 26
ENA (Enable pin for Motor A)GPIO 14
IN3GPIO 33
IN4GPIO 25
ENB (Enable pin for Motor B)GPIO 32

Start by connecting the ESP32 to the motor driver as shown in the schematic diagram. You can either use a mini breadboard or a stripboard to place your ESP32 and build the circuit.

ESP32 Car Robot - ESP32 on a chassis kit

After that, wire each motor to their terminal blocks. We recommend soldering a 0.1 uF ceramic capacitor to each motor’s positive and negative terminals, as shown in the diagram, to help smooth out any voltage spikes.

Additionally, you can solder a slider switch to the red wire that comes from the battery pack. This way, you can turn the power to the motors and motor driver on and off. Finally, power the motors by connecting a 4 AA battery pack to the motor driver power blocks. Since you want your robot to be portable, the ESP32 will be powered using a portable charger. You can attach the portable charger to the robot chassis using velcro, for example.

ESP32 Car Robot - ESP32 on a chassis kit

Don’t connect the power bank yet, because first, you need to upload the code to the ESP32.

Your robot should look similar to the following figure:

Building the Web Server

Let’s now create the web server. The following figure shows the web server we’ll build. You have five controls to move the robot forward, reverse, right, left, and stop. You also have a slider to control the speed. You can select 0, 25, 50, 75, or 100% to control the motor speed.

ESP32 Web Server to Control Robot

Arduino Code – ESP32 Remote-Controlled Wi-Fi Car Robot

To build the web server, we’ll use the Webserver.h library that is automatically installed when you install the ESP32 boards in Arduino IDE.

/*  
  Rui Santos & Sara Santos - Random Nerd Tutorials
  https://RandomNerdTutorials.com/esp32-wi-fi-car-robot-arduino/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/

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

// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Create an instance of the WebServer on port 80
WebServer server(80);

// Motor 1
int motor1Pin1 = 27; 
int motor1Pin2 = 26; 
int enable1Pin = 14;

// Motor 2
int motor2Pin1 = 33; 
int motor2Pin2 = 25; 
int enable2Pin = 32;

// Setting PWM properties
const int freq = 30000;
const int resolution = 8;
int dutyCycle = 0;

String valueString = String(0);

void handleRoot() {
  const char html[] PROGMEM = R"rawliteral(
  <!DOCTYPE HTML><html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="data:,">
    <style>
      html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center; }
      .button { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #4CAF50; border: none; color: white; padding: 12px 28px; text-decoration: none; font-size: 26px; margin: 1px; cursor: pointer; }
      .button2 {background-color: #555555;}
    </style>
    <script>
      function moveForward() { fetch('/forward'); }
      function moveLeft() { fetch('/left'); }
      function stopRobot() { fetch('/stop'); }
      function moveRight() { fetch('/right'); }
      function moveReverse() { fetch('/reverse'); }

      function updateMotorSpeed(pos) {
        document.getElementById('motorSpeed').innerHTML = pos;
        fetch(`/speed?value=${pos}`);
      }
    </script>
  </head>
  <body>
    <h1>ESP32 Motor Control</h1>
    <p><button class="button" onclick="moveForward()">FORWARD</button></p>
    <div style="clear: both;">
      <p>
        <button class="button" onclick="moveLeft()">LEFT</button>
        <button class="button button2" onclick="stopRobot()">STOP</button>
        <button class="button" onclick="moveRight()">RIGHT</button>
      </p>
    </div>
    <p><button class="button" onclick="moveReverse()">REVERSE</button></p>
    <p>Motor Speed: <span id="motorSpeed">0</span></p>
    <input type="range" min="0" max="100" step="25" id="motorSlider" oninput="updateMotorSpeed(this.value)" value="0"/>
  </body>
  </html>)rawliteral";
  server.send(200, "text/html", html);
}

void handleForward() {
  Serial.println("Forward");
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, HIGH); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, HIGH);
  server.send(200);
}

void handleLeft() {
  Serial.println("Left");
  digitalWrite(motor1Pin1, LOW); 
  digitalWrite(motor1Pin2, LOW); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, HIGH);
  server.send(200);
}

void handleStop() {
  Serial.println("Stop");
  digitalWrite(motor1Pin1, LOW); 
  digitalWrite(motor1Pin2, LOW); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, LOW);   
  server.send(200);
}

void handleRight() {
  Serial.println("Right");
  digitalWrite(motor1Pin1, LOW); 
  digitalWrite(motor1Pin2, HIGH); 
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, LOW);    
  server.send(200);
}

void handleReverse() {
  Serial.println("Reverse");
  digitalWrite(motor1Pin1, HIGH);
  digitalWrite(motor1Pin2, LOW); 
  digitalWrite(motor2Pin1, HIGH);
  digitalWrite(motor2Pin2, LOW);          
  server.send(200);
}

void handleSpeed() {
  if (server.hasArg("value")) {
    valueString = server.arg("value");
    int value = valueString.toInt();
    if (value == 0) {
      ledcWrite(enable1Pin, 0);
      ledcWrite(enable2Pin, 0);
      digitalWrite(motor1Pin1, LOW); 
      digitalWrite(motor1Pin2, LOW); 
      digitalWrite(motor2Pin1, LOW);
      digitalWrite(motor2Pin2, LOW);   
    } else { 
      dutyCycle = map(value, 25, 100, 200, 255);
      ledcWrite(enable1Pin, dutyCycle);
      ledcWrite(enable2Pin, dutyCycle);
      Serial.println("Motor speed set to " + String(value));
    }
  }
  server.send(200);
}

void setup() {
  Serial.begin(115200);

  // Set the Motor pins as outputs
  pinMode(motor1Pin1, OUTPUT);
  pinMode(motor1Pin2, OUTPUT);
  pinMode(motor2Pin1, OUTPUT);
  pinMode(motor2Pin2, OUTPUT);

  // Configure PWM Pins
  ledcAttach(enable1Pin, freq, resolution);
  ledcAttach(enable2Pin, freq, resolution);
    
  // Initialize PWM with 0 duty cycle
  ledcWrite(enable1Pin, 0);
  ledcWrite(enable2Pin, 0);
  
  // Connect to Wi-Fi
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Define routes
  server.on("/", handleRoot);
  server.on("/forward", handleForward);
  server.on("/left", handleLeft);
  server.on("/stop", handleStop);
  server.on("/right", handleRight);
  server.on("/reverse", handleReverse);
  server.on("/speed", handleSpeed);

  // Start the server
  server.begin();
}

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

View raw code

How Does the Code Work?

Continue reading to learn how the code works, or skip to the Demonstration section.

Including Libraries

Start by including the required libraries. We’re using the WiFi.h library to handle internet connection and the WebServer.h library to build the web server. Both libraries are included by default.

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

Web Server Instance

Create a web server instance of the WebServer library on port 80 called server.

// Create an instance of the WebServer on port 80
WebServer server(80);

Setting your Network Credentials

Start by typing your network credentials in the following variables so that the ESP32 can connect to your local network.

const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = " REPLACE_WITH_YOUR_PASSWORD";

Creating Variables for the Motor Driver Pins

Next, create variables for the motor input pins, and the motor enable pins. These pins are connected to the L298N motor driver, which controls the direction and speed of the motors.

// Motor 1
int motor1Pin1 = 27;
int motor1Pin2 = 26;
int enable1Pin = 14;
// Motor 2
int motor2Pin1 = 33;
int motor2Pin2 = 25;
int enable2Pin = 32;

Setting PWM Properties

To control the speed of the motors, you’ll send PWM signals to the enable pins. Define the PWM settings as follows:

// Setting PWM properties
const int freq = 30000;
const int resolution = 8;
int dutyCycle = 0;

Recommended reading: ESP32 PWM with Arduino IDE (Analog Output)

Slider Value

Create a variable called valueString to hold the slider value to control the speed of the motor.

String valueString = String(0);

setup()

In the setup(), you set the motor pins as outputs:

// Set the Motor pins as outputs
pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin2, OUTPUT);
pinMode(motor2Pin1, OUTPUT);
pinMode(motor2Pin2, OUTPUT);

Configure the enable pins as PWM outputs with the PWM properties defined earlier.

// Configure PWM Pins
ledcAttach(enable1Pin, freq, resolution);
ledcAttach(enable2Pin, freq, resolution);

Generate the PWM signal with a defined duty cycle using the ledcWrite() function. When the code first starts, we want the motors to be off by default, so we’re sending 0% duty cycle.

// Initialize PWM with 0 duty cycle
ledcWrite(enable1Pin, 0);
ledcWrite(enable2Pin, 0);

The following code in the setup() connects your ESP32 to your local network and prints the IP address in the Serial Monitor.

// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();

Controlling the Robot

Each motor action, such as moving forward, turning left, stopping, etc., is handled by specific routes. On the web server, when you click on the buttons, it will make different requests on different routes.

// Define routes
server.on("/", handleRoot);
server.on("/forward", handleForward);
server.on("/left", handleLeft);
server.on("/stop", handleStop);
server.on("/right", handleRight);
server.on("/reverse", handleReverse);
server.on("/speed", handleSpeed);

The handleForward(), handleLeft(), handleStop(), handleRight() and handleReverse() functions will turn the appropriate GPIOs on and off to achieve the desired result.

void handleForward() {
  Serial.println("Forward");
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, HIGH);
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, HIGH);
  server.send(200);
}
void handleLeft() {
  Serial.println("Left");
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, LOW);
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, HIGH);
  server.send(200);
}
void handleStop() {
  Serial.println("Stop");
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, LOW);
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, LOW);  
  server.send(200);
}
void handleRight() {
  Serial.println("Right");
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, HIGH);
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, LOW);   
  server.send(200);
}

void handleReverse() {
  Serial.println("Reverse");
  digitalWrite(motor1Pin1, HIGH);
  digitalWrite(motor1Pin2, LOW);
  digitalWrite(motor2Pin1, HIGH);
  digitalWrite(motor2Pin2, LOW);         

  server.send(200);
}

The combination of HIGH and LOW signals required to move the robot in a specific direction was explained previously.

Controlling the Speed

To control the motor speed using a slider, the slider’s value is sent to the server, which adjusts the PWM duty cycle accordingly.

This is the part of the javascript code that sends a request to the server with the current slider value when you move the slider to a new position:

function updateMotorSpeed(pos) {
  document.getElementById('motorSpeed').innerHTML = pos;
  fetch(`/speed?value=${pos}`);
}

It will make a request with the following format:

/speed?value=SELECTED_SPEED_VALUE

On the ESP32, on the handleSpeed() function, we start by getting the value of the slider and saving it on the value variable as follows.

void handleSpeed() {
  if (server.hasArg("value")) {
    valueString = server.arg("value");
    int value = valueString.toInt();

If the slider value is zero, the motors are stopped. So, we set the duty cycle to 0 and all motor pins to LOW.

if (value == 0) {
  ledcWrite(enable1Pin, 0);
  ledcWrite(enable2Pin, 0);
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, LOW);
  digitalWrite(motor2Pin1, LOW);
  digitalWrite(motor2Pin2, LOW);  
}

Otherwise, we set the speed of the motors by adjusting the duty cycle taking into account the slider value.

} else {
  dutyCycle = map(value, 25, 100, 200, 255);
  ledcWrite(enable1Pin, dutyCycle);
  ledcWrite(enable2Pin, dutyCycle);
  Serial.println("Motor speed set to " + String(value));
}

We calculate the duty cycle based on the slider value using the map()function (learn more about the Arduino map() function). In this case, we set the duty cycle to start at 200 because lower values won’t make the robot move, and the motors will make a weird buzz sound, you may need to adjust depending on the behavior of your motors.

Displaying the web page

The following part of the code displays a web page with 5 buttons to control the motor and a slider to set the motor speed. We use the javascript fetch() function to make requests when a button is clicked on.

void handleRoot() {
  const char html[] PROGMEM = R"rawliteral(
  <!DOCTYPE HTML><html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="data:,">
    <style>
      html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center; }
      .button { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #4CAF50; border: none; color: white; padding: 12px 28px; text-decoration: none; font-size: 26px; margin: 1px; cursor: pointer; }
      .button2 {background-color: #555555;}
    </style>
    <script>
      function moveForward() { fetch('/forward'); }
      function moveLeft() { fetch('/left'); }
      function stopRobot() { fetch('/stop'); }
      function moveRight() { fetch('/right'); }
      function moveReverse() { fetch('/reverse'); }

      function updateMotorSpeed(pos) {
        document.getElementById('motorSpeed').innerHTML = pos;
        fetch(`/speed?value=${pos}`);
      }
    </script>
  </head>
  <body>
    <h1>ESP32 Motor Control</h1>
    <p><button class="button" onclick="moveForward()">FORWARD</button></p>
    <div style="clear: both;">
      <p>
        <button class="button" onclick="moveLeft()">LEFT</button>
        <button class="button button2" onclick="stopRobot()">STOP</button>
        <button class="button" onclick="moveRight()">RIGHT</button>
      </p>
    </div>
    <p><button class="button" onclick="moveReverse()">REVERSE</button></p>
    <p>Motor Speed: <span id="motorSpeed">0</span></p>
    <input type="range" min="0" max="100" step="25" id="motorSlider" oninput="updateMotorSpeed(this.value)" value="0"/>
  </body>
  </html>)rawliteral";
  server.send(200, "text/html", html);
}

loop()

The following line in the loop() ensures that the ESP32 is always listening for incoming client requests.

server.handleClient();

Testing the Web Server

Now, you can test the web server. Make sure you’ve modified the code to include your network credentials. Don’t forget to check if you have the right board and COM port selected. Then, click the upload button. When the upload is finished, open the Serial Monitor at a baud rate of 115200.

Press the ESP32 Enable (EN) button, and you’ll get the ESP32 IP address.

Getting ESP32 Web Server IP address

Disconnect the ESP32 from your computer and power it with the portable charger.

powering ESP32 robot with portable charger

Make sure you also have the 4 AA batteries in place, and the slider switch turned on. Open your browser, and paste the ESP32 IP address to access the web server. You can use any device with a browser inside your local network to control the robot.

Now, you can control your robot.

ESP32 Wi-Fi Car Robot remote controlled demonstration

Important: If the motors are spinning in the wrong direction, you can simply switch the motor wires. For example, switch the wire that goes to OUT1 with OUT2. Or OUT3 with OUT4. That should solve your problem.

Demonstration

Congratulations! The ESP32 Wi-Fi Remote Controlled Car Robot is complete! You can make your robot go forward, backward, right, and left. You can stop it by tapping the STOP button. A cool feature of this robot is that you can also adjust the speed with the slider.

The robot works pretty well and responds instantaneously to the commands.

ESP32 Wi-Fi Controlled Car Robot

Wrapping Up

Now, we encourage you to modify the robot with some upgrades. For example:

  • Add an RGB LED that changes color depending on the direction the robot is moving;
  • Add an ultrasonic sensor that makes the robot stop when it senses  an obstacle;

We hope you had fun building the robot. Here are other similar projects you may like:

Learn more about the ESP32 with our resources:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

20 thoughts on “ESP32 Remote-Controlled Wi-Fi Car Robot (Arduino IDE)”

  1. Hello Sarah and Rui, another great tutorial, they always work, :-).

    One thing I am not sure of is, exactly what effect server.send(200); has on the client end. Could you illuminate? Thanks.

    Reply
  2. I have a question regarding wifi when I drive car from 1 mesh wifi access point to another mesh wifi access point Esp32 disconnect to AP1 when the wifi signal is very poor. Do you have ideas how can I achieve seamless roaming using esp32??

    Reply
  3. Sara, Rui, Nice tutorial, I’m going to buy the parts in the next days. Why are you connecting the power bank with the (low power consume) ESP32 and the batteries with the (high power consume) motors and not vice versa ?

    Reply
    • Hi.
      That’s a great question. In fact, I haven’t thought about it previously.
      Maybe it is a better solution.
      Regards,
      Sara

      Reply
  4. I ran out of ESP32 but I have 2 ESP8266, 30 pins NodeMCU (ESP-12E)
    Could you indicate which pin I should use using the same sketch with a 2 wheel robot car?
    Thank you

    Reply
  5. Bonjour Sara, Rui,
    Pourriez vous svp me donner le lien pour obtenir la bibliothèque WebServer.h . En vous remerciant. Bonne soirée. Jean-Charles

    Reply
  6. I built this project, but have a problem with GPIO25. The code never changes it’s state, in circuit or out. It is a DAC, but I know there has to be a register function to change it’s use. What code can I add to your sketch to make pin 25 function with the int motor2Pin2 = 25; ?

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.