ESP32 Web Server: Control a DC Motor (Arduino IDE)

In this guide, we’ll build a web server with the ESP32 to control a DC motor remotely. The web server will serve a web page with buttons to make the motor spin forward, backward, and stop. To control the motors we’ll use the L298N motor driver and the ESP32 will be programmed using Arduino IDE.

ESP32 Web Server Control a DC Motor Arduino IDE

To better understand how this project works, we recommend taking a look at our DC motor getting started guide with the ESP32:

Prerequisites

Before proceeding with the tutorial, make sure you check the following prerequisites.

ESP32 Control DC Motor Diagram

1) Parts Required

To follow this tutorial, you need the following parts:

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!

2) Arduino IDE and ESP32 Boards Add-on

We’ll program the ESP32 using Arduino IDE. So, you must have the ESP32 add-on installed. Follow the next tutorial if you haven’t already:

If you want to use VS Code with the PlatformIO extension, follow the next tutorial instead to learn how to program the ESP32:

3) Install Libraries

To build this project, you need to install the following libraries:

The ESPAsyncWebServer and AsynTCP libraries aren’t available to install through the Arduino Library Manager. You need to click on the previous links to download the library files. Then, in your Arduino IDE, go to SketchInclude Library > Add .zip Library and select the libraries you’ve just downloaded.

4) Schematic Diagram

The following schematic diagram shows how to connect a DC motor to the ESP32 using a L298N motor driver.

Not familiar with the L298N motor driver? Follow this tutorial first: ESP32 Servo Motor Web Server with Arduino IDE.

The motor we’ll control is connected to the motor A output pins, so we need to wire the ENABLEA, INPUT1, and INPUT2 pins of the motor driver to the ESP32. Follow the next schematic diagram to wire the DC motor and the L298N motor driver to the ESP32.

Wiring mini DC motor via L298N motor driver to ESP32
LN298N Motor DriverInput 1Input 2EnableGND
ESP32GPIO 27GPIO 26GPIO 14GND

We’re using the GPIOs on the previous table to connect to the motor driver. You can use any other suitable GPIOs as long as you modify the code accordingly. Learn more about the ESP32 GPIOs: ESP32 Pinout Reference Guide.

Powering the LN298N Motor Driver

The DC motor requires a big jump in current to move, so the motors should be powered using an external power source from the ESP32. As an example, we’re using 4AA batteries, but you can use any other suitable power supply. In this configuration, you can use a power supply with 6V to 12V.

The switch between the battery holder and the motor driver is optional, but it is very handy to cut and apply power. This way you don’t need to connect and then disconnect the wires to save power constantly.

We recommend soldering a 0.1uF ceramic capacitor to the positive and negative terminals of the DC motor, as shown in the diagram to help smooth out any voltage spikes. (Note: the motors also work without the capacitor.)

Mini DC Motor with capacitor

Project Overview

The following image shows the web page you’ll build for this project.

The web page has three buttons to control the DC motor. Each button makes a request on a different URL so that the ESP32 knows which button was pressed and acts accordingly.

ESP32 Control DC Motor
  • Forward button: makes a request on the /forward URL — the motor will spin forward
  • Backward button: makes a request on the /backward URL—the motor will spin backward
  • Stop button: makes a request on the /stop URL—the motor will stop

For simplicity, we’ll save the HTML and CSS to build the web page on a variable on the Arduino sketch.

ESP32 Web Server: Control a DC Motor – Arduino Sketch

The following code creates a web server that serves a web page with three different buttons that allows you to control a DC motor remotely via your web server.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete instructions at https://RandomNerdTutorials.com/esp32-web-server-dc-motor-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 <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Motor A pins
int motor1Pin1 = 27; 
int motor1Pin2 = 26; 
int enable1Pin = 14; 

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

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

//HTML and CSS to build the web page
const char index_html[] PROGMEM = R"rawliteral(
  <!DOCTYPE html>
  <html>
    <head>
      <title>ESP IOT DASHBOARD</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="stylesheet" type="text/css" href="style.css">
      <link rel="icon" type="image/png" href="favicon.png">
      <script src="https://kit.fontawesome.com/0294e3a09e.js" crossorigin="anonymous"></script>
      <style>
      html {
      font-family: Arial, Helvetica, sans-serif;
      text-align: center;
      }
      h1 {
          font-size: 1.8rem;
          color: white;
      }

      .topnav {
          overflow: hidden;
          background-color: #0A1128;
      }
      body {
          margin: 0;
      }
      .content {
          padding: 50px;
      }
      .card-grid {
          max-width: 800px;
          margin: 0 auto;
          display: grid;
          grid-gap: 2rem;
          grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      }
      .card {
          background-color: white;
          box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
      }
      .card-title {
          font-size: 1.2rem;
          font-weight: bold;
          color: #034078
      }
      .state {
          font-size: 1.2rem;
          color:#1282A2;
      }
      button {
          border: none;
          color: #FEFCFB;
          padding: 15px 32px;
          text-align: center;
          font-size: 16px;
          width: 150px;
          border-radius: 4px;
          transition-duration: 0.4s;
      }
      .button-on {
          background-color:#034078;
      }
      .button-on:hover {
          background-color: #1282A2;
      }
      .button-off {
          background-color:#858585;
      }
      .button-off:hover {
          background-color: #252524;
      }
      .button-stop {
          background-color:#5e0f0f;
          width: 100%;
      }
      .button-stop:hover {
          background-color: #9b332c;
      }
      </style>
    </head>
    <body>
      <div class="topnav">
        <h1>CONTROL DC MOTOR</h1>
      </div>
      <div class="content">
        <div class="card-grid">
          <div class="card">
            <p class="card-title"><i class="fa-solid fa-gear"></i> DC Motor A</p>
            <p>
              <a href="forward"><button class="button-on"><i class="fa-solid fa-arrow-up"></i> FORWARD</button></a>
              <a href="backward"><button class="button-off"><i class="fa-solid fa-arrow-down"></i> BACKWARD</button></a>
            </p>
            <p>
              <a href="stop"><button class="button-stop"><i class="fa-solid fa-stop"></i> STOP</button></a>
            </p>
          </div>
        </div>
      </div>
    </body>
</html>
)rawliteral";

void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

void moveForward(){
  Serial.println("Moving Forward");
  digitalWrite(enable1Pin, HIGH);
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, HIGH);
}

void moveBackward(){
  Serial.println("Moving Backward");
  digitalWrite(enable1Pin, HIGH);
  digitalWrite(motor1Pin1, HIGH);
  digitalWrite(motor1Pin2, LOW);
}

void stopMotor(){
  Serial.print("Motor Stopped");
  digitalWrite(enable1Pin, LOW);
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, LOW);
}


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

  // Set motor pins as outputs
  pinMode(motor1Pin1, OUTPUT);
  pinMode(motor1Pin2, OUTPUT);
  pinMode(enable1Pin, OUTPUT);

  initWiFi();

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html);
  });

  server.on("/forward", HTTP_GET, [](AsyncWebServerRequest *request) {
    moveForward();
    request->send(200, "text/html", index_html);
  }); 

  server.on("/backward", HTTP_GET, [](AsyncWebServerRequest *request) {
    moveBackward();
    request->send(200, "text/html", index_html);
  });

  // Route to set GPIO state to LOW (inverted logic for ESP8266)
  server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request) {
    stopMotor();
    request->send(200, "text/html", index_html);
  });

  server.begin();
}

void loop() {
  
}

View raw code

You just need to insert your network credentials in the code and it will work straight away.

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

How the Code Works

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

Including Libraries

First, include the required libraries to connect to Wi-Fi and create the web server.

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

Motor Pins

Define the GPIOs that will be controlling the motor.

// Motor A pins
int motor1Pin1 = 27; 
int motor1Pin2 = 26; 
int enable1Pin = 14; 

Network Credentials

Insert your network credentials on the following variables so that the ESP32 connects to your network.

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

Creating a Server Object

Create an AsyncWebServer object called server on port 80.

AsyncWebServer server(80);

Web Page

The index_html variable contains the text with the HTML and CSS to build the web page.

//HTML and CSS to build the web page
const char index_html[] PROGMEM = R"rawliteral(
  <!DOCTYPE html>
  <html>
    <head>
      <title>ESP IOT DASHBOARD</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="stylesheet" type="text/css" href="style.css">
      <link rel="icon" type="image/png" href="favicon.png">
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
      <style>
      html {
      font-family: Arial, Helvetica, sans-serif;
      text-align: center;
      }
      h1 {
          font-size: 1.8rem;
          color: white;
      }

      .topnav {
          overflow: hidden;
          background-color: #0A1128;
      }
      body {
          margin: 0;
      }
      .content {
          padding: 50px;
      }
      .card-grid {
          max-width: 800px;
          margin: 0 auto;
          display: grid;
          grid-gap: 2rem;
          grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      }
      .card {
          background-color: white;
          box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
      }
      .card-title {
          font-size: 1.2rem;
          font-weight: bold;
          color: #034078
      }
      .state {
          font-size: 1.2rem;
          color:#1282A2;
      }
      button {
          border: none;
          color: #FEFCFB;
          padding: 15px 32px;
          text-align: center;
          font-size: 16px;
          width: 150px;
          border-radius: 4px;
          transition-duration: 0.4s;
      }
      .button-on {
          background-color:#034078;
      }
      .button-on:hover {
          background-color: #1282A2;
      }
      .button-off {
          background-color:#858585;
      }
      .button-off:hover {
          background-color: #252524;
      }
      .button-stop {
          background-color:#5e0f0f;
          width: 100%;
      }
      .button-stop:hover {
          background-color: #9b332c;
      }
      </style>
    </head>
    <body>
      <div class="topnav">
        <h1>CONTROL DC MOTOR</h1>
      </div>
      <div class="content">
        <div class="card-grid">
          <div class="card">
            <p class="card-title"><i class="fa-solid fa-gear"></i> DC Motor A</p>
            <p>
              <a href="forward"><button class="button-on"><i class="fa-solid fa-arrow-up"></i> FORWARD</button></a>
              <a href="backward"><button class="button-off"><i class="fa-solid fa-arrow-down"></i> BACKWARD</button></a>
            </p>
            <p>
              <a href="stop"><button class="button-stop"><i class="fa-solid fa-stop"></i> STOP</button></a>
            </p>
          </div>
        </div>
      </div>
    </body>
</html>
)rawliteral";

Here, we create three buttons that will make a request on the following URL paths:

  • FORWARD: /forward
  • BACKWARD: /backward
  • STOP: /stop
<p>
    <a href="forward"><button class="button-on"><i class="fa-solid fa-arrow-up"></i> FORWARD</button></a>
    <a href="backward"><button class="button-off"><i class="fa-solid fa-arrow-down"></i> BACKWARD</button></a>
</p>
<p>
    <a href="stop"><button class="button-stop"><i class="fa-solid fa-stop"></i> STOP</button></a>
</p>

Initialize Wi-Fi

The initWiFi() function connects to wi-fi using the SSID and password you’ve defined previously.

void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
}

Functions to Control the DC Motors

We create three functions to control the DC Motor.

The moveForward() function will move the motor forward.

void moveForward(){
  Serial.println("Moving Forward");
  digitalWrite(enable1Pin, HIGH);
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, HIGH);
}

The moveBackward() function moves the motor backward.

void moveBackward(){
  Serial.println("Moving Backward");
  digitalWrite(enable1Pin, HIGH);
  digitalWrite(motor1Pin1, HIGH);
  digitalWrite(motor1Pin2, LOW);
}

Finally, the stopMotor() function stops the motor by setting all motor pins to LOW.

void stopMotor(){
  Serial.print("Motor Stopped")
  digitalWrite(enable1Pin, LOW);
  digitalWrite(motor1Pin1, LOW);
  digitalWrite(motor1Pin2, LOW);
}

setup()

In the setup(), initialize the Serial Monitor for debugging purposes.

Serial.begin(115200);

Set the motor pins as outputs.

// Set motor pins as outputs
pinMode(motor1Pin1, OUTPUT);
pinMode(motor1Pin2, OUTPUT);
pinMode(enable1Pin, OUTPUT);

Initialize Wi-Fi by calling the initWiFi() function.

initWiFi();

Handle Requests

Then, handle the web server. When you receive a request on the root (/) URL—this happens when you access the ESP IP address—send the HTML text to build the web page:

server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
  request->send(200, "text/html", index_html);
});

When you click the Forward button, you make a request on the /forward path. When the ESP32 receives that request, it will call the moveForward() function to make the motor move forward.

server.on("/forward", HTTP_GET, [](AsyncWebServerRequest *request) {
  moveForward();
  request->send(200, "text/html", index_html);
}); 

Similarly, when you click the Backward button, the ESP32 receives a request on the /backward path. When that happens, call the moveBackward() function to make the motor move in the other direction.

server.on("/backward", HTTP_GET, [](AsyncWebServerRequest *request) {
  moveBackward();
  request->send(200, "text/html", index_html);
});

When you click the Stop button, the ESP32 receives a request on the /stop path and calls the stopMotor() function.

// Route to set GPIO state to LOW (inverted logic for ESP8266)
server.on("/stop", HTTP_GET, [](AsyncWebServerRequest *request) {
  stopMotor();
  request->send(200, "text/html", index_html);
});

Initialize the Server

Finally, call the begin() method on the server object to initialize the server.

server.begin();

Demonstration

After inserting your network credentials, upload the code to the ESP32 board. After uploading open the Serial Monitor at a baud rate of 115200 and press the ESP32 RST button.

Open Serial Monitor Arduino 2

The ESP32 IP address will be printed in the Serial Monitor.

ESP32 DC Motor Web Server IP address printed on the Serial Monitor

With the motor connected to the ESP32 via the L298N motor driver and powered up with an external power supply, open any browser on your local network and type the ESP32 IP address. The following web page will load.

ESP32 Control DC Motor via Web Server

Click on the buttons to control the DC motor.

ESP32 Control DC Motor via Web Server

The server can handle multiple clients simultaneously. You can also control the DC motor from your smartphone as long as it is connected to your local network.

ESP32 Control DC Motor via Web Server Smartphone

Wrapping Up

In this tutorial, you’ve learned how to create a simple asynchronous web server to control a DC motor remotely from your computer or smartphone. You can now extend this project to control more motors or control other outputs or even display sensor readings. You can take a look at the following tutorials:

If you would like to learn more about building web servers with the ESP32 from scratch, we recommend taking a look at our exclusive eBook:

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!

6 thoughts on “ESP32 Web Server: Control a DC Motor (Arduino IDE)”

  1. It works well with L298N up to 12v but then there is a problem with the BTS7960 the 3.3v outputs are too weak and you need CrusS0108E it seems

    Reply
  2. Hi,
    My problem is with AsyncWebServer library. Have tried lots of suggestions
    off the web, no luck. here is the results readout.
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp: In function ‘bool getMD5(uint8_t*, uint16_t, char)’:
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:74:3: error: ‘mbedtls_md5_starts_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_starts’?
    74 | mbedtls_md5_starts_ret(&_ctx);
    | ^~~~~~~~~~~~~~~~~~~~~~
    | mbedtls_md5_starts
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:75:3: error: ‘mbedtls_md5_update_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_update’?
    75 | mbedtls_md5_update_ret(&_ctx, data, len);
    | ^~~~~~~~~~~~~~~~~~~~~~
    | mbedtls_md5_update
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\WebAuthentication.cpp:76:3: error: ‘mbedtls_md5_finish_ret’ was not declared in this scope; did you mean ‘mbedtls_md5_finish’?
    76 | mbedtls_md5_finish_ret(&_ctx, _buf);
    | ^~~~~~~~~~~~~~~~~~~~~~
    | mbedtls_md5_finish
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\AsyncEventSource.cpp: In member function ‘void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage
    )’:
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\AsyncEventSource.cpp:189:7: error: ‘ets_printf’ was not declared in this scope; did you mean ‘vswprintf’?
    189 | ets_printf(“ERROR: Too many messages queued\n”);
    | ^~~~~~~~~~
    | vswprintf
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\AsyncWebSocket.cpp: In member function ‘void AsyncWebSocketClient::_queueMessage(AsyncWebSocketMessage*)’:
    C:\Users\Ross\Documents\Arduino\libraries\ESPAsyncWebServer\src\AsyncWebSocket.cpp:549:7: error: ‘ets_printf’ was not declared in this scope; did you mean ‘vswprintf’?
    549 | ets_printf(“ERROR: Too many messages queued\n”);
    | ^~~~~~~~~~
    | vswprintf

    exit status 1

    Compilation error: exit status 1

    Reply
    • Hi.
      Make sure you install the libraries as we mention in the tutorial.
      Delete any libraries with the same name you may have installed previously.
      Once you have installed the libraries via .ZIP file, update them to the latest version in the Arduino IDe.
      Regards,
      Sara

      Reply
  3. Great tutorial and works perfectly for my actuator! However, I’ve been trying to add a delay of 30 seconds after selecting forward/backward movement, so it disables the outputs after the time period (essentially sending the stopMotor command automatically):

    void moveForward(){
    Serial.println(“Moving Forward”);
    digitalWrite(enable1Pin, HIGH);
    digitalWrite(motor1Pin1, LOW);
    digitalWrite(motor1Pin2, HIGH);
    delay(30000);
    digitalWrite(enable1Pin, LOW);
    digitalWrite(motor1Pin1, LOW);
    digitalWrite(motor1Pin2, LOW);
    }

    It does not seem to change the pin states after the time period though.

    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.