Arduino Projects

Arduino DC Motor Speed Control with Encoder, Arduino DC Motor Encoder

How to control dc motor with encoder:

Arduino DC Motor Speed Control with Encoder- I have been using different types of stepper motors, Servo motors, and DC Motors for quite a long time in different intermediate and advanced level projects. DC motors are more frequently used than Stepper Motors and Servo Motors. If you have watched my videos and read my articles then you should know DC motors are quite different from Stepper Motors and servo Motors. These three types of motors have a different construction. The stepper motors and servo motors are designed in a way that we can control their position. We can control the steps in the forward and reverse directions. Servos can move from 0 to 180 degrees, so you can move to any position between 0 and 180. Likewise, in stepper motors, you can control the steps precisely and this is the reason stepper motors are used in CNC machines, 3d printers, etc. On the other hand, when a dc motor is powered up it immediately starts rotating, it continuously rotates, you can’t exactly control its position until you use a specific technique.  You can’t 100% control a DC motor like Stepper motor and Servo, but if you add an encoder it can really change the whole game. With an encoder being added, you can keep track of the motor revolutions, the amount of distance it has covered, and this way you can make a nice feedback system that can be used to control the DC motor. Then you can stop the DC motor at the position where you want it to stop. The control of a DC motor using an encoder is not that simple, you just simply can’t start by adding an encoder with the DC Motor and start controlling the DC motor, to use an encoder you will need a controller, the controller will read the encoder and then will accordingly control the DC motor as per the pre-defined instructions written by the programmer.  for this project, you will need a microcontroller board like the Arduino Uno or Arduino Nano, or Arduino Mega, or Arduino pro mini, etc. I know beginners are more comfortable with Arduino Uno, Arduino mega, and Arduino Nano, so I will start with the Arduino Uno, the same connections and programs you can also try on  Arduino Nano and Arduino Mega. To get started, you will need Arduino Uno, a Motor driver, a DC Motor, and of course an Encoder. To read the Encoder, we will connect the encoder output pins with Arduino’s pins 2 and 3 which are the interrupt pins. The power wires of the encoder will be connected with the Arduino’s 5V and GND. To keep things simpler, I will start with the simple example code in which I will use pins 2 and 3 as the normal digital pins, we won’t activate the interrupts and then in the second example code, we will use the interrupts.  Without any further delay, let’s get started!!!



Amazon Links:

DC motors with Encoder

12v Adaptor:

Arduino Uno

Arduino Nano

Other Tools and Components:

Top Arduino Sensors:

Super Starter kit for Beginners

Digital Oscilloscopes

Variable Supply

Digital Multimeter

Soldering iron kits

PCB small portable drill machines

*Please Note: These are affiliate links. I may make a commission if you buy the components through these links. I would appreciate your support in this way!




DC Motor with Encoder + Arduino, Circuit Diagram:

The type of the DC motor as you can see in the circuit diagram given below has a built-in encoder. So, it’s totally up to you whether you want to use it as the simple dc motor or you want to use the encoder, or you can use the motor and encoder together at the same time. As you can see in the circuit diagram, the power wires which are the Red and Black wires are not connected but the encoder wires are connected. For this first example, we will only be using the encoder to understand the basics this way you will easily understand how an encoder works. So, we will rotate the motor shaft by hand to see the trigger signals.

Arduino DC Motor Encoder

An encoder works by observing changes to the magnetic field created by a magnet attached to the motor shaft, as the motor rotates the encoder outputs will trigger periodically. When the magnet spins clockwise, output “a” will trigger first, and when rotated counterclockwise on the other hand output “b” will trigger. This way you know exactly which way the motor shaft is rotating. This can be quite handy in situations where you need to control the forward and reverse movement of the DC Motor.

Arduino DC Motor Encoder

Let’s write a very simple program to understand how an encoder works and how to read the encoder outputs.


Arduino Encoder Code:

#define ENCA 2 // pin2 of the Arduino
#define ENCB 3 // Pin3 of the Arduino
int ENCA_DATA;
int ENCB_DATA;

void setup() {
  Serial.begin(9600); // Activates Serial communication
  pinMode(ENCA,INPUT); // sets pin2 as the input
  pinMode(ENCB,INPUT); // sets pin3 as the input
}

void loop() {
  ENCA_DATA = digitalRead(ENCA); 
// We simply read Pin2 of the Arduino and store the result in variable ENCA_DATA
  ENCB_DATA = digitalRead(ENCB); 
// We simply read Pin3 of the Arduino and store the result in variable b
  Serial.print(ENCA_DATA*5); 
  Serial.print(" ");
  Serial.print(ENCB_DATA*5);
  Serial.println();
}

Arduino Encoder Code Explanation:

We don’t need any libraries for this basic program. First of all, I started off by defining the Arduino pins 2 and 3. I will call these pins the ENCA and ENCB. These two pins will be connected with the Encoder outputs.

#define ENCA 2

#define ENCB 3

Next, I defined two variables ENCA_DATA and ENCB_DATA.

int ENCA_DATA;

int ENCB_DATA;

Next, we will need to tell the Arduino whether we want to use the Serial communication? And the pins we just defined are going to be used as the inputs or outputs? All this is done in the void setup() function.

void setup() {

Serial.begin(9600);

pinMode(ENCA,INPUT);

pinMode(ENCB,INPUT);

}

In the void setup() function, I simply activated the serial communication and I selected 9600 as the Baud rate. Next, I set the two pins ENCA and ENCB as the inputs using the pinMode() functions.



The void setup() function executes only once with the Arduino board is first turned ON. The actual code is placed inside the loop() function which executes repeatedly.

void loop() {

ENCA_DATA = digitalRead(ENCA);

ENCB_DATA = digitalRead(ENCB);

I simply defined a variable “ENCA_DATA” which is of the type integer. Then we read the ENCA pin using the digitalRead() function and store the value in variable ENCA_DATA, exactly the same thing I did for the ENCB.

Serial.print(ENCA_DATA*5);

Serial.print(” “);

Serial.print(ENCB_DATA*5);

Serial.println();

}

Next, to write outputs to the computer screen I used the Serial.print() and Serial.println() functions. Inside the parenthesis, you can see I am multiplying the values by 5 to make the plot easier to read.

All you need is to compile the code first to check if any keywords are misspelled, and then you can upload the code.

After the code has been uploaded, you can go ahead and open the Serial Monitor and select the 9600 Baud Rate. Now, you can start by rotating the DC Motor shaft which has the encoder. The Encode signal changes as you rotate the motor shaft, these changes are easier to understand with the serial plotter. So, output “a” is triggered when you rotate the shaft Clockwise and similarly, the output “b” is triggered when you rotate the shaft anti-clockwise. This code has nothing to do with the motor shaft position, the purpose of this code was just to help you understand how these two outputs “a” and “b” are triggered.

Now let’s measure the position of the DC motor shaft. Nothing is changed on the hardware side. We are using the same connections.


Position of the encoder Arduino Code:

/*
 * The purpose of this code is to count the ouput pulses or 
 * the encoder outputs as you rotate the Motor shaft. You can run the 
 * same code on the Arduino Uno, Arduino Nano, Arduino Mega, etc.
 */
#define Encoder_output_A 2 // pin2 of the Arduino
#define Encoder_output_B 3 // pin 3 of the Arduino
// these two pins has the hardware interrupts as well. 

int Count_pulses = 0;
void setup() {
Serial.begin(9600); // activates the serial communication
pinMode(Encoder_output_A,INPUT); // sets the Encoder_output_A pin as the input
pinMode(Encoder_output_B,INPUT); // sets the Encoder_output_B pin as the input
attachInterrupt(digitalPinToInterrupt(Encoder_output_A),DC_Motor_Encoder,RISING);
}

void loop() {
  Serial.println("Result: ");
  Serial.println(Count_pulses); 
}

void DC_Motor_Encoder(){
  int b = digitalRead(Encoder_output_B);
  if(b > 0){
    Count_pulses++;
  }
  else{
    Count_pulses--;
  }
}

Position of the encoder Arduino Code Explanation:

This code is the modified version of the code that I just explained above. I made some changes, which I am sure you can clearly see. Let’s talk about these changes in detail. The pins 2 and 3 connections remain exactly the same.

#define Encoder_output_A 2 // pin2 of the Arduino
#define Encoder_output_B 3 // pin 3 of the Arduino
// these two pins has the hardware interrupts as well.

I defined a global variable Count_pulses and initially stored a value of 0 in it. As this is a global variable so I can access this variable from anywhere inside my Arduino code.

int Count_pulses = 0;

Inside the void setup() function, you can clear see the first three lines of code are exactly the same.

void setup() {
Serial.begin(9600); // activates the serial communication
pinMode(Encoder_output_A,INPUT); // sets the Encoder_output_A pin as the input
pinMode(Encoder_output_B,INPUT); // sets the Encoder_output_B pin as the input
attachInterrupt(digitalPinToInterrupt(Encoder_output_A),DC_Motor_Encoder,RISING);
}

This time I added this attachInterrupt(digitalPinToInterrupt(Encoder_output_A),DC_Motor_Encoder,RISING); this line of code. The attachinterrupt() function is used to activate the hardware interrupt. The attachinterrupt() function takes three arguments as the input. The first one is the pin digitalPinToInterrupt(Encoder_output_A), the second argument is the function name, this function will execute each time when an interrupt happens on the Encoder_output_A pin of the Arduino which is pin 2. The 3rd argument is to tell whether to take action on the rising edge or the falling edge. So, our interrupt setting is completed.

This time inside the loop function many things are changed. This time inside the loop() function we are only using 2 lines of code, the first line of code prints the text Result and the “Serial.println(Count_pulses);” which prints the value stored in the variable Count_pulses.

void loop() {
Serial.println(“Result: “);
Serial.println(Count_pulses);
}




DC_Motor_Encoder() function is a user-defined function it has no return type and does not take any arguments as the input. This is the function that is executed when an interrupt happens on pin2 of the Arduino. So, inside this function we are simply reading the Encoder_output_B pin, the value is stored in variable b. Next, we use an if condition to check if a signal is detected then increment the Count_pulses by 1 or else decrement the Count_pulses.

void DC_Motor_Encoder(){
int b = digitalRead(Encoder_output_B);
if(b > 0){
Count_pulses++;
}
else{
Count_pulses–;
}
}

Upload the code, open the serial monitor, and start rotating the encoder. Rotate the motor shaft in the clockwise direction and also in the anti-clockwise direction. In one direction the value will increase and in the other direction the value will decrease.

Driving the Motor with Encoder and Arduino:

Now, I am sure you have completely understood how an Encoder works, how the Encoder outputs are triggered, and then how to write a simple code to count the pulses. So far we were manually rotating the DC motor shaft to trigger the Encoder outputs, now to do everything automatically it’s time to connect a motor driver so that we can read the position measurements from the Encoder.  Start by connecting the DC motor wires to the outputs of the motor driver circuit. The motor driver also needs an appropriate power supply, which you will need to select as per your DC motor specs. Next, connect the Motor driver ground to the Arduino ground, the PWM input of the motor driver should be connected to an Arduino PWM pin here I have used pin 5.

Arduino DC Motor Encoder

The other two motor driver pins can be connected to any of the remaining Arduino digital pins. Before writing the control algorithm, Let’s test the motor driver start by defining the pins that you connected to the motor driver.



Driving the Motor with Encoder, Arduino Code:

#define ENCA 2
#define ENCB 3
#define PWM 5
#define IN2 6
#define IN1 7

int pos = 0;

void setup() {
  Serial.begin(9600);
  pinMode(ENCA,INPUT);
  pinMode(ENCB,INPUT);
  attachInterrupt(digitalPinToInterrupt(ENCA),readEncoder,RISING);
}

void loop() {
  setMotor(1, 25, PWM, IN1, IN2);
  delay(200);
  Serial.println(pos);
  setMotor(-1, 25, PWM, IN1, IN2);
  delay(200);
  Serial.println(pos);
  setMotor(0, 25, PWM, IN1, IN2);
  delay(20);
  Serial.println(pos);
}

void setMotor(int dir, int pwmVal, int pwm, int in1, int in2){
  analogWrite(pwm,pwmVal);
  if(dir == 1){
    digitalWrite(in1,HIGH);
    digitalWrite(in2,LOW);
  }
  else if(dir == -1){
    digitalWrite(in1,LOW);
    digitalWrite(in2,HIGH);
  }
  else{
    digitalWrite(in1,LOW);
    digitalWrite(in2,LOW);
  }
}

void readEncoder(){
  int b = digitalRead(ENCB);
  if(b > 0){
    pos++;
  }
  else{
    pos--;
  }
}

It is useful to define a function that will set the motor direction and speed. The interface for the setMotor function, I have written here sets the direction and speed of a motor with the pins defined in the last three inputs. Inside the function, I have set the speed with an analog write command. if the direction integer is 1 then the motor will rotate one way by writing a high low combination to the input pins of the driver. If you reverse the order to a low-high combination the motor will rotate in the other direction. Inside the loop function, you can call the setMotor function to drive the motor also write the position to the serial line.


Control DC motor using Encoder feedback loop:

So far we have connected the controller motor driver and motor in a loop but we have not used the position signal from the encoder to control the motor position. We will use a feedback loop. In a feedback loop the control components are often referred to as the plant(motor) here that is the motor and the motor driver. The sensor(encoder) that we are using to measure position is the encoder. In order to actually control the position of the motor you need to provide it with a target position, then you take the difference between the target position and the measured position, the result is the error usually written as e(t).

Arduino DC Motor Encoder

Now that the error has been computed you can use a controller to compute a control signal that is sent to the plant(motor). The control signal is configured so that it will attempt to reduce the error. The control signal is typically written as u(t).

In this project, we will use the PID control algorithm to generate the control signal u(t) the PID control signal is constructed using a sum of three terms a proportional, derivative, and integral term that is what PID stands for. The proportional term is the most important as it is directly responsible for reducing the error, the derivative and integral terms are typically used to smooth out the control system response. The three constants kp, ki and kd determine how strongly each term is represented in the control loop; you can adjust these constants to tune your response. You can estimate the integral and derivative of the error using the simple finite difference approximation. The integral term accumulates the error over time and the derivative computes how quickly the error is changing with the feedback control loop. Complete, you’re ready to write code to control the position of the motor.


DC Motor control with Encoder Feedback, Arduino Code:

#define ENCA 2 
#define ENCB 3 
#define PWM 5
#define IN2 6
#define IN1 7

int pos = 0;
long prevT = 0;
float eprev = 0;
float eintegral = 0;

void setup() {
  Serial.begin(9600);
  pinMode(ENCA,INPUT);
  pinMode(ENCB,INPUT);
  attachInterrupt(digitalPinToInterrupt(ENCA),readEncoder,RISING);
  Serial.println("target pos");
}

void loop() {

  // set target position
  int target = 1200;
  //target = 250*sin(prevT/1e6);

  // PID constants
  float kp = 1;
  float kd = 0.025;
  float ki = 0.0;

  // time difference
  long currT = micros();
  float deltaT = ((float) (currT - prevT))/( 1.0e6 );
  prevT = currT;

  // error
  int e = pos-target;

  // derivative
  float dedt = (e-eprev)/(deltaT);

  // integral
  eintegral = eintegral + e*deltaT;

  // control signal
  float u = kp*e + kd*dedt + ki*eintegral;

  // motor power
  float pwr = fabs(u);
  if( pwr > 255 ){
    pwr = 255;
  }

  // motor direction
  int dir = 1;
  if(u<0){
    dir = -1;
  }

  // signal the motor
  setMotor(dir,pwr,PWM,IN1,IN2);

  // store previous error
  eprev = e;

  Serial.print(target);
  Serial.print(" ");
  Serial.print(pos);
  Serial.println();
}

void setMotor(int dir, int pwmVal, int pwm, int in1, int in2){
  analogWrite(pwm,pwmVal);
  if(dir == 1){
    digitalWrite(in1,HIGH);
    digitalWrite(in2,LOW);
  }
  else if(dir == -1){
    digitalWrite(in1,LOW);
    digitalWrite(in2,HIGH);
  }
  else{
    digitalWrite(in1,LOW);
    digitalWrite(in2,LOW);
  }  
}

void readEncoder(){
  int b = digitalRead(ENCB);
  if(b > 0){
    pos++;
  }
  else{
    pos--;
  }
}

Start by defining global storage variables that can be used to save values between time steps, these are used in the finite difference estimates for the integral and derivative. The first thing that you need to do in the loop function is set a target for the control loop; the control signal will be adjusted over time as the measured position becomes closer to the target. Next, define the constants used in the PID control algorithm start by setting kp to 1 and kd and ki to 0. you can come back and adjust these later to compute the finite difference approximations. We need to compute the time difference t start by recording the current time in microseconds using the micros function then compute t in seconds by taking the difference between the current time and the previous time, be careful that you are performing floating-point arithmetic not integer arithmetic; complete the calculation by storing the current time in the previous time variable for use. In the next iteration of the loop, the error is computed as the difference between the target and measured positions here I have reversed the order because of the way that I wired the motor leads if you find that your control algorithm is not working you can try switching the sign of the error term as I did now compute the derivative and integral of the arrow signal using the finite difference approximations with all that work done you are finally ready to compute your control signal. it is surprisingly simple is not it this signal will tell the plant the direction and speed to turn the motor to send the signal to the motor. We need to convert it into a speed and direction start by computing the PWM signal as the floating-point absolute value of the control signal U.




You also need to cap the PWM signal at 255 as that is the maximum value we can write next to determine the direction by computing the sign of the control signal u with the motor speed and direction computed from the control signal call the setMotor function to write to the motor driver to complete the loop function store the previous value of the error also print your target and measure positions to the serial com, so you can test how well your control algorithm is performing with these parameters. I am seeing a little overshoot after reaching the target. In other words, the motor spins too far and has to reverse directions to achieve the target position one way to reduce overshoot is to increase the derivative term here I have set kd equal to 0.025 this is enough to completely remove the overshoot for this system. Once your system works to achieve a constant target try setting a target that changes with time here I have set a sinusoidal target depending on your target and loading conditions you will need to further tune your PID parameters.

 

Engr Fahad

My name is Shahzada Fahad and I am an Electrical Engineer. I have been doing Job in UAE as a site engineer in an Electrical Construction Company. Currently, I am running my own YouTube channel "Electronic Clinic", and managing this Website. My Hobbies are * Watching Movies * Music * Martial Arts * Photography * Travelling * Make Sketches and so on...

7 Comments

  1. Thank you for an excellent, clear, collation of several interesting hardware and coding challenges. Your efforts a greatly appreciated.

  2. well this is an extraordinary elaborate projet explanation — congratulation!
    Just would like to have the code for a simple speed feedback 0-30VDC. Calculating speed with with the Z pulse of the encoder then using an D/A shield?
    Any code suggestions?

  3. BEST
    I’m looking for a program to control a stepper motor with this code
    Is there anyone who can help me to use this code with stepper motor

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button