0
votes

I have a simple question about how to read rotary encoder input. If I understand this image correctly, then every turn triggers a rise on pin A. Then, you have to check pin B, which is high if the encoder is turning clockwise and low if the encoder is turning counter clockwise. diagram

I've tried to write my own code and not using any libraries, because I thought this would be really simple, but it turned out it was not.

This is the code I've written:

#define rotary_A 2
#define rotary_B 3

void setup() 
{
  pinMode(rotary_A, INPUT);
  pinMode(rotary_B,  INPUT);

  attachInterrupt(digitalPinToInterrupt(rotary_A), rotary_spin, RISING);

  Serial.begin(9600);
}

void rotary_spin()
{
  if (digitalRead(rotary_B) == HIGH)
    Serial.println("+");
  else
    Serial.println("-");
}

I was expecting to get + when I turn it clockwise and - when I turn it counter clockwise. However, I'm getting several outputs for each turn, like there were several interrupts triggered in rapid succession. For example, when I turn the encoder clockwise:

-
-
+
+

and counter clockwise:

+
+
-
-
-
-

The outputs are different every time, but the last character is always the right one.
What am I getting wrong? Is it not that simple or are there different types of encoders?

3
Firstly, yes there are different types of encoders. You should consult the datasheet of the one you are using. Secondly, depending on the encoder you may need hardware and/or software debouncing.Rishikesh Raje
Whatever triggers your call of rotary_spin() is most probably the culprit. Because you check only whether A is currently high, not whether there is a flank. Can you give a minimal reproducible example code?TeaRex
What would the expected output be? + - +?Marco Bonelli
@MarcoBonelli + only for CW, - only for CCW, I'd assume.Fitzi
Since you mention steps during the turning, i guess that there may be some jitter in the outputs. You will probably require some sort of debouncing. I suggest you read up arduino.cc/en/tutorial/debounce For an encoder, it is somewhat more complicated. The timing depends on what speed you wish to support. A fast encoder typically cannot be debounced in software. If it is slow, you can possibly do it.Rishikesh Raje

3 Answers

1
votes

The question implies that there should only be a single interrupt per revolution. But encoders typically generate more than a single cycle per revolution--some in the thousands. That is probably why the interrupts seem to occur more rapidly than expected.

In a zero-latency interrupt environment, the code should work. But if the phase B pin is sampled too long after the phase A pin goes high, it will fail. This might occur if the next phase A rising edge occurs while Serial.println is still executing in the previous interrupt.

A simple test to see if this is the case is to turn the encoder very, very slowly. If this results in correct data, the problem is probably interrupt latency. Serial.println can then be replaced with something much quicker, like illuminating LEDs, to see if that resolves latency issues.

For actual use, you would need to make sure that the worse-case latency between phase A going high and phase B being sampled is adequate for the maximum rate of encoder rotation.

Final note: The code should be able to adequately detect direction, but cannot be used to increment/decrement a counter to track position. That requires more than one interrupt per cycle.

0
votes

check this repository of mine on Github.

https://github.com/KingZuluKing/Rotary-Encoder-Peripheral-System

It has to be something like this, more details inside the repo.

  void encode_Rotor_func()
{
    lcd.clear();
    Vol_Knob_stat=digitalRead(Vol_Knob);    
    if(Vol_Knob_stat==0)
    {
      Vol_Dir_stat=digitalRead(Vol_Dir); 
      if(Vol_Dir_stat==0 && deciBells<=9)
      {
        deciBells++;
        lcd.setCursor(0, 0);
        lcd.print("VOLUME=   .");
        lcd.setCursor(7, 0);
        lcd.print(deciBells);
        lcd.setCursor(10, 0);
        lcd.print("dB's");
       }
    else if(Vol_Dir_stat==1 && deciBells >=-79)
    {
      deciBells--;
      lcd.setCursor(0, 0);
      lcd.print("VOLUME=   .");
      lcd.setCursor(7, 0);
      lcd.print(deciBells);
      lcd.setCursor(10, 0);
      lcd.print("dB's");
    }
    else{
          do something else etc.. etc....
   }
   }
}
0
votes

I made a small sketch that makes clear how to interpret the encoder accordingly to the image sequence in your post:

Try this code with the equivalent pins CLK and DT connected to A2 and A1 receptively.

// Rotary Encoder Inputs
#define CLK A2
#define DT A1

int counter = 0;
String currentDir ="";
unsigned char currentPairBit = 0b0; // 8 bits
unsigned char lastPairBit = 0b0; // 8 bits

void setup() {

  Serial.begin(9600); // opens serial port, sets data rate to 9600 bps
  DDRC = 0b00000000; // Set Analog(C) encoder pins as inputs
  
}

void loop() {

  while(true) { // while cycle is faster than loop!

    // reads the analog states!
    currentPairBit = PINC >> 1 & 0b11; // Gets A2(PC2) and A1(PC1) bits on C (analog input pins) (>> 1 = Jumps pin 0)
    
    if ((lastPairBit & 0b11) != currentPairBit) {
  
      lastPairBit = lastPairBit << 2 | currentPairBit;
      
      // Bit Pairs Cyclic Sequence:
      //  1.   2.   3.   4.   5.
      //  11 | 01 | 00 | 10 | 11 for CCW
      //  11 | 10 | 00 | 01 | 11 for CW
      if (lastPairBit == 0b01001011 || lastPairBit == 0b10000111) {
    
        if (lastPairBit == 0b01001011) {
          currentDir = "CCW";
          counter--;
        } else {
          currentDir = "CW";
          counter++;    
        }
        
        Serial.print("Direction: ");
        Serial.print(currentDir);
        Serial.print(" | Counter: ");
        Serial.println(counter);
      
      }
    }
  }
}

Then check the Serial Monitor to see how fast it is. Note that you should avoid the delay() function in your code or any other that interrupts the cycle by too much time. Consider using a second auxiliary Arduino for anything else than counting.

Resulting Serial Output: enter image description here