Tags: ,

The ESP32 AC MotorShield is an extension board that allows an ESP32 NodeMCU to drive two DC motors or one stepper motor. We’ve seen how to drive a DC motor using an H-bridge, which can require a lot of wiring when using a simple IC. For an embedded application, such as a Willy robot, you’ll need to drive several motors in parallel. Shields are available to simplify assembly.

Hardware

  • Computer
  • NodeMCU ESP32
  • USB A Male cable
  • AC Motor Shield ESP32
  • DC motor x2 or stepper motor x1
  • External 9V power supply

Operating principle

The ESP32 AC MotorShield uses the SN751044NE double H-bridge. It can drive motors in direction and speed with a nominal voltage between 4.5 and 36V and a current of 1A with an external voltage source:

  • up to two DC motors or one bipolar stepper motor
  • Available GPIOs
  • I2C and UART buses
ac-motorshieldesp32-overview Using AC MotorShield ESP32

Diagram

Compatible with the NodeMCU ESP32 board, the shield is placed directly on the microcontroller. The motor power supply is connected to the VM terminal block.

  • 0.4 (motor A
  • 15, 2 (motor B
  • GPIOs available on other pins

In the case of a shield, the connections are predefined. Motor connections are detailed in the following diagrams.

ac-motorshieldesp32-dcmotor Using AC MotorShield ESP32
ac-motorshieldesp32-stepper Using AC MotorShield ESP32

In the following two examples, we’ve added a web interface to test motors in both directions of rotation.

CC motor management code

To interact with MotorShield ESP32 and drive DC motors, we don’t use any particular library. You can always create your own library to simplify your code.

#include <WiFi.h>
#include <WebServer.h>
#include <Arduino.h>
#include <analogWrite.h>

#define IN1 34    //sensor
#define OUT1  0   //A+ - Black
#define OUT2  4   //A- - Green
#define OUT3  15  //B+ - Red
#define OUT4  2   //B- - Blue

//Motor param
int Steps = 0;
int Direction = 0;
int speedMotor = 150;

//Wifi
const char *ssid = "****";
const char *password = "*****";

WebServer server(80);

const int led = 2;
int stateMotorA = 0,stateMotorB = 0;
char stateMotorTextA[3][10] = {"STOP","CCW!","CW!"};
char stateMotorTextB[3][10] = {"STOP","CCW!","CW!"};
String sensorValue;

/*********************************************************************************************
 * HANDLE FUNCTIONS
*********************************************************************************************/
void handleRoot()
{
    String page = "<!DOCTYPE html>";

    page += "<html lang='fr'>";

    page += "<head>";
    page += "    <title>ESP32MotorShieldV1</title>";
    page += "    <meta http-equiv='refresh' content='60' name='viewport' content='width=device-width, initial-scale=1' charset='UTF-8' />";
    page += "    <link rel='stylesheet' href='https://www.w3schools.com/w3css/4/w3.css'>";

   page += "<script>";

   page += "function getData() {";
   page += "  var xhttp = new XMLHttpRequest();";
   page += "  xhttp.onreadystatechange = function() {";
   page += "    if (this.readyState == 4 && this.status == 200) {";
   page += "      document.getElementById('SensorValue').innerHTML =this.responseText;";
   page += "      console.log(this.responseText);";
   page += "    }";
   page += "  };";
   page += "  xhttp.open('GET', 'readSensor', true);";
   page += "  xhttp.send();";
   page += "}";

   page += "setInterval(function() {getData();}, 2000);   // Call a function repetatively with 2s interval";

   page += "</script>";
 
    page += "</head>";

    page += "<body>";
    page += "    <div class='w3-card w3-padding-small w3-jumbo w3-center' style='color:#fff; background-color:#3aaa35;'>";
    page += "        <p>Motor State A: "; page += stateMotorTextA[stateMotorA]; + "</p>";
    page += "    </div>";

    page += "    <div class='w3-bar'>";
    page += "        <a href='/lefta' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>GAUCHE</a>";
    page += "       <a href='/stopa' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>STOP</a>";
    page += "        <a href='/righta' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>DROITE</a>";
    page += "    </div>";

    page += "    <div class='w3-card w3-padding-small w3-jumbo w3-center' style='color:#fff; background-color:#3aaa35;'>";
    page += "        <p>Motor State B: "; page += stateMotorTextB[stateMotorB]; + "</p>";
    page += "    </div>";

    page += "    <div class='w3-bar'>";
    page += "        <a href='/leftb' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>GAUCHE</a>";
    page += "       <a href='/stopb' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>STOP</a>";
    page += "        <a href='/rightb' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>DROITE</a>";
    page += "    </div>";
    
    page += "    <div class='w3-card w3-padding-small w3-jumbo w3-center' style='color:#fff; background-color:#3aaa35;'>";
    page += "        <p>Sensor value: <span id='SensorValue'>0</span></p>";
    page += "    </div>";

    page += "    <div class='w3-center w3-padding-16'>";
    page += "        <p>Server hosted on NodeMCU ESP32 - <i>Made by <a href='https://www.aranacorp.com' style='color:#3aaa35;'>AranaCorp</a></i></p>";
    page += "        ";

    page += "    </div>";

    page += "</body>";

    page += "</html>";

    server.setContentLength(page.length());
    server.send(200, "text/html", page);
}

void handleLeftA(){
    stateMotorA = 2;
    digitalWrite(led, HIGH);
    server.sendHeader("Location","/");
    server.send(303);
}

void handleRightA(){
    stateMotorA = 1;
    digitalWrite(led, LOW);
    server.sendHeader("Location","/");
    server.send(303);
}

void handleStopA(){
    stateMotorA = 0;
    digitalWrite(led, HIGH);
    server.sendHeader("Location","/");
    server.send(303);
}


void handleLeftB(){
    stateMotorB = 2;
    digitalWrite(led, HIGH);
    server.sendHeader("Location","/");
    server.send(303);
}

void handleRightB(){
    stateMotorB = 1;
    digitalWrite(led, LOW);
    server.sendHeader("Location","/");
    server.send(303);
}

void handleStopB(){
    stateMotorB = 0;
    digitalWrite(led, HIGH);
    server.sendHeader("Location","/");
    server.send(303);
}

void handleInput() {
 sensorValue = String(analogRead(IN1));
 server.send(200, "text/plain", sensorValue); //Send ADC value only to client ajax request
}

void handleNotFound(){
    digitalWrite(led, HIGH);
    stateMotorA = 0;
    stateMotorB = 0;
    server.send(404, "text/plain", "404: Not found");
}


/*********************************************************************************************
 * MAIN
*********************************************************************************************/
void setup()
{
    pinMode(IN1, INPUT); 
    pinMode(OUT1, OUTPUT); 
    pinMode(OUT2, OUTPUT);

    pinMode(OUT3, OUTPUT);
    pinMode(OUT4, OUTPUT);
    
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n");

    pinMode(led, OUTPUT);
    digitalWrite(led, HIGH);

    WiFi.persistent(false);
    WiFi.begin(ssid, password);
    Serial.print("Tentative de connexion...");

    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        delay(100);
    }

    Serial.println("\n");
    Serial.println("Connexion etablie!");
    Serial.print("Adresse IP: ");
    Serial.println(WiFi.localIP());

    server.on("/", handleRoot);
    server.on("/lefta", handleLeftA);
    server.on("/righta", handleRightA);
    server.on("/stopa", handleStopA);
    server.on("/leftb", handleLeftB);
    server.on("/rightb", handleRightB);
    server.on("/stopb", handleStopB);   
    server.on("/readSensor", handleInput);//To get sensor value update

 
    server.onNotFound(handleNotFound);
    server.begin();

    Serial.println("Serveur web actif!");
}


void loop()
{
    server.handleClient();
    //Handle motor A
    switch(stateMotorA) {
      case 0:
        analogWrite(OUT1, 0);
        analogWrite(OUT2, 0);
        break;
      case 1:
        analogWrite(OUT1, speedMotor);
        analogWrite(OUT2, 0);
        break;
      case 2:
        analogWrite(OUT1, 0);
        analogWrite(OUT2, speedMotor);
        break;
    }
    //Handle motor B
    switch(stateMotorB) {
      case 0:
        analogWrite(OUT3, 0);
        analogWrite(OUT4, 0);
        break;
      case 1:
        analogWrite(OUT3, speedMotor);
        analogWrite(OUT4, 0);
        break;
      case 2:
        analogWrite(OUT3, 0);
        analogWrite(OUT4, speedMotor);
        break;
    }
}
ac-motorshieldesp32-stepper-result Using AC MotorShield ESP32
ac-motorshieldesp32-dcmotor-webapp Using AC MotorShield ESP32

Stepper motor management code

To drive a stepper motor, you need to activate the motor coils in a precise sequence. This sequence is described in the stepper.h library’s step function.

/*
   Nema   | Board pin | NodeMCU GPIO |  Arduino IDE
   black        A+           0             0
   green        A-           4             4
   red          B+           15            15
   blue         B-           2             2
*/

#include <WiFi.h>
#include <WebServer.h>
#include <Arduino.h>
#include <analogWrite.h>

#include <Stepper.h>

#define IN1 34    //sensor
#define OUT1  0   //A+ - Black
#define OUT2  4   //A- - Green
#define OUT3  15  //B+ - Red
#define OUT4  2   //B- - Blue


//Stepper param
const int stepsPerRevolution = 200;
int speedMotor = 20;
Stepper myStepper(stepsPerRevolution, OUT1, OUT2, OUT3, OUT4);

//Wifi
const char *ssid = "*****";
const char *password = "********";

WebServer server(80);

const int led = 2;
int stateStepper = 0,stateStepperB = 0;
char stateStepperText[3][10] = {"STOP","CCW!","CW!"};
String sensorValue;
  
/*********************************************************************************************
 * HANDLE FUNCTIONS
*********************************************************************************************/
void handleRoot()
{
    String page = "<!DOCTYPE html>";

    page += "<html lang='fr'>";

    page += "<head>";
    page += "    <title>ESP32MotorShieldV1</title>";
    page += "    <meta http-equiv='refresh' content='60' name='viewport' content='width=device-width, initial-scale=1' charset='UTF-8' />";
    page += "    <link rel='stylesheet' href='https://www.w3schools.com/w3css/4/w3.css'>";

   page += "<script>";

   page += "function getData() {";
   page += "  var xhttp = new XMLHttpRequest();";
   page += "  xhttp.onreadystatechange = function() {";
   page += "    if (this.readyState == 4 && this.status == 200) {";
   page += "      document.getElementById('SensorValue').innerHTML =this.responseText;";
   page += "      console.log(this.responseText);";
   page += "    }";
   page += "  };";
   page += "  xhttp.open('GET', 'readSensor', true);";
   page += "  xhttp.send();";
   page += "}";

   page += "setInterval(function() {getData();}, 2000);   // Call a function repetatively with 2s interval";

   page += "</script>";
 
    page += "</head>";

    page += "<body>";
    page += "    <div class='w3-card w3-padding-small w3-jumbo w3-center' style='color:#fff; background-color:#3aaa35;'>";
    page += "        <p>Stepper State: "; page += stateStepperText[stateStepper]; + "</p>";
    page += "    </div>";

    page += "    <div class='w3-bar'>";
    page += "        <a href='/left' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>GAUCHE</a>";
    page += "       <a href='/stop' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>STOP</a>";
    page += "        <a href='/right' class='w3-bar-item w3-button w3-border w3-jumbo' style='width:33%; height:50%;'>DROITE</a>";
    page += "    </div>";
    
    page += "    <div class='w3-card w3-padding-small w3-jumbo w3-center' style='color:#fff; background-color:#3aaa35;'>";
    page += "        <p>Sensor value: <span id='SensorValue'>0</span></p>";
    page += "    </div>";

    page += "    <div class='w3-center w3-padding-16'>";
    page += "        <p>Server hosted on NodeMCU ESP32 - <i>Made by <a href='https://www.aranacorp.com' style='color:#3aaa35;'>AranaCorp</a></i></p>";
    page += "        ";

    page += "    </div>";

    page += "</body>";

    page += "</html>";

    server.setContentLength(page.length());
    server.send(200, "text/html", page);
}

void handleLeft(){
    stateStepper = 2;
    digitalWrite(led, HIGH);
    server.sendHeader("Location","/");
    server.send(303);
}

void handleRight(){
    stateStepper = 1;
    digitalWrite(led, LOW);
    server.sendHeader("Location","/");
    server.send(303);
}

void handleStop(){
    stateStepper = 0;
    digitalWrite(led, HIGH);
    server.sendHeader("Location","/");
    server.send(303);
}


void handleInput() {
 sensorValue = String(analogRead(IN1));
 server.send(200, "text/plain", sensorValue); //Send ADC value only to client ajax request
}

void handleNotFound(){
    digitalWrite(led, HIGH);
    stateStepper = 0;
    server.send(404, "text/plain", "404: Not found");
}


/*********************************************************************************************
 * MAIN
*********************************************************************************************/
void setup()
{
    pinMode(IN1, INPUT); 
    
    Serial.begin(115200);
    delay(1000);
    Serial.println("\n");

    pinMode(led, OUTPUT);
    digitalWrite(led, HIGH);

    myStepper.setSpeed(speedMotor);

    WiFi.persistent(false);
    WiFi.begin(ssid, password);
    Serial.print("Tentative de connexion...");

    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        delay(100);
    }

    Serial.println("\n");
    Serial.println("Connexion etablie!");
    Serial.print("Adresse IP: ");
    Serial.println(WiFi.localIP());

    server.on("/", handleRoot);
    server.on("/left", handleLeft);
    server.on("/right", handleRight);
    server.on("/stop", handleStop);  
    server.on("/readSensor", handleInput);//To get sensor value update

 
    server.onNotFound(handleNotFound);
    server.begin();

    Serial.println("Serveur web actif!");
}


void loop()
{
    server.handleClient();
    //Handle Stepper
    switch(stateStepper) {
      case 0:
        //stop
        break;
      case 1:
        myStepper.step(1);
        break;
      case 2:
        myStepper.step(-1);
        break;
    }
}
ac-motorshieldesp32-stepper-result Using AC MotorShield ESP32
ac-motorshieldesp32-stepper-webapp Using AC MotorShield ESP32

Applications

  • Control a two-wheeled robot like Willy via a WiFi or Bluetooth connection

Sources