Rotary incremental encoders - Page 1
Forum section: electronics / testgear

author: akis
author_rank: Frequent Contributor
timestamp: December 11, 2014, 08:52:56 pm
content: I am trying to understand how these workfor examplehttp://www.adafruit.com/datasheets/pec11.pdfAs I understand it there are 3 pins: A, B and GND. As you turn the knob it clicks. With each click the device closes A, closely followed by B and then opens A and then opens B, and it does that with each click. If then you had pulled A and B high, for every click it would pull A low, then B low, then A high and finally B high. If you turned it CCW, then it would go backwards, ie it would pull B low, then A low, then B high and finally A high.So to interface these with a MCU you'd have to be reading digitally A and B continuously so you'd need 2 digital inputs on the MCU. But you'd need to be reading all the time because you might miss a turn/click. Assuming the MCU does not have interrupts.Have I got it right?

author: madires
author_rank: Super Contributor
timestamp: December 11, 2014, 09:07:39 pm
content: Yes, the rotary encoder creates a Gray code output and you can determine the direction by observing the changes. If you're using the polling method to read the encoder, polling it every 5ms should be fine. It's even good enough to support a velocity factor (how fast or slow the encoder is turned). The "clicks" are called detents and a single detent could be a single Gray code step or more. That's based on how the encoder is designed. Some cheap encoders got the detent just at the position where the A and B change, i.e. A and B could change back and forward several times.

author: LaurenceW
author_rank: Frequent Contributor
timestamp: December 11, 2014, 09:08:50 pm
content: You're basically right, Akis, yes. As per the little table in the data sheet, if you see "A" go low, and then before anything else changes "B" also goes low, you know the shaft has been turned clockwise. But if "B" goes low and before anything else changes "A" goes low, then the shaft has been rotated counterclockwise.Ideally, you might do this with interupts (or "interupt on change" if your processor has it). This way, you can alsotime the gapbetween successive rotations, and set your software to do speed-dependant changes - i.e., the faster you spin the knob, themuch fasteryou increment or decrement the value.You could do it by polling every few ms if you don't want to use interupts, but you risk missing the occasional pusle, which will make the "action" of the knob feel a bit hit-and-miss.I scan both bits frequently, and compare to the previous scan. Both bits can only ever be 00, 01, 10 or 11. Compare with previous value and determine directions. Use 4K7 pull-up resistors and 0.1 uf glitch-catching caps to ground, to kill switch bounce. Job done!

author: electr_peter
author_rank: Unknown
timestamp: December 11, 2014, 09:10:53 pm
content: Yes, you described rotary encoders very well.MCU can read A/B signal via simple digital read or an interrupt. Keep in mind that these mechanical encoders can switch bounce significantly - add some analog filtering (preferable - pull up, cap+resistor for low pass, Schmitt trigger) or digital filter. Also, don't expect to quickly turn the knob, say 10 times CW and 10 times CCW, and get to the original position with 100% probability.Polarity is difficult to get right from the first go. Another point - MCU code must be able to start with encoder in the middle position between indents (when A is on and B is off).Bonus tip - you don't actually need to read positions of A and B signals at once - it is enough to keep track of A and B states (and stick to possible A/B combinations) and update to possible states only (so both A and B do not change at the same time).Read A - if A changed, then update A state. Read B ...Read A - if A did not change, continue. Read B ...

author: madires
author_rank: Super Contributor
timestamp: December 11, 2014, 09:34:14 pm
content: Quote from: LaurenceW on December 11, 2014, 09:08:50 pmIMHO, using the interrupt method is ok for high quality encoders like Alps, but not for cheap crap. The cheap encoder might have the detent just at the position where A and B change and that causes several fast changes without intent. When using interrupts such an encoder could cause a lot of fun with a an interrupt storm.

author: andrija
author_rank: Regular Contributor
timestamp: December 11, 2014, 09:49:46 pm
content: Quote from: madires on December 11, 2014, 09:34:14 pmEven the Alps encoder might no do it clean like you described. I implemented rotary volume control code in assembly on a PIC for my DAC and some encoders with detents also have a position in between two detents.

author: madires
author_rank: Super Contributor
timestamp: December 11, 2014, 10:00:16 pm
content: Quote from: andrija on December 11, 2014, 09:49:46 pmAre you talking about two Gray code steps for one detent (like Alps' EC11 series) or about unstable A/B at the detent?

author: andrija
author_rank: Regular Contributor
timestamp: December 11, 2014, 10:05:39 pm
content: Two codes. One right at the detent and other as you move shaft a bit farther to the next detent but not trigger the next detent. Then once you get the second code, you can move the shaft back and get the code to change back to what it was on the first detent. These in between codes are pretty non-deterministic in when exactly they happen since they are not at the detent but I don't know how much instability and bouncing there is, if any. I don't think my code had any debouncing at all and it always worked fine.

author: electr_peter
author_rank: Unknown
timestamp: December 11, 2014, 10:18:12 pm
content: Quote from: andrija on December 11, 2014, 09:49:46 pmI think you mean that encoder could stop in intermediate position ("on detent"). That is, encode goes 00, 01, 11, 10, 00, ... and stops on 01/11/01 position. This can be a problem if your code does not account for it. As I wrote earlier,Quote from: electr_peter on December 11, 2014, 09:10:53 pmProblem with these mechanical encoders is that all switching action happens only around detent (all 4 transitions happens on 1 detent - 00, detent starts, 01,11,10,00, detent ends). You have 4 events for 1 detent. This makes calculation slightly more tricky in my opinion and creates intermediate states (01/11/10) code must recognise.

author: madires
author_rank: Super Contributor
timestamp: December 11, 2014, 10:19:59 pm
content: Quote from: andrija on December 11, 2014, 10:05:39 pmI see. The trick is to track the steps in the same direction. If you got two steps clockwise consider it a successful turn clockwise and reset the step counter to 0. The third step clockwise, going a little bit further, is just a single step and not considered a successful turn. The next step is counterclockwise (back to the last detent), and since it's the other direction the step counter is reset to 1 again. Of course you have to remember the direction of the last step.

author: madires
author_rank: Super Contributor
timestamp: December 11, 2014, 10:33:20 pm
content: Maybe some code helps (for ATmega):Code:/* rotary encoder */typedef struct{uint8_t History; /* last AB status */uint8_t Dir; /* turn direction */uint8_t Pulses; /* number of pulses */uint8_t Velocity; /* turning velocity */} RotaryEncoder_Type;RotaryEncoder_Type Enc; /* rotary encoder */#define ENCODER_PULSES 2 /* number of pulses per detent */#define DIR_NONE 0b00000000 /* no turn or error */#define DIR_RIGHT 0b00000001 /* turned to the right */#define DIR_LEFT 0b00000010 /* turned to the left *//** read rotary encoder** returns user action:* - DIR_NONE for no turn or invalid signal* - DIR_RIGHT for right/clockwise turn* - DIR_LEFT for left/counter-clockwise turn*/uint8_t ReadEncoder(void){uint8_t Action = DIR_NONE; /* return value */uint8_t Temp; /* temporary value */uint8_t AB = 0; /* new AB state */uint8_t Old_AB; /* old AB state *//* set encoder's A & B pins to input */Old_AB = CONTROL_DDR; /* save current settings */CONTROL_DDR &= ~(1 << ENCODER_A); /* A signal pin */CONTROL_DDR &= ~(1 << ENCODER_B); /* B signal pin */wait500us(); /* settle time *//* get A & B signals */Temp = CONTROL_PIN;if (Temp & (1 << ENCODER_A)) AB = 0b00000010;if (Temp & (1 << ENCODER_B)) AB |= 0b00000001;/* restore port/pin settings */CONTROL_DDR = Old_AB; /* restore old settings *//* update state history */if (Enc.Dir == (DIR_RIGHT | DIR_LEFT)) /* first scan */{Enc.History = AB; /* set as last state */Enc.Dir = DIR_NONE; /* reset direction */}Old_AB = Enc.History; /* save last state */Enc.History = AB; /* and save new state *//* process signals */if (Old_AB != AB) /* signals changed */{/* check if only one bit has changed (gray code) */Temp = AB ^ Old_AB; /* get bit difference */if (!(Temp & 0b00000001)) Temp >>= 1;if (Temp == 1) /* valid change */{/* determine direction *//* gray code: 00 01 11 10 */Temp = 0b10001101; /* expected values for a right turn */Temp >>= (Old_AB * 2); /* get expected value by shifting */Temp &= 0b00000011; /* select value */if (Temp == AB) /* value matches */Temp = DIR_RIGHT; /* turn to the right */else /* value mismatches */Temp = DIR_LEFT; /* turn to the left *//* step/detent logic */Enc.Pulses++; /* got a new pulse */if (Temp != Enc.Dir) /* direction has changed */{Enc.Pulses = 1; /* first pulse for new direction */}if (Enc.Pulses >= ENCODER_PULSES) /* reached step */{Enc.Pulses = 0; /* reset pulses */Action = Temp; /* signal valid step */}Enc.Dir = Temp; /* update direction */}else /* invalid change */{Enc.Dir = DIR_RIGHT | DIR_LEFT; /* trigger reset of history */}}return Action;}That function is called every 5ms by another function which has a counter to determine the turning velocity.BTW, the code is from the Transistortester's m-firmware.

author: madires
author_rank: Super Contributor
timestamp: December 11, 2014, 10:44:19 pm
content: Quote from: wilfred on December 11, 2014, 10:27:57 pmBasically you just need to detect which signal (A or B) changes. The magic of the Gray code is that only one bit changes from step to step. But you can also read both signals at the same time. And it's a good idea to detect code violations.

author: akis
author_rank: Frequent Contributor
timestamp: December 11, 2014, 11:09:38 pm
content: After reading all comments, just some random thoughts.If the encoder has say 24 positions, and you can turn it max, say, 360 degrees in 0.5 seconds, that means 48 detents per second. Each detent has 4 transitions which you must read ie 00, 01, 11, 10, 00, so you will have to poll 48 * 5 reads per second = 240 reads per second. But to make sure you are catching them in time, since there are no interrupts to tell you precisely when a state actually became a state, you 'd need to be reading at least twice as many times per second. That means 480 reads per second on both A and B pins, therefore every 2ms give or take.In terms of high level algorithm you can read successive states into an array/queue and then process in a FIFO manner trying to extract correct sequences, disregarding anything invalid. So one part of the code fills the queue up with successive states, and some other part of the code tries to extract complete "messages" out the queue (simply by recognising 2 possible sequences). If your queue starts in the middle of a sequence you can throw it out. This is almost precisely the same way we used to read bytes coming down the sockets and putting them together into "messages" from the other side.

author: akis
author_rank: Frequent Contributor
timestamp: December 11, 2014, 11:21:09 pm
content: Quote from: madires on December 11, 2014, 09:07:39 pmThat means that even if the analogue circuitry is good, the cheap encoder will still "bounce" A and B and if I am reading too fast I will read garbage?QuoteSo I need : pullup resistor, followed by series resistor and a capacitor to ground to form a low pass filter? Or can I just place a capacitor to ground and not need the series resistor?

author: akis
author_rank: Frequent Contributor
timestamp: December 11, 2014, 11:24:04 pm
content: I wonder isn't there a chip to couple with the encoder and do all the reading and buffering and then simply give you the "messages" via say a serial interface? So then the only thing you have to do is "read next" and not have to tie down the MCU ?

author: electr_peter
author_rank: Unknown
timestamp: December 11, 2014, 11:30:00 pm
content: Quote from: akis on December 11, 2014, 11:09:38 pmTake into account that sequence 00,01,11,10,00 is not evenly spaced (if knob is turned at the same speed). Depends on the encoder, of course.Quote from: akis on December 11, 2014, 11:09:38 pmIt will probably work OK.I would suggest to keep track of current A/B states and update them sequentially - read A, update A; read B, update B. You check for proper state change and your states (A and B including) are always legal this way (legal, because all legal transitions change only A or B state, not both).Problem with throwing out "illegal codes"(or reading A/B incorrectly) would be possibility of turning the knob one way, but getting result +1,+1,+1,-1,+1 (there should be no -1 if you turn in one direction).I suggest to build a circuit and try out your approach and other approaches in practice. I presume you would use encoder for UI? Then hands on testing is necessary.

author: rs20
author_rank: Super Contributor
timestamp: December 11, 2014, 11:39:38 pm
content: Just a note -- people seem to be panicking about rotary encoders that have unstable outputs when resting on the detents. The ONLY time this is an issue is when your code gets bogged down by an interrupt storm. It may be unstable output, but it's unstable output that alternates between exactly two grey code states: in other words, no problem at all in terms of having the MCU keep track of where the encoder is. If the MCU is polling and misses any amount of random junk, it'll still just read the next valid value. Rotary encoders only break when the MCU misses two or more motions in the SAME direction, which this unstableness will never produce. The only problem is that your software will see the knob wobbling between those fixed two values; you just need to mask out/ignore the unstable LSB and you're fine. For newbies, though, it might be easier to debug with nice stable encoders.

author: SL4P
author_rank: Super Contributor
timestamp: December 12, 2014, 04:20:23 am
content: Capacitor - and you may like to add a small debounce period in the ISR to ignore changes faster than your max permitted pulse rate. e.g. reset a countdown timer and ignore state changes before that zeroes out

author: madires
author_rank: Super Contributor
timestamp: December 12, 2014, 11:13:42 am
content: Quote from: akis on December 11, 2014, 11:21:09 pmLet's take an example of an encoder with one step per detent. A good encoder would make A/B switch between two adjacent detents. First the A/B signals change, then the detent "clicks". That way you got a stable A/B signal (stable in the meaning of that A/B won't change if you wiggle the knob at the detent a little bit). Some cheap encoders got the A/B switching and the detent at the same position. Since there's some margin for the detent, A/B could change easily several times. That could be caused by you, reaching the detent, overshooting a little bit and turning back until the encoder snaps into the detent's position. Or by touching, moving or hitting the device. Of course you also got the beloved bouncing of the switching contacts

author: rs20
author_rank: Super Contributor
timestamp: December 12, 2014, 11:18:21 am
content: Quote from: madires on December 12, 2014, 11:13:42 amBut youwon't "read garbage"because your software will resolve the data into perfectly legitimate positions, with just a bit of uncertainty in the last bit that never accrues or gets out of hand. This is easily dealt with; in the simplest case, just mask out the last bit and you'll still unambiguously know which detent you're sitting in. Unless there's only one gray code per detent, in which case it becomes a big issue.

author: madires
author_rank: Super Contributor
timestamp: December 12, 2014, 11:33:35 am
content: Quote from: wilfred on December 11, 2014, 11:23:39 pmIncorrect outputs from the encoder and signal glitches. The output is a Gray code, so only A or B may change, but not both.Quote from: wilfred on December 11, 2014, 11:23:39 pmYes, that's one of the classic methods. But you have to take the delay between A and B, and the bouncing into account for reading B. The delay also varies with the speed of turning.Quote from: wilfred on December 11, 2014, 11:23:39 pmIt's the postion of the detent. In this case it's a proper encoder switching A/B between detents.

author: madires
author_rank: Super Contributor
timestamp: December 12, 2014, 11:35:32 am
content: Quote from: akis on December 11, 2014, 11:24:04 pmUse a dedicated MCU for the rotary encoders

author: madires
author_rank: Super Contributor
timestamp: December 12, 2014, 11:44:02 am
content: Quote from: rs20 on December 12, 2014, 11:18:21 amAnd what do we learn from this? If you buy cheap encoders get the type with two code steps per detent.

author: rs20
author_rank: Super Contributor
timestamp: December 12, 2014, 12:05:55 pm
content: Just to hammer my point home:Quote from: madires on December 12, 2014, 11:44:02 amFully agree. Do cheap decoders with only one code step per detent AND crappy detent position actually exist though?? Datasheet? I've only ever seen encoders with two or four code steps per detent personally. I think it's worth being crystal clear that you're only going to end up in trouble if you have the perfect storm of both crappy detents AND single code steps per detent.EDIT: Fixed broken image link.

author: SL4P
author_rank: Super Contributor
timestamp: December 12, 2014, 07:55:40 pm
content: Forget the detent.It's only there to make you feel like you're doing something, and to stop the shaft rotating under its own steam (and vibration).Use edge detection on the 'known' good pin (A/B) - then determine which phase the other (B/A) pin is in. Count up or down as needed. Finished.$0.30c 6/8 pin MPU to process if needed, or $0.00 - use an interrupt pin on your application MPU.

author: akis
author_rank: Frequent Contributor
timestamp: December 20, 2014, 12:31:11 pm
content: For whoever is reading this still.The rotary encoder module arrived from ebay and finally I put in on breadboard earlier on. It is vert badly made with the actual encoder soldered at 30 degree angle to the little PCB. It is also missing one 10K pullup resistor. But I got it to work 110% reliably.First of all it looks like this :http://www.ebay.co.uk/itm/321293171237?_trksid=p2059210.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AITIt is basically a simple rotary encoder which also has a momentary switch. The PCB contains 3 pullup resistors, one for the switch and two for the rotary encoder.Wiring is simple: 10K pullup resistors to the 5V line (already provided on the PCB) and then a low pass filter for each pin, in my case a 10K resistor followed by a 100nF capacitor to ground.WARNING: the momentaty switch, at least, will not function without the low pass filter. Do not try to fix it in the code, it simply sends multiple "phantom" presses.So the basic code is as follows.First the switch, the code would need to know if it has been pressed. Maybe also how many times - which means by the time you actually try to do something intelligent in the code, the switch may have been pressed again, so we need some sort of a queue, very similar to Windows mouse queue where clicks are stored for later processing. To keep things simple I dispensed with a queue and simply used a counter, you press the switch the counter goes up, you "read" the switch, the counter goes down. So you keep reading until the counter goes to 0. Of course it can be done more sophisticated with a queue storing the switch press as well as the time it was pressed, and like Windows mouse clicks it could also present double clicks. But it is not needed here.So I tested the switch and it works faultlessly, does not miss a press, and does not give more presses than needed.The code uses interrupt channel 0 (digital pin 2) on the Arduino for the switch and is set to trigger on going low. The ISR simply increments a counter could not be more simple than that.Now for the rotary encoder.From the two pulled-up lines, I have attached the "DT" line (whatever that means) to interrupt channel 1 (digital pin 3 on the Arduino). After some experimentation and head scratching, the ISR is also very simple, as simple as it gets. The ISR triggers on "change". I tested and unless you turn and click the encoder you will not get suprious interrupts. So if the ISR triggers it means the rotary encoder was turned either right or left (cw or ccw). After examining the values of the two data pins on each interrupt invokation, it is clear that the designer is ingenious! If the two pins are at the same value, either 0s or 1s, it means the switch was turned clockwise. If the pins have different values, it means the switch was turned counter clockwise.Rotary movements are stored in a circular buffer / queue so that there is some history of how the switch was turned. I have tested this and it is bullet proof. It is also so simple it defies belief.Here is the interrupt routine for the rotary encoder:Code:void ea_interrupt(){byte ea=digitalRead(ROTARY_ENCODER_EA)==HIGH?1:0;byte eb=digitalRead(ROTARY_ENCODER_EB)==HIGH?1:0;int iRotation = (ea ^ eb ) ? 2 : 1 ;insert_to_rotation(iRotation);}"insert_to_rotation" simply inserts a rotation action into the buffer/queue, where 1 means clockwise and 2 means counter. Can't get any simpler. I do remember reading a post with some very convoluted code, that had to remember previous positions and half-positions, I am not sure how this can happen, I have literally jumped up and down on this switch and the code above is infallible. And it is a shitty switch from ebay.I am not sure how "expensive" the two interrupts are, in terms of stealing CPU time, not my code, but the framework underneath. Would it be better to poll very often, it was suggested 500 times a second, that implies a "loop" delay of just 2ms, and it also assumes we do nothing else in the meantime. So I stick with the interrupts for the time being.Here is ALL the code for the breadboarded encoder switch - it may help someone to just get started with one of those ebay rotary encoders:Code:char buf;#define ROTARY_ENCODER_SW 2#define ROTARY_ENCODER_EA 3#define ROTARY_ENCODER_EB 4int switch_count;byte rotation_buffer;byte rotation_head, rotation_tail;bool ReadSwitch(){if (switch_count>0){switch_count--;return true;}elsereturn false;}// 1=cw, 2=ccwvoid insert_to_rotation(byte bDirection){rotation_tail++;if (rotation_tail>=sizeof(rotation_buffer)) rotation_tail=0;// fix overflow issues here, tail overtakes headrotation_buffer=bDirection;}// 0 nothing, 1=cw, 2=ccwbyte ReadRotation(){if(rotation_tail!=rotation_head){rotation_head++;if (rotation_head>=sizeof(rotation_buffer)) rotation_head=0;return rotation_buffer;}elsereturn 0;}void switch_interrupt(){switch_count++;}void ea_interrupt(){byte ea=digitalRead(ROTARY_ENCODER_EA)==HIGH?1:0;byte eb=digitalRead(ROTARY_ENCODER_EB)==HIGH?1:0;//snprintf(buf,sizeof(buf),"rotation ea:%u - eb:%u\r\n",ea,eb);//Serial.print(buf);int iRotation = (ea ^ eb ) ? 2 : 1 ;insert_to_rotation(iRotation);}void setup(){Serial.begin(115200);snprintf(buf,sizeof(buf),"in setup\r\n");Serial.print(buf);pinMode(ROTARY_ENCODER_SW, INPUT);pinMode(ROTARY_ENCODER_EA, INPUT);pinMode(ROTARY_ENCODER_EB, INPUT);switch_count=0;rotation_head=rotation_tail=0;attachInterrupt(0, switch_interrupt,FALLING);attachInterrupt(1, ea_interrupt,CHANGE);}long lLastTime=millis();void loop(){if (millis() - lLastTime > 1000){lLastTime=millis();snprintf(buf,sizeof(buf),"in loop()\r\n");Serial.print(buf);}if (ReadSwitch()){snprintf(buf,sizeof(buf),"switch is pressed\r\n");Serial.print(buf);}byte rotation=ReadRotation();if (rotation!=0){snprintf(buf,sizeof(buf),"rotation %s\r\n",rotation==1?"cw":"ccw");Serial.print(buf);}delay(250);}

author: dannyf
author_rank: Super Contributor
timestamp: December 20, 2014, 12:58:33 pm
content: QuoteThat's not very efficient. A better approach is to read a/b channels, in combination with prior reads to index a table that determines 1) validity of rotate; and 2) direction of rotation.Such an approach is very resistant to bounces, and allows the same velocity detection mechanism to work.

author: SL4P
author_rank: Super Contributor
timestamp: December 20, 2014, 12:59:46 pm
content: Congratulations - you've approached itexactlythe right way.I agree that some other threads suggest ridiculous amounts of code for what requires three lines of C to handle the encoder - well done.. You may identify tiny tweaks - like non-blocking delays - to ensure clean debounces, but in principal you're on the money.(Remember to keep (''insert_to_rotation'') as short as possible, as it forms part of the interrupt routine as currently coded (and uses stack) - or process the count value 'outside' the interrupt entirely (you have 250mS sitting idle already).The button sounds faulty. Worst case add 200mS delay after each button-down state change.Pushing and/or turning won't be affected in reality.

author: akis
author_rank: Frequent Contributor
timestamp: December 20, 2014, 05:11:39 pm
content: I think the button is OK, sometimes it bounces as most switches do (don't they?) so it needs a low pass filter and then there is absolutely no bounces. Someone else has mentioned above the importance of low pass filters anytime we are dealing with switches.An update: another two encoders arrived from ebay today. They are 100% identical to the one I was using before. However they behave differently. On turning the encoder I now receive two codes instead of one, and that more matches the datasheet timing diagrams. Most likely the first encoder was broken and was sending me alternative codes for the same rotation direction. The change in the code is again very simple, simply interrupt on "falling" rather than on "change". So 00 means we are going right and 01 means we are going left. Can't get easier than that.

author: jadew
author_rank: Frequent Contributor
timestamp: December 20, 2014, 09:40:41 pm
content: This might help:http://eenoob.com/article/rotary-encoder-tutorial