ESP32 Web Server: Control Stepper Motor (HTML Form)

In this tutorial, you’ll learn how to create a web server with the ESP32 to control a stepper motor remotely. The web server displays a web page with an HTML form that allows you to select the direction and number of steps you want the motor to move.

Control Stepper Motor with ESP32 Web Server HTML Form Arduino IDE

Table of Contents

  1. Control Stepper Motor with HTML Form (minimal setup)
  2. Control Stepper Motor with HTML Form + CSS (using LittleFS)
  3. Control Stepper Motor with WebSockets (HTML, CSS, JavaScript)

In the picture below, you can see the three web server projects we’ll build (number 3 is in this post).

Control Stepper Motor with ESP32 Web Server HTML Form Arduino IDE

This is a didactic tutorial where you’ll learn more about creating web pages and interaction between the ESP32 and the client. We’ll show you how to create the web page step-by-step with HTML and send the form results to the ESP32 via HTTP POST to control the stepper motor.

Later, you’ll add some CSS to style the web page to improve its look.

Finally, we’ll show you how to use Websockets for bidirectional communication between the server and the client. This will allow us to know on the web interface whether the motor is spinning or stopped. This section will add some JavaScript to handle WebSocket communication and add some cool animations to the web page.

The following articles might be useful to understand the concepts covered throughout this tutorial:

Prerequisites

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

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) Filesystem Uploader Plugin

To upload the HTML, CSS, and JavaScript files needed to build this project to the ESP32 filesystem (LittleFS), we’ll use a plugin for Arduino IDE: LittleFS Filesystem uploader. Follow the next tutorial to install the filesystem uploader plugin if you haven’t already:

If you’re using VS Code with the PlatformIO extension, read the following tutorial to learn how to upload files to the filesystem:

4) 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.

Installing Libraries (VS Code + PlatformIO)

If you’re programming the ESP32 using PlatformIO, you should add the following lines to the platformio.ini file to include the libraries and set the default filesystem to LittleFS (also change the Serial Monitor speed to 115200):

monitor_speed=115200
lib_deps = ESP Async WebServer
    arduino-libraries/Arduino_JSON @ 0.1.0
    arduino-libraries/Stepper @ ^1.1.3
board_build.filesystem = littlefs

5) Schematic Diagram

The following schematic diagram shows the connections between the stepper motor and the ESP32.

ESP32 with Stepper Motor 28BYJ-48 and ULN2003A Schematic Diagram Wiring

Note: You should power the motor driver using an external 5V power supply.

Motor DriverESP32
IN1GPIO 19
IN2GPIO 18
IN3GPIO 5
IN4GPIO 17

1. Control Stepper Motor with HTML Form

In this section, you’ll learn how to create a simple HTML form to control the stepper motor.

ESP32 ESP8266 Stepper Motor Control Web Server HTML Form

Here’s how it works:

  • On the web page, you can select whether you want the motor to turn Clockwise or Counterclockwise. Those are radio buttons. Radio buttons are usually displayed as small circles, which are filled or highlighted when selected. You can only select one radio button in a given group at a time.
  • There is an input field of type number where the user can enter a number—in this case, the number of steps.
  • Finally, a button called GO! of type submit sends the data to the server via an HTTP POST request.

HTML Form and Input Fields

In this section, we’ll take a look at the HTML to build the form.

In HTML, the <form> tag is used to create an HTML form to collect user input. The user input is then sent to the server (ESP32 or ESP8266) for processing. Based on the values collected on the form, your ESP board may perform different actions—in this case, spin the motor a determined number of steps.

Here’s the HTML we’ll use for this project.

<!DOCTYPE html>
<html>
<head>
  <title>Stepper Motor</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <h1>Stepper Motor Control</h1>
    <form action="/" method="POST">

      <input type="radio" name="direction" value="CW" checked>
      <label for="CW">Clockwise</label>

      <input type="radio" name="direction" value="CCW">
      <label for="CW">Counterclockwise</label><br><br><br>

      <label for="steps">Number of steps:</label>
      <input type="number" name="steps">

      <input type="submit" value="GO!">

    </form>
</body>
</html>

An HTML form contains different form elements. All form elements are enclosed inside the <form> tag. It contains controls <input> (radio buttons and number input field) and labels for those controls (<label>).

Additionally, the <form> tag must include the action attribute that specifies what you want to do when the form is submitted. In our case, we want to send that data to the server (ESP32/ESP8266) when the user clicks the submit button. The method attribute specifies the HTTP method (GET or POST) used when submitting the form data.

<form action="/" method="POST">

POST is used to send data to a server to create/update a resource. The data sent to the server with POST is stored in the body of the HTTP request.

Radio Buttons

HTML Radio Buttons Control Stepper Motor Web Server ESP32 ESP8266

A radio button is defined as follows:

<input type="radio">

For our project, we need two radio buttons, and only one can be selected at a time. So, we can create a group of radio buttons. To do that, the radio buttons must share the same name (the value of the name attribute—in this case direction).

<input type="radio" name="direction">

Finally, we also need the value attribute that specifies a unique value for each radio button. This value is not visible to the user, but it is sent to the server when you click on the submit button to identify which button was selected.

In our example, we created one radio button with the value CW (to select clockwise) and another CCW (to select counterclockwise).

<input type="radio" name="direction" value="CW">
<input type="radio" name="direction" value="CCW">

Finally, if you want one radio button to be selected by default, you can add the keyword checked. In our example, the clockwise direction is selected by default.

<input type="radio" name="direction" value="CW" checked>

So, this is how the radio buttons and corresponding labels look like:

<input type="radio" name="direction" value="CW" checked>
<label for="CW">Clockwise</label>

<input type="radio" name="direction" value="CCW">
<label for="CW">Counterclockwise</label><br><br><br>

Input Field

Number Input Field HTML Form Stepper Motor Web Server ESP32 ESP8266

Finally, we also need an input field where the user enters the number of steps—an input field of type number. The name attribute allows us to determine in which input field the user entered the data.

<input type="number" name="steps">

Submit Button

To complete the form, we need a submit button. A submit button is an input of type submit. When you click this button, the form’s data is sent to the server (the ESP32 or ESP8266 boards). The value attribute specifies the text to display on the button.

<input type="submit" value="GO!">

For example, if you select the clockwise direction and enter 2000 steps, the client will make the following request to the ESP:

POST /
Host: localhost
direction=CW&steps=2000

The ESP receives this request and can get the direction and number of steps from the body of the request.

ESP32 ESP8266 Web-Server Control Stepper HTML Form How it Works

Code

Now that you know how to create the HTML form, let’s go through the Arduino code.

The HTML text can be saved on an HTML file saved on the ESP32 filesystem (LittleFS) or it can be saved in a variable in your Arduino sketch.

Because the HTML text for this example is relatively simple and we don’t have CSS or JavaScript files, we’ll save the HTML text as a variable (index_html).

Here’s the code to build the web server (insert your network credentials and the code will work straight away).

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/stepper-motor-esp32-web-server/
  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>
#include <Stepper.h>

// Stepper Motor Settings
const int stepsPerRevolution = 2048;  // change this to fit the number of steps per revolution
#define IN1 19
#define IN2 18
#define IN3 5
#define IN4 17
Stepper myStepper(stepsPerRevolution, IN1, IN3, IN2, IN4);

// 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);

// Search for parameters in HTTP POST request
const char* PARAM_INPUT_1 = "direction";
const char* PARAM_INPUT_2 = "steps";

// Variables to save values from HTML form
String direction;
String steps;

// Variable to detect whether a new request occurred
bool newRequest = false;

// HTML to build the web page
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <title>Stepper Motor</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <h1>Stepper Motor Control</h1>
    <form action="/" method="POST">
      <input type="radio" name="direction" value="CW" checked>
      <label for="CW">Clockwise</label>
      <input type="radio" name="direction" value="CCW">
      <label for="CW">Counterclockwise</label><br><br><br>
      <label for="steps">Number of steps:</label>
      <input type="number" name="steps">
      <input type="submit" value="GO!">
    </form>
</body>
</html>
)rawliteral";

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


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

  initWiFi();

  myStepper.setSpeed(5);

  // Web Server Root URL
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html);
  });
  
  // Handle request (form)
  server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
    int params = request->params();
    for(int i=0;i<params;i++){
      const AsyncWebParameter* p = request->getParam(i);
      if(p->isPost()){
        // HTTP POST input1 value (direction)
        if (p->name() == PARAM_INPUT_1) {
          direction = p->value().c_str();
          Serial.print("Direction set to: ");
          Serial.println(direction);
        }
        // HTTP POST input2 value (steps)
        if (p->name() == PARAM_INPUT_2) {
          steps = p->value().c_str();
          Serial.print("Number of steps set to: ");
          Serial.println(steps);
        }
      }
    }
    request->send(200, "text/html", index_html);
    newRequest = true;
  });

  server.begin();
}

void loop() {
  // Check if there was a new request and move the stepper accordingly
  if (newRequest){
    if (direction == "CW"){
      // Spin the stepper clockwise direction
      myStepper.step(steps.toInt());
    }
    else{
      // Spin the stepper counterclockwise direction
      myStepper.step(-steps.toInt());
    }
    newRequest = false;
  }
}

View raw code

How the Code Works

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

Include Libraries

First, include the required libraries. The WiFi, AsyncTCP, and ESPAsyncWebServer to create the web server and the Stepper library to control the stepper motor.

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

Stepper Motor Pins and Steps per Revolution

Define the steps per revolution of your stepper motor—in our case, it’s 2048:

const int stepsPerRevolution = 2048;  // change this to fit the number of steps per revolution

Define the motor input pins. In this example, we’re connecting to GPIOs 19, 18, 5, and 17, but you can use any other suitable GPIOs.

#define IN1 19
#define IN2 18
#define IN3 5
#define IN4 17

Initialize an instance of the stepper library called myStepper. Pass as arguments the steps per revolution and the input pins. In the case of the 28BYJ-48 stepper motor, the order of the pins is IN1, IN3, IN2, IN4—it might be different for your motor.

Stepper myStepper(stepsPerRevolution, IN1, IN3, IN2, IN4);

Network Credentials

Insert your network credentials in the following lines.

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

Create an AsyncWebServer object called server on port 80.

AsyncWebServer server(80);

Initializing Variables

The PARAM_INPUT_1 and PARAM_INPUT_2 variables will be used to search for parameters in the HTTP POST request. Remember that it contains the direction and number of steps.

// Search for parameters in HTTP POST request
const char* PARAM_INPUT_1 = "direction";
const char* PARAM_INPUT_2 = "steps";

The following variables will save the direction and number of steps.

String direction;
String steps;

The newRequest variable will be used to check whether a new request occurred. Then, in the loop(), we’ll spin the motor when a new request is received—when the newRequest variable is true.

bool newRequest = false;

HTML Form

The index_html variable saves the HTML text to build the web page—we’ve seen previously how the HTML to build the web page with the form works.

// HTML to build the web page
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <title>Stepper Motor</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <h1>Stepper Motor Control</h1>
    <form action="/" method="POST">
      <input type="radio" name="direction" value="CW" checked>
      <label for="CW">Clockwise</label>
      <input type="radio" name="direction" value="CCW">
      <label for="CW">Counterclockwise</label><br><br><br>
      <label for="steps">Number of steps:</label>
      <input type="number" name="steps">
      <input type="submit" value="GO!">
    </form>
</body>
</html>
)rawliteral";

initWiFi()

The initWiFi() function initializes WiFi.

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

In this example, the ESP is set as a Wi-Fi station (it connects to your router). If you don’t have a router nearby, you can set your board as an Access Point. You can read the next tutorial to learn how to set the board as an access point:

setup()

In the setup(), initialize the Serial Monitor.

Serial.begin(115200);

Call the initWiFi() function to initialize WiFi.

initWiFi();

And set the stepper motor speed in rpm.

myStepper.setSpeed(5);

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);
});

Then, you need to handle what happens when the ESP receives a POST request with the form details.

server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {

First, we search for parameters in the HTTP POST request:

int params = request->params();
for(int i=0;i<params;i++){
  AsyncWebParameter* p = request->getParam(i);
  if(p->isPost()){

If one of the parameters is equal to PARAM_INPUT_1, we know its value contains the direction of the motor. If that’s the case, we get the value of that parameter and save it in the direction variable.

if (p->name() == PARAM_INPUT_1) {
  direction = p->value().c_str();
  Serial.print("Direction set to: ");
  Serial.println(direction);
}

We follow a similar procedure for PARAM_INPUT_2, but we save the value in the steps variable.

if (p->name() == PARAM_INPUT_2) {
  steps = p->value().c_str();
  Serial.print("Number of steps set to: ");
  Serial.println(steps);
}

Finally, we respond with the content of the HTML page—it will reload the page.

request->send(200, "text/html", index_html);

After this, we set the newRequest variable to true, so that it spins the motor in the loop().

newRequest = true;

loop()

Let’s take a look at the loop() section.

If the newRequest variable is true, we’ll check what’s the spinning direction: CW or CCW. If it is CW, we move the motor the number of steps saved in the steps variable using the step() method on the myStepper object.

To move the motor counterclockwise, we just need to pass the number of steps but with a minus sign.

if (direction == "CW"){
  // Spin the stepper clockwise direction
  myStepper.step(steps.toInt());
}
else{
  // Spin the stepper counterclockwise direction
  myStepper.step(-steps.toInt());
}

After spinning the motor, set the newRequest variable to false, so that it can detect new requests again.

newRequest = false;

Note: because the Stepper.h is not an asynchronous library, it won’t do anything else until the motor has stopped spinning. So, if you try to make new requests while the motor is spinning, it will not work. We’ll build an example using WebSocket protocol that will allow us to know on the web interface whether the motor is spinning or not—you can check that tutorial here.

Demonstration

After inserting your network credentials, you can upload the code to your board.

After uploading, open the Serial Monitor at a baud rate of 115200 and press the on-board RESET button. The ESP IP address will be displayed.

Open a browser on your local network and insert the ESP IP address. You’ll get access to the HTML form to control the stepper motor.

ESP32 ESP8266 Control Stepper Motor Web Server HTML Form no CSS Simple

Select the direction and enter a determined number of steps. Then, press GO!. The stepper motor will start spinning.

28BYJ-48 connected to ULN2003 Motor Driver 01 module

At the same time, you can see the values of the direction and steps variables on the Serial Monitor.

28BYJ-48 connected to ULN2003 Motor Driver 01 module Arduino IDE demonstration

2. Styling the Form with CSS

In the previous section, we’ve created a plain form without any formatting. By adding some CSS to your project, your HTML page will look much better.

ESP32 ESP8266 Stepper Motor Web Server HTML Form with CSS

When your HTML also includes CSS, it is easier to work if you have separated HTML and CSS files (apart from the Arduino sketch file). So, instead of writing HTML and CSS in the Arduino sketch, we’ll create separated HTML and CSS files.

These files will then be uploaded to the ESP32 filesystem (LittleFS) using the LittleFS Filesystem uploader plugin.

Organizing Your Files

The files you want to upload to the ESP filesystem should be placed in a folder called data under the project folder. We’ll move two files to that folder:

  • index.html to build the web page
  • style.css to style the web page
ESP32 Stepper Motor Web Server Folder Structure SPIFFS

You should save the HTML, CSS, and JavaScript files inside a folder called data inside the Arduino sketch folder, as shown in the previous diagram. We’ll upload these files to the ESP32 filesystem (LittleFS).

You can download all project files:

Page Overview

To better understand how styling the web page works, let’s take a closer look at the web page we’ll build.

ESP Web Server Style HTML Form

HTML File

We need to make some modifications to the HTML file to make it easier to format using CSS. Create a file called index.html and copy the following into that file.

<!DOCTYPE html>
<html>
<head>
  <title>Stepper Motor</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://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
</head>
<body>
  <div class="topnav">
    <h1>Stepper Motor Control <i class="fas fa-cogs"></i></h1>
  </div>
  <div class="content">
    <form action="/" method="POST">
      <input type="radio" name="direction" value="CW" checked>
      <label for="CW">Clockwise</label>
      <input type="radio" name="direction" value="CCW">
      <label for="CW">Counterclockwise</label><br><br><br>
      <label for="steps">Number of steps:</label>
      <input type="number" name="steps">
      <input type="submit" value="GO!">
    </form>
  </div>
</body>
</html>

View raw code

To use a CSS file to style the HTML page, you need to reference the style sheet in your HTML document. So you need to add the following between the <head> tags of your document:

<link rel="stylesheet" type="text/css" href="stylesheet.css">

This <link> tag tells the HTML file that you’re using an external style sheet to format how the page looks. The rel attribute specifies the nature of the external file. In this case, it is a style sheet—the CSS file—that will be used to alter the page’s appearance.

The type attribute is set to “text/css” to indicate that you’re using a CSS file for the styles. The href attribute indicates the file location; since the file is in the same folder as the HTML file, you just need to reference the filename. Otherwise, you need to reference its file path.

Now you have your style sheet connected to your HTML document.

To use the fontawesome icons in the web page like the cogs, we need to add the following line.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">

We created a <div> tag with the class topnav to make it easier to format the first heading.

<div class="topnav">
  <h1>Stepper Motor Control <i class="fas fa-cogs"></i></h1>
</div>

Then, we include the form inside a <div> tag with the class content. This will make it easier to format the area occupied by the form.

<div class="content">

CSS File

Create a file called style.css with the following content to format the form.

html {
  font-family: Arial, Helvetica, sans-serif;
}

h1 {
  font-size: 1.8rem;
  color: white;
}

.topnav {
  overflow: hidden;
  background-color: #0A1128;
  text-align: center;
}

body {
  margin: 0;
}

.content {
  padding: 20px;
  max-width: max-content;
  margin: 0 auto;
}

form{
  border-radius: 5px;
  background-color: #f2f2f2;
  padding: 20px;
}

input[type=number], select {
  width: 100%;
  padding: 12px 20px;
  margin: 8px 0;
  display: inline-block;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

input[type=submit] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: #034078;
  border: none;
  padding: 14px 20px;
  text-align: center;
  font-size: 20px;
  border-radius: 4px;
  transition-duration: 0.4s;
  width: 100%;
  color: white;
  cursor: pointer;
}

input[type=submit]:hover {
    background-color: #1282A2;
}

input[type="radio"] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border-radius: 50%;
  width: 16px;
  height: 16px;
  border: 2px solid #999;
  transition: 0.2s all linear;
  margin-right: 5px;
  position: relative;
  top: 4px;
}

input[type="radio"]:checked{
  border: 6px solid #1282A2;
}

View raw code

The html selector includes the styles that apply to the whole HTML page. In this case, we’re setting the font.

html {
  font-family: Arial, Helvetica, sans-serif;
}

The h1 selector includes the styles for heading 1. In our case, the heading 1 includes the text “Stepper Motor Control”. This sets the text font size and color.

h1 {
  font-size: 1.8rem;
  color: white;
}

To select the <div> with the topnav class, use a dot (.) before the class name, like this:

.topnav {

Set the .topnav background color using the background-color property. You can choose any background color. We’re using #0A1128. The text is aligned at the center. Additionally, set the overflow property to hidden like this:

.topnav {
  overflow: hidden;
  background-color: #0A1128;
  text-align: center;
}

It’s a bit difficult to explain what the overflow property does. The best way to understand it is to render your web page with and without that property to spot the differences.

The margin of the <body>—the container that includes the whole HTML page—is set to 0 so that it occupies all the browser window space.

body {
  margin: 0;
}

The following lines style the content div (that contains the form): padding and margin. Additionally, set its max-width to the maximum width of its content (the form itself).

.content {
  padding: 20px;
  max-width: max-content;
  margin: 0 auto;
}

The form is a container with round borders (border-radius property) and light gray background color (background-color property). We also add some padding.

form{
  border-radius: 5px;
  background-color: #f2f2f2;
  padding: 20px;
}

Then, we need to style each individual element of the form. To select the input number, we use the input[type=number] selector.

input[type=number], select {
  width: 100%;
  padding: 12px 20px;
  margin: 8px 0;
  display: inline-block;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

Note: the [attribute=value] selector selects elements with the specified attribute and value. In this case, we’re selecting the input elements of type number.

To style the submit button, use the input[type=submit] selector.

input[type=submit] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: #034078;
  border: none;
  padding: 14px 20px;
  text-align: center;
  font-size: 20px;
  border-radius: 4px;
  transition-duration: 0.4s;
  width: 100%;
  color: white;
  cursor: pointer;
}

To make the button change color when you hover your mouse over it, you can use the :hover selector.

input[type=submit]:hover {
  background-color: #1282A2;
}

Finally, to select the radio buttons, use the input[type=”radio”] selector.

input[type="radio"] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  border-radius: 50%;
  width: 16px;
  height: 16px;
  border: 2px solid #999;
  transition: 0.2s all linear;
  margin-right: 5px;
  position: relative;
  top: 4px;
 }

To style the selected radio button, you can use the :checked selector.

input[type="radio"]:checked{
  border: 6px solid #1282A2;
}

The form elements were styled based on an example provided by the W3Schools website. If you want to better understand how it works, you can check it here.

Arduino Sketch

In this project, the HTML and CSS files are saved in the ESP32 filesystem (LittleFS). So, we need to make some modifications to the sketch.

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/stepper-motor-esp32-web-server/
  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>
#include "LittleFS.h"
#include <Arduino_JSON.h>
#include <Stepper.h>

const int stepsPerRevolution = 2048;  // change this to fit the number of steps per revolution
#define IN1 19
#define IN2 18
#define IN3 5
#define IN4 17
Stepper myStepper(stepsPerRevolution, IN1, IN3, IN2, IN4);

// 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);

// Search for parameter in HTTP POST request
const char* PARAM_INPUT_1 = "direction";
const char* PARAM_INPUT_2 = "steps";

//Variables to save values from HTML form
String direction;
String steps;

bool newRequest = false;

// Initialize LittleFS
void initLittleFS() {
  if (!LittleFS.begin(true)) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  else {
  Serial.println("LittleFS mounted successfully");
  }
}

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

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);
  initWiFi();
  initLittleFS();
  myStepper.setSpeed(5);

  // Web Server Root URL
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/index.html", "text/html");
  });
  
  server.serveStatic("/", LittleFS, "/");

  server.on("/", HTTP_POST, [](AsyncWebServerRequest *request) {
    int params = request->params();
    for(int i=0;i<params;i++){
      const AsyncWebParameter* p = request->getParam(i);
      if(p->isPost()){
        // HTTP POST input1 value
        if (p->name() == PARAM_INPUT_1) {
          direction = p->value().c_str();
          Serial.print("Direction set to: ");
          Serial.println(direction);
        }
        // HTTP POST input2 value
        if (p->name() == PARAM_INPUT_2) {
          steps = p->value().c_str();
          Serial.print("Number of steps set to: ");
          Serial.println(steps);
          // Write file to save value
        }
        newRequest = true;
        //Serial.printf("POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
      }
    }
    request->send(LittleFS, "/index.html", "text/html");
  });

  server.begin();
}

void loop() {
  if (newRequest){
    if (direction == "CW"){
      myStepper.step(steps.toInt());
      Serial.print("CW");
    }
    else{
      myStepper.step(-steps.toInt());
    }
    newRequest = false;
  }
}

View raw code

Let’s take a look at the modifications you need to make.

First, you need to include the LittleFS.h library.

#include "LittleFS.h"

Then, you need to initialize LittleFS. We created a function called initLittleFS() to do that.

void initLittleFS() {
  if (!LittleFS.begin(true)) {
    Serial.println("An error has occurred while mounting LittleFS");
  }
  else {
  Serial.println("LittleFSmounted successfully");
  }
}

Then, you need to call that function in the setup() before initializing the web server.

initLittleFS();

Then, to handle requests, you need to indicate that your HTML file is saved in LittleFS, as follows:

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

When the HTML file loads on your browser, it will make a request for the CSS file. This is a static file saved on the same directory (LittleFS). So, we can simply add the following line to serve static files in a directory when requested by the root URL. It will serve the CSS file automatically.

server.serveStatic("/", LittleFS, "/");

Upload Code and Files

Before uploading, you can use the following link to:

  • Download All the Arduino Project Files
  • After inserting your network credentials, save the code. Go to Sketch > Show Sketch Folder, and create a folder called data.

    Arduino IDE Open Sketch Folder to create data folder

    Inside that folder, you should save the HTML and CSS files.

    Then, upload the code to your ESP32 board. Make sure you have the right board and COM port selected. Also, make sure you’ve added your network credentials.

    Arduino IDE 2 Upload Button

    After uploading the code, you need to upload the files. In the Arduino IDE, press [Ctrl] + [Shift] + [P] on Windows or [⌘] + [Shift] + [P] on MacOS to open the command palette. Search for the Upload LittleFS to Pico/ESP8266/ESP32 command and click on it.

    If you don’t have this option is because you didn’t install the filesystem uploader plugin. Check this tutorial.

    ESP32 Sketch Data Upload LittleFS Arduino IDE

    Important: make sure the Serial Monitor is closed before uploading to the filesystem. Otherwise, the upload will fail.

    When everything is successfully uploaded, open the Serial Monitor at a baud rate of 115200. Press the ESP32 EN/RST button, and it should print the ESP32 IP address.

    Demonstration

    Open a browser on your local network and paste the ESP32 IP address. You’ll get access to the HTML form to control the stepper motor. This works similarly to the example of the previous section, but with a better look.

    ESP32 ESP8266 Stepper Motor Web Server HTML Form with CSS

    Wrapping Up

    This tutorial is already quite long. So, we’ll include the third part of this tutorial in a separate publication. In that third part, the ESP32 and the client communicate using WebSocket protocol and the web page shows whether the motor is spinning or stopped. We’ve also included an animation with some gears spinning in the same direction as the motor.

    Continue to PART 3 ESP32 Web Server: Control Stepper Motor (WebSocket). Sneak peek at the third part in the video below.


    If you want to learn more about HTML, CSS, JavaScript, and client-server communication protocols to build your ESP32 and ESP8266 web servers from scratch, make sure you take a look at our 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!

    27 thoughts on “ESP32 Web Server: Control Stepper Motor (HTML Form)”

    1. Hi Sara, hi Rui,
      thank-you for the comprehensive collection!
      One question remains. Did you know of a breadboard friendly ESP32 dev. board or the other way around a ESP32 friendly breadboard? Somehow or other way they don’t fit together as just one row on one side of the board remains accessible. I for my self split a breadboard into to halves an glue the pieces apart. As side effect breadboardpower supplies don’t fit anymore, power- and ground rails aren’t connectet etc.
      Hope you or anywone else is areaware of a more suitiable solution.
      pleas share your experience, thank you.

      Reply
    2. What a perfect tutorial.
      Thank you so much for sharing so many great step-by-step instructions.
      Creating is so powerful; you make our days and be sure of my unfinished respect for all your work.

      Reply
    3. I have followed the instructions for this tutorial but am having problems. The compile is failing indicating AsyncTCP.h does not exist. Any ideas?

      Reply
    4. Hello, i’ve followed all of the instructions above, but i do a little modification since i am using L298N and NEMA 17 stepper. I have a problem that no matter direction i choose, it will rotate the motor clockwise. any suggestion?
      Thank you

      Reply
    5. Hi,
      Thanks for a great project built it last night and it works a treat. I don’t know if this question comes under the remit of the author but here goes anyway.
      I wish to use this project to drive a small focus stacking rig based on an old flatbed. The issue is that the steeper motor in the scanner is a 4 wire type not 5 wire. I was thinking that I could connect 2 of the motor leads to VCC and the other 2 to the usual pins on the driver board (ULN2003 Motor Driver). . If you think this would work, which output pins on the driver board would be best to use and any idea if the ESP software would need to be modified.
      Looking forward to your reply.
      Thanks in advance
      Tecko

      Reply
    6. Hi Sarah,
      New to the Arduino projects, I am p[lanning on using an Arduino Uno Rev2 WiFi and a Nema 17 stepper. Will these sketches work with this combination, as I have found that the Rev2 is a bit different to other Arduino boards and not all sketches will work on it?

      Reply
        • Afternoon Sara,
          Thank you for your help. I will keep on investigating and see if I can gain more information from your sample codes to be able to make small modifications to get the Arduino Uno working.

          Reply
    7. Hi Sara,
      I’d love to be able to control the RPM of the motor via the web inferface. I’ve tried multiple times but no matter what I change, the sppeed doesn’t change. Could you please let me know if it is possible to add a part in the code that would allow me to enter a number for the RPMs?

      Thanks,
      Nica

      Reply
    8. Could this be updated to work with those “new” includes. I tried and tried and not get it to work.
      Thank You in advance! Always error in AsyncTCP when compiling.

      Or ELI how to make this work.

      Reply
    9. Sara ajuda sff,
      no terminal não tenho erros, se tentar abrir a pag do ip do esp32, está simplesmente em preto. não dá erro nenhum no browser. usei os ficheiros que estavam no zip para colocar no spiffs.

      Reply
      • Fez upload dos ficheiros para a placa?
        Provavelmente não fez upload dos ficheiros ou não estão colocados no sitio certo.
        Devem estar dentro de uma pasta chamada data dentro da pasta do projeto.
        Depois deve fazer upload dos ficheiros para a placa e só depois do código.
        Espero que ajude.
        Cumprimentos,
        Sara Santos

        Reply
    10. hello admin, good tutorial. Regarding stepper motors, can they be controlled from anywhere and please make a tutorial, thank you

      Reply
    11. Hi Sara, thanks for the turorial. Regarding stepper motors, I tried to control 3 stepper motors and only 1 of them worked. please help thank you

      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.