How to control stepper motors over wifi access point on ESP32 wrover B?

Hi, I already asked this question on StackExchange, but I didn't got any answer, so I'm posting here too.

I'm trying to control 28BYj48 stepper motors over wifi, with creating an Access Point and connect to it, and send requests to the motor. My final goal is to control it from an app, but now I'm happy with a basic website. As I read and as I understood/tried the esp32 has 2 cores so the multithreading is possible, and the AsyncWebServer is doing it by default. I tried the AP demo code and the multiple stepper motor demo code and it worked, but after merging them it became very slow, that's why I choose multithreading with AsyncWebServer.

I tried based on these tutorials:

Now the code looks like this and the stepper not moving at all, how can I solve this problem?

/*
  Rui Santos
  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>
#include <WiFiClient.h>
#include <WiFiAP.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);


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


    IPAddress local_IP(192, 168, 1, 1);  // fix IP
    IPAddress gateway(192, 168, 1, 1);   // same as local_IP
    IPAddress subnet(255, 255, 255, 0);  // same as local_IP
    // Set these to your desired credentials.
    const char *ssid = "yourAP";
    const char *password = "123456789";

// 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());*/
   if (!WiFi.softAP(ssid, password)) {
    log_e("Soft AP creation failed.");
    while(1);
  }
  WiFi.softAPConfig(local_IP, gateway, subnet);
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
  server.begin();

  Serial.println("Server started");
}


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

The console output sometimes look like this:


ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1344
load:0x40078000,len:13964
load:0x40080400,len:3600
entry 0x400805f0
AP IP address: 192.168.1.1
Server started
Direction set to: CW
Number of steps set to: 1000

sometimes look like this:

E (3760) wifi:addba response cb: ap bss deleted
E (4304) wifi:addba response cb: ap bss deleted
E (4532) wifi:addba response cb: ap bss deleted
E (5134) wifi:addba response cb: ap bss deleted
E (5489) wifi:addba response cb: ap bss deleted
E (6063) wifi:addba response cb: ap bss deleted
E (6800) wifi:addba response cb: ap bss deleted
E (7505) wifi:addba response cb: ap bss deleted
E (8225) wifi:addba response cb: ap bss deleted
E (8815) wifi:addba response cb: ap bss deleted
E (9494) wifi:addba response cb: ap bss deleted
E (10257) wifi:addba response cb: ap bss deleted
E (11569) wifi:addba response cb: ap bss deleted
Direction set to: CCW
Number of steps set to: 100
Direction set to: CCW
Number of steps set to: 1000
Direction set to: CW
Number of steps set to: 1000

For example at this last output the motor turned on CW direction but didn't move in CCW direction.


Tried to make it using multithreading:

/*
  Rui Santos
  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>
#include <WiFiClient.h>
#include <WiFiAP.h>
#include <freertos/semphr.h>

SemaphoreHandle_t motorControlMutex;

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


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


    IPAddress local_IP(192, 168, 1, 1);  // fix IP
    IPAddress gateway(192, 168, 1, 1);   // same as local_IP
    IPAddress subnet(255, 255, 255, 0);  // same as local_IP
    // Set these to your desired credentials.
    const char *ssid = "yourAP";
    const char *password = "123456789";

// 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());*/
   if (!WiFi.softAP(ssid, password)) {
    log_e("Soft AP creation failed.");
    while(1);
  }
  WiFi.softAPConfig(local_IP, gateway, subnet);
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
  server.begin();
  Serial.println("Server started");
}

void taskMotorControl(void *parameter) {
  myStepper.setSpeed(5);
  while (1) {
    if (xSemaphoreTake(motorControlMutex, (TickType_t)10) == pdTRUE) {
      if (newRequest) {
        if (direction == "CW") {
            myStepper.step(steps.toInt());
        } else {
            myStepper.step(-steps.toInt());
        }
        newRequest = false;
      }   
      xSemaphoreGive(motorControlMutex);
    }
  }
}

void taskAP(void *parameter) {
  initWiFi();
    // Web Server Root URL
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(200, "text/html", index_html);
  });

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

void setup() {
  Serial.begin(115200);
  xTaskCreatePinnedToCore(taskMotorControl, "MotorControlTask", 10000, NULL, 1, NULL, 0);
  xTaskCreatePinnedToCore(taskAP, "APTask", 10000, NULL, 1, NULL, 0);
  motorControlMutex = xSemaphoreCreateMutex();
}

void loop() {}

With this the console output was:

Rebooting...
ets Jun  8 2016 00:22:57

rst:0xc (SW_CPU_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:1344
load:0x40078000,len:13964
load:0x40080400,len:3600
entry 0x400805f0

assert failed: xQueueSemaphoreTake queue.c:1545 (( pxQueue ))


Backtrace: 0x4008373d:0x3ffccb10 0x4008ccf9:0x3ffccb30 0x400922c5:0x3ffccb50 0x4008dd09:0x3ffccc80 0x400d291f:0x3ffcccc0

How can I solve this problem?

I moved your topic to a more appropriate forum category @sondor.

The Nano ESP32 category you chose is only to be used for subjects specific to the new Nano ESP32 board, NOT for subjects related to the ESP32 microcontroller in general.

In the future, please take the time to pick the forum category that best suits the subject of your question. There is an "About the _____ category" topic at the top of each category that explains its purpose.

Thanks in advance for your cooperation.

Split the project into smaller parts and make them work separately.

  1. Controlling the steppers.
  2. Receive wireless data.
  3. Managing the network parts like webserver.
  4. Merging the working test parts together.

Thank you!

1 Like

I already split it, and separately everything works fine, the problem appears when I merge the code snippets..

try creating the mutex first..
you spin up threads first and they try to access the mutex that hasn't yet been created..

good luck.. ~q

I changed, the same error, any other idea?

Is it possible that I have to power in both of the motor drivers from an external power source?

strange, was expecting a different error..

never used ESPAsyncWebServer but don't believe you can do what you are trying to do with it..
it looks like it works with call backs by setting server.on, can't do that in a forever loop inside a task..

try stripping out all the webserver code, leaving the 2 task accessing the globals protected by the mutex, add some toggling code so the 2 tasks will cooperate..

get that going first, then try to add some comms back into the task..
but probably going to have to use a different lib, remember your in a separate dedicated task, so it needs to be blocking in nature, no call backs..

good luck.. ~q

Yes, motors should have its own power supply..

I'm thinking go back to single task code and try to speed it up..

~q

I changed the power source, now the motor drivers have a different one, and still, one of the motors is moving one not, the interesting thing is that the first one is not moving the second one is moving.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.