Interrupt signal anomaly when using a motor

Hi Everyone,

I'm using Arduino Mega.
Use case - A high torque dc motor is rotating an external shaft (using 3:1 ratio gears) to which a 3-channel rotary encoder is attached that keeps track of the angle of that shaft. Now, when the motor is rotating and the rotary encoder is not rotating, the arduino is still seeing an interrupt with RISING edge and incrementing the counter in the ISR.

Specification -
Motor is 12 V, 8 Amps, 60 RPM
Encoder is 3-channel optical incremental encoder (using 4 pins - VCC (red), GND (black), A (green), B (white))

Diagram of connections -

Snippets of Code (Complete code) -

#define encoderPinA 2
#define encoderPinB 3
#define ppr 100
#define pi_val 3.14159265359
#define THRESHOLD 0.1

// length of the actual message
#define LEN 6 

// Motor relay - To break the circuit
// NO - Connected to positive of battery, NC - NILL, COM - NO of other two relays

// Clockwise relays - Change polarity of the current going to motor
// NO - COM of motor relay, NC - negative of battery
// COM1 of clockwise - +ve of motor
// COM2 of anticlockwise - -ve of motor

// messages expected 
// Stop - b000000e
// Rotate clockwsie - b000001e
// Clockwise - b-00001e 
int motorRelay = 8;
int clockwiseRelay = 9;
int anticlockwiseRelay = 10;

// Input from vision system
// Message from serial communication
// Position message
char msg[LEN];
char pos_msg[LEN];

// Current steering position
float current_steering_value = 0.0;
// Steering value
float steering_value;
// Steering direction
volatile boolean dir;

volatile int counter = 0;
//volatile int count = 0;
volatile boolean flag;

volatile int var_degrees = 0;


void pulseAtPinA(){
  flag = true;
  if(digitalRead(encoderPinA) == HIGH){
    if(digitalRead(encoderPinB) == LOW){
      counter = counter -1; //Counter-clockwise
    }
    else{
      counter = counter +1; //Clockwise
    }
  }
  else{
    if(digitalRead(encoderPinB) == LOW){
      counter = counter + 1; //CW
    }
    else{
      counter = counter - 1 ; //CCW
    }
  }

  if(flag == true){
    var_degrees = ((360/100.0)*counter);
    var_degrees = var_degrees/3.0;

    current_steering_value = getRadianFromDegree(var_degrees);
    Serial.print("Current steering value is ");
    Serial.print(current_steering_value);
    Serial.print(" Radian and ");
    Serial.print(var_degrees);
    Serial.print(" Degrees");
    Serial.print(" and reaching");
    Serial.println(steering_value);
   
  }

  // Stop the rotation when the steering angle is achieved
    if(dir == 1){
      // Clockwise
      if(current_steering_value >= steering_value){
        Serial.print("Stopping clockwise rotation");
        stopMotor();
      }
    }
    else if(dir == 0){
      // Anti Clockwise
      if(current_steering_value <= steering_value){
        stopMotor();
      }
    }
}

float getRadian(int c){
  return (float)(c*2.0*pi_val) / 100.0;
}

float getRadianFromDegree(int degree){
  return (float)degree*(pi_val/180.0);
}


void setup() {
  Serial.begin(115200);

  // Initialize to the neutral position of the steering
  current_steering_value = 0.0;
  steering_value = 0.0;
  counter = 0;
  
  Serial.print("STARTING");
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
  // Orginal
   attachInterrupt(digitalPinToInterrupt(encoderPinA), pulseAtPinA, RISING);

//  flag = true;

  pinMode(motorRelay, OUTPUT);
  pinMode(clockwiseRelay, OUTPUT);
  pinMode(anticlockwiseRelay, OUTPUT);

  digitalWrite(motorRelay, LOW);

  // NO direction 
  digitalWrite(clockwiseRelay, LOW);
  digitalWrite(anticlockwiseRelay, LOW);

}

void loop() {

	// Serial message from computer
  if (Serial.read() == 'b') {
    Serial.println("Begin");
    Serial.readBytes(msg, LEN);
    Serial.println(msg);
    if (Serial.read() == 'e') {
      Serial.println("End");

      // Angle to which the steering has to rotate
      steering_value = atof(msg);
      Serial.print("Steering Value: "); 
      Serial.println(steering_value);
      Serial.print("Current Steering Value: "); 
      Serial.println(current_steering_value);
      
      // Check direction of rotation
      // Clock wise
      if((steering_value - current_steering_value) > 0){
        
        dir = 1;
        // Serial.print("CLOCKWISE with dir = ");
        // Serial.println(dir);

        // Limit the rotation of steering
        if(steering_value > 2.4* pi_val){
          steering_value = 2.4*pi_val;
        }
      }
      else{
        
        dir = 0;
        // Serial.print("ANTI-CLOCKWISE with dir = ");
        // Serial.println(dir);
        // Limit the rotation of steering
        if(steering_value < -2.4* pi_val){
          steering_value = -2.4*pi_val;
        }
      }

      // Start rotation of the steering wheel
      // It will stop inside the ISR when the angle is achieved
      if(abs(steering_value - current_steering_value) > THRESHOLD){
        if(dir == 1){
          turnClockwise();
        }
        else if(dir == 0){
          turnAntiClockwise();
        }
      }

    }
  }

}

void turnClockwise(){
  // NO direction 
  digitalWrite(clockwiseRelay, LOW);
  digitalWrite(anticlockwiseRelay, HIGH);
  // Start motor
  digitalWrite(motorRelay, HIGH);
}

void turnAntiClockwise(){
  
  // NO direction 
  digitalWrite(clockwiseRelay, HIGH);
  digitalWrite(anticlockwiseRelay, LOW);
  
  // Start motor
  digitalWrite(motorRelay, HIGH);
}

void stopMotor(){
  // Turn the motor off
  digitalWrite(motorRelay, LOW);

  // Clockwise direction 
  digitalWrite(clockwiseRelay, LOW);
  digitalWrite(anticlockwiseRelay, LOW);
}

Switching of Relays run the motor.

Strange behaviour - When the motor is not connected to the circuit, and the relays are still switching off and on, the encoder works fine (there is no anomaly interrupt). But once, the motor is running, interrupt is there, no matter what the state of encoder is. It gives wrong angle.

Please help me solve this. Let me know the possible reasons of why the interrupt is going HIGH when the motor is running.

Updates -
I've updated the post with complete code. Attached the schematic picture in the post.

Image from Original Post so we don't have to download it. See this Simple Image Guide

...R

Please can you re-post the image the right way round?

A photo of your hardware would also be useful as it might suggest where interference is being picked up

Maybe you should use INPUT_PULLUP or the encoder pins?

...R

void pulseAtPinA(){
  counter++;
  Serial.println(counter);
// Ommitted some more Serial.println and angle calculation

  if(current_angle >= angle){
     stopMotor();
  }
}

You should not call any method of the Serial object in interrupt context as the Serial object needs interrupts for some of it's functions and interrupts are disabled in interrupt context.

Please post complete code, the code you posted doesn't compile.

Maybe you should use INPUT_PULLUP or the encoder pins?

Yes, many encoders are open collector output, and the the outputs will be floating without pullups. If it's a noisy environment, the internal pullups might not be strong enough and external pullups might be required.

Can you provide a data sheet for the encoder?

pylon:

void pulseAtPinA(){

counter++;
 Serial.println(counter);
// Ommitted some more Serial.println and angle calculation

if(current_angle >= angle){
    stopMotor();
 }
}




You should not call any method of the Serial object in interrupt context as the Serial object needs interrupts for some of it's functions and interrupts are disabled in interrupt context.

Please post complete code, the code you posted doesn't compile.

Sure. I'll remove all the Serial lines from the ISR. Also, I've posted the complete code that I was using. Please see.

cattledog:
Yes, many encoders are open collector output, and the the outputs will be floating without pullups. If it's a noisy environment, the internal pullups might not be strong enough and external pullups might be required.

Can you provide a data sheet for the encoder?

Hi,

The thing is encoder works absolutely fine when the motor is not running. But once the motor is running, the interrupt signal spikes even if the encoder's A and B channel are not connected to the Arduino's interrupt pins 0 and 1 (digital pin 2 and 3).
Those interrupt pins will call the ISR when a jumper wire is inside them with one end open and not connected to the encoder at all. But if you remove the jumper, there's no interrupt.

Following are the specifications of the encoder -

MH38-10-128-5-26F-1MTR
METRONIX MAKE HOLLOW SHAFT ENCODER

RESOLUTION:100 PPR
O/P: LINE-DRIVER
MOUNTING:SERVO
OUTER DIA:38MM
BORE DIA: 10MM
SUPPLY VOLT:5-26VDC
CHANNEL:A,B,Z,
CONNECTION:RADIAL CABLE 1 MTR LONG

O/P: LINE-DRIVER

You will need external pull downs on the input pins.

Line Driver
A line driver is a sourcing output. When in the on state, a line driver will supply Vcc. In the off state, a line driver will float. Because of this, a sinking input is required for proper operation.

cattledog:
You will need external pull downs on the input pins.

Oh yes, I read about line driver output earlier. But didn't pay attention during the implementation of the system as the system was working when operated alone.
I'm a newbie to the encoders. Can you please share some more details on how I can make it work and is the Interrupt routine in the code correct?

Encoder is 3-channel optical incremental encoder (using 4 pins - VCC (red), GND (black), A (green), B (white))

My implementation - Attaching green wire of encoder (channel A) to interrupt pin 0 (i.e. digital pin 2) and white wire to interrupt pin 1 (digital pin 3). And in code I check for rising edge on channel A.

attachInterrupt(digitalPinToInterrupt(encoderPinA), pulseAtPinA, RISING);

This worked great when the motor is not running (that's the reason, I didn't pay attention to Line driver output). But when motor is rotating, it keeps giving an interrupt signal on digital pin 2 and the angle is all messed up.

Thank you for your help.

I'm a newbie to the encoders. Can you please share some more details on how I can make it work and is the Interrupt routine in the code correct?

Have you tried adding 10K ohm pulldown resistors to pin A and B? You have floating inputs, the wires attached to them are acting as antennas, and picking up the electical motor noise from the motors.

I hope you have spent some time researching quadrature encoders. Your reading algorithm using a RISING interrupt on one pin is going to result in 100 counts per revolution. Your conversion of counts to degrees correctly reflects that.

There are potentially 400 counts per revolution available with different reading algorithms, but precision is not your current issue. The encoder routine is not optimized(for example, with a rising interrupt on the pin, you don't need to test for its state, you know its HIGH) but it should work properly as written.

cattledog:
Have you tried adding 10K ohm pulldown resistors to pin A and B? You have floating inputs, the wires attached to them are acting as antennas, and picking up the electical motor noise from the motors.

Hi,
Yes, I tried adding 10k Ohm pull down resistors on channel A and B. It is still giving unnecessary interrupts.

While I was trying to measure the voltage on the channels using a multimeter, I saw a strange behaviour.
When I connected the black wire of the Multimeter to the ground of Arduino on breadboard (which was connected to the ground of the encoder as well) and let the red wire of the Multimeter hanging, the setup seemed to be working. It was not taking interrupts on its own when the motor is rotating and the encoder isn't.

Can you explain this? And how can I recreate this scenario without the multimeter?
I'm sure the multimeter is somehow sinking the floating voltage due to which the interrupt is only working when the encoder rotates.

Multimeter that I'm using - http://amzn.in/d/gL9t1wA

Thank you!

I would think that some ground connection on the bread board is not solid.

Sometimes the breadboard holes and the jumper wires can be defective. Try moving things around, or try a soldered arrangement. Can you attach a photo of your breadboard?

One thing I don't like about your circuit is the 5v supply to the relays shared with the encoder. What happens if you separate them?

I have doubts that the posted code runs correctly if there are interrupts.

The interrupt code:

void pulseAtPinA(){
  flag = true;
  if(digitalRead(encoderPinA) == HIGH){
    if(digitalRead(encoderPinB) == LOW){
      counter = counter -1; //Counter-clockwise
    }
    else{
      counter = counter +1; //Clockwise
    }
  }
  else{
    if(digitalRead(encoderPinB) == LOW){
      counter = counter + 1; //CW
    }
    else{
      counter = counter - 1 ; //CCW
    }
  }

  if(flag == true){
    var_degrees = ((360/100.0)*counter);
    var_degrees = var_degrees/3.0;

    current_steering_value = getRadianFromDegree(var_degrees);
    Serial.print("Current steering value is ");
    Serial.print(current_steering_value);
    Serial.print(" Radian and ");
    Serial.print(var_degrees);
    Serial.print(" Degrees");
    Serial.print(" and reaching");
    Serial.println(steering_value);
   
  }

prints 64 bytes to the Serial object at least. The buffer is 64 bytes in size and if one additional byte has to be printed it will block and wait for the serial interrupt to remove some bytes from the buffer. As that interrupt is disabled during the ISR the buffer will never get emptied and the MCU will freeze.

From reply #5

Sure. I'll remove all the Serial lines from the ISR.

Can you post the complete current code.

Try 1k pulldowns, if 10k still isn't helping. Motors are very electrically noisy.

Don't make the mistake of running the encoder wiring alongside the motor's power wiring,
try to keep as separate as possible.

cattledog:
From reply #5

Can you post the complete current code.

Sure. Please see the latest code.

// Steering module v1.0
// @author - Himanshu Pahadia
// Contact - h.pahadia@zensar.com

#define encoderPinA 2
#define encoderPinB 3
#define ppr 100
#define pi_val 3.14159265359
#define THRESHOLD 0.1

// length of the actual message
#define LEN 6 

// Motor relay - To break the circuit
// NO - Connected to positive of battery, NC - NILL, COM - NO of other two relays

// Clockwise relays - Change polarity of the current going to motor
// NO - COM of motor relay, NC - negative of battery
// COM1 of clockwise - +ve of motor
// COM2 of anticlockwise - -ve of motor

// messages expected 
// Stop - b000000e
// Rotate clockwsie - b000001e
// Clockwise - b-00001e 
int motorRelay = 8;
int clockwiseRelay = 9;
int anticlockwiseRelay = 10;

// Input from vision system
// Message from serial communication
// Position message
char msg[LEN];
//char pos_msg[LEN];

// Current steering position
float current_steering_value = 0.0;
// Steering value
float steering_value;
// Steering direction
volatile boolean dir;

volatile int counter = 0;
//volatile int count = 0;
volatile boolean flag;

volatile int var_degrees = 0;


void pulseAtPinA(){
  flag = true;
  if(digitalRead(encoderPinA) == HIGH){
    if(digitalRead(encoderPinB) == LOW){
      counter = counter -1; //Counter-clockwise
    }
    else{
      counter = counter +1; //Clockwise
    }
  }
  else{
    if(digitalRead(encoderPinB) == LOW){
      counter = counter + 1; //CW
    }
    else{
      counter = counter - 1 ; //CCW
    }
  }

  if(flag == true){
    var_degrees = ((360/100.0)*counter);
    var_degrees = var_degrees/3.0;
    current_steering_value = getRadianFromDegree(var_degrees);   
  }
    if(dir == 1){
      // Clockwise
      if(current_steering_value >= steering_value){
        stopMotor();
      }
    }
    else if(dir == 0){
      // Anti Clockwise
      if(current_steering_value <= steering_value){
        stopMotor();
      }
    }

}

float getRadian(int c){
  return (float)(c*2.0*pi_val) / 100.0;
}

float getRadianFromDegree(int degree){
  return (float)degree*(pi_val/180.0);
}


void setup() {
  Serial.begin(115200);

  // Initialize to the neutral position of the steering
  current_steering_value = 0.0;
  steering_value = 0.0;
  counter = 0;
  
  Serial.print("STARTING");
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
  // Orginal
   attachInterrupt(digitalPinToInterrupt(encoderPinA), pulseAtPinA, RISING);
  // Testing 
//  attachInterrupt(digitalPinToInterrupt(encoderPinA), pulseAtPinA, CHANGE);
  // attachInterrupt(0, pulseAtPinA, RISING);
//  flag = true;

  pinMode(motorRelay, OUTPUT);
  pinMode(clockwiseRelay, OUTPUT);
  pinMode(anticlockwiseRelay, OUTPUT);

  digitalWrite(motorRelay, LOW);

  // NO direction 
  digitalWrite(clockwiseRelay, LOW);
  digitalWrite(anticlockwiseRelay, LOW);

}

void loop() {
//  Serial.println(counter);
  if (Serial.read() == 'b') {
    Serial.println("Begin");
    Serial.readBytes(msg, LEN);
    Serial.println(msg);
    if (Serial.read() == 'e') {
      Serial.println("End");
      steering_value = atof(msg);
      Serial.print("Steering Value: "); 
      Serial.println(steering_value);
      Serial.print("Current Steering Value: "); 
      Serial.println(current_steering_value);
      
      // Check direction of rotation
      // Clock wise
      if((steering_value - current_steering_value) > 0){
        
        dir = 1;
        if(steering_value > 2.4* pi_val){
          steering_value = 2.4*pi_val;
        }
      }
      else{
        dir = 0;
        if(steering_value < -2.4* pi_val){
          steering_value = -2.4*pi_val;
        }
      }

      if(abs(steering_value - current_steering_value) > THRESHOLD){
        if(dir == 1){
          turnClockwise();
        }
        else if(dir == 0){
          turnAntiClockwise();
        }
      }
    }
  }

}

void turnClockwise(){
  // NO direction 
  digitalWrite(clockwiseRelay, LOW);
  digitalWrite(anticlockwiseRelay, HIGH);
  // Start motor
  digitalWrite(motorRelay, HIGH);
}

void turnAntiClockwise(){
  
  // NO direction 
  digitalWrite(clockwiseRelay, HIGH);
  digitalWrite(anticlockwiseRelay, LOW);
  
  // Start motor
  digitalWrite(motorRelay, HIGH);
}

void stopMotor(){
  // Turn the motor off
  digitalWrite(motorRelay, LOW);

  // Clockwise direction 
  digitalWrite(clockwiseRelay, LOW);
  digitalWrite(anticlockwiseRelay, LOW);
}

cattledog:
Sometimes the breadboard holes and the jumper wires can be defective. Try moving things around, or try a soldered arrangement. Can you attach a photo of your breadboard?

I'm not sure how the multimeter negative pin getting attached to the ground solved my problem.
Here is a picture of the breadboard. Divided into three sections -

Yellow - Wiring from the encoder (4 wires), pulldown resistors between channels and ground.
Purple - Power for Relays
Green - The negative point of Multimeter attached to the ground of Arduino on breadboard.

cattledog:
One thing I don't like about your circuit is the 5v supply to the relays shared with the encoder. What happens if you separate them?

Actually, I was using L298n H-bridges for the motor control. But the motor (rated 8 Amp) was not receiving enough current to keep up the speed. It was giving the same encoder problem even with the H-bridge. With the relays, even though the motor works to its full capacity, the encoder is still giving aberration readings.
I could try using 12 V relays taking power from onboard batteries.
Thanks.

cattledog:
One thing I don't like about your circuit is the 5v supply to the relays shared with the encoder. What happens if you separate them?

Thank you everyone for the help. Thanks @cattledog, this worked for me -
Changing the power source of the three relays I was using. Enable for those is still from the same Arduino. And I kept the Arduino as away from the relays as possible because the pins on interrupts do act like Antennas.

  if(digitalRead(encoderPinA) == HIGH){
    if(digitalRead(encoderPinB) == LOW){
      counter = counter -1; //Counter-clockwise
    }
    else{
      counter = counter +1; //Clockwise
    }
  }
  else{
    if(digitalRead(encoderPinB) == LOW){
      counter = counter + 1; //CW
    }
    else{
      counter = counter - 1 ; //CCW
    }
  }

You don't have to read encoderPinA, it will always be HIGH as the interrupt is at the rising edge.

  if(flag == true){
    var_degrees = ((360/100.0)*counter);
    var_degrees = var_degrees/3.0;
    current_steering_value = getRadianFromDegree(var_degrees);  
  }

You shouldn't do floating point calculations inside an ISR. If you have to do such calculations do them in the loop().