Introduction: Wi-Fi Control of a Motor With Quadrature Feedback

About: Retired due to health. Oldish. My background is in Structural Engineering. Also smith of many trades. The majority of my project will be what can be made sat in a chair within arm's reach, on a plotter, 3D pri…

Please watch the video with Captions/Sub-Text turned on, I have added corrections.

There is a larger project I want to build; one of the main components I will be using is the Harmonic Drive I made.

To control the drive, there are several things I need to be able to do:

  • We need to know its position and direction; this is done by the use of a Quadrature Encoder.
  • We need to change the speed and direction of the motor; this is done using a Motor Driver.
  • We need some sort of communication to tell the motor what to do; this I will do via Wi-Fi.
  • We need some type of controller and visual representation of the Drives State; this I will do by using a Web Server and Web Page.

In this Instructable I hope to show how to do all the above.

Supplies

If you are going to copy this instruction as I have made it, then you will need to first make my Harmonic Drive.

I will be using the same motor, but I will be making my own Quadratic Encoder.

1 ESP8266

  • I am using a "New NodeMCU v3" breakout board. (Not sure about the new, it's what is printed on it)
  • Other microprocessors can be used as long as Wi-Fi, enough Memory and ISPFFS capabilities.

1 L298

  • Motor Driver.
  • Any version will do as long as it has it has its own power if it needs more than 3.3 volts.
  • It needs to be capable to handle 24 volts for the motor.

1 LM2596

  • DC-DC Step-Down Voltage Regulator. I have used an Adjustable Regulator set to 3.3 volts.
  • Any Voltage Regulator will do that can accept 24 volts and gives out 3.3 volts. (500mA should be fine)

A 24 volts DC Power Supply.

  • I have used an old AC Adaptor from an old Flat Bed Scanner had.
  • Any adapter will do, also a battery pack.

Cables will be needed to connect it all together.

Some 3D Printed parts for a new Quadrature Encoder.

  • Motor Mount E
  • Encoder Sensor Holder
  • 2 Encoder Mount
  • Disc

Some Acrylic paint to paint the Encoder Disc.

Step 1: Quadrature Encoder

The first thing I need to do is change the Quadrature Encoder.

  • When I first built my Harmonic Drive, I designed it with a motor I got of eBay cheap.
  • This worked very well but it had an Encoder Disk with 334 lines. This was way over the top for my needs.
  • If the motor was spun too fast the Microcontroller could not keep up with the pulses.

So, I have made my own with only 4 shutters.

  • With only 4 Shutters I can run the Motor at its full speed and the Microcontroller has no issue keeping up.

A Quadrature Encoder work by having a pair of sensors, detecting two states ON or OFF (Yes/No).

  • The two sensors are arranged so that by combining the two values, we can detect movement and direction.
  • If we take a look at incremental encoder on Wiki it gives details of the Quadrature Encoder output.
  • Wiki shows a disk with two sets of shutters, I am only using one set of shutters, so, the sensors I am using need to be set at an angle relative to the shutter angle.
  • The type of Quadrature Encoder I am making has sensors bespoke to the shutter disk.
  • One sensor has to be set so that when one sensor is over an edge, the other sensor should be in the centre of the shutter or the gap.
  • This offsets the pulses by half a pulse. as shown in the oscilloscope.

If you view the results over time on the oscilloscope, you will see the there is a possibility of the four states required for Quadrature Encoding.

  • HIGH, HIGH
  • LOW, HIGH
  • LOW, LOW
  • HIGH, LOW

Even though we only have four shutters (we could have more than four shutters), there are four states that can be read from the sensor each time one shutter passes to the next.

  • Therefor the resolution of this encoder is actually sixteen points per revolution.

Also knowing the sequence in which the pulses from each sensor occur, we know which direction the motor is rotating.

  • for example: If we start taking readings when both sensors are LOW. Depending which sensor goes HIGH first, tells us which direction the motor turned.

Step 2: Quadrature Encoder [Assembly]

Hopefully the exploded view of the new encoder shows you how it all goes together.

Ther are two circuits:

  • Both PBCs have Power (3 to 5 volts) and GND. The RX PBC also has two signal wires.
  • TX, has Infrared LEDs
  • RX, has Infrared receivers.

For those that have KiCad, here are the files I made.

I have done a zip file of the required files needed to get the PCB's made out.

Basically, all this is two of these: IR Infrared Slotted Optical Sensor Module but I needed something smaller.

Step 3: To Continue

The rest of the Instructible should work with any motor that has an AB Quadrature Encoder.

  • As long as you have the correct driver for your motor.

You should be able to apply the following with your own projects with a little tinkering.

I am assuming you have some knowledge of:

HTML web pages

Java Script

Arduino code.

I have done all the code needed to do this Instructable you just need to know how to upload it to your Arduino Board.

If I manage to explain it OK, then it should be just a matter of copy and paste certain elements should you wish to modify things for your own projects.

Step 4: Arduino IDE

I am using an ESP8266 "New NodeMcu v3" for this project, I want to control things via Wi-Fi.

  • I don't know about the "New" part, but "New NodeMcu v3" is what is written on the board.
  • Using the Arduino IDE with the ESP8266 Boards install there is a choice for "NodeMcu 1.0 (ESP-12E Module)". This is the choice I make.
  • Because it is based on the ESP microcontrollers; I will be using Libraries from Espressif Systems.
  • As well as the Arduino architecture.

There is also a plugin required for the Arduino IDE.

  • The plugin enables uploading files to the SPIFFS (Serial Peripheral Interface Flash File System)
  • I have zipped up a copy of the plugin on my Google Drive: ESP8266_FS_Plugin.zip

The latest Arduino IDE now makes it easy to add extras to it, no more looking for that user info file.

  • Addons and custom boards can be added to the "Arduino" folder in the user's "documents" Folder.
  • In the "Arduino" folder there are special folders to place such things.
  • Inside the "Arduino" folder, you need a folder called "Tools", if there is not one make one.
  • Place the unzipped folder "ESP8266FS" inside the "Tools" folder.
  • The full path should be as shown in the image.
  • Re-start the Arduino IDE if you have it open.
  • There should be a new command under "Tools".

There are several options under the "Board" chosen when using an ESP type microcontroller.

  • The first important selection is how much memory to allocate to ISPFFS.
  • I have chosen 2M. This splits the memory for code and files.
  • The second important selection is what do you want to overwrite?
  • If it is the first time with a new project I suggest "All".
  • Then as you just edit and change only certain things, select the appropriate choice.

Uploading "Files" to ISPFFS

  • It is very easy to upload files to the memory set aside for the ISPFFS.
  • Inside your "Project Folder" you need to make a folder called "Data".
  • Any file you place inside the "Data" folder will be uploaded to the memory for the ISPFFS, when you click the new command after installing the plugin.
  • Make sure the files in the "Data" folder do not exceed the size you have allocated.

Note!

  • There is another ISPFFS plugin called "ESP8266LittleFS". This did not work with the Board Manager version I am using.
  • The Board Manager version I am using is 2.5.0
  • Newer Board Manager versions did not work with the Interrupts.

I have uploaded my Front_View_360x360.png to my Google Drive.

  • Uploading a .png here automatically adds it to the media slot.
  • This file needs to be placed in the "Data" folder mentioned and uploaded to the memory allocated for ISPFFS.

Step 5: Server Code

I will not list the whole code here I will just do snippets of what I think are the important bits.

  • The complete code is attached and needs to be put in a folder of the same name, without the ".ino".

Note!

  • You will note that the "Setup" and the "Loop" are at the end of the code.
  • I don't think the Arduino architecture works well with the ESP Boards.
  • I think some code still needs to be defined before use.

Code

The code starts with comments with info on what the code is for and gives credit where due.

Lines 56-58

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "FS.h"

These are the libraries we are going to use.

  • I wanted to keep it to a minimum and simple.

Line 60

#include "Page_by_val_HTML.h"

This is for the web Page.

  • I have put the code in its own header file so that I can use other HTML editing software to modify it.
  • Using HTML editing software enables me to see what it will look like while I edit it.
  • I will do a separate step for the Web Page.

Lines 93-94

const char* ESP8266_SSID_AP = "ESP8266 WiFi Drive";
const char* ESP8266_PASSWORD_AP = "12345678";

Line 93 gives the Name (SSID) of the Server you will see when looking for Wi-Fi connections.

Line 94 is the Password you want to use.

  • The Password should be at least 8 characters long, or it will not be updated.
  • Also, a reminder that there is a setting in the Arduino IDE that needs to be set if changing Wi-Fi details.

Lines 135-138

IPAddress local_ip(192, 168, 4, 1);      //IP for AP mode
IPAddress gateway(192, 168, 100, 1);     //IP for AP mode
IPAddress subnet(255, 255, 255, 0);      //IP for AP mode
ESP8266WebServer server(80);

These set addresses for the server.

  • Line 135 can be changed if you have some conflicts with other Wi-Fi connections.
  • Line 138 is the port number, 80 is standard connection for HyperText Transfer Protocol (HTTP).

Lines 140-141

bool spiffsActive = false;
#define IMAGE_FILE "/Front_View_360x300.png"
  • The Boolean is just something to set to say we have activated SPIFFS
  • The definition just defines the path to the image I want to use that is stored in the memory of the SPIFFS.

Lines148-201

void PinChange() {

/*
Read A and B signals
*/
boolean A_val = digitalRead(SENS_A);
boolean B_val = digitalRead(SENS_B);

/*
Record the A and B signals in seperate sequences
*/
seqA <<= 1;
seqA |= A_val;

seqB <<= 1;
seqB |= B_val;

/*
Mask the MSB four bits
*/
seqA &= 0b00001111;
seqB &= 0b00001111;

/*
Compare the recorded sequence with the expected sequence
*/
if (seqA == 0b00001001 && seqB == 0b00000011) {
NewPossition++;
if (NewPossition >= PINGS_PER_REV) {
NewPossition = 0;
}
if (NewPossition >= ReqPossition) {
StopMotor = true;
}
if (NewPossition >= (ReqPossition - MOTOR_SLOWDOWN)) {
SlowMotor = true;
}
}

if (seqA == 0b00000011 && seqB == 0b00001001) {
NewPossition--;
if (NewPossition <= -PINGS_PER_REV) {
NewPossition = 0;
}
if (NewPossition <= ReqPossition) {
StopMotor = true;
}
if (NewPossition <= (ReqPossition + MOTOR_SLOWDOWN)) {
SlowMotor = true;
}
}
CurPossition = NewPossition;

}

This is called every time there is a pin state change on the signal pins from the encoder.

  • It will be called if either signal goes HIGH or LOW.
  • Because we are counting all state changes, we increase the resolution of the Encoder from 4 to 16 Tick per revolution of the disk.
  • The code also sets a Boolean to slow the motor down as it approaches its requested position. This is to reduce the amount of over-run.
  • In an Interrupt Routine it is best to only set Switches and Counts. The Switches then decide actions in the main loop.

Lines 517-523

	if (SPIFFS.begin()) {
Serial.println("SPIFFS Active\n");
spiffsActive = true;
}
else {
Serial.println("Unable to activate SPIFFS\n");
}

This starts the SPIFFS.

  • With the SPIFFS active, we can get files that have been uploaded to the memory used by the SPIFFS.

Lines 580-589

	server.on("/", Handle_Root);
server.on("/Front_View_IMAGE", HTTP_GET, Handle_Front_View_IMAGE);
server.on("/Get_Speed", Handle_Get_Speed);
server.on("/Get_Position", Handle_Get_Position);
server.on("/0", HTTP_GET, Handle_0);
server.on("/90", HTTP_GET, Handle_90);
server.on("/180", HTTP_GET, Handle_180);
server.on("/position", HTTP_GET, Handle_Position);
server.on("/Set_Speed", HTTP_GET, Handle_Set_Speed);
server.onNotFound(Handle_Not_Found);

This is the magic bit. This is how the Web Page tells the microcontroller what to do.

  • If you were to right-click an Image on a Web Page, and you got the Image Link. It may look something like this: http://192.168.4.1/Front_View_IMAGE
  • Everything on the page will start with the address we logged on to http://192.168.4.1 it is what comes after this what the Server looks for.
  • The "/" and anything following.
  • If it is just the "/", this is the root, the least the web page will send to the server. In this situation if you told the Web-Browser to refresh, then this would be sent, which in turn asks the server to resend the page.
  • Usually when any request is sent to the Server, we send back a (the) Web-Page anyways.

I will do two examples:

  • One where we return the Web-Page because this could involve changing the page.
  • One where we don't return the Web-Page because we want something that makes up the Web-Page.

The server.on(URL, Method, Function); Has three parts to it.

  • URL, this is what the Server is looking for, a certain piece of text sent by the Web-Page.
  • Method, this is how it deals with the string received. In our case it is always going to be "HTTP_GET" it is using the HyperText Transfer Protocol.
  • Function, this is the name of the function we will use when we receive this URL from the Web-Page.

First example, let's take the:

server.on("/0", HTTP_GET, Handle_0);

The Server sees the text sent is "/0", the Server sees use Hypertext Transfer Protocol and run the function: Handle_0.

  • The Server calls the function: Handle_0.
void Handle_0() {
MoveGear(0, true, Request_Speed);
Serial.println("Angle: 0");
server.send(200, "text/html", PAGE_HTML);
}

Here we:

  • Run the command to move the Gear to 0.
  • Print the angle requested to the Serial.
  • Send the code 200 = Success/OK we are using HTML text to send the Web-Page.

I will explain this later, basically the Web-Page is defined as a char[ ] array called PAGE_HTML.

If the Web-Page sent a message request that involves a different Web-Page, the other Web-Page would be sent instead.

The second example, let's take the:

server.on("/Front_View_IMAGE", HTTP_GET, Handle_Front_View_IMAGE);

This time the Web-Page is asking for this while the Web-Page is being loaded.

  • In the script for the Web-Page we have code asking for a picture.
  <img
    id="gear"
    src="Front_View_IMAGE"
    alt="Front_View">

The script says the source of the image = Front_View_IMAGE

  • So, the Web-Page asks the server for Front_View_IMAGE.
  • The Server sees the text sent is "/Front_View_IMAGE", the Server sees use Hypertext Transfer Protocol and run the function: Handle_Front_View_IMAGE
  • The Server calls the function: Handle_Front_View_IMAGE.
void Handle_Front_View_IMAGE() {

Serial.println("image request");
if (spiffsActive) {
File file = SPIFFS.open(IMAGE_FILE, "r");
size_t sent = server.streamFile(file, "image/png");
file.close();
}
else {
Serial.println("SPIFFS is not active.\nImage cold notbe loaded.");
}
}

Here we:

  • Send a message to the Serial. I put this here for debugging.
  • Check that SPIFFS is active.
  • Get a File, we call it file. The IMAGE_FILE which represents the path of the file.
  • IMAGE_FILE is equal to: "/Front_View_360x300.png"
  • we send that file (as a stream) to the Web-Page. we tell the Web-Page what type of file we are sending. It's an image in the png format.
  • Close the file.


Hopefully from these examples and looking at the code you can see where you can modify it to suit your own projects:

  • Change/Modify/Add server.on() watchers.
  • Change/Modify/Add you own functions to do things when key text is received.
  • Add and get your own files in the memory allocated for the SPIFFS.

Step 6: Web Page

It seems .h files are not supported here. I have put the Web-Page on my Google Drive: Page_by_val_HTML.h

  • This header file needs to be in the same folder as the .ino file.

The Web-Page is encased in a char array:

static const char PAGE_HTML[] = R"rawliteral(     ----- all script is here -----       )rawliteral";

Putting the script for the Web-Page in a separate header file and encasing it in an array lets me:

  • Call the Web-Page into the server using its defined name: PAGE_HTML.
  • Edit it using HTML software.

The majority of the script is HTML to make a page that you like.

  • It has buttons to press.
  • Each button calls a function in the Java Script.

Note!

Remember when creating <buttons> or <input> to give them all the watchers needed to work with whatever device is going to be used. e.g.:

  • onmousedown
  • ontouchstart
  • onmouseup
  • ontouchend
  • onmousemove
  • ontouchmove

Java Script

    window.onload = function () {
      Get_Speed();
    }

This is telling the Web-Page to run the function: Get_Speed(); once the page has loaded.

  • I do this when the page loads, because I want it to be synchronized by what is set on the microcontroller.

Let's start the magic.

Get Information

        function Get_Speed() {
            var xhttp = new XMLHttpRequest();
            xhttp.onreadystatechange = function () {
                if (this.readyState == 4 && this.status == 200) {
                    var Speed = this.responseText;
                    Speed_Recieved = true;
                    document.getElementById("textSpeedValue").innerHTML = Math.ceil((Speed / 1023) * 100).toString() + "%";
                    document.getElementById("speedSlider").value = Speed;
                }
            }
            xhttp.open("GET", "Get_Speed", true);
            xhttp.send();

        }

This is a typical way to request something and attach it to a variable.

var xhttp = new XMLHttpRequest();
  • First, we need to create a new XMLHttpRequest. I have called this one xhttp.

We are going to open and send this request, but before we do we need to state what to ask and what to do with the result, when the server is ready to send it.

When the server is ready and sends the requested data run this function.

xhttp.onreadystatechange = function () {   --- What you want it to do when the server send what you ask for ---   }

It checks that the server is ready = code 4 and checks all is OK = code 200.

  • The response text is put into a variable I have named Speed.
document.getElementById("textSpeedValue").innerHTML = Math.ceil((Speed / 1023) * 100).toString() + "%";

This finds the element in the Web-Page with the ID "textSpeedValue" and using a formula changes the Text to the value of Speed in a percentage.

  • The element in the Web-Page with the ID "textSpeedValue" is the text above the Speed slider.
document.getElementById("speedSlider").value = Speed;

This finds the element in the Web-Page with the ID "speedSlider" and sets the value to the Speed.

  • the element in the Web-Page with the ID "speedSlider" is the Speed slider, when you set the value, it moves the slider.
 xhttp.open("GET", "Get_Speed", true);
 xhttp.send();

Once we have told the XMLHttpRequest that I have called xhttp to do, we now open it with the code "Get_Speed" for the server to use (The server will see "/Get_Speed"). We then send it.

Send Information

    function Move_To(x) {
      Get_Speed();
      var xhr = new XMLHttpRequest();
      xhr.open("GET", "/position?value=" + x, true);
      xhr.send();
      document.getElementById("textPositionValue").innerHTML = x + "&deg;";
      document.getElementById("positionSlider").value = x;

    }

This function is called when a button is pressed, or the position slider is moved.

  • Get_Speed(); Justs calls the function I have explained, it may not be needed.
var xhr = new XMLHttpRequest();
  • First, we need to create a new XMLHttpRequest. I have called this one xhr.

Because we are not expecting any data back, we do not need to tell it to do anything when the server replies.

      xhr.open("GET", "/position?value=" + x, true);
      xhr.send();
  • we just open it with the code "position" for the server to recognize.
  • But this time we also add a Question Mark "?" and an argument called "value" which equals some text.
  • The server will see "/position" and the rest of the text. When we send it.

The Server Side

  • When we send this request, the server will see the "/position" part of the string, and respond using:
	server.on("/position", HTTP_GET, Handle_Position);
  • When it is caught here by the server; it calls the function "Handle_Position".
void Handle_Position() {
if (server.arg("value") == "stop") {
MotorStop();
Serial.println("Motor Stoped");
}
else {
int thisAngle = server.arg("value").toInt();
MoveGear(thisAngle, true, Request_Speed);
Serial.print("Angle: ");
Serial.println(thisAngle);
}
server.send(200, "text/html", PAGE_HTML);
}

Here we check the argument sent.

  • The argument is what we wrote after Question Mark (?), which I named value.
  • value is basically a variable that can equal anything (as text).
  • In this case it will be what the value of "x" was when it was sent by the Web-Page.
  • "x" could be any word or number. Either way it all comes as text.
  • I know that the possibilities could be "stop" or a number "0" to "359".
  • So, the function is split into two outcomes.
  • If the value = "stop" we just call the MotorStop() function.
  • If it not "stop" then we know it is going to be a number.
  • If it is a number, then we have to convert the text to an integer. (Everything sent is text)
  • When it has been converted to an actual number, we can use it in a function or do some other thing with it.
  • Here the outcome could be: Stop or Move to a Position.
  • When all is done, we send a message back to the Web-Page saying all is OK = code 200.
  • In this situation we send the Web-Page back, we are only using one Web-Page.

So hopefully you are now able to use/modify this code for your own projects.

  • For now, just upload it as is to your microprocessor.
  • The first step to making something, is to have something that works before you modify it.
  • Perhaps start by changing the image or the title, then work from there.

To learn more about making webpages and Java Script I recommend: W3Schools

Step 7: The Circuit

I have done a fritzing to make it easy to see how it all connect together.

  • There are PDFs if you just want to view them.

Note!

The Pin Numbers on an ESP Breakout Board don't always refire to the Pin Definitions in the Arduino IDE.

  • It is best looking for a chart when using ESP microcontrollers.
  • I have put reference to the pins in the code. The GPIO Pin Numbers are what the Arduino IDE refire to.

Important!

The Output voltage of the Buck, DC-DC Step-Down Voltage Regulator. must not exceed 3.3 volts.

Step 8: Wi-Fi Connection

The Wi-Fi connection to the ESP is an "Access Point"

  • This is not connected to your local network; you connect to the Network of the ESP8266.

This means that the device you use to connect to the ESP8266 must have:

  • Wi-Fi
  • A Web Browser.

Because I want to connect from my computer which is connected to the internet with a cable, I have a USB Wi-Fi adapter plugged in to my computer.

  • With the adapter plugged in I can connect to the ESP8266 server, the same way I would using my Phone.

The only difference between the phone and computer is:

  • If you are connecting to the ESP8266 server for the first time.
  • You should wait on the Wi-Fi Screen for a message.
  • The message will be asking you if you want to stay connected.
  • The reason being it is not the internet.
  • If you don't tell it to say connected to the ESP8266, it will disconnect.

The password will be: 12345678

  • The name shown and password used can be changed in the code.

Once you are connected with your Computer or Phone.

  • You need to open your Web Browser.
  • In the Web Address type: 192.168.4.1
  • This also can be changed in the code.

You should now have the motor control page.

  • Using the buttons or sliders, you should be able to control your motor.

Step 9: Closing

Hopefully this will act as a template for those that want to design their own web-based control pages.

  • No more having to use the predesigned wizards on the internet.

The projects don't have to be controlling motors.

  • Because it is the GPIO pins you are controlling, the type of control is endless.

Need more GPIO pins?

  • There is no reason why this code could not work for the ESP32. (Need to use ESP32 Libraries)
  • A different plugin will be required for the ISPFFS.

Hope to see some cool projects.

Anything Goes Contest

Participated in the
Anything Goes Contest