Sunday, December 11, 2011

Arduino + Rotary Encoders, rev2

This version of the rotary encoder code handles the random misreading and also has a 2 stage debouncer to help minimize the annoyance of the big debounce time. Here's the code:

/* 
   read a rotary encoder with interrupts
   Encoder hooked up with common to GROUND,
   encoderPinA to pin 2, encoderPinB to pin 3
   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
   
   initially based on http://www.arduino.cc/playground/Main/RotaryEncoders
*/ 

#define encoderPinA 2
#define encoderPinB 3
#define interruptPin 0

const long encoderDebounce = 175;
const long encoderDebounceFast = 10;
const long encoderFastTimeout = 200;
const int encoderStepsTilFast = 2;
long nextEncoderTime = 0;
boolean encoderActive = false;
int encoderLastDirection = 0;

void setup() { 
  pinMode(encoderPinA, INPUT); 
  digitalWrite(encoderPinA, HIGH);
  pinMode(encoderPinB, INPUT); 
  digitalWrite(encoderPinB, HIGH);
  attachInterrupt(interruptPin, startEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");
} 

void loop() {
  if (encoderActive) {
    encoderActive = false;
    long now = millis();
    if (now > nextEncoderTime) {
      //reset fast debounce since there was a pause
      if (now > nextEncoderTime + encoderFastTimeout) {
        encoderLastDirection = 0;
      }
      int a = digitalRead(encoderPinA);
      int b = digitalRead(encoderPinB);
      int encoderDirection = 0;
      if (a == b && a == HIGH) {
        encoderDirection = 1;
      } else if (a != b && b == HIGH) {
        encoderDirection = -1;
      } else {
        encoderDirection = 0;
      }
      boolean fastMode = abs(encoderLastDirection) > encoderStepsTilFast;
      //if we get a random direction change while fast, ignore it
      if (fastMode
            && (encoderLastDirection > 0 && encoderDirection < 0
            || encoderLastDirection < 0 && encoderLastDirection > 0)) {
        encoderDirection = 0;
      }
      encoderLastDirection += encoderDirection;

      if (encoderDirection != 0) {
        nextEncoderTime = now + (fastMode ? encoderDebounceFast : encoderDebounce);
        Serial.println(encoderDirection);
      }
    }
  }

}

void startEncoder() {
  encoderActive = true;
}

Arduino + Rotary Encoders, rev1

After a few days of reading forums of how people can't get accurate results from a rotary encoder from an arduino, I finally figured out a way to make it work for me. In my application, its ok to drop a few "ticks" of the encoder but I really wanted to avoid over-counter and much worse, count in wrong direction. Other solutions often cause both when rotating at slow speeds. Attached is my 1st implementation that only uses the interrupt to notify the arduino that it should look at the encoder pins and instead uses the program loop to read the values. This may not be optimal for code that uses excessive delays but it works well for my application.

/* 
   read a rotary encoder with interrupts
   Encoder hooked up with common to GROUND,
   encoderPinA to pin 2, encoderPinB to pin 3
   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
   
   initially based on http://www.arduino.cc/playground/Main/RotaryEncoders
*/ 

#define encoderPinA 2
#define encoderPinB 3
#define interruptPin 0

// we can probably have 2 stage debounce where 2nd stage 
// debounce lower when a few ticks in same direction
const long encoderDebounce = 150;
long nextEncoderTime = 0;
boolean encoderActive = false;

void setup() { 
  pinMode(encoderPinA, INPUT); 
  digitalWrite(encoderPinA, HIGH);
  pinMode(encoderPinB, INPUT); 
  digitalWrite(encoderPinB, HIGH);
  attachInterrupt(interruptPin, startEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");
} 

void loop() {
  if (encoderActive) {
    encoderActive = false;
    long now = millis();
    if (now > nextEncoderTime) {
      int a = digitalRead(encoderPinA);
      int b = digitalRead(encoderPinB);
      int encoderDirection = 0;
      if (a == b && a == HIGH) {
        encoderDirection = 1;
      } else if (a != b && b == HIGH) {
        encoderDirection = -1;
      } else {
        encoderDirection = 0;    
      }
      if (encoderDirection != 0) {
        Serial.println(encoderDirection);
        nextEncoderTime = now + encoderDebounce;
      }
    }
  }

}

void startEncoder() {
  encoderActive = true;
}

As you can see if relies heavily on a high debounce time. If I reduce the debounce time to less than 50, I get lots of double-counts on slow rotations so I just played it safe and set it really high. I'll address the limited count speed in my next post.

Friday, December 02, 2011

JavaJson

http://javajson.thedeanda.com is a great little open source JSON parser I wrote a few years ago. I've used it in lots of little projects including Android development. The project is hosted at sourceforge.