What should be simple: reading a rotary encoder

Hi,

We're trying to read a rotary encoder. I've got a document with 27 pages of thing (code) we've already tried, but it gave either nothing or random things as output.

I don't know what is usually done when troubleshooting these kind of things, so ask me any information you want about the project.

First. The encoder we use. https://www.sparkfun.com/products/10596 24 pulses / 360° for each phase incremental encoder (not absolute) quadrature

Second. The code we tried:

Example 1.

/* read a rotary encoder with interrupts
   Encoder hooked up with common to GROUND,
   encoder0PinA to pin 2, encoder0PinB to pin 4 (or pin 3 see below)
   it doesn't matter which encoder pin you use for A or B  

   uses Arduino pullups on A & B channel outputs
   turning on the pullups saves having to hook up resistors 
   to the A & B channel outputs 

*/ 

#define encoder0PinA  2
#define encoder0PinB  3

volatile unsigned int encoder0Pos = 0;

void setup() { 


  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");                // a personal quirk

} 

void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}

void doEncoder() {
  /* If pinA and pinB are both high or both low, it is spinning
   * forward. If they're different, it's going backward.
   *
   * For more information on speeding up this process, see
   * [Reference/PortManipulation], specifically the PIND register.
   */
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }

  Serial.println (encoder0Pos, DEC);
}

/*  to read the other two transitions - just use another attachInterrupt()
in the setup and duplicate the doEncoder function into say, 
doEncoderA and doEncoderB. 
You also need to move the other encoder wire over to pin 3 (interrupt 1). 
*/

That didn't work. The serial monitor did start, but when I turned the encoder nothing happened. Pins of the encoder are on 2,3 and gnd.

Then we edited it according to the last paragraph.

#define encoder0PinA  2
#define encoder0PinB  3

volatile unsigned int encoder0Pos = 0;

void setup() { 


  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

  attachInterrupt(0, doEncoderA, CHANGE); // encoder pin on interrupt 0 - pin 2
attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 - pin 3
    Serial.begin (115200);
  Serial.println("start");                // a personal quirk
  Serial.println (encoder0Pos, DEC);

} 

void loop(){

}


void doEncoderA() {
  /* If pinA and pinB are both high or both low, it is spinning
   * forward. If they're different, it's going backward.
   *
   * For more information on speeding up this process, see
   * [Reference/PortManipulation], specifically the PIND register.
   */
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }
Serial.println ("pos:");                                 //interrupt fired
  Serial.println (encoder0Pos, DEC);
}

void doEncoderB() {
   if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }
Serial.println ("pos:");                                 //interrupt fired
  Serial.println (encoder0Pos, DEC);
}

But same result.

When using the serial monitor the output of above code should be the position of the encoder, relative to the starting point.

Our end goal is to control a stepper motor with the rotary encoder, where if you turn the encoder 90 degrees the stepper should also turn 90 degrees.

Arduino Uno.

Breadboard layout:

You are using Serial.print() in your ISR Serial.print() depends on interrupts for some functionality Interrupts are automatically disabled in ISRs

Can you see a potential problem ?

UKHeliBob:
You are using Serial.print() in your ISR
Serial.print() depends on interrupts for some functionality
Interrupts are automatically disabled in ISRs

Can you see a potential problem ?

I understand I’m using Serial.print() in my ISR.
I just googled about Serial.print() in interrupts.

The serial mechanism depends on interrupts to get all the characters out. Since you’re in an interrupt, other interrupts are disabled, and the serial just hangs.

In general, you want to keep your interrupt code as simple as possible, and avoid calling other functions, especially library functions, since they might depend on interrupts.

I don’t understand what is meant by ‘depending on interrupts’ , but I do understand the last paragraph: “you want to avoid calling other functions”. That would mean a Serial.print() would not, or not good, work in an ISR.
This follows from your two statements too, even though I don’t understand them.
Is that a correct conclusion?

So to get to a solution, I took the Serial.print() out of the ISR, and put it in the loop with a delay.

void loop(){
Serial.println ("pos:");                                
  Serial.println (encoder0Pos, DEC);
  delay(1000);
}

Result when turning the encoder:

I'm looking at the second piece of code in your original Post.

Change loop() so it looks like this

void loop() {
   Serial.println(encoder0Pos);
   delay (300); // so it does not loop too fast
}

and take the Serial.print stuff out of the interrupt routines.

...R

Try this

#define encoder0PinA  2
#define encoder0PinB  3

volatile unsigned int encoder0Pos = 0;
boolean volatile interrupted = false;

void setup() 
{ 
  pinMode(encoder0PinA, INPUT_PULLUP);       // turn on pullup resistor 
  pinMode(encoder0PinB, INPUT_PULLUP);      // turn on pullup resistor 
  attachInterrupt(0, doEncoderA, CHANGE); // encoder pin on interrupt 0 - pin 2
  attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 - pin 3
  Serial.begin (115200);
  Serial.println("start");                // a personal quirk
  Serial.println (encoder0Pos, DEC);
} 

void loop()
{
  if (interrupted)       //interrupt fired
  {
    Serial.println ("pos:");
    Serial.println (encoder0Pos, DEC); 
    interrupted = false;  
  }
}

void doEncoderA() 
{
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) 
  {
    encoder0Pos++;
  } 
  else 
  {
    encoder0Pos--;
  }
  interrupted = true;
}

void doEncoderB() 
{
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) 
  {
    encoder0Pos++;
  } 
  else 
  {
    encoder0Pos--;
  }
  interrupted = true;
}

The Serial.print()s are not in the ISRs but will be triggered if an interrupt occurs. As interrupts will have automatically been re-enabled at the end of the ISRs then Serial.print() will work as normal.

Robin2: I'm looking at the second piece of code in your original Post.

Change loop() so it looks like this

void loop() {
   Serial.println(encoder0Pos);
   delay (300); // so it does not loop too fast
}

and take the Serial.print stuff out of the interrupt routines.

...R

Okay. The only difference to the last code in my last post is, as far as I can see, you omitted the DEC in Serial.println(encoder0Pos) right?

When I try that, the serial monitor gives as output when I turn slowly clockwise:

I've had this before, and I couldn't make anything of it.

UKHeliBob: The Serial.print()s are not in the ISRs but will be triggered if an interrupt occurs. As interrupts will have automatically been re-enabled at the end of the ISRs then Serial.print() will work as normal.

Ow, that I hadn't thought of that! That seems a good one for debugging.

When I turn the encoder slowly clockwise, it gave the first time:

Which seems very much in the right direction! Unfortunately, 9 out of 10 times it doesn't do anything. I am not sure why, it just randomly seems to work and then the next moment it doesn't. Maybe it's in the hardware connections, but I'm not sure what I could do about that. I really hope that isn't the problem.

Unfortunately, 9 out of 10 times it doesn't do anything. I am not sure why, it just randomly seems to work and then the next moment it doesn't.

Your ISRs, which incidentally are exactly the same as each other, only vhange the count if both encoder pins return the same value. How often will that be the case ?

If you want to know, add another boolean variable that is set to true only when the values match in the ISR and use that to trigger a print in loop().

To get reliable encoder counting you must read both pins simultaneously and you must respond to every change of either pin. If both pins change at the same time this is a hardware fault which you should ideally detect and report. You must also read pins at a high enough rate to catch the fastest movement of the shaft.

Code which doesn't stick to these rules will mis-count sometimes (or often for a high speed encoder).

In particular if you use an ISR, install it on both pins as CHANGE and read both pins in the ISR (with direct port manipulation for a high speed encoder). To respond to changes you must have a record of the last state read from the pins.

I posted code here to read an encoder without using interrupts. No idea how fast it can go though, before it misses counts.

I also have a page about rotary encoders:

http://www.gammon.com.au/forum/?id=11130

That uses interrupts, and has a few suggestions about switch bounces.

I think I spotted the problem straight away.

PHPirates: /* read a rotary encoder with interrupts

With due credit to Nick, who knows how to do things, using interrupts to read an encoder with mechanical contacts and turned by hand is a really, really bad idea, most particularly because it is completely unnecessary to use interrupts for such a slow function and is far more difficult to effect de-bouncing using interrupts, than by polling.

And that to say nothing about the oh-so-common newbie blunder of calling any time-dependent routines or "while" functions in interrupt routines.

Not sure from where you dredged up that unhelpful code, but if JimboZA has offered you a more competent version (not that I have examined it myself), I would swap over to that forthwith before proceeding any further.

(Were I not up to my ears in alligators, I would be getting out an encoder just now. Nevertheless, I stand by the lesson.)

Paul__B: if JimboZA has offered you a more competent version (not that I have examined it myself)

I'm not for a second suggesting that my code is more competent as anyone's; I'm not even suggesting it is competent. It worked for me: my purpose was to try to understand how an encoder works.

I have no idea how fast an encoder may be turned until that code loses counts. YMMV, E&OE, Ts and Cs apply.... :P

This is a quite effective way of reading an encoder without interrupts. In this example the encoder is connected to pins 2 and 3

const char encTable[16] ={0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0};//gives -1, 0 or 1 depending on encoder movement
void loop(){
    //aquire input values
    inp = PIND;//read inputs 0..7
    encState = ((encState<<2)|((inp>>2)&3))&15;//use encoder bits and last state to form index
    actPos += encTable[encState];//update actual position on encoder movement
}//loop()

I have given an explanation of the workings here: http://forum.arduino.cc/index.php?topic=272681.msg1925198#msg1925198

But be aware, like all timing dependent code it is easy to mess things up by blocking the cpu too long. If you are not reading at least once every change no code will work

JimboZA: I have no idea how fast an encoder may be turned until that code loses counts. YMMV, E&OE, Ts and Cs apply.... :P

I can't find my own encoder (and board) that I used before, so I can't offer to test one method against the other right now.

I think for high speed situations (like, perhaps, timing a machine turning) the interrupts would be the way to go. For human turning, polling the contacts may well be adequate. Interrupts are not something to be worried about, however there are some rules that need to be followed for them to work successfully.

UKHeliBob:

Unfortunately, 9 out of 10 times it doesn't do anything. I am not sure why, it just randomly seems to work and then the next moment it doesn't.

Your ISRs, which incidentally are exactly the same as each other, only vhange the count if both encoder pins return the same value. How often will that be the case ?

If you want to know, add another boolean variable that is set to true only when the values match in the ISR and use that to trigger a print in loop().

I know my ISR's are the same, because I copy-pasted the first one so that that ISR can work for the second pin. I hope I did it right...

This is how I think the interrupts I used work.

for example the interrupt void doEncoderA()

void doEncoderA() {
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }

The interupt is fired by a change in pin A. If the encoder goes clockwise, every time the interrupt is fired, A is in the same state as B. When rising, they’re both high, and when A’s falling, they’re both low. If it goes CCW, when A rises A is high and B = LOW, so they’re different. When A falls A is low and B high.

Do you think I need to check that by adding a boolean like you say?

MarkT: To get reliable encoder counting you must read both pins simultaneously and you must respond to every change of either pin. If both pins change at the same time this is a hardware fault which you should ideally detect and report. You must also read pins at a high enough rate to catch the fastest movement of the shaft.

Code which doesn't stick to these rules will mis-count sometimes (or often for a high speed encoder).

In particular if you use an ISR, install it on both pins as CHANGE and read both pins in the ISR (with direct port manipulation for a high speed encoder). To respond to changes you must have a record of the last state read from the pins.

Isn't that what I just tried?

JimboZA: I posted code here to read an encoder without using interrupts. No idea how fast it can go though, before it misses counts.

JimboZA: Thought I'd share this code, which might help de-mystify the inards of a rotary encoder. It's not intended as a real-life solution- there are libraries for that and interrupts seems to be the way to go. It's more of a tutorial (...)

If it's not intended as a real-life solution, what is then the way to go for me? How should I use that code? It seems a little bit too complicated for me on first sight. Would it go good enough in our project with the stepper?

Anyway, when I try it, it says " 'Bounce' does not name a type ". I think I might miss bounce2.h?

[quote author=Nick Gammon link=topic=273296.msg1926740#msg1926740 date=1413696576] I also have a page about rotary encoders:

http://www.gammon.com.au/forum/?id=11130

That uses interrupts, and has a few suggestions about switch bounces. [/quote]

I think you code very interesting, but when I try it, the serial monitor just stays blank. I didn't change anything in the code, maybe I missed something I had to edit?

Paul__B: I think I spotted the problem straight away.

PHPirates: /* read a rotary encoder with interrupts

With due credit to Nick, who knows how to do things, using interrupts to read an encoder with mechanical contacts and turned by hand is a really, really bad idea, most particularly because it is completely unnecessary to use interrupts for such a slow function and is far more difficult to effect de-bouncing using interrupts, than by polling.

(Were I not up to my ears in alligators, I would be getting out an encoder just now. Nevertheless, I stand by the lesson.)

Your lesson is quite a good one there. I was told I had to use interrupts because since my 'main' program is busy with turning the stepper motor, I had to use interrupts because otherwise the program would miss signals from the encoder when polling constantly.

Is it right that for our purposes (turning an encoder slowly) polling will work better than interrupts? (As Nick Gammon says in his last post) If so, and I will try to google for some polling sketches, I will report later on the results of that. Do you by coincidence have any lesson about polling...?

PHPirates: [quote author=Nick Gammon link=topic=273296.msg1926740#msg1926740 date=1413696576] I also have a page about rotary encoders:

http://www.gammon.com.au/forum/?id=11130

That uses interrupts, and has a few suggestions about switch bounces.

I think you code very interesting, but when I try it, the serial monitor just stays blank. I didn't change anything in the code, maybe I missed something I had to edit?

[/quote]

Did you get the baud rate right? I usually use 115200 because that is faster. I just tried it with a spare rotary encoder from my parts box and it worked without modification.

Count = 28
Count = 23
Count = 28
Count = 25
Count = 30
Count = 29
Count = 24
Count = 22
Count = 27
Count = 22
Count = 19
Count = 14
Count = 11
Count = 6
Count = 3
Count = -2
Count = -5
Count = -10
Count = -13
Count = -16
Count = -17
Count = -20
Count = -19

It skips some counts, by design, if you turn the knob quickly, like some appliances do, so you can go from a low number to a high one by turning faster.

Here is some polled code which reads both pins and increments or decrements on the full quadrature pattern. It uses a similar algorithm to what Nilton61 posted.

Switch bounce can be a significant problem with these low end mechanical rotary encoders. If you see erratic result with this code, I suggest that you get the bounce2 library, and debounce the digital reads in this code. You could also try a .1uf cap between the A,B pins and ground.

If it turns out you really need to use interrupts in your final code because of the servo, you might chose to pay a bit more and get an optical or magnetic rotary encoder which will not require debouncing.

//Based on code from: http://bildr.org/2012/08/rotary-encoder-arduino//
//uses quadrature bit pattern from current and previous reading

//Changes
//Polled rather than interrupts
//Added start up position check to make index +1/-1 from first move

#define encoderPinA  3  
#define encoderPinB  2
#define buttonPin 5 //reset button on encoder shaft

int lastEncoded = 0;
int encoderValue = 0;
int lastencoderValue = 0;

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

  pinMode(encoderPinA, INPUT_PULLUP); 
  pinMode(encoderPinB, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);

  //get starting position
  int lastMSB = digitalRead(encoderPinA);
  int lastLSB = digitalRead(encoderPinB);

  Serial.print("Starting Position AB  " );
  Serial.print(lastMSB);
  Serial.println(lastLSB);

  //let start be lastEncoded so will index on first click
  lastEncoded = (lastMSB << 1) | lastLSB;

}

void loop(){ 

  int MSB = digitalRead(encoderPinA); //MSB = most significant bit
  int LSB = digitalRead(encoderPinB); //LSB = least significant bit

  int encoded = (MSB << 1) | LSB; //converting the 2 pin value to single number
  
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

  //test against quadrature patterns CW and CCW
  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;

  lastEncoded = encoded; //store this value for next time


  if(encoderValue != lastencoderValue){
    Serial.print("Index:  ");
    Serial.print(encoderValue);
    Serial.print('\t');

    Serial.print("Old-New AB Pattern:  ");

    for (int i = 3; i >= 0; i-- )
    {
      Serial.print((sum >> i) & 0X01);//shift and select first bit
    }

    Serial.println();

    lastencoderValue=encoderValue;
  }

  //reset index
  if(digitalRead(buttonPin)==LOW){
    encoderValue=0;
  }

}

I know my ISR's are the same, because I copy-pasted the first one so that that ISR can work for the second pin.

As they are both the same then why not trigger the same ISR from both pins ?

Do you think I need to check that by adding a boolean like you say?

I would say that it is worth a try.

[quote author=Nick Gammon link=topic=273296.msg1927728#msg1927728 date=1413750567]

Did you get the baud rate right? I usually use 115200 because that is faster. I just tried it with a spare rotary encoder from my parts box and it worked without modification.

Count = 28
Count = 23
Count = 28
Count = 25
Count = 30
Count = 29
Count = 24
Count = 22
Count = 27
Count = 22
Count = 19
Count = 14
Count = 11
Count = 6
Count = 3
Count = -2
Count = -5
Count = -10
Count = -13
Count = -16
Count = -17
Count = -20
Count = -19

It skips some counts, by design, if you turn the knob quickly, like some appliances do, so you can go from a low number to a high one by turning faster. [/quote]

Do I understand correctly that with your code, say you turn the encoder slowly 180 degrees and the count goes from 0 to 50. When you turn the encoder fast, it skips some counts, but it would still end up at 50?

I seem to have problems with my arduino breadboard, when I push my encoder down it works better... No matter where I place it on the board, that's maybe why some codes did only work sometimes.

Anyway, when I push it down so that it makes connections... it gives, when turning slowly clockwise,

Count = -10
Count = -9
Count = -10
Count = -15
Count = -20
Count = -19
Count = -14
Count = -9
Count = -4
Count = 1
Count = 0
Count = -5
Count = -10
Count = -15
Count = -20
Count = -21
Count = -22
Count = -24
Count = -23
Count = -22
Count = -17

Instead of going only up or down, it goes up and down and I can't make anything of it. What is happening here? Can I work with these values to control a stepper motor?

cattledog: Here is some polled code which reads both pins and increments or decrements on the full quadrature pattern. It uses a similar algorithm to what Nilton61 posted.

I had hoped this cheap (very cheap, I know) encoder would be sufficient... Anyway, I don't really understand your code but when I run it:

Index:  0  Old-New AB Pattern:  0111
Index:  1  Old-New AB Pattern:  1101
Index:  0  Old-New AB Pattern:  0111
Index:  1  Old-New AB Pattern:  1101
Index:  0  Old-New AB Pattern:  0111
Index:  1  Old-New AB Pattern:  1101
Index:  0  Old-New AB Pattern:  0111
Index:  1  Old-New AB Pattern:  1101
Index:  0  Old-New AB Pattern:  0111
Index:  1  Old-New AB Pattern:  1101
Index:  0  Old-New AB Pattern:  0111
Index:  1  Old-New AB Pattern:  1101

I'm not sure what is supposed to be the output though.

Switch bounce can be a significant problem with these low end mechanical rotary encoders. If you see erratic result with this code, I suggest that you get the bounce2 library, and debounce the digital reads in this code. You could also try a .1uf cap between the A,B pins and ground.

Debouncing like in JimboZA's code? That includes the bounce2library. Maybe I'm looking in the wrong direction, but I can't find it.

When I google for .1uf cap it says it's a capacitor, but I don't really understand what that has to do with my project.

UKHeliBob: I would say that it is worth a try.

Okay, I will try that this afternoon.

I’m not sure what is supposed to be the output though.

This is the output the code just gave me on my encoder.

Starting Position AB  01
Index:  1	Old-New AB Pattern:  0100
Index:  2	Old-New AB Pattern:  0010
Index:  3	Old-New AB Pattern:  1011
Index:  4	Old-New AB Pattern:  1101
Index:  5	Old-New AB Pattern:  0100
Index:  6	Old-New AB Pattern:  0010
Index:  7	Old-New AB Pattern:  1011
Index:  8	Old-New AB Pattern:  1101
Index:  7	Old-New AB Pattern:  0111
Index:  6	Old-New AB Pattern:  1110
Index:  5	Old-New AB Pattern:  1000
Index:  4	Old-New AB Pattern:  0001
Index:  3	Old-New AB Pattern:  0111
Index:  2	Old-New AB Pattern:  1110
Index:  1	Old-New AB Pattern:  1000
Index:  0	Old-New AB Pattern:  0001

The output should go up and down smoothly as you turn the encoder. You can see the repeating quadrature patterns. I see the pattern you get when I turn my encoder back and forth one step when I start at a pattern which matches yours (1101/0111)

Starting Position AB  11
Index:  1	Old-New AB Pattern:  1101
Index:  0	Old-New AB Pattern:  0111
Index:  1	Old-New AB Pattern:  1101
Index:  0	Old-New AB Pattern:  0111
Index:  1	Old-New AB Pattern:  1101
Index:  0	Old-New AB Pattern:  0111
Index:  1	Old-New AB Pattern:  1101
Index:  0	Old-New AB Pattern:  0111
Index:  1	Old-New AB Pattern:  1101
Index:  0	Old-New AB Pattern:  0111
Index:  1	Old-New AB Pattern:  1101
Index:  0	Old-New AB Pattern:  0111

I’m not sure why you are seeing the pattern that you are.

One possibility is that this routine is designed to read every quadrature transition. When I look at the data sheet from Sparkfunfor your encoder 10596 (http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Components/LED/EC12PLRVF-D-24K-24-24C-0206-6H(SPEC).pdf) it shows one click/one pulse for encoders with detents and the encoder may not show every transition between clicks.

Does your encoder have detents?

If you have detents, can you run the following debug code to help understand the situation. Just turn the encoder one click between readings. Go one 360 degree rotation. This should show the pin readings A and B at each detent position. What do you see?

#define encoderPinA  3  
#define encoderPinB  2

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

 pinMode(encoderPinA, INPUT_PULLUP); 
 pinMode(encoderPinB, INPUT_PULLUP);

}

void loop(){ 
  
  
  Serial.print(digitalRead(encoderPinA));
  Serial.print('\t');
  Serial.println(digitalRead(encoderPinB));
  delay(2000);
  
}