Let's Make Robots!

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:

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?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

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.

The combination of your internal pullup resistors and the capacitors should work as an RC filter but a schematic would be 10 times more useful than an oscilloscope.

Try adding some 10uF caps. Combined with your pullup resistors you should effectivly have an RC filter on each input and may simply need to experiment with the capacitor values.

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 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.

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.

Hmmm...  Optical, Mechanical.... we seemed to all be plagued by the same thing...

My system is getting too many counts here.
Robot X is having a similar problem here.

So let's   "Break It On Down" ....

3 Parts of the system

  1. Sensor itself - interestingly your rotational encoder is behaving somewhat like my optical encoder
  2. Electrical connection - and any filtering
  3. Software

Optical Encoder - this is what I'm using and have noticed that I get too many counts occasionally, I have come to the conclusion that it is not the Top end or Low end bounce, but the state of transition from low to high or high to low which is causing the problem.  It manifests itself more often (I believe) when moving at a slow speed, however, I have no hard data for this assumption - although I'm guessing that is the case since the transition time would be faster at higher speed (I should test).

I would think that a mechanical switch would have less noise through the transition state, and more on the top and low end, what do you think?  Could you test?

#1  Sensor's are'nt perfect (Damn that sucks), but its very true - It would be nice to have a manufacture make a sensor which makes perfect square wave, but I don't think that will happen in my lifetime.

#2 Electronic Filters ... The mantra - "a cap will solve that problem", seems popular solution.  Oddbot suggested a 100nF capacitor between the arduino pin and gnd.  I "think" it helped, but the problem has not gone away.  There is no end to elaborate filtering systems.  In the area of encoders I see the LM324 mentioned often.  For example.

Quote from here.

"Depending on how you detect the stasis wheel rotation, will determine the electronics interface. In Paami, we used a Fairchild QRB1114 opto reflector module and detected the IR reflection from a printed paper wheel encoder. This required not only an LM324 opamp for a signal buffer, but was followed with a comparator with hysteresis to form a Schmidt trigger arrangement for the input into the processor."

So you can cap + op amp + schmitt trigger + da da da ....    All kinds of possibilities.

#3 Software - I recently thought that this issue could be managed in software by actually delaying the sample. (Did he say pause?)  Yes, to prevent two or more samplings in the transition state - once the state was changed a very small amount of time would need to pass before another sample was allowed to be counted. 

Now if your pause is too long - you start loosing counts..... arggh !

But if your pause self adjusts to be smaller you don't .. (whoa!)    Adaptive Sampling !

Definitely a case of software cleaning up a hardware issue.  

Pick your poison - Sensor, Filter, Software !

I think I'll try Software - and add a delay - I'll keep you posted

... I would define an interrupt handler like below which should also handle the debouncing:

<code>

/*define the interrupt handler for int 0 (pin 2) for a state CHANGE on the pin*/

attachInterrupt(0, count, CHANGE);

void count(void){

/* crude debounce might work well with just one read need to test */

if (digitalRead(ChanAPin) && digitalRead(ChanAPin)) {

//increment counter on iterrupt

encoderCounterA++;

}

}

</code>

So the above would be for one channel a second would be needed for the other channel.