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:
//
// 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
}
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/.
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;
}
lnxsrt:
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:
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.
MarkT:
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...
MarkT:
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.
MarkT:
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.