Monday, September 9, 2013

Using a Rotary Encoder with the Arduino

This is the third tutorial in our 37 in 1 Sensor Kit series. Normal volume controls and potentiometers have stops at each end of the scale. A rotary encoder is a bit different. There are no stops, it's continuous 360 degree motion, and sends a signal to the controller for each "step" (you can feel the notches or detents). This works well for dialing in a temperature, speed, volume, or even making menu choices. This sketch will output a digit for each stop, so you only need to make that digit value (2,3,4, etc) equal something in your sketch. For instance, set 140 on the encoder to keep a water heater at the correct temperature, or dial in 3600 to set a motor speed. Pushing down on the shaft is a reset button that resets the encoder value to zero. Rotating the shaft clockwise increments the index by one per step, rotating counter clockwise decrements the index. There are 20 steps in one full revolution, and this sketch can count over 65000 steps before starting from zero again. Because this sketch uses interrupts instead of a loop, it responds quickly and there's no missed pulses.

Sensor Kit (or get the rotary encoder by itself)
Male to Female Jumper Kit



/* interrupt routine for Rotary Encoders


   The average rotary encoder has three pins, seen from front: A C B
   Clockwise rotation A(on)->B(on)->A(off)->B(off)
   CounterCW rotation B(on)->A(on)->B(off)->A(off)

   and may be a push switch with another two pins, pulled low at pin 8 in this case
  

*/

// usually the rotary encoders three pins have the ground pin in the middle
enum PinAssignments {
  encoderPinA = 2,   // right (labeled DT on our decoder, yellow wire)
  encoderPinB = 3,   // left (labeled CLK on our decoder, green wire)
  clearButton = 8    // switch (labeled SW on our decoder, orange wire)
// connect the +5v and gnd appropriately
};

volatile unsigned int encoderPos = 0;  // a counter for the dial
unsigned int lastReportedPos = 1;   // change management
static boolean rotating=false;      // debounce management

// interrupt service routine vars
boolean A_set = false;              
boolean B_set = false;


void setup() {

  pinMode(encoderPinA, INPUT_PULLUP); // new method of enabling pullups
  pinMode(encoderPinB, INPUT_PULLUP); 
  pinMode(clearButton, INPUT_PULLUP);
 // turn on pullup resistors (old method)
  //digitalWrite(encoderPinA, HIGH);
 // digitalWrite(encoderPinB, HIGH);
 // digitalWrite(clearButton, HIGH);

// encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
  attachInterrupt(1, doEncoderB, CHANGE);

  Serial.begin(9600);  // output
}

// main loop, work is done by interrupt service routines, this one only prints stuff
void loop() { 
  rotating = true;  // reset the debouncer

  if (lastReportedPos != encoderPos) {
    Serial.print("Index:");
    Serial.println(encoderPos, DEC);
    lastReportedPos = encoderPos;
  }
  if (digitalRead(clearButton) == LOW )  {
    encoderPos = 0;
  }
}

// Interrupt on A changing state
void doEncoderA(){
  // debounce
  if ( rotating ) delay (1);  // wait a little until the bouncing is done

  // Test transition, did things really change? 
  if( digitalRead(encoderPinA) != A_set ) {  // debounce once more
    A_set = !A_set;

    // adjust counter + if A leads B
    if ( A_set && !B_set ) 
      encoderPos += 1;

    rotating = false;  // no more debouncing until loop() hits again
  }
}

// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) delay (1);
  if( digitalRead(encoderPinB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) 
      encoderPos -= 1;

    rotating = false;
  }
}