Pin Change Interrupts For Rotary Encoder Readings Producing Wrong Values

Hey all,

So I'm trying to read an rotary encoder by creating a loop of possible states (finite state machine). Basically, there are two values in an array that track the A and B phases of the encoder. When either phase is high, the value is 1 and when the phase is low the value is 0. By this, the state loop is as follows:

[0 0] - Both Phases Low

[0 1] - B phase High, A phase Low

[1 1] - Both Phases High

[1 0] - A phase high, B phase Low

[0 0] - Both phases low, back to the original state

I implement this by having pin change interrupts on the A and B phases of the encoder, and the interrupt service routine then checks the state of the pin to determine whether it has risen high or dropped low, and modifies the state and encoder count accordingly. Each state loop contributes two counts, so going forward adds two counts and going backward subtracts two counts. I don't know why, but when I monitor the state I get weird values like -26214 and 9612 written to the state variables rather than 0 and 1. This causes me to miss counts of the encoder. This should all be handled by the interrupts at the bottom of my code. I had a more simple method to track the encoder previously and it worked decently so I think the encoder hardware is fine. If anyone could check this out and let me know if you spot an issue, that would be very much appreciated. Thank you all for your time and help!

volatile int totalRotations = 0, target = 0, targetAdd, counter = 0, targetInProg, eCounter;
volatile float dgrees = 0, kp, ki, kd, dt, targetAddD, u, dedt, eint, e, previousE;
volatile long previousT, currentT;
const int ppr = 600;
#define aphase 3 //white wire encoder phase a
#define bphase 2 //green wire encoder phase b
#define pwm 6 //pwm control pin 1
#define joystick 0 //joystick in analog pin 0
#define enableh 8
#define redout 13
#define blackout 12
#define reset 11
int joypos;
int pwmVal;
int state[1];


void setup() {
  digitalWrite(reset, HIGH);
  Serial.begin(115200);
  pinMode(aphase, INPUT_PULLUP);
  pinMode(bphase, INPUT_PULLUP);
  pinMode(enableh, OUTPUT);
  pinMode(redout, OUTPUT);
  pinMode(blackout, OUTPUT);
  pinMode(pwm, OUTPUT);
  pinMode(reset, OUTPUT);
  digitalWrite(enableh, HIGH);
  attachInterrupt(digitalPinToInterrupt(bphase), ai0, CHANGE);
  attachInterrupt(digitalPinToInterrupt(aphase), ai1, CHANGE);
  previousT = 0;
  previousE = 0.0;
  eint = 0;
  targetInProg = 0;

  //Assign kp,ki, and kd values

  Serial.println("Kp:");
  while (Serial.available() == 0) {}
  kp = Serial.parseFloat();
  Serial.print("kp=");
  Serial.println(kp);
  Serial.println("Ki:");
  while (Serial.available() == 0) {}
  ki = Serial.parseFloat();
  Serial.print("ki=");
  Serial.println(ki);
  Serial.println("Kd:");
  while (Serial.available() == 0) {}
  kd = Serial.parseFloat();
  Serial.print("kd=");
  Serial.println(kd);
  delay(500);
}

void loop() {



  //If Serial Monitor has input, read that input for a degree value, convert to counter value, and create target

  if ((Serial.available() > 0) && (targetInProg == 0)) {

    targetAddD = Serial.parseFloat();

    //Reset when input is 0, otherwise set target

    if (targetAddD == 0) {
      digitalWrite(reset, LOW);
    }
    else {
      targetAdd = targetAddD * 1200 / 360;
      target = target + targetAdd;
      targetInProg = 1;
    }

  }

  //Timing

  currentT = micros();
  dt = ((float)currentT - (float)previousT) / (1.0e6);
  previousT = currentT;

  //Calculate Error and parameters for PID using Finite Difference

  e = target - counter;
  dedt = ((float)e - (float)previousE) / dt;
  eint = eint + (float)e * dt;


  //Compute Control Signal and send to motor

  float u = (float)kp * (float)e + ki * eint + kd * dedt;
  if (u > 0) {
    if (u > 255) {
      u = 255;
    }

    analogWrite(redout, fabs(u));
    digitalWrite(blackout, LOW);
  }
  if (u < 0) {
    if (u < -255) {
      u = -255;
    }

    analogWrite(blackout, fabs(u));
    digitalWrite(redout, LOW);
  }

  //read Counter and Degrees to the Serial Monitor

  //Serial.print("Counter: ");
  //Serial.println(counter);
  dgrees = counter * 360 / 1200.0 - 360.0 * totalRotations;
  if ( dgrees >= 360.0) {
    dgrees = dgrees - 360;
    totalRotations++;
  }
  if (dgrees <= -360.0) {
    dgrees - dgrees + 360.0;
    totalRotations--;
  }
  //Serial.print("Target: ");
  //Serial.println(target);
  //Serial.print("Error: ");
  Serial.println(e);
  Serial.print(state[0]);
  Serial.print(" ");
  Serial.println(state[1]);
  //Serial.print("DT: ");
  //Serial.println(dt,6);
  //Serial.print("targetInProg: ");
  //Serial.println(targetInProg);

  //When error is equal for 15 loops in a row during a target operation, kill the target operation and read "Target Reached"

  if ((targetInProg == 1) && (previousE == e)) {
    eCounter++;
  }
  if ((eCounter == 15) && (targetInProg == 1)) {
    targetInProg = 0;
    Serial.println("Target Reached");
    eCounter = 0;
  }

  //Record error for next loop

  previousE = e;

}

void ai0() {

  //B Change

  if (digitalRead(bphase) == HIGH) {
    //B Rising
    if (state[0] == 0 && state[1] == 0) {
      counter--;
      state[1] = 1;
    }
    else {
      state[1] = 1;
    }
  }
  else {
    //B Falling
    if (state[0] == 0 && state[1] == 1)
    {
      state[1] = 0;
      counter++;
    }
    else {
      state[1] = 0;
    }
  }


}

void ai1() {
  //A Change

  if (digitalRead(aphase) == HIGH) {
    //A Rising
    if (state[0] == 0 && state[1] == 0) {
      counter++;
      state[0] = 1;
    }
    else {
      state[0] = 1;
    }
  }
  else {
    //A Falling
    if (state[0] == 1 && state[1] == 0) {
      state[0] = 0;
      counter--;
    }
    else {
      state[0] = 0;
    }
  }

}

This is the serial monitor output of the state and the error. Error (target position - current position) is marked in red and the state is marked in blue, see how the state has numbers that are not 1 and 0

image

What you are doing is not a state machine. Draw the state diagram of the state machine you are trying to implement. It will have states and more importantly it will have the conditions for the transitions between the states.

A state machine will be self correcting as to contact bounce.

This is what you must implement. This is not what you are doing.

See:

Hey Grumpy_Mike,

Here is a diagram of the FSM:

If you carefully read the whole post above, this is what I was trying to explain. I'm an engineer not a computer scientist, but I'm pretty sure it's a FSM. Hopefully this helps you understand. Please let me know if you read the code and find any fixes. Thanks :slight_smile:

Hey gfvalvo

Thanks for the help, I've seen implementations like that that don't use pin change interrupts but hopefully I can figure it out using interrupts, it's almost there

Interrupts are causing one of the fatal problems. But first, the array "state", as declared, can hold only 1 value, and state[] has a maximum index of 0.

int state[1];

Second, all multibyte variables shared with interrupts MUST be declared volatile, and MUST be protected against corruption by the interrupt, while being accessed by the main program. A typical approach is as follows:

noInterrupts();
int state0_copy = state[0];
interrupts();
Serial.print(state0_copy);

And, of course, your approach makes it very difficult to deal with contact bounce, if you are using switch-type encoders.

I read carefully and saw what you were trying to achieve.

and I am certain this is not a state machine.

This is a state machine for a rotary encoder.

Also what makes you think you are using a pin change interrupt in that code. It looks to me like you are using a normal interrupt, the sorts that only work with pins 2&3.

Hey jremington,

I believe the arrays use 0 indexing, so state[1] would be able to hold 2 variables

I figured out the issue, I increased the array size to 3 (state[2]) and that fixed the problem which was memory bleed. I think the interrupts work fine. Also, it's an optical quadrature encoder. After increasing the array size, the code doesn't lose position and I think bouncing has been taken care of, which was my previous issue that caused extra counts and the code would lose the true position.

You would be wrong.

int state[2]; holds two variables, state[0] and state[1].

Hey Grumpy_Mike,

My state machine works now, thanks for your help

Also I misspoke, i meant change interrupts not pin change interrupts

I hope i'm in the right forum, for arduino right?

The language is C/C++. Look up basic array indexing before you continue down this silly path.

I wasn't kidding about protecting shared variables from corruption, either.

code works, thanks for your help !!!

yea you were right the number inside the brackets is the size of the array

It seems to work fine without declaring the array as volatile but i'll add that declaration in just in case, thanks again

Care to share your working code so others can benefit from your work?

My code HERE does exactly that.