Quadrature Encoder function

Hi!

Im working on DC servo controller and i have problem with controlling at low speed. I would like to increase the resolution of encoder but im struggling with isr function.

My actual function is this:

void doEncoderA() {                                                                           // ENCODER ISR

  if ( bitRead(PINE, 4) != A_set ) {
    A_set = !A_set;
    if ( A_set && !B_set ) encoderPos += 1;
  }
}


void doEncoderB() {                                                                           // ENCODER ISR

  if ( bitRead(PINE, 5) != B_set ) {
    B_set = !B_set;
    if ( B_set && !A_set ) encoderPos -= 1;
  }
}

And i would like to use something where i can change resolution and it will be fast enough. My encoder is 500 pulses/rotation. Max speed i will use with servo controller is 3000 rpm. I found something that i can use but i dont really know how to set input pins in that function.

const int QEM [16] = {0,-1,1,2,1,0,2,-1,-1,2,0,1,2,1,-1,0};               // Quadrature Encoder Matrix
static unsigned char New, Old;

void doEncoderMotor(){ 
  Old = New;
  New = (PINB & 1 )+ ((PIND & 4) >> 1); //
  encoder0Pos+= QEM [Old * 4 + New];
}

This second function is originally written for uno and pinchangeinterrupt pins. So i have to change pins in function to get it work. Im using mega 2560 interrupts on pins 2,3 pins PE4,PE5.

Thank You

I've seen a lot of convoluted code for reading an encoder. If you look at the gray code though it's really dirt simple. When you see a transition on the A pin, if the B pin is the same state then you're going CW and if it is different you're going CCW. When you see a transition on the B pin then if the A pin is in the same state then you're going CCW and if it is different you're going CW.

NOTE: I may have my CW and CCW backwards, but the point is the same. You only need to compare the states of the two pins.

On UNO pins 2 and 3 are PORTD bits 2 and 3.

volatile int count = 0;

void pin_A_ISR(){
  if(!(PORTD & 4) == !(PORTD & 8)){
    count++;
  }
  else {
    count--;
  }
}

void pin_B_ISR(){
  if(!(PORTD & 4) == !(PORTD & 8)){
    count--;
  }
  else {
    count++;
  }
}

void setup(){
  Serial.begin(9600);
  attachInterrupt(0, pin_A_ISR, CHANGE);
  attachInterrupt(1, pin_B_ISR, CHANGE);
}

void loop(){
  Serial.println(count);
  delay(500);
}

This example is uncompiled and untested, but I've used the same code before and it works. The NOT (!) keeps me from having to bitshift one of the two values before I compare them.

Hi!

Thanks for the answer. I tried your code and it works. For testing now I have encoder with 2000 increments on one revolution so with this function it has 8000 inc/rev. When im moving shaft of encoder slowly everything is ok. But when i start moving it fast from from side to side it start to increment only. It start count correctly only when I move with shaft slower. If i use code without NOT like

 void pin_A_ISR() {
  if ((PINE & 0b00010000) == (PINE & 0b00100000)) {
    encoderPos++;
  }
  else {
    encoderPos--;
  }
}

void pin_B_ISR() {
  if ((PINE & 0b00010000) == (PINE & 0b00100000)) {
    encoderPos--;
  }
  else {
    encoderPos++;
  }
}

i have 4000 inc/rev and it work good also with "fast" moving with encoder shaft. Is it too fast for arduino or is there something i can improve?

The only thing about not having the not is that when both pins are high the two aren't equal. If both are low then both give zero, but when both are high those are two different numbers.

Well i think i understand what is going on. I also tried it with my original 500inc/rev encoder and it works very well. Probably that encoder with 2000imp/rev was to fast for arduino at 4x resolution. Thanks again :)

Hi, What is the model/make of your encoder, a link to specs will help. What model Arduino? Please post all your code, I don't know if your encoder is open collector output and needs pull-up resistors. You can do this in software, or use resistors from encoder outputs to 5V, your spec/data sheet should tell you.

Tom... :)

i have 4000 inc/rev and it work good also with "fast" moving with encoder shaft. Is it too fast for arduino or is there something i can improve?

Could this be something electronic? There's some capacitance somewhere that's delaying the input from one of the pins? Maybe one of the leads is longer than the other, and it becomes a thing at high speed? Maybe one of the signals isn't rising fast enough to trigger one of the interrupts, and so it's skipping. And maybe the reason it works one way and not the other is that because you're right-handed, when you flick the encoder one way you are flicking it faster than when you are flicking it the other way. Something like that, perhaps.

Put a two-trace scope on the input that the arduino is getting, and see what happens to the waveform as you increase the speed. It should be

FORWARD:

A +---+   +---+   +---+ +---+
A |   |   |   |   |   |   |
A +   +---+   +---+ +---+

B   +---+   +---+   +---+ +---+
B   |   |   |   |   |   |   |
B --+   +---+   +---+   +---+


REVERSE
A +---+   +---+   +---+   +---+
A |   |   |   |   |   |   |
A +   +---+   +---+   +---+

B --+   +---+   +---+   +---+
B   |   |   |   |   |   |   |
B   +---+   +---+   +---+   +---+

Hi!

Encoders are Avago brand first is HEDL-5540 and second is HEDS-9040. Im using pullups 1.5k on A and B pin to +5V. Im using mega 2560. This is my testing code for encoder

#define encoderPinA   2
#define encoderPinB   3

volatile long encoderPos = 0;


void setup() {

  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);

  attachInterrupt(0, pin_A_ISR, CHANGE);
  attachInterrupt(1, pin_B_ISR, CHANGE);

  Serial.begin(115200);  // output
}


void loop() {

  Serial.println(encoderPos);
  delay (1000);

}


void pin_A_ISR() {
  if (!(PINE & 0b00010000) == !(PINE & 0b00100000)) {
    encoderPos++;
  }
  else {
    encoderPos--;
  }
}

void pin_B_ISR() {
  if (!(PINE & 0b00010000) == !(PINE & 0b00100000)) {
    encoderPos--;
  }
  else {
    encoderPos++;
  }
}

Waveform in attachment. One thing i konw is that encoder are good they were tested on cnc machine.

Thank You.

ok.

Well, when the encoder is moving either way, you should see a sequence of interrupts. So perhaps add some stuff to detect when the sequence is not what you would normally expect.

#define encoderPinA   2
#define encoderPinB   3

volatile long encoderPos = 0;


volatile int transitions[4][4];
int prevtransition = 0;


void setup() {

  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);

  attachInterrupt(0, pin_A_ISR, CHANGE);
  attachInterrupt(1, pin_B_ISR, CHANGE);

  Serial.begin(115200);  // output
}


void loop() {

  Serial.println(encoderPos);

  for(int a = 0; a<4; a++) {
    Serial.print(a);
    for(int b = 0; b<4; b++) {
      Serial.print(" -> ");
      Serial.print(b);
      Serial.print(" : ");
      Serial.print(transition[a][b]);
      transition[a][b] = 0;
    }
    Serial.println();
  }

  delay (1000);

}


void pin_A_ISR() {
  if (!(PINE & 0b00010000) == !(PINE & 0b00100000)) {
    transitions[prevtransition][0] ++;
    prevtransition = 0;
    encoderPos++;
  }
  else {
    transitions[prevtransition][1] ++;
    prevtransition = 1;
    encoderPos--;
  }
}

void pin_B_ISR() {
  if (!(PINE & 0b00010000) == !(PINE & 0b00100000)) {
    transitions[prevtransition][2] ++;
    prevtransition = 2;
    encoderPos--;
  }
  else {
    transitions[prevtransition][3] ++;
    prevtransition = 3;
    encoderPos++;
  }
}

When moving the encoder one way, you should expect to see a bunch of 0-2 transitions and an equal number of 2-0 transitions.

When moving the encoder the other way, you should expect to see a bunch of 1-3 transitions and an equal number of 3-1 transitions.

If you don’t see that, then maybe you have a clue what’s going on.

Hi,

I see the transitions during movement but when i start to move it fast its just increasing encoderPos value. I made text file you can see it in attachment.

transitions.txt (8.9 KB)

volatile long encoderPos = 0;
 Serial.println(encoderPos);

encoderPos is a multibyte variable, and can possibly change value while being read. Try to transfer to a protected copy for printing.

noInterrupts();
copy_encoderPos = encoderPos:
interrupts();
Serial.println(copy_EncoderPos);