Need to count state changes on rotary encoder

I recently added a rotary encoder to a hacked servo.

I'm trying to count the number of state changes (Channel A or Channel B), so I can use this to determine how far the encoder has rotated.

Arduino code posted below.

<code>

/*
  State change detection (edge detection) for a rotary encoder
  
 We want to know when the state of either ChannelA or ChannelB changes.
 This will help determine the total number of state changes per revolution.
 
  The circuit:
 * rotary encoder ChannelA attached to pin 2
 * rotary encoder ChannelB attached to pin 3
 * rotary encoder ground attached to ground
 * LED attached from pin 13 to ground (or use the built-in LED on
   most Arduino boards)
 
 Modified from Tom Igoe's ButtonStateChange example found at:
  http://arduino.cc/en/Tutorial/ButtonStateChange
 
 */

// Constants
const int  ChanAPin = 52;    // pin for encoder ChannelA
const int  ChanBPin = 53;    // pin for encoder ChannelB
const int ledPin = 13;      // pin for LED indicator

// Variables
int encoderCounter = -1;   // counter for the number of state changes
int ChanAState = 0;         // current state of ChanA
int ChanBState = 0;        // current state of ChanB
int lastChanAState = 0;     // previous state of ChanA
int lastChanBState = 0;    // previous state of ChanB

void setup() {
  // initialize the encoder pins as inputs:
  pinMode(ChanAPin, INPUT);
  pinMode(ChanBPin, INPUT);
  // Set the pullup resistors
  digitalWrite(ChanAPin, HIGH);
  digitalWrite(ChanBPin, HIGH);
  // initialize the LED as an output:
  pinMode(ledPin, OUTPUT);
  // initialize serial communication:
  Serial.begin(19200);
  Serial.println("Rotary Encoder Counter");
}

void loop() {
  // read the encoder input pins:
  ChanAState = digitalRead(ChanAPin);
  ChanBState = digitalRead(ChanBPin); 

  // compare the both channel states to previous states
  if (ChanAState != lastChanAState || ChanBState != lastChanBState) {
    // if the state has changed, increment the counter
      encoderCounter++;
      Serial.print("Channel A State = ");
      Serial.println(ChanAState);
      Serial.print("Channel B State = ");
      Serial.println(ChanBState);     
      Serial.print("State Changes = ");
      Serial.println(encoderCounter, DEC);
    // save the current state as the last state,
    //for next time through the loop
    lastChanAState = ChanAState;
    lastChanBState = ChanBState;   
  }
}

</code>

TinHead has already pointed out that I should use an interrupt instead of a loop. He suggested a state variable to track pulses set to 1 within the interrupt (after checking that it is not already set to 1), and then cleared within the main program loop after incrementing a counter. 

I will do that, but my big problem is that I suspect the encoder is liable to the same sort of mechanical bounce you will have with any switch. So I'm getting extra state changes.

I may need to add capacitors between each channel pin and ground. Depending on the expected rotation speed, this may or may not work.

Any thoughts on the code above or the debounce problem?


Update 2010-10-29

I tried adding two capacitors between each channel pin of the encoder and ground. This definitely improved things, but it is still not perfect.I used a 0.1 uF and a 0.022 uF. I may switch out the smaller cap for a 0.01 uF capacitor.

I'm seeing approximately 60 transitions per revolution on one rotary encoder channel. However, It is not consistant. I'm clearly still seeing some bounces. I REALLY wish I had an oscilloscope!

Below is my updated code using an interrupt. Still working on the problem.

<code>

// Constants
const int  ChanAPin = 2;    // pin for encoder ChannelA
const int  ChanBPin = 3;    // pin for encoder ChannelB
const int ledPin = 13;      // pin for LED indicator

// Variables
int oldCount = 0;          // previous count
int lastChanAState = 0;     // previous state of ChanA
int lastChanBState = 0;    // previous state of ChanB
// The following variables are declared volatile, because they may be changed during an interrupt
volatile int ChanAState = 0;         // current state of ChanA.
volatile int ChanBState = 0;        // current state of ChanB
volatile int encoderCounter = 0;   // counter for the number of state changes

void setup() {
  // initialize the encoder pins as inputs:
  pinMode(ChanAPin, INPUT);
  pinMode(ChanBPin, INPUT);
  // Set the pullup resistors
  digitalWrite(ChanAPin, HIGH);
  digitalWrite(ChanBPin, HIGH);
  // initialize serial communication:
  Serial.begin(19200);
  Serial.println("Rotary Encoder Counter");
  attachInterrupt(0, count, CHANGE);
//  attachInterrupt(1, count, CHANGE);
}

void loop() {

    if (oldCount != encoderCounter) {
     
      Serial.print("Channel A State = ");
      Serial.println(ChanAState);
//      Serial.print("Channel B State = ");
//      Serial.println(ChanBState);     
      Serial.print("State Changes = ");
      Serial.println(encoderCounter, DEC);
    // save the current state as the last state,
    //for next time through the loop
    oldCount = encoderCounter;
    lastChanAState = ChanAState;
//    lastChanBState = ChanBState;   
  }
}

void count() {
  // compare the both channel states to previous states
//  if (ChanAState != lastChanAState || ChanBState != lastChanBState) {
    // if the state has changed, increment the counter
    //    encoderCounter++;

//   if (ChanAState == digitalRead(ChanAPin) && ChanBState == digitalRead(ChanBPin)) {
//   if (digitalRead(ChanAPin) == digitalRead(ChanAPin) && digitalRead(ChanBPin) == digitalRead(ChanBPin)) {    
//                                   delay(20);
    ChanAState = digitalRead(ChanAPin);
//    ChanBState = digitalRead(ChanBPin); 

    encoderCounter++;
      // read the encoder input pins:

//  }
   }

</code>

 


Update 2010-10-30

Some test results:

I ran a few tests last night and tonight. In these tests I used a motor to turn some of my encoders one revolution. The encoders included:

  1. the original one installed in the hacked servo
  2. another encoder with no caps installed
  3. an encoder with 0.1uF and 0.022uF capacitors installed between the channel pins and ground
  4. an encoder with 10uF caps installed between the channel pins and ground
  5. an encoder with 0.1uF and 0.01uF capacitors installed between the channel pins and ground

Results:

  1. Original encoder saw hundreds (typically 350-550) of transitions per revolution
  2. Other encoder with no caps saw hundreds (typically 320-340) of transitions per revolution
  3. Encoder with 0.1uF and 0.022uF caps saw 60-70 transitions per revolution
  4. Encoder with 10uF caps saw 0-5 transitions per revolution
  5. Encoder with 0.1uF and 0.01uF caps saw 60-63 transitions per revolution

So I seem to be getting closer to optimizing the filtering. I may try adjusting the smaller cap some more to see if I can get any better, but it gives me hope to have reduced the bouncing so much with just caps already. I'll see how much I can do with software on top of this.

Later that evening...

I tested comparing two digitalReads on each channel pin during the interrupt as a crude software debounce. This did not work with no capacitors. With capacitors, I was still getting some extra increments counted on one interrupt. I made it a four time read on each interrupt, and that seemed to make it so that I always counted every interrupt as one pulse.

<code>

void countB() {//    ChanBState = digitalRead(ChanBPin);    if (digitalRead(ChanBPin) == digitalRead(ChanBPin) && digitalRead(ChanBPin) == digitalRead(ChanBPin)) {    encoderCounter++;  }} 

</code>


Update 2010-11-02

It seems likely that I will need additional hardware debouncing besides the simple addition of capacitors between the channel leads and the common (ground) lead of the encoder.

Here's a schematic of what I am considering:

Debounce_Rotary_Encoder.jpg

For channel A of the encoder, the two capacitors will be charged via the internal pull-up resistor of the Arduino, through R2. Increasing R2's value will increase the amount of time it takes to charge the capacitors. R2 may not be necessary at all; I will certainly test without it and try to get it to work with R1 alone.

Increasing R1 will increase the discharge time, which should hold the digital input to the Arduion high while the switch is bouncing. The 0.1uF and 0.01uF capacitors seem to almost be enough to do this by themselves. At least they seem to have brought the number of bounces down from the hundreds to the just a handful per rotation.

I will test an encoder while slowly increasing R1 and R3 to see if I can eliminate the bounces. Any thoughts on this approach?

Some progress. I switched

Some progress. I switched over to an interrupt for the code, but didn’t have much luck with software only debouncing.

Then I found this article, which suggested a hardware tweak of using both a 0.1 uF and 0.01 uF capacitor. I wound up using a 0.1 and a 0.022 uF. With both capacitors, my software interrupt seems to be catching every state change on one channel. I’ll continue to play with it and report my progress.

HW Debounce

I would suggest hardware debounce for this. Doing it in software within an ISR is asking for trouble.

Would back Tinhead’s  simple counter type ISR.

As for HW debounce, the following is well worth a read. The simple Schmitt trigger circuit should do the business.

http://www.labbookpages.co.uk/electronics/debounce.html#digita

 

This is one of those occassions where a scope would be useful.

I would love to have an

I would love to have an o-scope, but it is just not in the cards right now. Someday, though…

Thanks for the advice. I will give the article a read through. I’m already getting better results with simple capicitive filtering, but I may need something better to cover all operational speeds.

I would try it with a timer

I would try it with a timer interrupt instead of a pin change interrupt. Normally no debounce needed for encoders, when you use a state machine. The quadrature encoder output is a 2-bit gray code, so only 1 bit can change from state to state. I’ve done this on successfully for my Ardubot.

Code can be found here.