The answer seems to be no. I added the RC filter suggested by the manufacturer and the results still jump around (The values sometimes increase by several steps at once, or decrease even when turning the decoder clockwise.)
I added a CMOS Schmitt trigger and it resolves the problem.
I really don't understand why that is, since apparently Arduino digital inputs have integrated Schmitt triggers. This thread from the Arduino forum discusses it at length:
http://forum.arduino.cc/index.php?topic=452599.15
The only thing I can think of is that the hysteresis band of the built-in Schmitt triggers is too narrow (2.08 - 2.63V at VCC of 5V and typical operating conditions) and the filtered voltage still bounces between those values.
I'm using a MC14584B Schmitt trigger that I had lying around, and it looks like it's hysteresis band is ≈2.1V to 2.7V (typical at 25°C) which is slightly wider and goes slightly higher than the hysteresis range of the Arduino itself.
The differences seem quite small though, so I'm confused as to why the Schmitt trigger eliminates the "bobbling" of values I'm seeing.
I could add delay based debouncing code to my Arduino program, but the decoder handler is written as an ISR and those are supposed to be as fast as possible, and the millis() function is supposed to be quite slow, making it difficult.
EDIT:
I asked a related question regarding filter component values on the EE stack exchange, and somebody there pointed out that I should only be seeing "bounce" on one pin at a time, and that I should be able to reject spurious changes purely through software. I'm trying to do that now.
My new code looks like this:
bool rotaryValueChanged = false;
bool pinAHasChanged = false;
bool pinBHasChanged = false;
enum ChangeTypes {
fallingA,
changingA,
changingAOrB
};
ChangeTypes changesToDetect = fallingA;
void encode() {
pinAHasChanged = true;
//Read the PinA & PinB (Digital Pins 2 & 3) using port register PINE, 4th and 5th bit
//Fast equivalent to pinAState = digitalRead(rotaryPinA) == LOW
bool pinAState = (PINE & (1 << 4)) == 0;
bool pinBState = (PINE & (1 << 5)) == 0;
//Ignore state changes to pinA unless pinB has changed since the last change.
//Also only pay attention if this is a falling edge
//or we're tracking both rising and falling edge changes
if (pinBHasChanged && (pinAState == false || changesToDetect != fallingA)) {
rotaryValue += (pinAState == pinBState) ? -1 : 1;
rotaryValueChanged = true;
pinBHasChanged = false;
}
}
void encodePinB() {
pinBHasChanged = true;
//Unless we're counting 48 ticks/rotation, don't count PinB changes.
if (changesToDetect != changingAOrB) {
return;
}
//Read the PinA & PinB (Digital Pins 2 & 3) using port register PINE, 4th and 5th bit
bool pinAState = (PINE & (1 << 4)) == 0;
bool pinBState = (PINE & (1 << 5)) == 0;
//Ignore state changes to pinB unless pinA has changed since the last change
if (pinAHasChanged) {
rotaryValue += (pinAState != pinBState) ? -1 : 1;
rotaryValueChanged = true;
}
}
void setup() {
attachInterrupt(digitalPinToInterrupt(rotaryPinA), encode, CHANGE);//FALLING
attachInterrupt(digitalPinToInterrupt(rotaryPinB), encodePinB, CHANGE);
//More setup code here...
}
This code is written to handle 3 possible options:
- Only falling edge state changes on pin A (12 steps/rotation)
- Rising or falling changes on pin A (24 steps/rotation)
- Rising or falling state changes on either pin (48 steps/rotation.)
The key part of this is to have interrupts on both pins. When one pin changes, its interrupt handler sets a "this pin changed" bool to true. In the ISR (Interrupt Service Routine) for the other pin, it only pays attention to a state change if the other pin has changed. That way, when Pin A changes states, if it bounces, the other pin should not bounce at the same time and so unless pin B has changed, I can simply ignore additional changes to Pin A and handle my debouncing in software, and without having to do timing delays which are too slow for an ISR.
However, the code above doesn't quite work. Sometimes I get bogus reverse rotation readings. If I'm rotating the encoder clockwise, the values I get increase one by one as expected, but occasionally decrement by 1 and then go back to incrementing.
What am I doing wrong?
Since the bouncing should only ever occur on one pin at a time, the above code should give correct readings from a bare rotary encoder without any hardware debouncing needed, but it doesn't.
My test case is only counting falling edge transitions for Pin A of my encoder, so picking up spurious counterclockwise rotation readings suggests that sometimes Pin B is changing state at the same time as pin B. That shouldn't happen, so I'm confused.