Rotary encoder with PIC16F887 troubles - Page 1
Forum section: electronics / general

author: newtekuser
author_rank: Frequent Contributor
timestamp: January 01, 2023, 06:43:49 pm
content: Hello all and Happy New Year!I'm starting the first day of the year with some new experiments trying to make a rotary encoder blink some LEDs depending on rotation using the PIC16F887. The trouble I have is I cannot get them to light up properly. If I remove the 100nF caps attached to pins A and B of the encoder, the LEDs blink erratically, even if the shaft is not rotated. If I leave them on, the LEDs don't toggle when I rotate the shaft. (I also tried with lower cap values, the LEDs blink but not correctly)I know the encoder works properly - if I remove the PIC and just connect the encoder terminals to the LEDs and 300ohm resistors, then they turn off based on the rotation of the shaft (LED to A turns off when rotated CCW, LED to B turns off when rotated CW).I think my problem is in the code logic.As seen from the code, each LED is connected to RD1 and RD2 of the PIC while terminal A of the encoder is connected to RD0 and terminal B to port RD3.Anything that stands out to you?Code:// CONFIG1#pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)#pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled)#pragma config MCLRE = OFF // RE3/MCLR pin function select bit (RE3/MCLR pin function is digital input, MCLR internally tied to VDD)#pragma config CP = OFF // Code Protection bit (Program memory code protection is disabled)#pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)#pragma config BOREN = OFF // Brown Out Reset Selection bits (BOR disabled)#pragma config IESO = ON // Internal External Switchover bit (Internal/External Switchover mode is enabled)#pragma config FCMEN = ON // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)#pragma config LVP = OFF // Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming)// CONFIG2#pragma config BOR4V = BOR40V // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V)#pragma config WRT = OFF // Flash Program Memory Self Write Enable bits (Write protection off)#include <xc.h>#define _XTAL_FREQ 8000000#include <stdio.h> // for sprintf#define Encoder_CLK PORTDbits.RD0#define Encoder_DT PORTDbits.RD3int position;void main(void){OSCCON = 0X70; // set internal oscillator to 8MHzTRISD0 = 1; // Encoder CLKTRISD1 = 0;TRISD2 = 0;TRISD3 = 1; // Encoder DATAposition = Encoder_CLK;while (1){if (Encoder_CLK != position){if (Encoder_DT != position){ // CW rotationRD2 = 1;RD1 = 0;}else{ // CCW rotationRD1 = 1;RD2 = 0;}}position = Encoder_CLK;}}// End of code.

author: Ian.M
author_rank: Super Contributor
timestamp: January 01, 2023, 08:03:29 pm
content: Two things are against you:The Read-Modify Write effect, which can corrupt your outputs. SeeRMW and solutions for iton the Microchip forums.Mechanical encoders typically have *LOTS* of contact bounce.Due to the contact bounce, using both edges of one channel to clock latching the direction from the other channel generally gives poor results, with the direction output toggling on the spurious bounce edges. While it is possible to implement effective debouncing in hardware, it isn't as simple as just swamping it with a capacitor across the contacts, which if too large can damage the encoder. SeeDebouncing Contacts and Switchesby Jack Ganssle.Its generally preferable to use a state machine for the quadrature decoding, which even with severe contact bounce will only jitter by one edge step, so simply delay updating the direction outputs till its 'seen' two steps in the same direction. I posted C code and a hardware version of the state machine here:https://www.eevblog.com/forum/projects/quadrature-rotary-encoder/

author: TomS_
author_rank: Frequent Contributor
timestamp: January 01, 2023, 10:53:21 pm
content: Do the outputs from the encoder need any kind of pull up/down resistors? What is the signal state when the encoder is stationary? Is it a complete circuit or floating? If the LEDs go crazy without the caps it sounds like you could have floating inputs on the PIC which are interpreted as rapid changes in encoder state.

author: newtekuser
author_rank: Frequent Contributor
timestamp: January 02, 2023, 01:09:38 am
content: Quote from: Ian.M on January 01, 2023, 08:03:29 pmI did read up on the bouncing issue with rotary encoders and it makes sense after reading it. However, this article shows it working w/o anything special done to it:https://circuitdigest.com/microcontroller-projects/pic16f877a-rotary-encoder-interfacingThe only difference I see is use of slightly older PIC mcu and external crystal. How did that person get it to work? Maybe there's some other magic involved that wasn't documented?

author: newtekuser
author_rank: Frequent Contributor
timestamp: January 02, 2023, 01:12:45 am
content: Quote from: TomS_ on January 01, 2023, 10:53:21 pmUnfortunately the rotary encoder was purchased off Amazon and no datasheet was provided. However, one of the reviewers posted a diagram showing two 100nF caps attached to each channel which is how I'm using it. Do these normally also need pull up/down resistors?https://www.amazon.com/gp/product/B07D3DF8TK/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1I did connect all unused pins on the PIC to GND and while the erratic behavior w/o caps is largely gone (LEDs still toggle randomly but less frequent), when caps are attached, rotating the shaft does nothing.LE: I did see a review just now that mentions the use of 10K pull-up resistor on each channel. I may have to give that a try.

author: DrGeoff
author_rank: Frequent Contributor
timestamp: January 02, 2023, 01:23:05 am
content: When using rotary encoders I've always found using edge-triggered interrupts from the encoder to be the better method.

author: cv007
author_rank: Frequent Contributor
timestamp: January 02, 2023, 01:33:40 am
content: You can use portb pins which have the ability to enable internal pullups. The port b pins also allow interrupt on change. These pins may need ansel cleared as the ansel bits are set by default for the ones that can do analog.

author: MikeK
author_rank: Super Contributor
timestamp: January 02, 2023, 01:45:35 am
content: Quote from: newtekuser on January 02, 2023, 01:12:45 amDigital inputs require pullup resistors. As mentioned, this may be done via the micro's internal weak pullups if that PIC has them. Floating the inputs mean they will have an undefined state, neither high (5V) nor low (0V)...so of course the behavior will be wacky. 10K pullups are typical, don't require any software, and are faster than internal pullups (which are actually a current source).

author: Ian.M
author_rank: Super Contributor
timestamp: January 02, 2023, 01:46:37 am
content: Its a clone of an Alps EC11 series encoder:https://tech.alpsalpine.com/assets/products/catalog/ec11.en.pdfExpect reduced life, lower max. rotation speed and increased contact bounce and noise compared to the original.It definately needs pullups. Alps suggests 5K resistors as pullups in their test circuit and also specifies the *MINIMUM* current is 500uA @5V DC, so the PIC's internal pullups are too high resistance and 10K pullups would be marginal. This would be to provide adequate wetting current to break through any film of oxides and contaminants that may build up over time. Any resistor value near 5K would be suitable, e.g 4K7 or 5K6.As far as the circuit digest project goes, the encoder can clearly be seen to be mounted on a breakout board, which includes 10K pullup resistors:https://www.rcscomponents.kiev.ua/datasheets/ky-040-datasheet.pdfThe problems with the simplistic edge triggered code typically only become apparent as the encoder contacts start to wear, though if you are unlucky and get a particularly low quality one, it may need small value capacitors (e.g. 10nF) from A and B to ground to work at all.

author: MikeK
author_rank: Super Contributor
timestamp: January 02, 2023, 01:55:16 am
content: And worth pointing out, in case the OP doesn't know...larger value pullups are weaker (slower) and smaller value pullups are stronger (faster).

author: themadhippy
author_rank: Super Contributor
timestamp: January 02, 2023, 03:19:11 am
content: After spending far to long trying to get, all be it an arduino ,to work reliable with rotary encoders either within software or resistor capacitor networks i gave up and went old skool and added a schmitt trigger or 6 ,problem solved

author: newtekuser
author_rank: Frequent Contributor
timestamp: January 02, 2023, 06:13:32 am
content: Thank you all for the suggestions!I did wire 10K pull-up resistors to CLK and DATA pins as well as 10nF caps on each and got it almost to work to my liking. At first when the PIC is powered up, rotating the shaft slowly in each direction toggles the LEDs correctly, but then it only works if I rotate the shaft faster (i.e.: 2 detents instead of just one), otherwise it skips steps. For the purpose of my experiment this works well enough.I'll have to look into the schmitt trigger option at some point.

author: Ian.M
author_rank: Super Contributor
timestamp: January 02, 2023, 09:49:20 am
content: Try the state machine code - it shouldn't take much to convert it to XC8.You need to:Put the state machine in your main loop (or in a function and call it from your main loop).Supply pin names or #defined pin names in place of A and B in the if() statements.Declare vector, count and an additional variable old_count as uint8_t globals.After the state machine has run, add code to evaluate (int8_t)(count-old_count), and based on the result, set your LEDs. i.e. if its >=2 the encoder is moving in one direction, and if<=-2, the other. If (and only if) either movement is detected, update old_count from count to 'rearm' the detection for the next movement.In real life, you *probably* wouldn't choose to poll the encoder in the main loop as it may not poll fast enough due to other stuff in the loop so may miss edges. Alps says the EC11M is 15 pulses per rev, so on A and B combined it will deliver 60 edges per rev. To guarantee it doesn't miss an edge, the code needs to poll faster than the edge rate. A polling rate of 1KHz (off a timer tick interrupt) would be reliable up to around 500 RPM which is significantly faster (over 8 revs/sec) than anyone is likely to spin a manual knob. Between 500 and 1000 RPM it *may* keep up depending on the amount of contact bounce. Above 1000 RPM it will fail miserably, but you've probably toasted the encoder due to friction at such a high speed!As you can see from the above, interfacing high line count optical shaft encoders is a nastier problem, as they may have a line count orders of magnitude higher than these simple mechanical knob encoders, and the shaft speeds may be thousands or even tens of thousands of RPM, so the required polling rate to run the state machine can be several orders of magnitude higher. For that, you'd either need a much faster MCU or hardware assistance - some PICs have the state machine implemented in hardware, called a QEI module, capable of polling at a significant fraction of the system clock speed and accumulating the resulting up/down count till you get around to dealing with it.

author: cv007
author_rank: Frequent Contributor
timestamp: January 02, 2023, 09:52:08 am
content: This is a loose translation and simplification of what I have done with other mcu's, minus the interrupt usage. I had a 16F1619 in a curiosity board and an encoder laying nearby, so I created something to test. The idea is that you detect any change on one pin and record the state of the other at that point. When both recorded states are 0 an increment/decrement took place.Normally interrupts are doing the work in the background- one pins edge interrupts (both) are enabled, the isr takes care of the rest of the details as below and enables the other pin edge interrupts. You use the 'bouncy' pin to get you to the interrupt where the stable pin can be read, and that stable pin now has its edge interrupt enabled.Seems to work pretty good for me anyway (human interaction), plus its very simple code.Code://16F1619#include <xc.h>#include <stdbool.h>#include <stdint.h>//4 leds on curiosity low pin count dev board//led D4=A5, D5=A1, D6=A2, D7=C5//led chaser moved by encoder, encoder also determines directionstatic void ledSet(uint8_t v){TRISA5 = 0, TRISA1 = 0, TRISA2 = 0, TRISC5 = 0;//v is bit position 0-3v = (uint8_t)(1<<(v&3)); //compiler complains, so castLATA5 = v & 1 ? 1 : 0; //compiler does not these bits as bool and there is no implicit conversion, so need 1/0LATA1 = v & 2 ? 1 : 0;LATA2 = v & 4 ? 1 : 0;LATC5 = v & 8 ? 1 : 0;}int main(){OSCCONbits.IRCF = 0b1101; //default is 500khz MF, set to 4MHz HF//RC1=encoderA, RC2=encoderBANSC1 = 0, ANSC2 = 0; //analog input off, digital on, already set to input out of resetnWPUEN = 0; //global enable weak pullupsWPUC1 = 1, WPUC2 = 1; //enable pullups of AB pinsledSet( 0 ); //init ledsbool a = 1, b = 1;uint8_t count = 0; //value for leds (0-3, other bits ignored)while(1){while( a == RC1 ){} //wait for A to changeb = RC2; //get B stateif( a==0 && b==0 ){ ledSet( ++count ); }while( b == RC2 ){} //wait for B to changea = RC1; //get A stateif( a==0 && b==0 ){ ledSet( --count ); }}}

author: Ian.M
author_rank: Super Contributor
timestamp: January 02, 2023, 09:57:08 am
content: That's another algorithm and a fairly good one, as in its interrupt driven form, provided you disable the interrupt on the pin that's just triggered, and re-enable when the other pin next triggers (and visa-versa) it cant cause an interrupt storm due to angular shaft vibration and/or contact bounce.

author: ppTRN
author_rank: Regular Contributor
timestamp: January 02, 2023, 12:45:25 pm
content: The solution i found to be the most effective while dealing with encoders was to choose a phase and use it as a source of interrupt. So when an interrupt on change is detected, the mcu check for the other phase, and based on his value it determine if it is moving CW or CCW. I found MANDATORY to filter both phases with capacitors in range 5 - 20 nF. Bigger capacity values won't work, and whidouth caps it is a mess.Here is the code i used on arduino UNO, that check for an encoder movement or pushbutton pressing using sw debaunce:Code:void encoderPress(){ //PRESSING BUTTON ISRif(!TimerBuisy){ //if TIM2 is not buisyTimerBuisy = 1; //set it buisybtnDebounce = 1; //flag to signal the usage for the button and not for the encoderTCCR2B |= 1<<2 | 1<<1 | 1<<0; //turn on TIM2 for 16ms ovfl time}}void encoderMovement(){if(!(PINB & 1))rotation++;else rotation--;}ISR(TIMER2_OVF_vect){TCCR2B = 0; //timer stoppedif(btnDebounce){ //was it to debounce the btn or the encoder?btnDebounce = 0; //clear flagTimerBuisy = 0; //timer no more buisyif(!(PIND & 1<<3)) //btn still pressed?press = 1; //if so, than it's positive, BTN PRESSED!}}

author: newtekuser
author_rank: Frequent Contributor
timestamp: January 07, 2023, 06:30:46 am
content: I ended up using a TI CD74AC14E Schmitt trigger to fix this. However, it only worked perfectly after adding an additional 100ms delay before toggling the LEDs otherwise it would still skip once or twice.Screenshots showing the signal before and after the Schmitt trigger.R1, R2 = 10KR3, R4 = 1KC1, C2 = 10nF

author: paulca
author_rank: Super Contributor
timestamp: January 08, 2023, 11:51:07 am
content: Not sure if it helps here, I only scan read.The encoder has 2 contacts. They can bounce all over the place. If you draw a truth table for the transitions, only 2 are valid, but as they are transitions you need the previous state.So store the previous pin states and in each interrupt shift them left 2 and add the current states.EG:void InteruptHandlerForBothPins() {state = state <<2;state &= (pinA<<1);state &= (pinB);So you have 4 bits showing the previous state and the current state.Then with the encoder datasheet, work out which 2 transitions are valid and they equate to an integer each.So, it's then just something like:if( ( state & 0xF ) == 7 ) {direction = ENCODER_LEFT;} else if (( state & 0xF ) == 14 ) {direction = ENCODER_RIGHT;} else {direction = ENCODER_NULL;}I tested this and it doesn't matter how fast or harsh you spin the encoder it doesn't skip, jump back or miss behave.I found this by copying someone elses supposedly "model implementation" in arduino land. However as I have seen many times before the approach they took was similar to the above, but for some bizarre reason they went ahead and implmented the WHOLE state machine, including all the bounce and invalid states. I looked at it pulling an increasing ugly face and exclaimed, "Why would you do that?" and removed almost all of his code and put just the two VALID transitions. Code was half the size and 10 times neater.It pays when working on fiddly bits of low level code to keep stepping back and looking at the big picture. It's not uncommon for a software engineer when asked to open a file to go off and open read and decode the file and then be told.... "em.... we just needed the file size, we didn't need to you to read and decode the image, delete that code."

author: Ian.M
author_rank: Super Contributor
timestamp: January 08, 2023, 12:49:52 pm
content: I linked to the4 bit state machineversion back in reply #2, but thanks for independently suggesting it, providing a simplified form, and testifying to its robustness.Why would you implement the bounce/invalid states? Well one good reason would be to detect excessive contact wear - if the ratio of invalid to valid events increases too much, alert the user that service is required, *before* the encoder becomes unusable.Also the switch() ... case implementation of the algorithm is preferable when you want to increment/decrement on two or four edges per cycle for a higher resolution, as the if() ... else if() version gets fugly.

author: Peabody
author_rank: Super Contributor
timestamp: January 08, 2023, 04:01:06 pm
content: Quote from: paulca on January 08, 2023, 11:51:07 amThe standard state machine versions I've seen use the 4-bit previous/current value as an index into a 16-byte lookup table, with each value in the table being 1, -1, or 0. Zero being no change or an invalid change. I don't quite understand whether your simplified version is different from that. Could you post an Arduino sketch demonstrating your version if you have one?It seems that in any case you will still get an interrupt on each transition of either line, so all the noise transitions will generate interrupts that have to be serviced even if there is no change to the count.Also, if you have an invalid transition, do you shift left the current state into the previous state position next time, even though you know it's an invalid state, or do you completely ignore the invalid transition and retain the most recent state that you know is valid? Does it make any difference which way you do it?Also, when you read the current state of the lines following an interrupt, you could get a false value on the interrupting pin because of noise.It seems to me that the solution to all this ought to be enabling interrupts on only one line at a time, so that when a line interrupts, in the ISR you disable further interrupts on that line, and enable interrupts on the other line, which should now be stable. And since you know which line is interrupting, and which transition it must have made to generate the interrupt, you don't actually have to read its current value. I wrote such a version, and while it generated far fewer interrupts, it produced more errors than the standard lookup table method. I think that's because sometimes the other line, which should be stable, isn't.

author: paulca
author_rank: Super Contributor
timestamp: January 08, 2023, 04:16:58 pm
content: Quote from: Peabody on January 08, 2023, 04:01:06 pmExactly and what use are all those invalids in the table, why bother with a look up, only 2 of them are valid. Just use an IF ELSE and no lookup table. It's not needed.

author: DavidAlfa
author_rank: Super Contributor
timestamp: January 08, 2023, 05:06:01 pm
content: Yeah, always use pullup resistors.Can't remember if that PIC could enable internal pullups for PortD, I think they were only available for PortB.Try this.Count direction or output pins might be reversed, easy fix. Tested, works fine.Code:int encoder_pos; // Absolute position (You might need to increase the storage type to signed long)unsigned char encoder_update; // 0=Nothing, 1=Left_rotation, 2=Right_rotation (Might need reversing). User must clear it after reading.void manage_encoder(void){static unsigned char stable;static signed char add;unsigned char now = PORTD & 0xF9; // Single read to avoid changes, mask RD1&2unsigned char now_a = (now & 1) &&1; // RD0unsigned char now_b = (now & 8) &&1; // RD3if (now_a && now_b && stable) { // Both inputs high after stable condition reachedstable=0;encoder_pos += add; // Update absolute positionencoder_update = (add>0 ? 1 : 2); // Update flagnow |= add>0 ? 4 : 2; // Update LEDS with the last valid value (10/01 depending on the rotation direction)PORTD = now; // Update RD1 & 2 outputs}else if(!now_a && !now_b && !stable) { // Both inputs low and unstable statestable = 1; // Reached stable state}else if(!stable) { // Only one pin low, sample pinsadd = now_a ? 1 : -1; // Update direction}}

author: Peabody
author_rank: Super Contributor
timestamp: January 08, 2023, 06:40:14 pm
content: Quote from: paulca on January 08, 2023, 04:16:58 pmI still don't understand. If you're tracking the transitions of both pins, then for the typical encoder you will have four legal transitions between detents, which can occur in either direction. So there are eight valid entries, four invalid entries, and four no-change entries (which if using interrupts are also invalid). Are you only looking for the *last* valid entry going into the detent position?

author: Doctorandus_P
author_rank: Super Contributor
timestamp: January 08, 2023, 06:46:24 pm
content: Never ever put ceramic caps directly over those encoder switches.(Don't feel ashamed, it's a common mistake)The problem is that such capacitors can deliver very high peak currents. 10A or more is quite possible, and that means you have 10A though your switches right when they are bouncing, and this will wear out your switches prematurely.Edit:Further on I see a schematic with added series resistors. That's much better.

author: paulca
author_rank: Super Contributor
timestamp: January 08, 2023, 07:10:44 pm
content: Quote from: Peabody on January 08, 2023, 06:40:14 pmOnly two transitions are valid. One says it moved right, one says it moved left. All the others are pointless to know.CodeWorks perfectly.Put another way, yes the encoder can produce more transitions, but they are invalid, bounces, null states and ultimately all you want to know is... did it move anti-clock wise or clockwise. The datasheets often show the state transition diagrams. People too often just implement the whole statemachine. Yet I, again, quesion, what is the point in knowning it bounced? What is the point in knowing it went to it's detent and back again? None. So just ignore them and pick the 2 transitions you want.Balancing the encoder in the both on, both off is not a state.

author: Peabody
author_rank: Super Contributor
timestamp: January 08, 2023, 11:12:27 pm
content: Ok, you're just ignoring the first three of the four transitions between one detent and the next. When you get to a detent (11), you just see how you got there, and if one of the two possible valid transitions has occurred, you recognize a tick. I think some would contend that your approach is somewhat more susceptible to errors when there is bouncing. If you instead pay attention to all transitions, you have to have accumulated a net four transitions is the same direction when you arrive at a detent before recognizing that a valid tick has taken place. But you know, if it works for you, it works, and there's no need to do more.But I do want to talk you into using the lookup table. If 14 of the 16 possible entries are zero, and if the two valid entries are +.5 and -.5, then you can just add the indexed lookup table value to your volume, and you're done. No IF statements required.

author: PCB.Wiz
author_rank: Super Contributor
timestamp: January 09, 2023, 01:08:43 am
content: Quote from: newtekuser on January 07, 2023, 06:30:46 amIf resting power matters, you can increase those R values significantly and lower the C.Such aSchmitt + series RCdesign is 'solid' and the benefit it gives you isthe hardware RC defines a minimum time possible between edges.Series RC is better than simpler parallel C, as parallel C is tougher on the contacts, and fall time is still very short.In theory, quadrature designs can track bouncing contacts, but that theory assumes the time between edges is sufficiently long for the code to act on each one. Nasty contacts can mean that is not true.Some MCUs have Schmitt pins/options, so you can use that feature if available.Another advantage of using a hardware RC cleanup of contact bounce, is you can more easily apply rate-of-spin non linear smarts to your quad interface.

author: paulca
author_rank: Super Contributor
timestamp: January 09, 2023, 08:22:32 am
content: Quote from: Peabody on January 08, 2023, 11:12:27 pmIt won't suffer from bounces. I the case of my encoder all valid transitions end with both contacts closed. All you really need to know is, was is at a valid LEFT or a valid RIGHT before it got there. Hence 0111 or 1011. It can't get there from a bounce.Using the look up table and being able to just add transitions[ state&0xF ] does sound nice, but you still have to define the whole 16 value lookup table. I'm not sure that is a benefit over an if/else.

author: Ian.M
author_rank: Super Contributor
timestamp: January 09, 2023, 08:37:26 am
content: Quote from: paulca on January 09, 2023, 08:22:32 amUnfortunately you can get there from overlapping bounces on both A and B, which *CAN* look like the quadrature signal you are expecting from movement, The encoder is made with multi-finger contacts to reduce the risk of this happening, but as it ages and surface contamination builds up false counts become inevitable. Short of complex code that monitors the encoder transition rate, and rejects implausibly fast transition sequences, your only defence is asymmetric RC filtering - fast to pull down but slow to pull up to 'bridge' the bounces, with enough wetting current to keep the contacts clean.However, the simple approach works well with optical and hall effect quadrature encoders, as although rotational vibration can cause jitter on one channel, the other channel can never change state near a transition of the first, and visa-versa.

author: paulca
author_rank: Super Contributor
timestamp: January 09, 2023, 09:03:10 am
content: Quote from: Ian.M on January 09, 2023, 08:37:26 amI might have read the datasheet wrong, but it shows several full complete transitions and there are gaps between the contacts aligning which limits the plausible bounces to single pins.I can suffer a bounce, say for example I get:0111 - which I take as clockwise.If a pin bounces it could go to:1100 - which is invalidor1101 - which is invalidA double bounce I suppose could go:0111 - Valid CW11010111 - Valid - ERROR double bounce.Maybe my vTaskDelay( 1 milisecond ) is helping as well here. I was going to replace it with a yeild. I don't really like hot looping because in other hardware it's very expensive and frowned upon to do so unless absolutely necessary.EDIT: On rotary encoders as a user. Yes they seem to be one of the first things to go on items like HiFis and similar devices. My Sony Amp circa 1995 lost it's volume control a long while ago. Have to use the remote. The EQ preset encoder jumps all over the place. I expect the volume control has "anti-bounce" coding or hardware and that's why it's mode of failure was just that it started ignoring more and more inputs until it just stopped altogether, it no long sees any valid transitions!But if you use a pot and an ADC it too is subject to wear and tear causing bouncing due to "scratchy tracks" syndrome.Hall effect encoders seem the only way. Might see how expensive they are. I did try and find a hall effect analogue joystick for a different project and found they tend to be expesnive.

author: Ian.M
author_rank: Super Contributor
timestamp: January 09, 2023, 09:48:33 am
content: Typically, low cost mechanical encoders use sliding contacts and a sectored contact disk attached to the shaft. Fairly obviously, if an insulating sector is under a contact, it *can't* bounce low. However it only takes a brief open circuit while the contact is on a conducting sector for it to bounce high, and any bounces on different channels that overlap look like a valid movement as I noted above.

author: Peabody
author_rank: Super Contributor
timestamp: January 09, 2023, 02:41:45 pm
content: @paulca, lets say you have a 0111 valid transition. But then the first line bounces back low temporarily. When that happens you shift 0111 left and add in the bounce state, yielding 1101, which is invalid. But then the line bounces back high again, and you shift left and add again, giving you 0111, which is another valid transition. So you've recognized two valid transitions when only one (plus bouncing) has taken place.If you follow all the transitions, looking to accumulate four in the same direction on arriving at a detent, only one valid tick would be recognized. Even that method isn't perfect because the processor may not be able to recognize all the bouncing transitions if they occur fast enough, and it still suffers from having to service an interrupt on each bounce transition, which can really slow you down if you have a noisy encoder. That's why I was working on enabling only one interrupt at a time, hoping to avoid any bounce-generated interrupts. But I suspect a hardware solution is probably the best fix in the end.