Rotary encoder jocks: a question about the lookup table - Page 1
Forum section: electronics / projects

author: Peabody
author_rank: Super Contributor
timestamp: March 13, 2018, 03:59:23 am
content: From looking through the search results for "rotary encoder" here, I see that we have a number of bona fide experts on rotary encoders. I recently developed my own polling and pin interrupt routines for cheap mechanical quadrature encoders, using no hardware debouncing, and I thought they were pretty cool. And indeed they worked fine on the pretty good Bourns encoder that I had on hand, but not so well with the noisier encoder that came with my DSO150. So back to the drawing boards, including taking another look at the lookup table method made popular by Oleg Mazurov of CircuitsAtHome.com (a member here too) and Bex Buxton of Buxtronix.net.But I've always had a question about the polling version of the lookup table method. Here's Oleg's code:/* returns change in encoder state (-1,0,1) */int8_t read_encoder(){static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};static uint8_t old_AB = 0;/**/old_AB <<= 2; //remember previous stateold_AB |= ( ENC_PORT & 0x03 ); //add current statereturn ( enc_states[( old_AB & 0x0f )]);}So as I understand it, the encoder pins are bits 0 and 1 of the port. Going in, the variable old_AB contains the pins state from the last poll, and that is shifted left two bits, then the current state is read in, ANDed with 0b11, and ORed into old_AB. We now have a 4-bit value containing the previous poll state in the upper two bits, and the current state in the lower two bits. With four bits there are 16 possible combinations, and the current one is used as an index into the lookup table. The value fetched there is either a valid CW transition (1), a valid CCW transition (-1), no change (0), or an invalid transition caused by bouncing (also 0). There are eight possible valid transitions, four possible no-changes, and four possible invalid results.The problem I have is that if an invalid result occurs, we presumably have a current value that does not represent a nominal valid state of the switches, but only bouncing noise. Yet that value is carried forward to the following poll and used as the old_AB for that poll. So we are comparing what may be a valid reading on the new poll with what is likely a fictitious state for the old poll, and that seems likely to produce another invalid transition, or one that appears to be valid, but isn't.It seems that what should be done if an invalid transition is detected is to shift old_AB back to the right two places to restore the old (valid) reading as the current reading, and discard for all purposes the invalid reading we just got. Then the next poll is much more likely to generate a valid transition.I think this would require changing the 0 values in the lookup table to maybe 0x55 for the four invalid transitions, which are at indexes 3, 6, 9 and 12. Then for a 55 you would still have 0 change, but would restore old_AB. The values at indexes 0, 5, 10 and 15 would remain as no-change 0's not requiring old_AB restoration.Has this ever come up before? Would this change make any difference? Seems like it would. Well, I have more cheap encoders on the way, and will test this, but I just thought I would see what the experts thought.

author: BrianHG
author_rank: Super Contributor
timestamp: March 13, 2018, 05:32:11 am
content: Give this one a shot (I deliberately simplified this so you can see whats going on...):/* returns change in encoder state (-1,0,1) */int8_t read_encoder(){static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};static uint8_t old_AB = 0;static uint8_t old_enc_port1 = 0;static uint8_t old_enc_port2 = 0;static uint8_t old_enc_port3 = 0;int8_t out_state = 0;old_enc_port3 = old_enc_port2;old_enc_port2 = old_enc_port1;old_enc_port1 = ( ENC_PORT & 0x03 ); //store current stateif (old_enc_port3==old_enc_port2 && old_enc_port2==old_enc_port1) // only proceed if you get 3 consecutive equal reads{/**/old_AB <<= 2; //remember previous stateold_AB |= old_enc_port1; //add current triple verified stateout_state = enc_states[( old_AB & 0x0f )]; // set the return value}return ( out_state );}What I have done here is only if 3 consecutive reads of you 'ENC_PORT' are equal, then, the routine will proceed to accept that input as valid stable position and continue to update the state of the encoder. Depending on your sample speed and turn-rate, you may need to call this routine now at 2x or 3x speed. This is basically a digital low pass filter. Note that in your circuit, you may want to add a series 10k with a 1nf cap as an analog filter as well, but, I find that with the right sample speed, this routine should run clean at the expense of 3 extra bytes of ram.

author: Tom45
author_rank: Frequent Contributor
timestamp: March 13, 2018, 06:48:37 am
content: Read up on X1 vs. X4 quadrature encoding.The code you show implements X4 encoding. That's OK if you have reliable signals.X1 encoding only counts 1 per each 00->10->11->01 sequence instead of 4 counts. You can look at this as an 8 bit index into a lookup table. Only 2 of the 256 entries will be non-zero. 00101101 for +1 and 01111000 for -1. Random noise will land on one of the 254 0 entries in the table with high probability. In practical terms, there is no need for a 256 entry lookup table for X1 as you only care about 2 values.We use rotary shaft encoders with differential signals for the A and B channels, so noise is rare.

author: Peabody
author_rank: Super Contributor
timestamp: March 13, 2018, 02:31:29 pm
content: Quote from: BrianHG on March 13, 2018, 05:32:11 amThanks very much. Based on a previous post of yours in another thread, I tried that method in my MSP430 test rig, with the processor running at 8 MHz and polling at 1ms intervals, and coding in assembler. I tried everything from 1 match to 4 matches before considering the input valid. With the Bourns encoder, which is pretty good, there was no difference in errors (few in any case), but there was an increase in dropped transitions with increased turning speed, which I assume is expected. I'll have to wait until my other encoders arrive to test this with them. They are not Bourns.I understand the idea of the digital low-pass filter, but am just not sure there won't be lots of missed clicks if the encoder is fairly noisy. But I will test it.Still looking for a reaction to my old_AB restore idea.

author: Peabody
author_rank: Super Contributor
timestamp: March 13, 2018, 02:43:17 pm
content: Quote from: Tom45 on March 13, 2018, 06:48:37 amI will research that. If you have an encoder with half as many pulses as detents, so that a detent could be either 11 or 00, how many entries would you have in the table? There would be four possible valid sequences, but they are only half as long. Well, a 6-bit sequence would mean a 64-byte table, right? Of which four are valid.The main problem though is that if you get any bounce, which you would almost always have on each transition, you would never get a good string. But I will read up on the X1 stuff. Thanks very much.

author: xavier60
author_rank: Super Contributor
timestamp: March 13, 2018, 04:14:15 pm
content: A long time ago I got good results using Alps mechanical rotary encoders, the type where both phases change to the other state with each click. They were used as hand turned input controls.I polled at 500us and temporarilly accepted a changed 2 bit value after a number of consecutive samples of the same value. 3 samples at low speeds, dropping back to 2 samples for high speed rotation. The new sample would be then shifted into a 6 bit string only if it settled to something different to the previously accepted value.I didn't bother dealing with invalid sequences like a "00" followed by a "11".I didn't use a lookup table, just tested the 6 bit string for one of the 4 accepted valid values.All of the cheap encoders I'm able to find on ebay now are the type where both phases do a full cycle per click, too fast.I don't want to poll any faster, so I filter them externally. The A and B pins switch to ground with a small capacitor across each. At poll time, I briefly turn on the PIC's pullup resistors to possibly charge the capacitors then read the ports a few microseconds later. The idea is that even if a switch makes brief contact, the capacitor will be discharged causing the port to stay low until it's read.I'm not certain if this is really better than simple RC filtering.

author: mikerj
author_rank: Super Contributor
timestamp: March 13, 2018, 04:45:35 pm
content: Quote from: xavier60 on March 13, 2018, 04:14:15 pmI've always been a bit puzzled why this type of encoder even exists. They throw away potential angular resolution (you can't stop at half a detent), as well as being more difficult to use and more error prone. One phase (i.e. one grey code) per detent just seems superior in every respect, as well as making the encoder more robust and potentially cheaper.

author: xavier60
author_rank: Super Contributor
timestamp: March 13, 2018, 04:54:20 pm
content: Quote from: mikerj on March 13, 2018, 04:45:35 pmI read somewhere that it is to save power. Because both switches are open while the encoder is sitting in its dent and not drawing current through the pullup resistors. This isn't a concern when the micro-controllers pullups can be turned off anyway.

author: Tom45
author_rank: Frequent Contributor
timestamp: March 13, 2018, 06:01:21 pm
content: Quote from: Peabody on March 13, 2018, 02:43:17 pmRather than a lookup table, the better way to decode X1 quadrature is to use a state machine. A 1000 count per revolution encoder goes through the 00->10->11->01 sequence 1000 times per revolution. At each state you are only looking for two possible values. So in the second state (10) the state will only change if the inputs become 00 (go to previous state) or 11 (go to next state). You only get a count after advancing through all 4 states.The problem to avoid is for the encoder to stop right on the edge. For example 00->10->11->10-> ... 11-10->11->01. The bit 0 bouncing obviously needs to be ignored, but I've seen people get in trouble when that happens because of inadequate decoding. Switching to a state machine fixed it.You mention detents. Any method that depends on the outputs being stable when stopped at a detent can get into trouble if that isn't always true.We use encoders for industrial motion controls such as the BEI H25 series.http://www.beisensors.com/pdfs/h25-optical-incremental_encoder.pdf

author: Peabody
author_rank: Super Contributor
timestamp: March 13, 2018, 06:13:45 pm
content: Quote from: xavier60 on March 13, 2018, 04:54:20 pmYes, I think that's right. These days, your pullup resistors can draw more current than your processor. So to always have both switches open at every detent could be a big benefit. And some think the internal pullups have too much resistance, and use more responsive external ones that can't be turned off. Also, while the "good" encoders may have 15 pulses and 30 detents, the full cycle ones are usually more like 20 or 24 pulses and detents, so the actual difference in performance isn't so great.xavier60, I think using your Alps encoders would simplify things for everyone. But unfortunately not all encoders are of that quality. I just finished developing my own routines based on the perfectly reasonable assumption that when a switch changes state, it may bounce for a while but will then settle down before the other switch changes state, so the two switches will never be bouncing at the same time. But some encoders continue to bounce to some degree after making contact so long as the shaft is turning. They never really settle in the contact state. So my elegant routines work well for Alps or Bourns encoders, but not so well for Ebay encoders. That's why I'm taking another look at the lookup table method.

author: BrianHG
author_rank: Super Contributor
timestamp: March 13, 2018, 08:32:14 pm
content: Quote from: Peabody on March 13, 2018, 02:31:29 pmYour switch noise may still be faster than your sample period. So, try using a sample period of 0.5ms, or 0.3ms. You should be able to run at the encoder at full speed.However, since it is a a mechanical switch contact, an analog RC filter on the input from the encoder might really help.Something like a 10k series resistor from the output of the encoder to your MCU input pin and a 1nf-10nf cap from that input pin to GND. Also, the pull-up at the encoder output pin should also be around 10k.

author: Peabody
author_rank: Super Contributor
timestamp: March 13, 2018, 08:43:21 pm
content: Quote from: BrianHG on March 13, 2018, 08:32:14 pmBefore devoting most of the processor's power to servicing the encoder, or adding hardware, I'd first like to see if I can find an algorithm that works well enough with a noisy encoder. I don't think anything is going to produce perfect results with the average 99-cent Ebay encoder.

author: BrianHG
author_rank: Super Contributor
timestamp: March 14, 2018, 09:23:04 pm
content: Have you tried different filters on the mechanical switch encoders. IE, 220k-470k pullup, 1-10nf cap to gnd. I know this makes the switch on faster than the switch off, but, the noise you are getting is there only during a mechanical sliding on contact state. And this noise is series resistance going up and down, not always to a complete open. During the open point in the sliding encoder switch, there is no noise or bounce, unless there is metallic residue build up which I don't think you switch will be there for quite some time.I know there is a little more during the transition point, but, with a scope, you can easily find the best cap an pullup value.

author: BrianHG
author_rank: Super Contributor
timestamp: March 14, 2018, 09:34:34 pm
content: Another switch bounce method I've seen is instead of regular interval polling, combining a timer with a interrupt on pin change algorithm. This eats very little, virtually minimum CPU resources. It works something like this.Interrupt on pin change service algorithm:Turn off interrupt on pin changeRemember all pin states as debounce check & turn on and set interrupt timer to X ms. (Tune X so that it is longer than the switch noise you measure on your scope)Interrupt on timer service algorithm:Turn off timer.If current pin states = debounce remembered pin state, then the reading is clean and process your encoders new position.Turn back on interrupt on encoder pin change.The timer is only used once every step or change in encoder position. 0 cpu resources are being used until an interupt on pin change at the encoder inputs come in.

author: mikerj
author_rank: Super Contributor
timestamp: March 15, 2018, 10:46:10 am
content: Quote from: xavier60 on March 13, 2018, 04:54:20 pmThat does make perfect sense for power sensitive applications, thanks.

author: Peabody
author_rank: Super Contributor
timestamp: March 15, 2018, 04:13:14 pm
content: Quote from: BrianHG on March 14, 2018, 09:34:34 pmYes, and in fact I've already written a pin interrupt algorithm that switches the pin interrupt back and forth between the two pins. So when one pin goes low and triggers the interrupt, the servicing routine disables interrupts on that pin, and enables them on the other pin, which of course is supposed to already be stable. So all the bouncing on the first pin simply never has any effect, and for a full cycle of both switches, you have exactly four interrupts to service. There are some complications, but basically this works extremely well with a good encoder.But this assumes, as I think your method does as well, that at some point the bouncing switch settles down, and does so before the other switch changes state. And that's what's not true with noisy switches, as discussed before. For such switches it may be that hardware debouncing is the only choice, but I just want to be sure there isn't a software solution that even works when there is "drag" noise in the closed position.I'm working now on an idea that accumulates the value of (A-B) on each poll. So starting with both high, if B goes low, then TOTAL increments by 1 on each poll until A goes low. Then when B goes back high, TOTAL decrements. So for a perfect encoder, TOTAL will go back and forth between two values which depend on the rotation speed (assuming the poll frequency is fixed). But recognizing a tick as an increment or decrement would take place at two less extreme values which are still far apart. So in effect it's a schmitt trigger in software that allows for a certain amount of bouncing, and even some drag noise. A change of direction complicates things, and I'm not sure this idea will work with the extremes of turning speed. In other words, with polls at 1 ms intervals, with slow turning, TOTAL might go back and forth between zero and, say, 100. So the trigger points could be at 80 going up, and 20 going down. But for fast turning, it might only be zero to 8, which would require triggers at 6 and 2. Well, will 6 and 2 work ok with zero and 100? Maybe. Or maybe there's a better way that would be obvious if I remembered any calculus.Anyway, the idea is that noise may occur at any time when a switch is closed, so rather than depend on any particular reading, you aggregate the general trend in some way that provides a high-confidence result, with significant hysteresis in changing that result.I'll try to get this programmed sometime today and test it out.I guess I'm not going to get any response to my original question about old_AB restoration in the lookup table method. I'll test that too when I get a chance.Edit: Forgot to say - any time A and B are both high (switches open, so no drag noise) three polls in a row, TOTAL is reset to zero. That should happen at least once each full cycle.