Page 1 of 1

Arduino + rotary encoder

Posted: Sat Jan 31, 2009 6:36 pm
by metsfan
I recently got this rotary encoder for use in a project I'm working on. Here's some simple code to make it count up and down when you turn it clockwise or counter-clockwise.

Code: Select all

int state, prevstate = 0, count = 0;
int nextEncoderState[4] = { 2, 0, 3, 1 };
int prevEncoderState[4] = { 1, 3, 0, 2 };

void setup()
{
  pinMode(7, INPUT);
  pinMode(5, INPUT);
  pinMode(6, OUTPUT);
  digitalWrite(6, LOW);
  digitalWrite(5, HIGH);
  digitalWrite(7, HIGH);
  Serial.begin(9600);  
}

void loop()
{
  state = (digitalRead(7) << 1) | digitalRead(5);
  if (state != prevstate) {
    if (state == nextEncoderState[prevstate]) {
       count++;
    } else if (state == prevEncoderState[prevstate]) {
       count--; 
    }
    Serial.println(count, DEC); 
    prevstate = state;
  }
}
The encoder is connected to digital pins 5,6 and 7 on the Arduino. This will just output the count to the serial port, which can be monitored from the Arduino GUI.

Posted: Wed Jun 03, 2009 5:23 pm
by boomy
This works great out of the box!
Only think I can't find out is why every tick at the rotary encoder adds 4 instead of 1 to the count.

(I'm new to arduino, so I could be missing some knowledge :oops: )

I found the next code also usefull, but this one also count's a few extra for one click on the rotary encoder.
I'm trying to add a delay in somewhere, but I'm not sure where.....

Code: Select all

/* 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  4

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 (digitalRead(encoder0PinA) == HIGH) {   // found a low-to-high on channel A
    if (digitalRead(encoder0PinB) == LOW) {  // check channel B to see which way
                                             // encoder is turning
      encoder0Pos = encoder0Pos - 1;         // CCW
    } 
    else {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
  }
  else                                        // found a high-to-low on channel A
  { 
    if (digitalRead(encoder0PinB) == LOW) {   // check channel B to see which way
                                              // encoder is turning  
      encoder0Pos = encoder0Pos + 1;          // CW
//      delay(10);
    } 
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
//      delay(10);
    }

  }
  Serial.println (encoder0Pos, DEC);          // debug - remember to comment out
                                              // before final program run
  // you don't want serial slowing down your program if not needed
}

/*  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). 
*/ 

Posted: Fri Jun 05, 2009 11:33 am
by pwillard
You probably need to de-bounce your rotary encoder switches.

http://www.ganssle.com/debouncing.pdf

Posted: Sat Dec 19, 2009 9:46 pm
by eayars
I don't think debouncing is the problem: more likely the detents on the switch are spaced one per complete cycle, and the code given counts changes in both lines. There are four changes per complete cycle, so that's why you get the 4 counts.

What I've found works better is to run an interrupt off a falling (or rising) edge on one line, and check direction on the other line in the interrupt routine. Here's a post describing the method more thoroughly:

http://hacks.ayars.org/2009/12/using-qu ... witch.html

Posted: Sat Feb 06, 2010 4:31 pm
by bramm
Thanks, this works great.
I'm completely new to this, but why do you use pin 6 (the output pin)?
It also works if I connect the middle pin of the encoder to GND. Am I missing something?

+ this solves the 4 step increments:

Code: Select all

...
void loop()
{
  state = (digitalRead(inp1) << 1) | digitalRead(inp2);
  if (state != prevstate) {
    prevstate = state;
    if(state==1){
      if (state == nextEncoderState[prevstate]) {
        count++;
      } 
      else if (state == prevEncoderState[prevstate]) {
        count--;
      }
      Serial.println(count);
    }
  }
} 
[/code]

Posted: Wed Feb 10, 2010 5:28 am
by mac
You should debounce the input if encoder is mecanical and not optical.

Even with high quality mecanical ones there could be some bouncing.
If they are bouncing it's most of the time short bursts of pulses which can be very annoying if you work with interrupts.

The idea with debounce is to double check pin input by at least two time separated samples, and then call you decoding logic when state has changed and is confirmed. Easier to do with fixed polling.

Don't forget also that with this kind of switch, the quadratic output is not symetrical at all: transitions are very short compared to mechanical stable state.

Re: Arduino + rotary encoder

Posted: Sun Feb 28, 2010 10:41 pm
by sazhapooh
void loop()
{
state = (digitalRead(inp1) << 1) | digitalRead(inp2);
if (state != prevstate) {
prevstate = state;
if(state==1){
if (state == nextEncoderState[prevstate]) {
count++;
}
else if (state == prevEncoderState[prevstate]) {
count--;
}
Serial.println(count);
}
}
}

have you declared your inp1 and inp2 ?

Re: Arduino + rotary encoder

Posted: Sat Mar 26, 2011 6:40 pm
by Aggrav8d
Not to beat a dead horse but here's a version that's a little easier to configure and doesn't require any if statements in the main loop (so it can go a lot faster). I've also copied it to my blog.

Code: Select all

#define PIN_HIGHBIT (7)
#define PIN_LOWBIT  (5)
#define PIN_PWR     (3)
#define BAUDRATE    (9600)
#define DEBUG         (1)

// globals
int state, prevState = 0, count = 0;
//int nextEncoderState[4] = { 2, 0, 3, 1 };
//int prevEncoderState[4] = { 1, 3, 0, 2 };
/* o n dir
 * 0 2 +
 * 1 0 +
 * 2 3 +
 * 3 1 +
 * 0 1 -
 * 1 3 -
 * 2 0 -
 * 3 2 -
 */
int encoderStates[4][4] = {
 {  0, -1,  1,  0 }, 
 {  1,  0,  0, -1 }, 
 { -1,  0,  0,  1 }, 
 {  0,  1, -1,  0 }, 
};

void setup() {
  pinMode(PIN_HIGHBIT, INPUT);
  pinMode(PIN_LOWBIT, INPUT);
  pinMode(PIN_PWR, OUTPUT);
  digitalWrite(PIN_PWR, LOW);
  digitalWrite(PIN_LOWBIT, HIGH);
  digitalWrite(PIN_HIGHBIT, HIGH);
  Serial.begin(BAUDRATE); 
}

void loop() {
  state = (digitalRead(PIN_HIGHBIT) << 1) | digitalRead(PIN_LOWBIT);
  count += encoderStates[prevState][state];

#ifdef DEBUG
  if (state != prevState) {
    Serial.print(state, DEC);
    Serial.print(' ');
    Serial.print(prevState, DEC);
    Serial.print(' ');
    Serial.println(count, DEC);
  }
#endif

  prevState = state;
}
Cheers!

Re: Arduino + rotary encoder

Posted: Thu Oct 06, 2011 2:49 am
by buxtronix
Resurrecting this thread again, as my recent need for one led me to do some useful stuff..

The main issue with a lot of these routines is that they are susceptible to switch contact bounce, and can give spurious output. I looked for a lot and didn't come up with much (just a lot of routines with debounce code in them). My application required no bounce, and had to be as small as possible.

So I set about writing a routine from scratch (that also happens to use a state machine), and I'm pretty convinced that it's quite effective. No contact bounce, very small code footprint, and handles 2 and 4 step encoders. It also supports interrupt driven mode as well as polled.

I have it as an Arduino library too (see my blog)

Please give it a try:

Code: Select all

/* Rotary encoder handler for arduino.
 *
 * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
 * Contact: bb@cactii.net
 *
 * Quick implementation of rotary encoder routine.
 *
 * More info: http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
 *
 */

// Half-step mode?
#define HALF_STEP
// Arduino pins the encoder is attached to. Attach the center to ground.
#define ROTARY_PIN1 2
#define ROTARY_PIN2 3
// define to enable weak pullups.
#define ENABLE_PULLUPS


#ifdef HALF_STEP
// Use the half-step state table (emits a code at 00 and 11)
const char ttable[6][4] = {
  {0x3 , 0x2, 0x1,  0x0}, {0x83, 0x0, 0x1,  0x0},
  {0x43, 0x2, 0x0,  0x0}, {0x3 , 0x5, 0x4,  0x0},
  {0x3 , 0x3, 0x4, 0x40}, {0x3 , 0x5, 0x3, 0x80}
};
#else
// Use the full-step state table (emits a code at 00 only)
const char ttable[7][4] = {
  {0x0, 0x2, 0x4,  0x0}, {0x3, 0x0, 0x1, 0x40},
  {0x3, 0x2, 0x0,  0x0}, {0x3, 0x2, 0x1,  0x0},
  {0x6, 0x0, 0x4,  0x0}, {0x6, 0x5, 0x0, 0x80},
  {0x6, 0x5, 0x4,  0x0},
};
#endif
volatile char state = 0;

/* Call this once in setup(). */
void rotary_init() {
  pinMode(ROTARY_PIN1, INPUT);
  pinMode(ROTARY_PIN2, INPUT);
#ifdef ENABLE_PULLUPS
  digitalWrite(ROTARY_PIN1, HIGH);
  digitalWrite(ROTARY_PIN2, HIGH);
#endif
}

/* Read input pins and process for events. Call this either from a
 * loop or an interrupt (eg pin change or timer).
 *
 * Returns 0 on no event, otherwise 0x80 or 0x40 depending on the direction.
 */
char rotary_process() {
  char pinstate = (digitalRead(ROTARY_PIN2) << 1) | digitalRead(ROTARY_PIN1);
  state = ttable[state & 0xf][pinstate];
  return (state & 0xc0);
}

void setup() {
  Serial.begin(9600);
  rotary_init();
}

void loop() {
  char result = rotary_process();
  if (result)
    Serial.println(result == 0x40 ? "LEFT" : "RIGHT");   
}