Wireless Stepper Motor Controller with ESP32 and TMC2240

Published  August 19, 2025   0
 Wireless Stepper Motor Driver with ESP32 and TMC2240

The Internet of Things (IoT) has revolutionized how we control devices, enabling seamless remote monitoring and operation over a network. This tutorial presents an innovative solution for remote stepper motor control, allowing you to precisely adjust its position, track its performance, and receive real-time feedback from a simple web browser. For those new to robotics and automation, understanding the basics of a stepper motor and how it worksstepper motor and how it works is a great first step before diving into this advanced project.

In this guide, we introduce a custom-built Wireless Stepper Motor Controller with ESP32, a simple yet powerful way to add Wi-Fi stepper motor control to any project. This wireless stepper motor driver integrates robust control with modern connectivity, making it perfect for applications in automation, robotics, and industrial prototyping. With this wireless stepper driver design, you can control the motor directly through a local web interface, no extra software or complex setup required.

This project was made possible thanks to our sponsors DigiKey. All the components used in this project were built using parts from DigiKey. You can check out our Project Files with PCB Gerber and BOM to build this wireless stepper motor controller project on your own. 

Wireless Stepper Motor Controller - Explained

The Wireless Stepper Motor Controller with esp32 is a smart motor control system that combines precision stepper motor control with wireless connectivity and intelligent power management.

Wireless Stepper Motor Controller

Wireless Stepper Motor Control: Operate your motor remotely via a simple web interface on any browser or smartphone.

Precision and Accuracy: Achieve high-precision positioning with up to 1/256 microstepping and real-time encoder feedback from the AS5600 sensor.

Smart Power Management: Automatically negotiate optimal power levels from USB-C Power Delivery (PD) sources, from 5V to 20V.

Real-time Monitoring: Monitor critical data, including motor temperature, current draw, and exact position, all updated live on the web dashboard.

Intuitive Visual Feedback: Use color-coded RGB LED indicators to instantly see the system's power status and motor activity.

Key Features of our Wireless Stepper Driver

  • Web-Based Control: Simple HTTP commands control all motor functions - no special software needed
  • Precision Movement: up to 1/256 microstepping with closed-loop positioning using AS5600 magnetic encoder
  • Smart Power Management: Automatically selects 5V, 9V, 12V, or 15V from USB-PD sources
  • Visual Feedback: RGB LEDs show power level (colour-coded) and motor activity (blinking patterns)
  • Real-Time Monitoring: Live status updates including temperature, current draw, and position
  • Non-Blocking Operation: The Web server stays responsive while the motor runs smoothly

Components Required

Component

Quantity

Description

ESP32-S3-WROOM-1

1

Wi-Fi and Bluetooth 5.0-enabled microcontroller module with dual-core Xtensa LX7 processor, 512KB SRAM, and 8MB flash memory.
TMC2240ATJ+T

1

High-precision stepper motor driver with SPI control, supporting up to 2A current and ultra-quiet operation.
AS5600-ASOM

1

Contactless magnetic rotary position sensor with 12-bit resolution for accurate angle measurement.
ADPL44002AUJZ

1

Low-dropout (LDO) linear regulator supporting 2.7V–40V input and up to 200mA output current.
FUSB302BMPX

1

USB Type-C controller with Power Delivery (PD) support for voltage negotiation and data role switching.
ADM803SAKSZ

1

Voltage supervisor IC with push-pull output for reliable system reset management.
AO3400A

1

P-channel MOSFET (30V, 4A) for low-side switching applications.
EVQ-P7A01P

4

Tactile push-button switches (6x6mm, 160gf actuation force) for user input.
SMF40A

1

40V transient voltage suppression (TVS) diode for circuit protection from voltage spikes.
SD05

1

TVS diode array designed for USB and ESD protection.
LTST-C191KRKT

1

Red surface-mount LED (0603 package) for status indication.
XL-1615RGBC-YG

1

RGB LED (common cathode, 1615 package) for multi-colour visual feedback.
Other Passive Components

-

Includes capacitors, resistors, and necessary discretes.
Connectors

-

Miscellaneous connectors as required.
Custom PCB

-

Main printed circuit board for the system.
Laser Cutting Parts

-

Enclosures and mechanical supports.
Other Tools & Consumables

-

Assembly and testing materials.

Wireless Stepper Motor Controller with ESP32 Wiring Diagram

The complete circuit diagram for the Wireless Stepper Motor Driver is shown below. This comprehensive wireless stepper motor controller with ESP32 wiring diagram demonstrates how to control a stepper motor with ESP32 effectively It can also be downloaded in PDF format from the GitHub repo linked at the end.

Wireless Stepper Motor Controller Schematic Diagram

The schematic is fully customizable. You can tweak any part of the design to suit your specific needs.

Power Section DIY Stepper Motor

First, the power section, which combines a USB Power Delivery (PD) controller and a Low Dropout (LDO) regulator to manage voltage input and provide a stable 3.3V output. The USB PD controller used is the FUSB302BMPX, which negotiates power profiles through the USB-C connector. It connects to the CC1 and CC2 lines via 5.1kΩ pull-down resistors to detect cable orientation and voltage profiles. Once a power contract is negotiated, the VBUS line delivers the required voltage (e.g., 9V or 12V), which is then passed to the LDO regulator.

The LDO used is the ADP4140002, which steps down the negotiated voltage to a clean 3.3V. This 3.3V is used to power the ESP32-S3 microcontroller, the stepper motor driver’s logic section, the magnetic position sensor, and the indicator LEDs.

The LDO includes input and output capacitors for voltage stability and noise filtering, while the PD controller communicates with the ESP32-S3 via I²C and notifies it of any power events using an interrupt pin.

ESP32 S3 Section

The core of the system is the ESP32-S3-WROOM-1 module, which provides Wi-Fi and Bluetooth connectivity along with multiple GPIOs for control and communication. This setup demonstrates exactly how to control a stepper motor with ESP32 efficiently. Its GPIO0 and EN pins are connected to push buttons for boot mode and reset functionality. It communicates via USB using its native D+ and D– pins, which are routed through 33Ω resistors. Previously, we have built many projects using ESP32 board, you can also check them out if interested. 

The I²C bus on GPIO9 (SCL) and GPIO8 (SDA) connects to the USB PD controller and the magnetic position sensor, allowing two-way communication for power and position monitoring. The ESP32 also interfaces with the TMC2240 stepper driver through both SPI and dedicated control pins. It uses GPIO10–13 for SPI communication and GPIO5 and GPIO6 for STEP and DIR signals.

In addition, three LEDs are connected to GPIO40, GPIO41, and GPIO42 via 330Ω resistors, which serve as status indicators.

TMC2240 Circuit Diagram

The TMC2240 stepper motor driver handles the precise control of a stepper motor. It receives its motor power input (VM) from an external power source and logic power from the 3.3V LDO. The motor is controlled using STEP and DIR signals from the ESP32, while the SPI bus is used to configure internal settings and read diagnostics. The driver also includes enable and diagnostic pins for fine control and feedback. Protection is implemented using a TVS diode across the VM line to handle voltage spikes and sense resistors for monitoring motor current. The driver’s output pins are connected directly to the motor coils, enabling smooth microstepping operation. For those using A4988 drivers, our A4988 Arduino interface tutorial will guide you step by step.

Magnetic Position Sensor

A magnetic rotary position sensor, based on the AS5600, is included for detecting the angular position of a rotating shaft. This sensor communicates over I²C, sharing the same bus as the PD controller. It is powered by the 3.3V rail and includes a decoupling capacitor for noise filtering. The sensor provides precise angle data to the ESP32, enabling closed-loop motor control or position tracking applications.

With this, the Schematic part gets covered. Next comes the PCB Part.


Custom PCB Design for Wireless Stepper Driver

For this project, we decided to create a custom PCB. This ensures that the final product is as compact as possible and easy to assemble and use. The PCB was designed using KiCad, and all the design files are available for download from the GitHub repository linked below this article.
The PCB dimensions are approximately 42.3 mm x 42.3 mm, which is exactly the general size of stepper motors like the NEMA 17. So, it easily mounts behind the motor.

PCB Design of Wireless Stepper Motor Controller

Once the PCB design was complete and fully verified, we sent the respective Gerber files to a PCB fabrication service for manufacturing. Below, you can see the raw fabricated PCB.

PCB Design Wireless Stepper Motor Controller


Assembling Guide: Building the Stepper Motor Controller

The first step in assembling the PCB was to sort all the required components as listed in the BOM (Bill of Materials). After sorting, we placed the components on the PCB and soldered them one by one.

To simplify this process, you can use an SMD stencil to apply solder paste and then place the components before reflowing the PCB using either an SMD rework station or a reflow oven. However, you are not limited to these methods; manual soldering works just as well for small batches.

Wireless-Stepper Motor Controller Schematic Diagram

Above is the image of a fully assembled Wireless Stepper Motor Controller PCB.

Laser Cut Enclosure for Stepper Driver

To make the enclosure simple and quick, we chose laser cutting. There are two primary parts we need, along with some washers. At the bottom, we actually need a spacer to compensate for the magnetic encoder IC’s height. On the top, we need to cover the PCB to avoid external damage.

Wireless Stepper Motor Controller Laser Cutout

Above, you can see the image of the cutouts.

Below, you can see the components required for assembling the Wireless Stepper Motor Driver.

Components Wireless Stepper Motor Controller

The assembly is very simple. The only part that requires extra attention is the placement of the magnet on the shaft. This is important, as it is responsible for sensing the shaft's position.

I broke a normal 3mm diameter magnet in half and placed one half as shown in the image below. I used Feviquick to stick the magnet.

Magnet Placement on the Shaft

Next comes the enclosure. It is assembled as shown in the following image.

3D Illustration of Wireless Stepper Motor Design

After full assembly, the final project looks like the image below. You can see the compactness of this project.

The reason for not including any side enclosures is to provide ventilation for the driver IC, making it a perfect companion for fast prototyping.

Final Assembled Wireless Stepper Motor

This DIY wireless stepper motor project offers a powerful and compact solution for robotics projects and home automation projects. By integrating a versatile ESP32 microcontroller with the high-performance TMC2240 driver and an AS5600 encoder, this system provides unmatched precision and real-time feedback. Its web-based control and smart power management make it an ideal tool for rapid prototyping, robotics, and industrial applications. You can download all the code and design files from the GitHub repository linked below to build your own.

Code and Schematics Wireless Stepper Motor ControllerCode and Schematics Wireless Stepper Motor Controller Zip

Programming the ESP32: Arduino Code Overview

The core of this wireless stepper motor controller project is its versatile Arduino code, which seamlessly integrates several advanced technologies. The firmware demonstrates exactly how to control a stepper motor with ESP32 and is designed to combine:

TMC2240 Stepper Driver: For ultra-quiet and precise stepper motor control.

USB-C Power Delivery: To enable intelligent, variable power sourcing.

AS5600 Magnetic Encoder: For high-resolution, closed-loop position feedback.

RGB Status LEDs: To provide clear visual indication of system states.

Web Server Interface: For convenient remote control and real-time monitoring.

The system allows you to control a stepper motor through a web browser while monitoring power, temperature, and position in real-time.

Libraries and Dependencies

This code uses several libraries to handle different components.

#include <SPI.h>        
#include <WiFi.h>       
#include <WebServer.h>    
#include "AS5600.h"     
#include <Wire.h>       
#include <PD_UFP.h>     
#include <ArduinoJson.h>
#include "index_page.h"
  • "SPI.h" – Communicates with the TMC2240 driver using Serial Peripheral Interface (Built-in library)
  • "Wire.h" – Handles I2C communication with the magnetic encoder (Built-in library)
  • "WiFi.h" and "WebServer.h" – Enable WiFi connectivity and web-based control (Built-in libraries)
  • "AS5600.h" – Controls the magnetic encoder for position sensing
  • "PD_UFP.h" – Manages USB-C Power Delivery negotiation
  • "ArduinoJson.h" – Formats data for web API responses
  • "index_page.h" – Contains the HTML/CSS/JavaScript for the web interface (Custom library)

Pin Definitions and Hardware Connections

The system uses specific microcontroller pins for different components to ensure proper communication and control. 

const int MOSI_PIN = 11, MISO_PIN = 13, SCK_PIN = 12, CS_PIN = 10;
const int EN_PIN = 14;
const int STEP_PIN = 5;
const int DIR_PIN = 6;  

The TMC2240 stepper driver uses an SPI interface with MOSI on pin 11, MISO on pin 13, SCK on pin 12, and CS on pin 10, while motor control pins include Enable on pin 14, Step on pin 5, and Direction on pin 6.
 Wire.setPins(8, 9);

The AS5600 magnetic encoder communicates via I2C using pins 8 for SDA and 9 for SCL.

const int RGB_RED_PIN = 40;
const int RGB_GREEN_PIN = 42;
const int RGB_BLUE_PIN = 41;

The RGB LED uses three separate pins for colour control with Red on pin 40, Green on pin 42, and Blue on pin 41. 

#define FUSB302_INT_PIN 7

The USB-C Power Delivery system uses pin 7 as an interrupt pin for the FUSB302 controller.

Global Variables and Configuration

The system maintains various global variables to track different aspects of operation.

bool isMotorEnabled = false;  
bool currentDirection = true; 
bool isMovingToAngle = false;
bool pd_negotiation_complete = false; 
int currentSpeed = DEFAULT_SPEED;
uint16_t motorCurrent = DEFAULT_CURRENT;  
uint8_t microSteps = DEFAULT_MICROSTEPS;  
int STEPS_PER_REVOLUTION = STEPS_PER_REV * DEFAULT_MICROSTEPS;

Motor control variables include isMotorEnabled to track whether the motor is powered on, currentDirection to store the rotation direction, and isMovingToAngle to indicate when automatic angle positioning is active. Motor parameters such as currentSpeed, motorCurrent, and microSteps can be adjusted during operation to fine-tune performance.

float targetAngle = 0.0;                 
float currentAngleTolerance = 0.0;      
float cachedCurrentAngle = 0.0;          
float cachedTemperature = 0.0;          
int cachedCurrentB = 0;        

Position control variables like targetAngle and currentAngleTolerance handle precise angle-based movements, while the system caches sensor readings in variables like cachedCurrentAngle and cachedTemperature to reduce communication overhead and improve performance.

struct PDProfile {
   float voltage;        
   float current;         
   const char* name;     
};
const PDProfile pd_profiles[] = {
   {5.0, 3.0, "5V/3A"}, 
   {9.0, 2.0, "9V/2A"}, 
   {9.0, 3.0, "9V/3A"}, 
   {12.0, 1.5, "12V/1.5A"}, 
   {15.0, 2.0, "15V/2A"},  
   {20.0, 1.5, "20V/1.5A"}
};

The power delivery configuration uses a pd_profiles array that defines different voltage and current combinations, including 5V, 9V, 12V, 15V, and 20V options that can be negotiated with USB-C power sources. 

const RGBColor COLOR_OFF = { 0, 0, 0 };         
const RGBColor COLOR_RED = { 255, 0, 0 };       
const RGBColor COLOR_GREEN = { 0, 255, 0 };    
const RGBColor COLOR_BLUE = { 0, 0, 255 };      
const RGBColor COLOR_YELLOW = { 255, 255, 0 };  
const RGBColor COLOR_PURPLE = { 255, 0, 255 }; 
const RGBColor COLOR_CYAN = { 0, 255, 255 };    
const RGBColor COLOR_WHITE = { 255, 255, 255 };
const RGBColor COLOR_ORANGE = { 255, 165, 0 };  
const RGBColor COLOR_PINK = { 255, 20, 147 };  

LED status management relies on RGB color constants and timing variables to control the visual feedback system, with different colors indicating various states such as WiFi connectivity, power levels, and motor operation status.

 

Setup Function - System Initialisation

The setup function initialises all system components in a carefully planned sequence to ensure proper operation for esp32 stepper motor control Wi-Fi functionality.


 Serial.begin(115200);
 Serial.println("TMC2240 Stepper Motor Control");
 
 initRGBLED();
 Wire.setPins(8, 9); 
 Wire.begin();
 PD_UFP.init(FUSB302_INT_PIN, PD_POWER_OPTION_MAX_5V);
 PD_UFP.set_power_option(PD_POWER_OPTION_MAX_5V);
 as5600.begin(4);  
 as5600.setDirection(AS5600_CLOCK_WISE);
 if (!as5600.isConnected()) {
   Serial.println("Warning: AS5600 encoder not connected!");
 }
 pinMode(EN_PIN, OUTPUT);      
 pinMode(STEP_PIN, OUTPUT);    
 pinMode(DIR_PIN, OUTPUT);     
 pinMode(CS_PIN, OUTPUT);      
 pinMode(MOSI_PIN, OUTPUT);   
 pinMode(MISO_PIN, INPUT);     
 pinMode(SCK_PIN, OUTPUT);    
 pinMode(UART_EN_PIN, OUTPUT);
 digitalWrite(CS_PIN, HIGH);                    
 digitalWrite(EN_PIN, HIGH);                 digitalWrite(UART_EN_PIN, LOW);              
 digitalWrite(DIR_PIN, currentDirection); 
 SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);

The process begins with serial communication initialisation for debugging purposes, followed by RGB LED testing with a colour sequence of red, green, and blue to verify functionality. Hardware component setup includes configuring I2C communication for the magnetic encoder, initialising the Power Delivery controller with 5V as the starting profile, and setting up all GPIO pins for motor control and SPI communication.

// Connect to WiFi network
 WiFi.begin(ssid, password);
 Serial.print("Connecting to WiFi");
 setRGBColor(COLOR_YELLOW);  // Yellow while connecting
 // Wait for WiFi connection
 while (WiFi.status() != WL_CONNECTED) {
   delay(500);
   Serial.print(".");
 }
 Serial.println("\nConnected to WiFi");
 Serial.print("IP Address: ");
 Serial.println(WiFi.localIP());
 setRGBColor(COLOR_GREEN);  // Green when connected
 delay(1000);
 // Main page route - serves the web interface
 server.on("/", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   server.send(200, "text/html", html_page);
 });
 // Motor enable route - enables the stepper motor
 server.on("/enable", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   digitalWrite(EN_PIN, LOW);       
   isMotorEnabled = true;
   updateMotorStatusColor();         
   server.send(200, "text/plain", "Motor Enabled");
 });
 // Motor disable route - disables the stepper motor
 server.on("/disable", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   digitalWrite(EN_PIN, HIGH);       
   isMotorEnabled = false;
   updateMotorStatusColor();         
   server.send(200, "text/plain", "Motor Disabled");
 });
 // Emergency stop route - immediately stops all motor activity
 server.on("/stop", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   digitalWrite(EN_PIN, HIGH);      
   digitalWrite(STEP_PIN, LOW);     
   isMotorEnabled = false;
   isMovingToAngle = false;          
   currentSpeed = DEFAULT_SPEED;    
   updateMotorStatusColor();        
   String response = "{\"status\":\"Emergency Stop Activated\",\"motorEnabled\":false}";
   server.send(200, "application/json", response);
   Serial.println("EMERGENCY STOP ACTIVATED");
 });
 // Speed control route - sets motor speed
 server.on("/speed", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   if (server.hasArg("value")) {
     currentSpeed = server.arg("value").toInt();
     server.send(200, "text/plain", "Speed set to " + String(currentSpeed));
   }
 });
 // Manual movement route - moves motor by specified steps
 server.on("/move", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   if (server.hasArg("steps")) {
     int targetSteps = server.arg("steps").toInt();
     moveMotor(currentDirection, targetSteps);
     server.send(200, "text/plain", "Moving " + String(targetSteps) + " steps");
   }
 });
 // System status route - returns current system status
 server.on("/status", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   bool pd_ready = PD_UFP.is_power_ready();
   float pd_voltage = (pd_ready && pd_negotiation_complete) ? PD_UFP.get_voltage() : 0.0;
   float pd_current = (pd_ready && pd_negotiation_complete) ? PD_UFP.get_current() : 0.0;
   String status = "{\"temperature\":" + String(cachedTemperature, 1) +
                  ",\"current\":" + String(cachedCurrentB) +
                  ",\"pd_ready\":" + String(pd_ready ? "true" : "false") +
                  ",\"pd_negotiated\":" + String(pd_negotiation_complete ? "true" : "false") +
                  ",\"pd_voltage\":" + String(pd_voltage, 2) +
                  ",\"pd_current\":" + String(pd_current, 2) + "}";
   server.send(200, "application/json", status);
 });
 // Motor current control route - sets motor current
 server.on("/current", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   if (server.hasArg("value")) {
     motorCurrent = server.arg("value").toInt();
     setCurrent(motorCurrent);
     server.send(200, "text/plain", "Motor current set to " + String(motorCurrent) + " mA");
   }
 });
 // Microstepping control route - sets microstepping resolution
 server.on("/microsteps", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   if (server.hasArg("value")) {
     microSteps = server.arg("value").toInt();
     setMicrostepping(microSteps);
     server.send(200, "text/plain", "Microstepping set to 1/" + String(microSteps));
   }
 });
 // Encoder status route - returns current encoder readings
 server.on("/encoder", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   String json = String("{\"speed\":0") +
                ",\"rawAngle\":" + String(cachedCurrentAngle, 1) +
                ",\"current\":" + String(cachedCurrentB) + "}";
   server.send(200, "application/json", json);
 });
 // Power status route - returns PD power status
 server.on("/power_status", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   StaticJsonDocument<200> doc;
   bool power_ready = PD_UFP.is_power_ready();
   float voltage = 0.0;
   float current = 0.0;
   if (power_ready && pd_negotiation_complete) {
     voltage = PD_UFP.get_voltage();
     current = PD_UFP.get_current();
   }
   bool pd_connected = power_ready && pd_negotiation_complete && voltage > 0.0 && current > 0.0;
   doc["pd_connected"] = pd_connected;
   doc["pd_negotiated"] = pd_negotiation_complete;
   if (pd_connected) {
     doc["voltage"] = voltage;
     doc["current"] = current;
     doc["power"] = voltage * current;
   }
   String response;
   serializeJson(doc, response);
   server.send(200, "application/json", response);
 });
 server.on("/direction", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   if (server.hasArg("dir")) {
     currentDirection = (server.arg("dir") == "true");
     digitalWrite(DIR_PIN, currentDirection);
     server.send(200, "text/plain", "Direction set to " + String(currentDirection ? "Forward" : "Backward"));
   } else {
     server.send(400, "text/plain", "Missing direction parameter");
   }
 });
 server.on("/set_angle", HTTP_GET, []() {
   lastClientRequest = millis();
   webClientConnected = true;
   if (server.hasArg("angle")) {
     targetAngle = server.arg("angle").toFloat();
     currentAngleTolerance = calculateAngleTolerance(microSteps);
     isMovingToAngle = true;
     server.send(200, "text/plain", "Moving to angle " + String(targetAngle) + "° with accuracy ±" + String(currentAngleTolerance) + "°");
   } else {
     server.send(400, "text/plain", "Missing angle parameter");
   }
 });
// Web server endpoint to set Power Delivery (PD) profile
server.on("/setPD", HTTP_GET, []() {
 lastClientRequest = millis();
 webClientConnected = true;
 if (server.hasArg("profile")) {
   int profile = server.arg("profile").toInt(); 
   PD_power_option_t power_option; 
   switch (profile) {
     case 5:
       power_option = PD_POWER_OPTION_MAX_5V;
       currentPDProfile = 0; 
       break;
     case 9:
       power_option = PD_POWER_OPTION_MAX_9V;
       currentPDProfile = 1; 
       break;
     case 12:
       power_option = PD_POWER_OPTION_MAX_12V;
       currentPDProfile = 2; 
       break;
     case 15:
       power_option = PD_POWER_OPTION_MAX_15V;
       currentPDProfile = 3; 
       break;
     case 20:
       power_option = PD_POWER_OPTION_MAX_20V;
       currentPDProfile = 4; 
       break;
     default:
       server.send(400, "text/plain", "Invalid profile");
       return;
   }
   PD_UFP.set_power_option(power_option);
  
   for (int i = 0; i < 5; i++) {
     PD_UFP.run(); 
     delay(100); 
   }
  
   forceUpdatePDColors();
  
   server.send(200, "text/plain", "PD profile set to " + String(profile) + "V");
 } else {
   server.send(400, "text/plain", "Missing profile parameter");
 }
});
// Start the web server
server.begin();
Serial.println("HTTP server started");
// Configure the TMC2240 stepper motor driver
configureDriver();


WiFi connection establishment is a crucial step where the system connects to the specified network, displaying a yellow LED during the connection process and switching to green when successful. The system then displays the assigned IP address for web access. Web server route configuration creates multiple HTTP endpoints including the root path for serving the main control interface, enable and disable routes for motor power control, parameter adjustment routes for speed, current, and microstepping settings, movement command routes for basic stepping and angle positioning, real-time data routes for status and encoder information, and power delivery management routes. Finally, the TMC2240 driver is configured with default settings, including current limits, microstepping resolution, and chopper timing parameters. To ensure the best performance for your specific motor, you can use our dedicated stepper motor calculator toolstepper motor calculator tool to get essential specifications and calculations.

Main Loop - Continuous Operation

The main loop continuously handles multiple tasks to maintain system operation and responsiveness.

 server.handleClient();
 PD_UFP.run();
 updateLEDBlink();
 static unsigned long lastStatusLEDUpdate = 0;
 if (millis() - lastStatusLEDUpdate >= 100) {
   updateStatusLED(); 
   lastStatusLEDUpdate = millis();
 }
 if (millis() - last_pd_check >= PD_CHECK_INTERVAL) {
   bool was_negotiated = pd_negotiation_complete;  
  
   if (PD_UFP.is_power_ready()) {
     if (!pd_negotiation_complete) {
       Serial.println("PD Power Negotiation Complete!");
       pd_negotiation_complete = true;
       lastPDVoltageCheck = 0;
     }
   } else {
     if (pd_negotiation_complete) {
       Serial.println("PD Power Negotiation Lost!");
       pd_negotiation_complete = false;
       pdColorModeActive = false;        }
   }
   last_pd_check = millis(); 
 }

Request processing involves the server.handleClient() to handle incoming web requests and PD_UFP.run() to maintain power delivery negotiation with USB-C sources. Visual feedback management updates the LED system based on priority levels, where WiFi connection issues display red blinking, successful PD negotiation shows voltage-coded colours, and normal operation indicates connection status.

 if (millis() - lastEncoderUpdate >= ENCODER_UPDATE_INTERVAL) {
   updateCachedValues();      lastEncoderUpdate = millis();
 }
 if (isMovingToAngle) {
   moveToAngle();    }
 if (millis() - lastStatusUpdate >= STATUS_UPDATE_INTERVAL) {
   Serial.println("System Status: Motor " + String(isMotorEnabled ? "Enabled" : "Disabled") +
                  ", Temp: " + String(cachedTemperature, 1) + "°C" +
                  ", WiFi: " + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") +
                  ", Web Client: " + String(webClientConnected ? "Active" : "Inactive") +
                  ", PD Mode: " + String(pdColorModeActive ? "Active" : "Inactive"));
   lastStatusUpdate = millis();
 }

Sensor updates occur periodically to read the magnetic encoder and TMC2240 temperature sensor, with values cached to avoid excessive communication that could slow down the system. Automatic movement functionality activates when isMovingToAngle is true, causing the moveToAngle() function to continuously adjust motor position toward the target angle. Status reporting provides periodic serial output with system information, including motor state, temperature readings, and connection status for debugging and monitoring purposes.

Key Functions Explained

void setRGBColor(RGBColor color) {
 analogWrite(RGB_RED_PIN, color.red);     // Set red component
 analogWrite(RGB_GREEN_PIN, color.green); // Set green component
 analogWrite(RGB_BLUE_PIN, color.blue);   // Set blue component
 currentLEDColor = color;                 // Update current color
}
void updateStatusLED() {
 // Highest Priority: WiFi disconnected - Red fast blink
 if (WiFi.status() != WL_CONNECTED) {
   pdColorModeActive = false;
   enableLEDBlink(COLOR_RED, 200);  // Red fast blink for WiFi issues
   return;
 }
 // Second Priority: PD is ready and negotiated - show voltage colors
 if (PD_UFP.is_power_ready() && pd_negotiation_complete) {
   // Only update PD colors periodically to avoid constant changes
   if (millis() - lastPDVoltageCheck >= PD_CHECK_INTERVAL) {
     updatePDVoltageColor();
     lastPDVoltageCheck = millis();
   }
   return; // Exit here - PD colors are active
 }
 // Third Priority: WiFi connected but no PD - show connection status
 pdColorModeActive = false;
 // Update client connection status based on timeout
 if (millis() - lastClientRequest > CLIENT_TIMEOUT) {
   webClientConnected = false;
 }
 if (webClientConnected) {
   // WiFi connected and web client active - Solid Green
   setRGBColor(COLOR_GREEN);
   ledBlinkEnabled = false;
 } else {
   // WiFi connected but no web client - Blue slow blink
   enableLEDBlink(COLOR_BLUE, 1000);
 }
}

RGB LED control functions such as setRGBColor() and updateStatusLED() manage the visual feedback system that provides immediate status information to users. The system uses a colour-coding scheme where green indicates 5V power, blue represents 9V, purple shows 12V, orange displays 15V, and pink indicates 20V power levels.

// Write data to TMC2240 register via SPI
void writeRegister(uint8_t address, uint32_t value) {
 digitalWrite(CS_PIN, LOW);                    // Select TMC2240
 SPI.transfer(address | 0x80);                 // Send address with write bit
 SPI.transfer((value >> 24) & 0xFF);          // Send MSB
 SPI.transfer((value >> 16) & 0xFF);          // Send byte 2
 SPI.transfer((value >> 8) & 0xFF);           // Send byte 1
 SPI.transfer(value & 0xFF);                  // Send LSB
 digitalWrite(CS_PIN, HIGH);                   // Deselect TMC2240
}
// Read data from TMC2240 register via SPI
uint32_t readRegister(uint8_t address) {
 digitalWrite(CS_PIN, LOW);                    // Select TMC2240
 SPI.transfer(address & 0x7F);                 // Send address with read bit
 uint32_t value = 0;
 value |= (uint32_t)SPI.transfer(0) << 24;    // Read MSB
 value |= (uint32_t)SPI.transfer(0) << 16;    // Read byte 2
 value |= (uint32_t)SPI.transfer(0) << 8;     // Read byte 1
 value |= (uint32_t)SPI.transfer(0);          // Read LSB
 digitalWrite(CS_PIN, HIGH);                   // Deselect TMC2240
 return value;
}
// Set motor current in milliamps
void setCurrent(uint16_t current) {
 uint32_t ihold_irun = 0;
 // Set hold current (current when motor is stationary)
 ihold_irun |= ((current / 100) & 0x1F) << 0;
 // Set run current (current when motor is moving)
 ihold_irun |= ((current / 100) & 0x1F) << 8;  
 // Set hold delay (time before reducing to hold current)
 ihold_irun |= ((current / 200) & 0x0F) << 16;
 writeRegister(IHOLD_IRUN, ihold_irun);
}
// Set microstepping resolution (1, 2, 4, 8, 16, 32, 64, 128, 256)
void setMicrostepping(uint8_t microsteps) {
 uint32_t chopconf = readRegister(CHOPCONF);
 chopconf &= ~(0x0F << 24);  // Clear MRES bits
 // Convert microsteps to MRES register value
 uint8_t mres;
 switch (microsteps) {
   case 256: mres = 0x0; break;  // 1/256 microstepping
   case 128: mres = 0x1; break;  // 1/128 microstepping
   case 64:  mres = 0x2; break;  // 1/64 microstepping
   case 32:  mres = 0x3; break;  // 1/32 microstepping
   case 16:  mres = 0x4; break;  // 1/16 microstepping
   case 8:   mres = 0x5; break;  // 1/8 microstepping
   case 4:   mres = 0x6; break;  // 1/4 microstepping
   case 2:   mres = 0x7; break;  // 1/2 microstepping (half step)
   case 1:   mres = 0x8; break;  // Full step
   default:  mres = 0x4; break;  // Default to 1/16 microstepping
 }
 chopconf |= (mres << 24);  // Set MRES bits
 chopconf |= (1 << 28);     // Enable interpolation
 writeRegister(CHOPCONF, chopconf);
 // Update global calculation variables
 STEPS_PER_REVOLUTION = STEPS_PER_REV * microsteps;
 currentAngleTolerance = calculateAngleTolerance(microsteps);
 Serial.print("Microstepping set to 1/");
 Serial.println(microsteps);
}

TMC2240 communication relies on writeRegister() and readRegister() functions that handle SPI communication with the motor driver, while functions like setCurrent() and setMicrostepping() configure motor parameters by writing to specific hardware registers.

// Move motor a specified number of steps in the given direction

void moveMotor(bool direction, uint32_t steps) {
 digitalWrite(DIR_PIN, direction);  // Set direction pin
 static unsigned long lastStepTime = 0;
 static uint32_t stepsRemaining = 0;
 // Initialize step counter on first call
 if (stepsRemaining == 0) {
   stepsRemaining = steps;
   lastStepTime = millis();
 }
 // Generate step pulses at controlled speed
 if (stepsRemaining > 0 && millis() - lastStepTime >= (1000 / currentSpeed)) {
   digitalWrite(STEP_PIN, HIGH);  // Start step pulse
   delayMicroseconds(10);         // Short pulse duration
   digitalWrite(STEP_PIN, LOW);   // End step pulse
   stepsRemaining--;              // Decrement remaining steps
   lastStepTime = millis();       // Update timing
 }
}
// Automatically move motor to target angle with precision control
void moveToAngle() {
 if (!isMovingToAngle) return;  // Exit if not in angle movement mode
 // Calculate angular difference to target
 float angleDifference = targetAngle - cachedCurrentAngle;
 // Normalize angle difference to [-180, 180] range for shortest path
 while (angleDifference > 180) angleDifference -= 360;
 while (angleDifference < -180) angleDifference += 360;
 // Check if target angle is reached within tolerance
 if (abs(angleDifference) <= currentAngleTolerance) {
   isMovingToAngle = false;        // Stop angle movement mode
   digitalWrite(STEP_PIN, LOW);    // Ensure step pin is low
   Serial.println("Target angle reached");
   return;
 }
 // Set motor direction based on angle difference
 currentDirection = (angleDifference > 0);  // Positive = forward
 digitalWrite(DIR_PIN, currentDirection);
 // Enable motor if not already enabled
 if (!isMotorEnabled) {
   digitalWrite(EN_PIN, LOW);  // Enable motor (active low)
   isMotorEnabled = true;
 }
 // Dynamic speed control based on remaining angle
 int stepDelay;
 float absDiff = abs(angleDifference);
 // Slower speeds as we approach target for better precision
 if (absDiff <= 5.0) {
   stepDelay = MIN_STEP_DELAY * 4;      // Very slow for final approach
 } else if (absDiff <= 20.0) {
   stepDelay = MIN_STEP_DELAY * 2;      // Slow for precision zone
 } else if (absDiff <= 45.0) {
   stepDelay = MIN_STEP_DELAY;          // Normal speed
 } else {
   stepDelay = MIN_STEP_DELAY / 2;      // Fast for large movements
 }
 // Take multiple steps toward target (up to 10 per loop iteration)
 for (int i = 0; i < 10 && isMovingToAngle; i++) {
   digitalWrite(STEP_PIN, HIGH);        // Start step pulse
   delayMicroseconds(stepDelay);        // Pulse high time
   digitalWrite(STEP_PIN, LOW);         // End step pulse
   delayMicroseconds(stepDelay);        // Pulse low time
   // Update current angle reading after each step
   cachedCurrentAngle = (as5600.readAngle() * 360.0) / 4096.0;
   angleDifference = targetAngle - cachedCurrentAngle;
  
   // Re-normalize angle difference
   while (angleDifference > 180) angleDifference -= 360;
   while (angleDifference < -180) angleDifference += 360;
   // Check if target reached during movement
   if (abs(angleDifference) <= currentAngleTolerance) {
     isMovingToAngle = false;
     Serial.println("Target angle reached during movement");
     break;
   }
 }
}
// Calculate angle tolerance based on microstepping resolution
float calculateAngleTolerance(uint8_t microsteps) {
 // Calculate minimum angle step and multiply by 2 for tolerance
 return (360.0 / (200.0 * microsteps)) * 2.0;
}

Motion control functionality includes moveMotor() for generating step pulses for basic movement and moveToAngle() for precise positioning using encoder feedback. The system automatically adjusts movement speed based on the distance remaining to the target angle, using slower speeds for fine positioning and faster speeds for large movements.

Power management through the PD system negotiates appropriate power levels with USB-C sources, allowing the motor to receive optimal voltage ranging from 5V to 20V based on system requirements and source capabilities.

Firmware Upload and Web UI Testing

To begin, connect the custom PCB to your computer and upload the code using the Arduino IDE.

If you want to debug via Serial Monitor, don’t forget to enable the CDC (Communication Device Class) setting in the Arduino IDE under Tools > USB Mode before uploading the code.

Uploading Code DIY Wireless Stepper Motor Driver ESP32 TMC2240

Now let's take a look at the UI. It is simple and self-explanatory.
There are a total of 5 cards,

  • Motor Configuration (Sets the motor's current (1000 mA) and microstepping mode (1/16 step))

  • PD Power Control (Selects voltage profile)

  • System Status (Shows the current system status, commands executed, etc.)

  • Motor Control (Provides operational controls with enable/disable buttons, emergency stop, directional movement options, speed adjustment, and positioning controls that allow movement by specific step counts or to target angles using either input fields or a slider interface.)

  • Motor Status (The right panel displays real-time status information, including the current motor angle, rotational speed (RPM), actual motor current draw (mA), and operating temperature.)

UI of Wireless Stepper Controller

Above, you can see the User Interface of this Wireless Stepper motor controller project. which perfectly demonstrates esp32 stepper motor control wifi capabilities through a modern web interface.

 

Stepper Motor Interfacing Projects

Explore other electronics projects and tutorials, covering circuit design, coding, and control techniques. These projects help you understand motor driver integration, speed control, and precise positioning for robotics and automation applications.

 Stepper Motor Interfacing with 8051 Microcontroller

Stepper Motor Interfacing with 8051 Microcontroller

Learn how to control a stepper motor using the 8051 microcontroller (AT89S52) with detailed circuits, drive modes (wave, full-step, half-step), and troubleshooting tips—perfect for robotics, CNC, and automation applications.

Interfacing Stepper Motor with ARM7-LPC2148

Interfacing Stepper Motor with ARM7-LPC2148

Learn how to interface a unipolar stepper motor with the ARM7 LPC2148 microcontroller using the ULN2003 driver

Interfacing Stepper Motor with AVR Microcontroller Atmega16

Interfacing Stepper Motor with AVR Microcontroller Atmega16

In this tutorial we will interface 28BYJ-48 Stepper Motor with Atmega16 AVR Microcontroller using Atmel Studio 7.0. We will be interfacing the stepper motor with both the motor drivers i.e. ULN2003 and L293.

Arduino Stepper Motor Tutorial - Interfacing 28-BYJ48 Stepper Motor with Arduino Uno

Arduino Stepper Motor Tutorial - Interfacing 28-BYJ48 Stepper Motor with Arduino Uno

In this Arduino stepper motor tutorial we will learn about the most commonly available stepper motor 28-BYJ48 and how to interface it with Arduino using ULN2003 stepper motor module.

Interfacing Stepper Motor with STM32F103C8

Interfacing Stepper Motor with STM32F103C8

Learn how to interface a 28BYJ-48 stepper motor with the STM32F103C8 (Blue Pill) using the ULN2003 driver IC. This tutorial covers circuit connections, potentiometer-based speed control, Arduino IDE programming, and stepper motor rotation in both clockwise and anticlockwise directions with practical examples.

 

Video

Have any question related to this Article?

Add New Comment

Login to Comment Sign in with Google Log in with Facebook Sign in with GitHub