Tuesday, January 4, 2011

a transition state is not bounce

I've had the ?joy? of working with a cheap rotary encoder ($1.49 @ Elec Goldmine: http://www.goldmine-elec-products.com/prodinfo.asp?number=G16267) and an arduino lately.  My ultimate goal is a hot tub controller and this is just one of the pieces, but more on that later... back to the encoder.

There is a lot of information on rotary encoders and arduino available online, but the majority of it only deals with a fraction of the states that the encoder displays and so makes a direction-of-rotation determination into a bit of a WAG.  Much of what I found uses some form of delay to "debounce" the rotary encoder so as to only read the stable detent position of the knob (think of a car stereo knob or something... the 'clicks').  The problem is that they are ignoring a valid transition state and treating it as circuit noise.  It mostly works, but can lead to some bad readings in my limited experience (http://en.wikipedia.org/wiki/Rotary_encoder#Incremental_rotary_encoder explains this better than I could).  The other issue is that some of the state transitions are so fast that if individual pins are read sequentially, the state might have changed by the time you get to the second one - you need to read the pins at exactly the same time.

The best description (and code) for this that I found was http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino.  Unfortunately I couldn't use his code unchanged as I wanted to use interrupts to capture the state changes and I therefore needed to adapt his port reads to use different pins (digital 2 and 3 instead of 0 and 1 in this case).

I started out knowing jack about bitwise operators (and probably still don't know much) but here we are.

The adapted code is below and should work for any two sequential pins within a given port (http://www.arduino.cc/en/Reference/PortManipulation) by changing the offset (bitShift).  This code does not ignore state changes and the encoder still needs to have a hardware debounce (because bouncing is very real, just different from a transitional state).  As the encoder I am using has 2 state changes between each detent, I do ignore half the state changes for the purpose of counting (I keep track of them internally so I always know the direction of rotation).

as blogger doesn't seem to have a <code> format option...
---begin code---
/*
  RotaryInterrupts - a port-read and interrupt based rotary encoder sketch
  Created by Joshua Layne (w15p), January 4, 2011.
  based largely on: http://www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino
  Released into the public domain.
*/

#define ENC_A 2
#define ENC_B 3
#define ENC_PORT PIND
uint8_t bitShift = 2; // change to suit your pins (offset from 0,1 per port)

int counter;
boolean ticToc;

void setup()
{
  pinMode(ENC_A, INPUT);
  digitalWrite(ENC_A, HIGH);
  pinMode(ENC_B, INPUT);
  digitalWrite(ENC_B, HIGH);
  Serial.begin (115200);
  Serial.println("Start");
  counter = 0;
  ticToc = false;
}

void loop()
{
 attachInterrupt(0, read_encoder, CHANGE);
 attachInterrupt(1, read_encoder, CHANGE);
}

void read_encoder()
{
  int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
  static uint8_t encoderState = 0;
  static uint8_t stateIndex = 0;
  static uint8_t filteredPort = 0;
  uint8_t filter = 0x03; // base filter: 0b00000011
  filter <<= bitShift;
 
  Serial.print("raw port value: ");
  Serial.println(ENC_PORT, BIN);

  Serial.print("filter bitmask: ");
  Serial.println(filter, BIN);
 
  filteredPort = ENC_PORT & filter;
  Serial.print("filtered port state: ");
  Serial.println(filteredPort, BIN);

  Serial.print("old encoder state: ");
  Serial.println(encoderState, BIN);
 
  encoderState &= filter; // filter out everything except the encoder pins
  Serial.print("filtered old encoder state: ");
  Serial.println(encoderState, BIN);
 
  encoderState <<= 2; // shift existing value two bits to the left
  Serial.print("filtered and shifted (<<2) old encoder state: ");
  Serial.println(encoderState, BIN);
 
  encoderState |= filteredPort; // add filteredport value
  Serial.print("old encoder state + port state: ");
  Serial.println(encoderState, BIN);
 
  stateIndex = encoderState >> bitShift;
  Serial.print("encoder state index: ");
  Serial.println(stateIndex, DEC);
 
  if (ticToc) {
  Serial.print("counter tic: ");
  Serial.println(enc_states[stateIndex], DEC);
  counter += enc_states[stateIndex];
  Serial.print("counter: ");
  Serial.println(counter, DEC);
  }
  ticToc = !ticToc;
 
  Serial.println("----------");
}
---end code---

Obviously if you actually use this code, you're going to want to comment out all of the Serial commands, but this should help you understand what is going on with the bitmasks and such.

No comments:

Post a Comment