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 ChanBvoid 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 changesvoid 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:
- the original one installed in the hacked servo
- another encoder with no caps installed
- an encoder with 0.1uF and 0.022uF capacitors installed between the channel pins and ground
- an encoder with 10uF caps installed between the channel pins and ground
- an encoder with 0.1uF and 0.01uF capacitors installed between the channel pins and ground
Results:
- Original encoder saw hundreds (typically 350-550) of transitions per revolution
- Other encoder with no caps saw hundreds (typically 320-340) of transitions per revolution
- Encoder with 0.1uF and 0.022uF caps saw 60-70 transitions per revolution
- Encoder with 10uF caps saw 0-5 transitions per revolution
- 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?