Magnetic Encoder Control of Geared DC Motor

Hello all,

In my current project I am attempting to control the back and forth rotation of a geared DC motor using a magnetic encoder attached to the motor’s axle. I understand that I must use interrupts to read the position of the encoder and have done so successfully - I have a working sketch that displays the encoder’s position in the serial console as either the motor rotates the encoder or I rotate the encoder by hand (when the motor is powered off). As briefly mentioned, I have also figured out how to direct the motor back and forth inside of the sketch main loop.

My issue is when I try to stop the DC motor (setting its voltage input pin to LOW) by using the encoder’s current position determined by the interrupt.

What I am doing now is using the interrupt to keep track of the motor axle’s position while the motor is rotating. Once the axle’s position reaches some predefined value, I tell the program to stop the motor. I do this by the following method: in the main loop, I turn the motor on, then the interrupt is used to keep track of the motor’s position (I log this position to the console just for my sanity until it functions properly - I know it slows down the program), I then use an if statement to check if the motor’s position has met or passed the predefined value, if this statement is passed, then the motor’s voltage is set to LOW and it should stop.

When I run the sketch, the motor’s position appears in the console as desired but it does not stop at all. This is a programming issue, from my understanding, so I will not post the circuit diagram (as all the components work properly).

Attached is my code, any help is greatly appreciated, I have spent hours reading to no avail.

volatile boolean fired;
volatile boolean up;

volatile long rotaryCount = 0;

#define PINA 2
#define PINB 3
#define INTERRUPT 0  // that is, pin 2

int BIN_1 = 11;
int BIN_2 = 10;
int MAX_PWM_VOLTAGE = 240;

// Interrupt Service Routine for a change to encoder pin A
void isr (){
 if (digitalRead (PINA))
   up = digitalRead (PINB);
 else
   up = !digitalRead (PINB);
 fired = true;
}  // end of isr

void setup() {
    pinMode(BIN_1, OUTPUT); // motor output pin 1
    pinMode(BIN_2, OUTPUT); // motor output pin 2
    digitalWrite (PINA, HIGH);     
    digitalWrite (PINB, HIGH);
    attachInterrupt (INTERRUPT, isr, CHANGE);   // interrupt 0 is pin 2, interrupt 1 is pin 3

    Serial.begin (9600);//115200);
    Serial.println("READY");
    Serial.println(rotaryCount);
}

int stepSize = -10;

void loop() {
    
    analogWrite(BIN_1, MAX_PWM_VOLTAGE);//MAX_PWM_VOLTAGE); // turn input 1 on full (reverses motor direction)
    if (fired){
      if (up)
        rotaryCount++;
      else
        rotaryCount--;
      fired = false;
      Serial.print ("Count = ");  
      Serial.println (rotaryCount/12);
    if (rotaryCount/12 <= stepSize) { // divided by 12 to find the actual number of rotations of the encoder.  there are 12 detection points on the encoder corresponding to 1 full rotation
      analogWrite(BIN_1, 0); // turn input 1 off
    }
   }  // end if fired
    
    
}

Please provide details of the motor and particularly the motor driver you are using. And a circuit diagram might also be useful…you can’t really be confident that it all works properly if it doesn’t stop when you want it to.

Steve

You should manage the rotaryCount variable entirely in the ISR. Then the main code just needs to read it (in a critical section) to determine what actions to make.

You are turning the motor on everytime through loop(), rather than once, so it will always turn.

Thanks for your responses,

slipstick:
Please provide details of the motor and particularly the motor driver you are using. And a circuit diagram might also be useful…you can’t really be confident that it all works properly if it doesn’t stop when you want it to.

Steve

Steve,

All the components are from Pololu. The motor is a Micro Metal Gearmotor rated for 2.7 - 18V paired with Magnetic Encoder 12 CPR. The motor controller is a Pololu DRV8833 Dual Motor Controller rated for 1.2A, 2.7V-10.8V. My circuit and code works properly when using the motor and encoder by themselves i.e. I can spin the motor back and forth and can read its position. My issue arises when I try to use encoder’s position to stop the motor; I am just misunderstanding the interrupt protocol as I am a beginner.

MarkT:
You should manage the rotaryCount variable entirely in the ISR. Then the main code just needs to read it (in a critical section) to determine what actions to make.

You are turning the motor on everytime through loop(), rather than once, so it will always turn.

Mark,

That made so much sense! I adjusted my code and it now runs properly. For any who are interested in this project, here is my code. I use the - and = keys to manually control the position of a geared motor which is being checked against a magnetic encoder attached to its axle. In a sense, it now acts as a stepper motor.

volatile boolean fired;
volatile boolean up;

String serialReceived;
char commandChar;

volatile long rotaryCount = 0;

#define PINA 2
#define PINB 3
#define INTERRUPT 0  // that is, pin 2

int BIN_1 = 11;
int BIN_2 = 10;
int MAX_PWM_VOLTAGE = 240;
const int CPR = 12; // number of encoder 'ticks' per encoder rotation


void isr (){ // interrupt service routine for the motor pins
 if (digitalRead (PINA)) { // read the encoder pin
   up = digitalRead (PINB); // find movement direction of motor whether forward or reverse
 }
 else {
   up = !digitalRead (PINB);
 }
 if (up) // if encoder is moving up
  rotaryCount++; // increment the rotaryCount to match the encoders motion
 else // if the encoder  is moving down
  rotaryCount--; // increment the rotaryCount negatively to match the encoders motion
 fired = true;
}  // end of isr

void setup() {
    pinMode(BIN_1, OUTPUT);
    pinMode(BIN_2, OUTPUT);
    digitalWrite (PINA, HIGH);     // enable pull-ups
    digitalWrite (PINB, HIGH);
    attachInterrupt (INTERRUPT, isr, CHANGE);   // interrupt 0 is pin 2, interrupt 1 is pin 3

    Serial.begin (9600); // set for serial communication
    Serial.println("READY");
}

int stepSize = 6000; // motor step resolution ROUGHLY CORRESPONDS TO ONE REVOLUTION OF THE THREADED AXLE
int setPoint = rotaryCount;

void loop() { // will loop over these commands indefinitely, BUT will 'pause' operation until the nested loops are complete
  if(Serial.available()){
        commandChar = Serial.read();
        if (commandChar == '-'){ // incremental down
          setPoint = setPoint - stepSize; // set the setpoint
          while (setPoint <= rotaryCount) { // while the motor is not in the correct position do the following:
            analogWrite(BIN_1, MAX_PWM_VOLTAGE); // move the motor in the reverse direction 
          } // once the motor has reached the setpoint do the following:
          analogWrite(BIN_1, LOW); // stop the motor
        }
        else if (commandChar == '=') { // incremental up
          setPoint = setPoint + stepSize; // set the setpoint
          while (setPoint >= rotaryCount) { // while the motor is not in the correct position do the following:
            analogWrite(BIN_2, MAX_PWM_VOLTAGE); // move the motor in the forward direction
          } // once the motor has reached the setpoint do the following:
          analogWrite(BIN_2, LOW); // stop the motor
        }
    }
}

Thank you both!