Help Newbie with Rotary Encoder Please!

I just can't seem to find some code that works right, including examples and tutorials!

I just want to read the encoder values so that they increase with clockwise and decrease with counterclockwise. All the examples increase the value in both directions! I just can't figure it out.
I think code with interrupts might be better, but I'll take anything that works.

Thanks for your help!

Read_Rotary_Encoder.ino (718 Bytes)

Have a look here Buxtronix: Rotary encoders, done properly

This site mentions an encoder with three pins....aside from the power pins, the ones on mine are data, clock, and button (which I am using successfully); there is no "wipe" pin. Not only that, but the example given there uses four Arduino inputs...I already am scraping the bottom of the barrel, and must use just two to read the encoder.

All the examples I find seem to use just the data and clock pins, but as I said, they will only increment the counter.

Please post a link to your encoder.

The cheap standard encoders which I use don't have data nor clock, just switches.

Here is another sketch. Encoder only goes up in each direction. I am beginning to think there might be a problem with the encoder? The program looks clean. Please advise!

rotary_encoder_menus.ino (2.3 KB)

Please post a link to yoúr encoder.

If you attach code please use code tags, attach only if too big.

PABresler:
Here is another sketch. Encoder only goes up in each direction. I am beginning to think there might be a problem with the encoder? The program looks clean. Please advise!

The program calls delay() inside an ISR, so its thoroughly broken.

No need for an ISR for a manual rotary encoder, it doesn't move fast enough!

int position = 0 ;  // maintained by handle_rotenc()
int errors = 0 ;

void handle_rotenc ()
{
  static byte prev_code = 0 ;
  byte code ;
  if (digitalRead (ROT_ENC1))
    code = digitalRead (ROT_ENC2) ? 0 : 1 ;  // note the 0, 1, 2, 3 go clockwise in a square
  else
    code = digitalRead (ROT_ENC2) ? 3 : 2 ;  // on these two lines - no accident

  byte diff = (code - prev_code) & 3 ;
  switch (diff)   // determine the sense of change and catch changes of 2 steps as errors
  {
    case 1: position ++ ; break ;
    case 3: position -- ; break ;  // anding with 3 means 3 represents -1
    case 2: errors ++ ; break ;
  }
  prev_code = code ;
}

void loop ()
{
  handle_rotenc () ; // every time round loop call the handler
  ..
}

I added stuff to make the example sketch functional; it just gives 1s and 2s when my encoder is turned, and not completely consistently in each direction.

I think I am beginning to understand what is going on here (maybe)...there are two switches which are equal, A and B, so the problem is which goes first in each direction, right??

I might be able to eventually write some code for that, but I am sure its been done a few times already! I just want this so I can use the encoder to set an alarm time!

This is what I put together from the last example:

const int ROT_ENC1=2;
const int ROT_ENC2=3;
const int Button=10;
int position = 0 ; // maintained by handle_rotenc()
int errors = 0 ;

void setup()
{
pinMode(ROT_ENC1, INPUT);
pinMode(ROT_ENC2, INPUT);
pinMode(Button, INPUT);
Serial.begin(9600);
Serial.print("Start");
}
void handle_rotenc ()
{
static byte prev_code = 0 ;
byte code ;
if (digitalRead (ROT_ENC1))
code = digitalRead (ROT_ENC2) ? 0 : 1 ; // note the 0, 1, 2, 3 go clockwise in a square
else
code = digitalRead (ROT_ENC2) ? 3 : 2 ; // on these two lines - no accident

byte diff = (code - prev_code) & 3 ;
switch (diff) // determine the sense of change and catch changes of 2 steps as errors
{
case 1: position ++ ; break ;
case 3: position -- ; break ; // anding with 3 means 3 represents -1
case 2: errors ++ ; break ;
}
prev_code = code ;
}

void loop ()
{
handle_rotenc () ; // every time round loop call the handler
Serial.println(position);
}

Man, I finally found something that works! Don't have a clue how it does, if anyone else has this problem, here it is!

Encoder_Works.ino (967 Bytes)

PABresler:
Don't have a clue how it does

It uses INPUT_PULLUP on the switches that make up your encoder.

PABresler:
All the examples I find seem to use just the data and clock pins, but as I said, they will only increment the counter.

There are no data and clock pins. Please show me one such example.

PABresler:
Man, I finally found something that works! Don't have a clue how it does, if anyone else has this problem, here it is!

I cannot open the file you have attached. Can you post the code here?

I have been working on my version of the Gray encoder, but if what you got works I'll keep it for myself.
It needs some "what if" checking anyway to make it bulletproof.

Most Gray code encoder programs I have seen so far do not utilize Gray code properties, hence they are unnecessary complicated.

For Vaclav's benefit

/* Rotary encoder read example */
#define ENC_A 14 // Analog A0 encoder Data 
#define ENC_B 15 // Analgo A1 encoder Clock
#define ENC_PORT PINC
 
void setup()
{
  /* Setup encoder pins as inputs */
  pinMode(ENC_A, INPUT);
  digitalWrite(ENC_A, HIGH);
  pinMode(ENC_B, INPUT);
  digitalWrite(ENC_B, HIGH);
  Serial.begin (9600);
  Serial.println("Start");
}
 
void loop()
{
 static uint8_t counter = 0;      //this variable will be changed by encoder input
 int8_t tmpdata;
 /**/
  tmpdata = read_encoder();
  if( tmpdata ) {
    Serial.print("Counter value: ");
    Serial.println(counter, DEC);
    counter += tmpdata;
  }
}
 
/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
  static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static uint8_t old_AB = 0;
  /**/
  old_AB <<= 2;                   //remember previous state
  old_AB |= ( ENC_PORT & 0x03 );  //add current state
  return ( enc_states[( old_AB & 0x0f )]);
}

It uses the static variable old_AB to store the previous and the new state of 2 pins. It does this by shifting its value two bits to the left ( <<= 2 ) and then adding the lower 2 bits of ENC_PORT. (Direct access to the register rather than digitalRead().)

So the lower 4 bits of old_AB contain the previous state and the new state. That makes a number from 0 to 15 which is looked up in this table: (Edit: it is not looked up. It is used as the index.)

static int8_t enc_states[] =
{     // Edit to add more comments:
 0,   // 00 00  no changes
 -1,  // 00 01  low, up
 1,   // 00 10  up, low
 0,   // 00 11  both changed
 1,   // 01 00  low, down
 0,   // 01 01  no changes
 0,   // 01 10  both changed
 -1,  // 01 11  up, high
 -1,  // 10 00  down, low
 0,   // 10 01  both changed
 0,   // 10 10  no changes
 1,   // 10 11  high, up
 0,   // 11 00  both changed
 1,   // 11 01  down, high
 -1,  // 11 10  high, down
 0    // 11 11  no changes
};

If the first 2 bits are the same as the last 2 bits, there is no change, so the result is zero.

If more than 1 bit is different between the two (Edit: so that would be both bits), it is an impossible transition, so the result is also zero.

If only one bit has changed, it can tell whether you rotated one step to the left or right, so the result is 1 or -1.

Jobi-Wan:
It uses the static variable old_AB to store the previous and the new state of 2 pins. It does this by shifting its value two bits to the left ( <<= 2 ) and then adding the lower 2 bits of ENC_PORT. (Direct access to the register rather than digitalRead().)

So the lower 4 bits of old_AB contain the previous state and the new state. That makes a number from 0 to 15 which is looked up in this table:

static int8_t enc_states[] =

{
0,  // 00 00
-1,  // 00 01
1,  // 00 10
0,  // 00 11
1,  // 01 00
0,  // 01 01
0,  // 01 10
-1,  // 01 11
-1,  // 10 00
0,  // 10 01
0,  // 10 10
1,  // 10 11
0,  // 11 00
1,  // 11 01
-1,  // 11 10
0    // 11 11
};




If the first 2 bits are the same as the last 2 bits, there is no change, so the result is zero. 

If more than 1 bit is different between the two, it is an impossible transition, so the result is also zero. 

If only one bit has changed, it can tell whether you rotated one step to the left or right, so the result is 1 or -1.

Yes, there are many ways to skin a cat.

I just use "^" to check for validity of the code change and Gray code array index to find the direction.
So far don't see much of the benefits in using registers directly on Due.
Would using "register" variable be same?

Vaclav:
So far don't see much of the benefits in using registers directly on Due.

Me neither.
I'm not very familiar with the inner workings of Arduino, so take the following with a grain of salt:
It is done to read the two pins really at the same time, and guarantee that nothing changes between readings.
But I don't think it matters, because only one bit should flip at a time, so if you miss it between readings, you will catch it on the next round, which will be overwhelmingly likely to be in time, since even a slow microprocessor is much faster than a human turning a knob.

Vaclav:
Would using "register" variable be same?

No. 'register' just tells the compiler to use a register for this variable, rather than a memory location. I would generally leave such decisions up to the compiler.

Jobi-Wan:
Me neither.
I'm not very familiar with the inner workings of Arduino, so take the following with a grain of salt:
It is done to read the two pins really at the same time, and guarantee that nothing changes between readings.
But I don't think it matters, because only one bit should flip at a time, so if you miss it between readings, you will catch it on the next round, which will be overwhelmingly likely to be in time, since even a slow microprocessor is much faster than a human turning a knob.

No. 'register' just tells the compiler to use a register for this variable, rather than a memory location. I would generally leave such decisions up to the compiler.

Thanks.
I honestly believe the whole issue of encoders programs missing inputs, including usage of interrupts, missing encoder reversal , adding caps and or debouncing ( especially optical encoders ) boils down to not analyzing the Gray code properly.

If it does not change by one bit and only one bit in correct sequence it is not valid input.

As you said , you can get it on next (loop()) go around and that is one of the reasons I prefer to have my own "go around again" code instead of waiting for loop to finish.

And it has nothing to do with how fast the loop gets around, it is a matter of choosing to control the code.