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.
Table of Contents
- Control Stepper Motor with HTML Form (minimal setup)
- Control Stepper Motor with HTML Form + CSS (using LittleFS)
- 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).
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:
- ESP32 with Stepper Motor (28BYJ-48 and ULN2003 Motor Driver)
- Input Data on HTML Form ESP32/ESP8266 Web Server using Arduino IDE
- ESP32 WebSocket Server: Control Outputs (Arduino IDE)
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:
- 28BYJ-48 Stepper Motor + ULN2003 Motor Driver
- ESP32 (read Best ESP32 Development Boards)
- Jumper Wires
- 5V Power Supply
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:
- ESPAsyncWebServer (.zip folder)
- AsyncTCP (.zip folder)
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 Sketch> Include 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.
Note: You should power the motor driver using an external 5V power supply.
Motor Driver | ESP32 |
IN1 | GPIO 19 |
IN2 | GPIO 18 |
IN3 | GPIO 5 |
IN4 | GPIO 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.
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
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
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.
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;
}
}
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.
Select the direction and enter a determined number of steps. Then, press GO!. The stepper motor will start spinning.
At the same time, you can see the values of the direction and steps variables on the Serial Monitor.
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.
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
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.
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>
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;
}
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;
}
}
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:
After inserting your network credentials, save the code. Go to Sketch > Show Sketch Folder, and create a folder called data.
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.
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.
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.
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.
I look forward to your tutorial on how to Control Stepper Motor with WebSockets (HTML, CSS, JavaScript.
Exciting..
That’s great!
It will be published next week. Stay tuned.
Regards,
Sara
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.
Hi Joschen,
you may use ESP32 Lolin LOLIN32 boards, which are smaller than normal ESP32 dev. boards.
Another advantage: The board can be powered by a Lithium accu. I bought my Lolin boards from http://www.az-delivery.de/products/esp32-lolin-lolin32.
Regards Peter
Hi.
I also use two breadboard halves, or I use the GPIOs just on one side of the board.
Regards,
Sara
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.
Thank you so much.
Regards,
Sara
I have followed the instructions for this tutorial but am having problems. The compile is failing indicating AsyncTCP.h does not exist. Any ideas?
Hi.
Install the AsyncTCP library.
Regards,
Sara
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
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
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?
Hi.
I’m not familiar with that board. But I don’t think it is compatible with ESP32 sketches.
Regards,
Sara
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.
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
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.
Hi.
What is exactly the error that you get?
Regards,
Sara
thank you
what if i want to use AJAX and also send live sensor data to the webpage,
Hi.
Check the version with websocket: https://randomnerdtutorials.com/stepper-motor-esp32-websocket/
Regards,
Sara
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.
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
hello admin, good tutorial. Regarding stepper motors, can they be controlled from anywhere and please make a tutorial, thank you
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
Hi Sara!
Thank you for a great project! Do you plan to make it with synchronous web server?
Since the library stepper.h block the microcontroller while the stepmotor is moving, I decide to create a ninblocking stepmotor library based on timer/interrupts so that the microcontroller is free even when the stepmotor is moving. The Stepm.h is available at https://github.com/AntonioFromBrazil/Stepm.h-library-for-ESP-32 It can be usefull in some cases.
*nonblocking
I have just published the NewStepper.h library in substitution to that above. This new one accept 28BYJ-48 in 3 different modes and also Nema17 as well. The link to it is: https://github.com/AntonioFromBrazil/NewStepper.h-Nonblocking-ESP-32-Nema17-28byj-48