Go Down

Topic: Controlling no. of rotations of DC motor with quadrature encoder and arduino due (Read 4681 times) previous topic - next topic

RooDonald

Hi,
I am currently doing a project in which I am using a Twisted String Actuator for an assistive leg brace. My part of the project is coding the DC motors to run a set number of turns at a set speed and then reverse direction. I am extremely new to Arduino and have very little experience in any form of coding. From what I have learned so far and found online from other similar projects I have created a code (that will eventually control 2 DC motors but just 1 for just now) that should turn the motor at full speed (255) for 40 revolutions then pause for a few seconds before turning in the opposite direction at full speed for 40 turns. I was curious to see if tit was actually turning the set amount so I changed the code to run it at 50 speed (roughly 20% of max speed) for just 5 revolutions. When I run this code however, it appears to just be running at the speed I have set for the duration of the delay. I am sure this is what is happening as I have kept the number of turns constant but changed the speed and delay values and they seem to match with what is happening.
Can anyone help me with my code and see where I am going wrong?
The Encoder Counting section of the code I have taken from a project that was very similar and had a similar goal with how they wanted their dc motor to run and so I am not sure that it is correct for what I require.
Thanks in advance.

I am using an Arduino Due
a motor driver: https://www.pololu.com/product/708
DC motor with encoder: https://www.pololu.com/product/2270
the encoder is stated as having 211.2 counts per revolution of the gearbox shaft. It is a 48 cpr quadrature encoder.
Although the wiring pictures attached are for different arduino boards I think it should not matter

Paul_KD7HB

Please explain your plan to STOP the motors after the required number of turns!

Paul

RooDonald

Please explain your plan to STOP the motors after the required number of turns!

Paul
Hi Paul, thanks for the reply.
The end goal will be to have one motor turn clockwise causing the actuation which will be the method for assisting in bending the leg and then once it hits the set number of turns it will stop and go the other direction undoing the actuation. Then I am hoping to have the second motor then do the same pattern and keep alternating between the two motors so there wont be a stop to it. It should just keep going until the whole system is turned off.

cattledog

OP's code formatted
Code: [Select]
// MD03A_Motor_basic + encoder

#define InA1            13                      // INA motor pin
#define InB1            12                      // INB motor pin
#define PWM1            11                      // PWM motor pin
#define encodPinA1      18                      // encoder A pin
#define encodPinB1      19                      // encoder B pin

#define LOOPTIME        100                     // PID loop time
#define FORWARD         1                       // direction of rotation
#define BACKWARD        2                       // direction of rotation

unsigned long lastMilli = 0;                    // loop timing
unsigned long lastMilliPrint = 0;               // loop timing
volatile long count = 0;                        // rotation counter
long countInit;
long tickNumber = 0;
boolean run = false;                            // motor moves

void setup() {
  //Serial.begin(9600); // baud rate
  //Serial.flush();
  pinMode(InA1, OUTPUT);
  pinMode(InB1, OUTPUT);
  pinMode(PWM1, OUTPUT);
  pinMode(encodPinA1, INPUT);
  pinMode(encodPinB1, INPUT);
  digitalWrite(encodPinA1, HIGH);               // turn on pullup resistor
  digitalWrite(encodPinB1, HIGH);
  attachInterrupt(2, rencoder, FALLING);        // 2 is pin 18
}


void loop() {
  moveMotor(FORWARD, 100, 211.2 * 5);            // direction, PWM, ticks number
  delay(2500);
  moveMotor(BACKWARD, 100, 211.2 * 5);           // 211.2=360°
  delay(2500);
  //Serial.print("countInit : " );
  //Serial.println(countInit);

}

void moveMotor(int direction, int PWM_val, long tick)  {
  countInit = count;    // abs(count)
  tickNumber = tick;
  if (direction == FORWARD)          motorForward(PWM_val);
  else if (direction == BACKWARD)    motorBackward(PWM_val);
}


// ********* Encoder counting ********

void rencoder()  {                                  // pulse and direction, direct port reading to save cycles    NB so this might be the bit that tells if the motor is going forwards or backwards
  if (encodPinB1 & 0b00000001)   {
    count++;    //if (digitalRead(encodPinB1)==HIGH)   count ++;
  }
  else                   {
    count--;   // if (digitalRead(encodPinB1)==LOW)   count --;
  }
  if (run)
    if ((abs(abs(count) - abs(countInit))) >= tickNumber)      motorBrake();
}

// ****Code for forward backward and brake  ****

void motorForward(int PWM_val)  {
  analogWrite(PWM1, PWM_val);
  digitalWrite(InA1, LOW);
  digitalWrite(InB1, HIGH);
  run = true;
}

void motorBackward(int PWM_val)  {
  analogWrite(PWM1, PWM_val);
  digitalWrite(InA1, HIGH);
  digitalWrite(InB1, LOW);
  run = true;
}

void motorBrake()  {
  analogWrite(PWM1, 0);
  digitalWrite(InA1, HIGH);
  digitalWrite(InB1, HIGH);
  run = false;
}



Quote
I am using an Arduino Due
Code: [Select]
attachInterrupt(2, rencoder, FALLING);        // 2 is pin 18

For the Due the interrupt number = pin number.

Code: [Select]
void rencoder()  {                                  // pulse and direction, direct port reading to save cycles    NB so this might be the bit that tells if the motor is going forwards or backwards
if (encodPinB1 & 0b00000001)   { count++;   }            //if (digitalRead(encodPinB1)==HIGH)   count ++;                     
else                   { count--;  }              // if (digitalRead(encodPinB1)==LOW)   count --;                     
if(run) 
  if((abs(abs(count)-abs(countInit))) >= tickNumber)      motorBrake();
}


The commented code for the digital read is correct. encodPinB1 is a pin number, not its state. The interrupt is entered when the A pin falls, the state of the B pin gives direction for count++ or count --.  With this encoder algorithm with interrupt on one pin/one edge you will see one quarter of the available counts. This is likely ok for the resolution you need.


RooDonald

Please explain your plan to STOP the motors after the required number of turns!

Paul
Hi Paul, I misunderstood your question earlier. I think my issue is that the motor is running at the speed I want for the correct number of turns I have set but I have no STOP function and so the motor is keeping its speed for the duration of the delay rather than being stopped. Can you help me with the coding for this? Do I simply 'moveMotor' again but set speed to 0 then proceed to the delay function?

RooDonald

The commented code for the digital read is correct. encodPinB1 is a pin number, not its state. The interrupt is entered when the A pin falls, the state of the B pin gives direction for count++ or count --.  With this encoder algorithm with interrupt on one pin/one edge you will see one quarter of the available counts. This is likely ok for the resolution you need.


Thanks Cattledog. For the state of of pin B I had it originally as just 'PINB' however upon verifying the code it says I have 'not declared in this scope'. How do I define the state of pin B in the scope?
Sorry I am still very new to all of this.

cattledog

Quote
For the state of of pin B I had it originally as just 'PINB' however upon verifying the code it says I have 'not declared in this scope'. How do I define the state of pin B in the scope?
Sorry I am still very new to all of this.
I recommend that you use digitalRead() to determine the state of the pin.

Quote
I think my issue is that the motor is running at the speed I want for the correct number of turns
I do not believe that this is the case at all. Are you using a Due or a Mega?
Attached image shows a Mega, but the posting says you are using a Due.

I'm not certain you are ever entering the interrupt with your syntax
Code: [Select]
attachInterrupt(2, rencoder, FALLING);
 
This would be pin 21 on a mega and pin 2 on a due, but the code indicates
Code: [Select]
#define encodPinA1      18                      // encoder A pin


https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

Paul_KD7HB

Hi Paul, I misunderstood your question earlier. I think my issue is that the motor is running at the speed I want for the correct number of turns I have set but I have no STOP function and so the motor is keeping its speed for the duration of the delay rather than being stopped. Can you help me with the coding for this? Do I simply 'moveMotor' again but set speed to 0 then proceed to the delay function?
I glanced at one of the data sheets for the control chip. I did not see any mention of "braking" capability. What have you found?

I am quite familiar with braking AC induction motors using DC. They STOP immediately! But DC motors I am familiar with have a brake on the motor shaft if they need to stop. The problem you are having is caused by inertia, or the energy stored in the moving parts. If you stop the motor suddenly, this energy has to be dissipated by converting it to torque in the motor mounts. That torque is then converted to heat in your mechanism.

Another way to slow and stop the motor is to use the back EMF generated by the motor and available if the power is immediately removed, by connecting the power wires to a small value resistor. For a small motor, the winding resistance is enough to allow the power wires to be just shorted together until the motor stops. IF you do this often enough, the motor will heat.

Paul


siddhu108

hi cattledog
would u plzzz explain me the code you have written  for controlling the dc motor to turn paticular no.of turns and then turn backwards using encoder. i am also doing a similar project which would require the motor to turn particular no.of turns to move the carriage forward and backwards . i have been trying it with limit switches but i need to imrove the accuracy with the introduction of encoder . if i can understand your code u can slightly modify your code to make it work for my project.

Code: [Select]
// MD03A_Motor_basic + encoder

#define InA1            13                      // INA motor pin
#define InB1            12                      // INB motor pin
#define PWM1            11                      // PWM motor pin
#define encodPinA1      18                      // encoder A pin
#define encodPinB1      19                      // encoder B pin

#define LOOPTIME        100                     // PID loop time
#define FORWARD         1                       // direction of rotation
#define BACKWARD        2                       // direction of rotation

unsigned long lastMilli = 0;                    // loop timing
unsigned long lastMilliPrint = 0;               // loop timing
volatile long count = 0;                        // rotation counter
long countInit;
long tickNumber = 0;
boolean run = false;                            // motor moves

void setup() {
  //Serial.begin(9600); // baud rate
  //Serial.flush();
  pinMode(InA1, OUTPUT);
  pinMode(InB1, OUTPUT);
  pinMode(PWM1, OUTPUT);
  pinMode(encodPinA1, INPUT);
  pinMode(encodPinB1, INPUT);
  digitalWrite(encodPinA1, HIGH);               // turn on pullup resistor
  digitalWrite(encodPinB1, HIGH);
  attachInterrupt(2, rencoder, FALLING);        // 2 is pin 18
}


void loop() {
  moveMotor(FORWARD, 100, 211.2 * 5);            // direction, PWM, ticks number
  delay(2500);
  moveMotor(BACKWARD, 100, 211.2 * 5);           // 211.2=360°
  delay(2500);
  //Serial.print("countInit : " );
  //Serial.println(countInit);

}

void moveMotor(int direction, int PWM_val, long tick)  {
  countInit = count;    // abs(count)
  tickNumber = tick;
  if (direction == FORWARD)          motorForward(PWM_val);
  else if (direction == BACKWARD)    motorBackward(PWM_val);
}


// ********* Encoder counting ********

void rencoder()  {                                  // pulse and direction, direct port reading to save cycles    NB so this might be the bit that tells if the motor is going forwards or backwards
  if (encodPinB1 & 0b00000001)   {
    count++;    //if (digitalRead(encodPinB1)==HIGH)   count ++;
  }
  else                   {
    count--;   // if (digitalRead(encodPinB1)==LOW)   count --;
  }
  if (run)
    if ((abs(abs(count) - abs(countInit))) >= tickNumber)      motorBrake();
}

// ****Code for forward backward and brake  ****

void motorForward(int PWM_val)  {
  analogWrite(PWM1, PWM_val);
  digitalWrite(InA1, LOW);
  digitalWrite(InB1, HIGH);
  run = true;
}

void motorBackward(int PWM_val)  {
  analogWrite(PWM1, PWM_val);
  digitalWrite(InA1, HIGH);
  digitalWrite(InB1, LOW);
  run = true;
}

void motorBrake()  {
  analogWrite(PWM1, 0);
  digitalWrite(InA1, HIGH);
  digitalWrite(InB1, HIGH);
  run = false;
}/code]
 
what does the code do?? will it turn the motor simultaneously with the encoder wheel until the required no of ticks are reached?? you have inserted function within a function and also have done local declaration to the function parameters . i am so confused  would u plz expalain in what order the program works pkzzzzz it would be a big help for me

MarkT

Firstly you must use INPUT_PULLUP mode on the Due, the trick with digitalWrite only works on ATmega
processors.

Secondly you are powering your encoder with 5V and sending its outputs direct to the Due pins which
are instantly destroyed by 5V...  I would be more circumspect with this even if the outputs are open-collector
which is liekly - perhaps the encoder works at 3.3V anyway?

Thirdly if you want accurate position feedback you need a PID loop(*), you can't just hope the motor stops
instantly, because they don't.  However if such accuracy isn't an issue the naive "brake now" approach may
be OK (depending on inertia in the system not being too high).

(*) Such a loop takes as input the difference in actual encoder count from desired encoder count,
and its output drives the motor _bidirectionally_.  This means it can handle any overshoot and end up
in the right place.
[ I will NOT respond to personal messages, I WILL delete them, use the forum please ]

RooDonald

Firstly you must use INPUT_PULLUP mode on the Due, the trick with digitalWrite only works on ATmega
processors.

Secondly you are powering your encoder with 5V and sending its outputs direct to the Due pins which
are instantly destroyed by 5V...  I would be more circumspect with this even if the outputs are open-collector
which is liekly - perhaps the encoder works at 3.3V anyway?

Thirdly if you want accurate position feedback you need a PID loop(*), you can't just hope the motor stops
instantly, because they don't.  However if such accuracy isn't an issue the naive "brake now" approach may
be OK (depending on inertia in the system not being too high).

(*) Such a loop takes as input the difference in actual encoder count from desired encoder count,
and its output drives the motor _bidirectionally_.  This means it can handle any overshoot and end up
in the right place.
Hi MarkT,
Thank you for your input. I have recently played about with my code and removed the motor control section and just tried to ensure that I could get results for my encoder counting and this code I have now does this.
Code: [Select]

// MD03A_Motor_basic + encoder

#define InA1            2                      // INA motor pin
#define InB1            4                      // INB motor pin
#define PWM1            6                      // PWM motor pin
#define encodPinA1      18                      // encoder A pin
#define encodPinB1      19                      // encoder B pin

#define LOOPTIME        100                     // PID loop time
#define FORWARD         1                       // direction of rotation
#define BACKWARD        2                       // direction of rotation

unsigned long lastMilli = 0;                    // loop timing
unsigned long lastMilliPrint = 0;               // loop timing
volatile long count = 0;                        // rotation counter
long countInit;
long tickNumber = 0;
boolean run = false;                            // motor moves
int aLastState;  
int aState;
int counter = 0;

void setup() {
 Serial.begin(9600); // baud rate
 //Serial.flush();
 pinMode(InA1, OUTPUT);
 pinMode(InB1, OUTPUT);
 pinMode(PWM1, OUTPUT);
 pinMode(encodPinA1, INPUT);
 pinMode(encodPinB1, INPUT);
 digitalWrite(encodPinA1, HIGH);               // turn on pullup resistor
 digitalWrite(encodPinB1, HIGH);
 attachInterrupt(2, rencoder, FALLING);        // 2 is pin 18
    aLastState = digitalRead(encodPinA1);  

}


void loop() {
  aState = digitalRead(encodPinA1); // Reads the "current" state of the outputA
  // If the previous and the current state of the outputA are different, that means a Pulse has occured
  if (aState != aLastState){    
    // If the outputB state is different to the outputA state, that means the encoder is rotating clockwise
    if (digitalRead(encodPinB1) != aState) {
      counter ++;
    } else {
      counter --;
    }
    Serial.print("Position: ");
    Serial.println(counter);
  }
  aLastState = aState; // Updates the previous state of the outputA with the current state
   

  //  delay(1000);

/*
 moveMotor(FORWARD, 255, 211.2*10);              // direction, PWM, ticks number                    
 delay(2000);
 moveMotor(BACKWARD, 255, 211.2*10);             // 211.2=360°
 delay(2000);
Serial.print("countInit : " );                
Serial.println(count);
*/
}

void moveMotor(int direction, int PWM_val, long tick)  {
 countInit = count;    // abs(count)
 tickNumber = tick;
 if(direction==FORWARD)          motorForward(PWM_val);
 else if(direction==BACKWARD)    motorBackward(PWM_val);
}


// ********* Encoder counting ********

void rencoder()  {                                  // pulse and direction, direct port reading to save cycles    NB so this might be the bit that tells if the motor is going forwards or backwards
if (encodPinB1 & 0b00000001)   { count++;   }            //if (digitalRead(encodPinB1)==HIGH)   count ++;                      
else                   { count--;  }              // if (digitalRead(encodPinB1)==LOW)   count --;                    
if(run)  
 if((abs(abs(count)-abs(countInit))) >= tickNumber)      motorBrake();
}

// ****Code for forward backward and brake  ****

void motorForward(int PWM_val)  {
 analogWrite(PWM1, PWM_val);
 digitalWrite(InA1, LOW);
 digitalWrite(InB1, HIGH);
 run = true;
}

void motorBackward(int PWM_val)  {
 analogWrite(PWM1, PWM_val);
 digitalWrite(InA1, HIGH);
 digitalWrite(InB1, LOW);
 run = true;
}

void motorBrake()  {
 analogWrite(PWM1, 0);
 digitalWrite(InA1, HIGH);
 digitalWrite(InB1, HIGH);
 run = false;
}


Regards to your second point, I am using the 3.3V pin in the Due and you are right, the encoder works just fine with this.
I was wondering if you could help me with your third point about using PID loop? I need the motor to stop where I want it to and to know where it is positioned when it begins rotating again after each loop. The inertia in my setup will be relatively high as the motor will be spinning at 1600rpm. Thanks for any help you can give me.

Go Up