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.
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:
- ESP32 DOIT DEVKIT V1 Board
- Smart Robot Chassis Kit (or your own DIY robot chassis + 2x DC motors)
- L298N motor driver
- 1x Power bank – portable charger
- 4x 1.5 AA batteries
- 2x 100nF ceramic capacitors
- 1x SPDT Slide Switch
- Jumper wires
- Breadboard or stripboard
- Velcro tape
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.
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).
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 Pin | Motor State |
HIGH | Motor not enabled |
LOW | Motor enabled |
PWM | Motor 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.
DIRECTION | INPUT 1 | INPUT 2 | INPUT 3 | INPUT 4 |
Forward | 0 | 1 | 0 | 1 |
Backward | 1 | 0 | 1 | 0 |
Right | 0 | 1 | 0 | 0 |
Left | 0 | 0 | 0 | 1 |
Stop | 0 | 0 | 0 | 0 |
Wiring the Circuit
After assembling the robot chassis, you can wire the circuit by following the next schematic diagram.
L298N MOTOR DRIVER | ESP32 |
IN1 | GPIO 27 |
IN2 | GPIO 26 |
ENA (Enable pin for Motor A) | GPIO 14 |
IN3 | GPIO 33 |
IN4 | GPIO 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.
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.
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.
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();
}
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.
Disconnect the ESP32 from your computer and power it with the 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.
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.
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:
- ESP32-CAM Remote Controlled Car Robot Web Server
- ESP32-CAM Pan and Tilt Video Streaming Web Server (2 Axis)
- ESP32 Web Server: Control a DC Motor (Arduino IDE)
- ESP32 Servo Motor Web Server with Arduino IDE
- ESP32 Web Server: Control Stepper Motor (WebSocket)
Learn more about the ESP32 with our resources:
- Learn ESP32 with Arduino IDE (eBook)
- Build Web Servers with ESP32 and ESP8266 (eBook)
- Free ESP32 Projects and Tutorials
Thanks for reading.
Buongiorno.
Ottima esposizione.
Complimenti.
I have this error
error: ‘ledcAttach’ was not declared in this scope
ledcAttach(enable1Pin, freq, resolution);
^~~~~~~~~~
You probably have esp32 firmware 3 installed, this code seems to be for the 2. version firmware. Just go to the boards search esp32 and change the version for ESP32 by Espressif.
Hi.
No. This was tested with version 3.
Regards,
Sara
My mistake, I misunderstoond the migration guide.
https://randomnerdtutorials.com/esp32-migrating-version-2-to-3-arduino/
where is says
LEDC – Removed API
ledcSetup
ledcAttachPin
You guys do great work.
Cheers,
Hi.
Make sure you’re using ESP32 version 3 in Tools > Boards > Boards Manager > ESP32.
Regards,
Sara
Are there any plans to have blue tooth, IR or RF control? I like hand held controllers.
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.
Hi.
200 is an HTTP response code that means that the client received the request successfully.
Regards,
Sara
Understood, but what does the client do, if code 200 is not received? Eg, send the fetch() code again, or?
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??
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 ?
Hi.
That’s a great question. In fact, I haven’t thought about it previously.
Maybe it is a better solution.
Regards,
Sara
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
Hi.
You can use any output pins that are not critical. Take a look at the pinout: https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
For example: GPIOs 5, 4, 14 and 12.
Regards,
Sara
Thanks I will try that
Claude
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
Hi.
That library is installed by default. You don’t need to install it.
Just make sure you’re seleting an ESP32 board in Tools > Board before compiling.
Regards,
sara
Thank you very much. Havé a good day . JCD
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; ?