It's all about tradeoffs. A series of randomly organized thoughts about quad encoder input:
- Interrupts directly from the switches without some form of HW fltering or debouncing are not a good idea as you could get a large number of interrupts from bouncing contacts. If you have a dso, take a look at the contact bounce to get a good sense of how nasty it is.
- Polling at a 2 mS period (500 hz as you mentioned) is faster than needed. You will probably need at least 10 to 20 mS for the contacts to settle down. I typically use a 40 mS debounce period with no loss of responsiveness. I will poll at 100 - 125 hz and a string of 4 consecutive readings the same means the switch is made or broken.
My approach to polling an encoder is completely different, and doesn't ever wait for a switch to settle. I operate on the assumption that the switches will never be bouncing at the same time. Now I accept the possibility that the assumption may not be true for crappy encoders, or for any encoder if it's turned fast enough. But looking at the datasheet for a typical Bourns encoder that I like, it specs the maximum rotation rate at 60 RPM, or one full revolution per second. For a 30/30 type encoder, that's 120 edges per revolution, or one every 8ms. For a 15/30 encoder, you would have 16ms between edges, so it would be even more unlikely to have concurrent bouncing. They also spec the maximum bounce duration at 2ms, but that's with an unknown amount of R/C hardware debouncing. In any case, in the examples I know of where a polling method similar to mine has been used, based on the assumption, it has worked quite well. So I think the assumption is pretty realistic for decent encoders and reasonable spin rates. And, you know, if both switches ever do bounce at the same time, you're going to be in trouble no matter what servicing routine you use.
So here is the routine I like for polling. It's not code as such, just the logic.
Assume:
-------
Switch bits are adjacent bits in I/O port.
Right-most (LS) bit changes first on CW rotation.
Setup:
------
Target = 00b
Read & mask switch state bits, save as Current
If Current = 00b then Target = 11b
Set up timer interrupt at 500 per second (2 mSec) or whatever works
End of Setup
Timer Interrupt Service Routine:
--------------------------------
Copy Current to Previous
Read & mask switch state bits, save as Current
If Current = Target
Target = Target XOR 11b
Direction = Current XOR Previous // 10b = CW, 01b = CCW
Process one tick for that Direction
Return
Else
Return
Note: For 30/30 type encoder, process only when both go high:
Target = Target XOR 11b
If Target = 11b then Return // add this line
Direction = ... etc.
For a 15/30 type encoder starting with both bits high, on each interrupt the routine saves the most recent state in Previous, then reads the new Current state. But it doesn't do anything else until the first time both switches are low (the Target it's looking for is 00b). So it takes no further action when the first switch goes low, and bounces, but only continues to save the current value each time until the the first reading when the second switch goes low. When that happens it immediately changes the Target to 11b, and then it processes one tick as per the Direction value based on which switch was the last to go low.
The second switch will continue to bounce for a while after this, but Current readings will never match the new Target of 11b because the first switch is already settled low during that bouncing, hence a Current reading of 11b is not possible no matter what the second switch does.
Continuing, it will not react to the first switch going back high, or bouncing then, because the second switch has now already settled low. It is only when the second switch first goes high that both are high, whereupon the routine again moves the goalposts by inverting the target to 00b, then processes the new tick. So basically, the routine records a tick on the FIRST reading when both switches are in the same state, but instead of doing any formal debouncing, it just looks forward for the next state which will result in a tick.
As stated before, this all depends on the two switches never bouncing at the same time - when one switch is changing state and bouncing, the other must have already settled. I have found that this works quite well.
On the pullups, if power consumption is an issue, try 100K. At 3.3V that's 66 uA. While not 0, it's not going to drain batteries anytime soon.
How about this: Don't tie the pullups high, but connect them to a spare I/O pin. Then bring that pin high at the beginning of the polling interrupt service routine, take the switches reading, then take it back low before you return. You only use current when you're reading the switch pins. There shouldn't be any bounce associated with doing that. What do you think?
Edit: You would probably just use the internal pullup resistors on the pins the switches are connected to. For a processor which also has pull-down resistors, you would just change between pulldown and pullup when reading the pins. If there are no internal pulldowns, you could still change the pin from output-low to input-with-pullup. You just can't have a floating input when a switch is open.