Pages: [1]   Go Down
Author Topic: Reading rotary encoder  (Read 1181 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

In my frustration with not getting any of the example code to reliably read my rotary encoder, I wrote my own. It works very well for me on my 20 detent 10 ppr encoder. I am using this encoder for a small user interface, so I don't need perfect accuracy at high speeds.

Here is the code:

Code:
//
//  YAWTRARE - Yet Another Way To Read A Rotary Encoder
//
//  This example is set up for encoders that have 2 detents per pulse.
//    I am using a 20 detent 10 ppr. 00 && 11,.. are stable AB values in detents.
//    00,01,11 && 11,10,00 would represent one detent move CW.
//    00,10,11 && 11,01,00 would represent one detent CCW.
//
//  The sketch uses a moving average and a trigger to accurately sense position.
//    It is essentially a delay, but instead of doing
//    nothing, we are gathering data as to what the true move will be.
//

#define ENC_A 2                         //Encoder Pin A
#define ENC_B 3                         //Encoder Pin B

int counter;
boolean SIG_A[11];                      //Moving average array for Pin A
boolean SIG_B[11];                      //Moving average array for Pin B
boolean trigger = false;
int SIG_A_AVG_LAST;
int SIG_B_AVG_LAST;

void setup()
{
  pinMode(ENC_A, INPUT);
  digitalWrite(ENC_A, HIGH);            //Use builtin pullup resistor
  pinMode(ENC_B, INPUT);
  digitalWrite(ENC_B, HIGH);            //Use builtin pullup resistor
 
  Serial.begin(9600);
}

void loop()
{
  int SIG_A_AVG = 0;
  int SIG_B_AVG = 0;
 
  for (int i=9; i>0; i--)               //Shift moving average array values to the right
  {
    SIG_A[i] = SIG_A[i-1];
    SIG_A_AVG = SIG_A_AVG + SIG_A[i-1]; //Create moving average for Pin A
    SIG_B[i] = SIG_B[i-1];
    SIG_B_AVG = SIG_B_AVG + SIG_B[i-1]; //Create moving average for Pin B
  }
 
  SIG_A[0] = digitalRead(ENC_A);        //Read Pin A value to first position in array
  SIG_B[0] = digitalRead(ENC_B);        //Read Pin B value to first position in array
 
  SIG_A_AVG = SIG_A_AVG + SIG_A[0];     //Add new value to moving average
  SIG_B_AVG = SIG_B_AVG + SIG_B[0];     //Add new value to moving average
 
  if (SIG_A_AVG < 10)                   //See if moving average is at least 0.9
  {
    SIG_A_AVG = 0;
  }
  else
  {
    SIG_A_AVG = 1;
  }

  if (SIG_B_AVG < 10)
  {
    SIG_B_AVG = 0;
  }
  else
  {
    SIG_B_AVG = 1;
  }
 
  if (SIG_B_AVG != SIG_B_AVG_LAST && SIG_A_AVG == SIG_A_AVG_LAST && trigger == false)
  {
    counter++;                          //Register CW turn
    Serial.println(counter);
    trigger = true;                     //Set trigger
  }
 
  if (SIG_A_AVG != SIG_A_AVG_LAST && SIG_B_AVG == SIG_B_AVG_LAST && trigger == false)
  {
    counter--;                          //Register CCW turn
    Serial.println(counter);
    trigger = true;                     //Set trigger
  }
 
  if (SIG_A_AVG == SIG_B_AVG && trigger == true)
  {
    trigger = false;                    //Release trigger once encoder settles in detent
  }
 
  SIG_A_AVG_LAST = SIG_A_AVG;           //Save last value of Pin A
  SIG_B_AVG_LAST = SIG_B_AVG;           //Save last value of Pin B
}
Logged

Offline Offline
Sr. Member
****
Karma: 1
Posts: 462
I am a amateur.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Here is an example from Arduino Evil Genius with the A Pin in pin 6 and the B Pin in pin 7. It returns -1 when turning CCW, 0 when not turning and 1 when turning CW. You have to repeatedly call this function (at whatever speed you need). I am not sure which pins they are on the encoder, but you can check out the book http://www.arduinoevilgenius.com/.

Code:
int aPin = 6;
int bPin = 7;
int getEncoderTurn()
{
  // return -1, 0, or +1
  static int oldA = LOW;
  static int oldB = LOW;
  int result = 0;
  int newA = digitalRead(aPin);
  int newB = digitalRead(bPin);
  if (newA != oldA || newB != oldB)
  {
    // something has changed
    if (oldA == LOW && newA == HIGH)
    {
      result = -(oldB * 2 - 1);
    }
  }
  oldA = newA;
  oldB = newB;
  return result;
}

Hope this helps.
Logged


0
Offline Offline
Shannon Member
****
Karma: 200
Posts: 11694
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

In my frustration with not getting any of the example code to reliably read my rotary encoder, I wrote my own. It works very well for me on my 20 detent 10 ppr encoder. I am using this encoder for a small user interface, so I don't need perfect accuracy at high speeds.


The problem with that code is that you run moving averages on the wrong variables.

You treat the A and B phases independently, which will lead to occasional errors since the
mark/space ratio of each phase is not the same thing as the phase or position state.

A more reliable approach is to read both pins simultaneously using PIND register, maintain the correct
phase/position information from that, then use moving averages (or any other de-bouncing technique) on the
phase/position variable.

One way to maintain the postion variable is this:
Code:
void setup ()
{
  attachInterrupt (0, handle_enc, CHANGE) ;
  attachInterrupt (1, handle_enc, CHANGE) ;
}

volatile long phase = 0 ;
volatile byte portd = (PIND & 0x0C) >> 2 ;   // initial state of pins 2 and 3

void handle_enc ()
{
  byte pd = (PIND & 0x0C) >> 2 ;  // read pins 2 and 3 simultaneously (we know one has probably just changed)
  pd ^= (pd >> 1) ;               // 00 01 11 10 sequence converted to 00 01 10 11 sequence
  byte sig = (pd - portd) & 3 ;   // difference from last observation (crop to 2 bits)
  if (sig == 1)  // sig should be either 0, 1 or 3 (2 is an illegal state for a working encoder)
    phase -- ;
  else if (sig == 3)
    phase ++ ;
 
  portd = pd ;   // remember this pins' state for next time
}

Then in your loop() function you can implement de-bouncing on the value of phase using your moving
average if you wish.
Logged

[ I won't respond to messages, use the forum please ]

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5345
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Then in your loop() function you can implement de-bouncing on the value of phase using your moving
average if you wish.

Rotary encoders use grey-code to make debouncing unnecessary. It's what those codes are for. Only one switch can be in transition at any time.

If both went high then both went low again, that's a click. All you need to know is which one went high first - that gives you the direction.

Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

0
Offline Offline
Shannon Member
****
Karma: 200
Posts: 11694
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Then in your loop() function you can implement de-bouncing on the value of phase using your moving
average if you wish.

Rotary encoders use grey-code to make debouncing unnecessary. It's what those codes are for. Only one switch can be in transition at any time.

If both went high then both went low again, that's a click. All you need to know is which one went high first - that gives you the direction.



Debouncing is sometimes desired, since the cheap rotary switches often transition very close to the centre of
a detent, causing the value to oscillate as the spring settles into a detent - the user interface behaves most
intuitively if such rapid variations are hidden from the business-logic of a sketch.  Rotary switches usually have
4 transitions per detent, so the chance of such false pulses is high as the distance from an arbitrary stopping
point and the nearest detent is 0 to 2 transistions, average of 1...

[ and its Gray code, not grey code. http://en.wikipedia.org/wiki/Frank_Gray_%28researcher%29 ]
Logged

[ I won't respond to messages, use the forum please ]

United Kingdom
Offline Offline
Tesla Member
***
Karma: 224
Posts: 6593
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Debouncing is sometimes desired, since the cheap rotary switches often transition very close to the centre of
a detent, causing the value to oscillate as the spring settles into a detent - the user interface behaves most
intuitively if such rapid variations are hidden from the business-logic of a sketch.  Rotary switches usually have
4 transitions per detent, so the chance of such false pulses is high as the distance from an arbitrary stopping
point and the nearest detent is 0 to 2 transistions, average of 1...

I don't believe debouncing helps much in that situation, because it isn't bouncing that is the problem, it is the indeterminate state of the encoder at the detent. What does help is to write the code to use a 2-state hysterisis, so that the code isn't sensitive to the encoder moving back just one state transition.

If you look at the datasheets, you will find that Alps rotary encoders are specified to have the detent at the same point as a transition, whereas Bourns encoders are specified to have a detent midway between two transitions. I always use Bourns encoders.
Logged

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Valencia, Spain
Offline Offline
Faraday Member
**
Karma: 144
Posts: 5345
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Debouncing is sometimes desired, since the cheap rotary switches often transition very close to the centre of
a detent, causing the value to oscillate as the spring settles into a detent

It doesn't matter. The only thing that matters is that they don't both oscillate at the same time.

[ and its Gray code, not grey code. http://en.wikipedia.org/wiki/Frank_Gray_%28researcher%29 ]

Thanks.
Logged

No, I don't answer questions sent in private messages (but I do accept thank-you notes...)

Pages: [1]   Go Up
Jump to: