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
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.
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; } }
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; } }
Applications
- Control a two-wheeled robot like Willy via a WiFi or Bluetooth connection