Interrupt Driven Rotary Encoder Becomes Un-Responsive and then Responds again(Cyclic) - Why?

I’ve got an interrupt driven rotary encoder which controls a menu. It seems to work just fine and then it becomes unresponsive for a certain amount of time(1-10 seconds) and then starts responding again and again it becomes unresponsive. While it’s not responding, the interrupts are not queued. It’s like they never were triggered.

I’m trying to debug it, but I can’t figure it out. Is the garbage collector getting in the way? Is there a way to find out what’s going on? I can post of it video if needed.

In my code I load the menu to the display and then call this method…basically waiting for an interrupt. The interrupt handlers take care of rest of the system flow.

            public override void MainTask(ModeContext mode)
            {
                while (true)
                {
                    Thread.Sleep(50);
                }
            }

@ Gismofx - when connected to the debugger, you should see GC messages in the output window. Each message tells you the elapsed time for the GC.

@ Mike I’m not seeing any GC messages…come to think of it, I don’t think I’ve ever seen a GC message. Is it a setting?

I did put some error messages on my encoder driver which I am seeing on occasion. My encoder is hardware debounced and I’m getting a clear square wave output from it. I can’t see why I would be getting an error with it.

@ Gismofx - If you are not seeing GC messages, then most likely GC is not the problem.

Could you post a small complete program, which demonstrates the issue? The program should show how you are handling the rotary interrupts.

It is likely that something you are doing in your program is causing the issue, and you need to start simple, and keep adding functionality until the problem occurs.

@ Mike

Circuit(but using 3.3 instead of 5v):
[url]https://hifiduino.files.wordpress.com/2010/10/analogdeb.jpg[/url]

Main Code:

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHI.Pins;
using Hardware.RotaryEncoderDriver;

namespace TestEncoder
{
    public class Program
    {

        static RotaryEncoder encoder;
        static Cpu.Pin EncoderPinA = Generic.GetPin('C', 12);
        static Cpu.Pin EncoderPinB = Generic.GetPin('D', 2);
        static int RotationCount = 0;

        static string direction;
        
        public static void Main()
        {
            encoder = new RotaryEncoder(EncoderPinA, EncoderPinB);
            encoder.RotationEventHandler += encoder_RotationEventHandler;
            while (true)
            {
                Thread.Sleep(100);
            }

        }

        static void encoder_RotationEventHandler(uint data1, uint data2, DateTime time)
        {
            {
                RotationCount++;
                if (data1 == 1)
                {
                    direction = "Clockwise";
                    Debug.Print(direction);
                }
                else
                {
                    direction = "Counter-Clockwise";
                    Debug.Print(direction);
                }
                Debug.Print("Count: " + RotationCount.ToString());
            }
        }


    }
}

Encoder Driver:

using System;
using Microsoft.SPOT;
using System.Threading;
using Microsoft.SPOT.Hardware;


namespace Hardware.RotaryEncoderDriver
{
    /// <summary>
        /// </summary>
    public class RotaryEncoder : IDisposable
    {

        #region Things Used Here Globals/Publics/Privates
        
        /// <summary>/// Pin A Interrupt Port/// </summary>
        private static InterruptPort PinA = null;
        
        /// <summary>/// Pin B Interrupt Port/// </summary>
        private static InterruptPort PinB = null;

        /// <summary>/// Rotary Push button is momentary/// </summary> 
        private static InterruptPort RotaryButton = null;

        /// <summary>
        /// Default Value is 80ms
        /// I use hardware debounce
        /// </summary>
        public TimeSpan DebounceInterval
        {
            get { return _DebounceInterval; }
            set { _DebounceInterval = value; }
        }

        public static uint DebounceMillis = 5;
        
        private TimeSpan _DebounceInterval = TimeSpan.FromTicks(DebounceMillis * TimeSpan.TicksPerMillisecond);

        public uint ButtonPressAndHoldTimeMillis = 3000;
        //private int ButtonEventStartMillis;
        //private byte ButtonInterruptCount;//will never exceed value of 4

        /// <summary>
        /// Subscriber Event for Rotary Interrupts
        /// This is the Public EventHandler that is passed through the code and eventually handed off with rotation results.
        /// </summary>
        public event NativeEventHandler RotationEventHandler = null;


        /// <summary>
        /// Subscribe for Button Interrupts
        /// This will return SingleClick 1, DoubleClick 2[not implemented] , and PressAndHold 0 Events in data1
        /// </summary>
        public event NativeEventHandler MomentaryButtonEventHandler=null;

        public static byte CLOCKWISE = 1;
        public static byte COUNTERCLOCKWISE = 0;

        //public struct ResultSet
        //{
        //    public bool PinA;
        //    public bool PinB;
        //}
        //private ResultSet[] results = new ResultSet[2];//why use result set? read only?
        //private bool InProcess; //no longer used.

        private static byte StateCount;
        /// <summary>/// This Holds the 4 states of a "click" of the momentary/// </summary>
        private static byte[] State = new byte[4];

        private static bool SkipFirstInterrupt;//This is used
        
        private DateTime button_timestamp = DateTime.Now;
        
        #endregion

        #region Constructors
        
        /// <summary>
        /// Only if Rotary Encoder is being used.
        /// </summary>
        /// <param name="pinA"></param>
        /// <param name="pinB"></param>
        public RotaryEncoder(Cpu.Pin pinA, Cpu.Pin pinB) : this(pinA, pinB, Cpu.Pin.GPIO_NONE) { }

        /// <summary>
        /// This is the main constructor. Rotary Encoder + Momentary Button
        /// </summary>
        /// <param name="pinA"></param>
        /// <param name="pinB"></param>
        /// <param name="buttonPin">Pin for the Momentary Switch</param>
        public RotaryEncoder(Cpu.Pin pinA, Cpu.Pin pinB, Cpu.Pin buttonPin=Cpu.Pin.GPIO_NONE)
        {
            //InProcess = false;
            SkipFirstInterrupt = true;
            StateCount = 0;
            
            PinA = new InterruptPort(pinA, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeBoth);//Pins are pulled high.
            PinB = new InterruptPort(pinB, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeBoth);//Interrupt Needed here.

            PinA.OnInterrupt += RotationInterrupt;
            PinB.OnInterrupt += RotationInterrupt;
            
            #region Unused Example Code
            ////RotationEvent = new NativeEventHandler(RotationEvent_OnInterrupt);
            //NativeEventHandler RotationEvent =
            //    delegate { NewRotationResult(PinA.Read(), PinB.Read(), ref InProcess, ref results, RotationEventHandler); };//Pin A will always be low because interrupt edge};
            ////Alternative definition
            ////NativeEventHandler RotationEvent = (data1, data2, time) =>
            ////{
            ////    NewNotaryResult(PinA.Read(), PinB.Read(), ref InProcess, ref results,RotationEventHandler);
            ////};

            //PinA.OnInterrupt += RotationEvent;//Use Deletage NewRotatationResult
            //PinB.OnInterrupt += RotationEvent;//not needed but will leave here
            #endregion


            //setup button
            // Setup and initialize the Button Event
            if (buttonPin != Cpu.Pin.GPIO_NONE)//more generic definition//Pins.GPIO_NONE)
            {
                //play here
                RotaryButton = new InterruptPort(buttonPin,true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
                RotaryButton.OnInterrupt += RotaryButton_OnInterrupt;      
                //RotaryButton.OnInterrupt += (data1, data2, time) =>
                //{
                //    OnRaiseButtonEvent(time, MomentaryButtonEventHandler, ref button_timestamp, this.DebounceInterval);
                //};
            }


        }


              
        #endregion

        #region Built-In Momentary Push Button Methods
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="data1">Pin Number [Not Used]]</param>
        /// <param name="data2">Pin Value on Interrupt 0 or 1 // High or Low</param>
        /// <param name="time">TimeStamp</param>
        private void RotaryButton_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            Thread.Sleep(100);//let things settle
            //MomentaryButtonEventHandler(1, 0, time);
            if (RotaryButton.Read() == false)//button is still held low
            {
                //DateTime.Now.AddSeconds(4);
                //int millisnow = DateTime.Now.Ticks+100000;
                while (DateTime.Now < time.AddSeconds(3))
                {
                    if (RotaryButton.Read() == true)
                    {
                        MomentaryButtonEventHandler(1, 0, time);
                        break;
                    }
                }
                if (RotaryButton.Read() == false) { MomentaryButtonEventHandler(0, 0, time); }
            }
            else
            {
                MomentaryButtonEventHandler(1, 0, time);
            }

        }

     
        
        
        
        /// <summary>
        /// Creates the event for subscribers
        /// </summary>
        private static void OnRaiseButtonEvent(DateTime time, NativeEventHandler handler, ref DateTime buttonTimestamp, TimeSpan debounceInterval)
        {
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                if (time > buttonTimestamp + debounceInterval)
                {
                    buttonTimestamp = time;
                    handler(0, 0, DateTime.Now);
                }
            }
        }
        #endregion

        #region Rotary Methods

        /// <summary>
        /// This Handles all Interrupts From the Rotary Encoder
        /// </summary>
        /// <param name="data1">The Pin Number {uint}, which Generated the Interrupt</param>
        /// <param name="data2">The Pin.Read() Value at time of Interrupt 0 or 1 (LOW or HIGH)</param>
        /// <param name="time">Timestamp</param>
        private void RotationInterrupt(uint data1, uint data2, DateTime time)//Data1 Is Pin Data 2 Is Pin.Read()
        {
            //Debug.Print("Pin A: " + PinA.Read().ToString() + " || " + "Pin B: " + PinB.Read().ToString());
            
            if (RotationEventHandler == null)//Check to see if user has added an external event handler
            {
                throw new Exception("You must define a handler in your main code. Ex: RotaryEncoder.RotationEventHanlder+=DoSomething;");
            }
      

            //There are 4 states/conditions that can occur:
            if (data1 == (uint)PinA.Id && data2 == 0)//Pin A Goes LOW
            {
                State[StateCount] = 1;
            }
            else if (data1 == (uint)PinA.Id && data2 == 1)//Pin A Goes HIGH
            {
                State[StateCount] = 2;
            }
            else if (data1 == (uint)PinB.Id && data2 == 0)  //Pin B Goes LOW
            {
                State[StateCount] = 3;
            }
            else if (data1 == (uint)PinB.Id && data2 == 1) //Pin B Goes HIGH
            {
                State[StateCount] = 4;
            }

            else throw new Exception("No State recognized");

            StateCount++;
            if (StateCount == 2)//we've collected two states. Let's compare them and return a rotation direction
            {
                StateCount = 0;  //reset the state count              
                if (!SkipFirstInterrupt)
                {
                    if ((State[0] == 1 && State[1] == 3) || (State[0] == 2 && State[1] == 4)) //|| (State[0] ==1 && State[2] == 4))
                    {
                        //do counterclockwise
                        RotationEventHandler(COUNTERCLOCKWISE, 0, DateTime.Now);
                    }

                    else if ((State[0] == 3 && State[1] == 1) || (State[0] == 4 && State[1] == 2) ) //|| (State[0] ==3 && State[2] == 2) )
                    {
                        //do clockwise
                        RotationEventHandler(CLOCKWISE, 0, DateTime.Now);
                    }
                    else 
                    {
                        Debug.Print ("Error in Encoder: State0: " + State[0].ToString() + " || State1: " + State[1].ToString());
                    }
                }
                SkipFirstInterrupt = !SkipFirstInterrupt;
            }

        }
        
        
        #endregion

        /// <summary>
        /// Frees all pins and disposes this object
        /// </summary>
        public void Dispose()
        {
            PinA.Dispose();
            PinB.Dispose();
            RotaryButton.Dispose();
        }
    }
}

//EXAMPLE EVENT HANDLER
//static void RE_RotationEventHandler(uint data1, uint Rotation, DateTime time)
//{
//    //throw new NotImplementedException();
//    RotCount++;
//    if (Rotation==1)
//    Debug.Print(RotCount.ToString() +  " Rotation Clockwise!");
//    else
//        Debug.Print(RotCount.ToString() + " Rotation Counter-Clockwise!");

//}

//Quadrature Signal Timing Offset
//        __   
//    |__|  |__|
//         __   
//     |__|  |__|

First, does this code exhibit the issue?

I don’t know how fast the interrupts are occurring, but if they are coming fast, the Debug.Print statements in the interrupt handler could be your problem. They are very slow, and they can backup and cause delays.

@ Mike,

Yes this code exhibits it. You can remove the debug.print lines from the main code and it still exhibits it. The other debug outputs are within the driver and are called after some error occurred.

@ Gismofx - should line 253 be “SkipFirstInterrupt = false;”?

It appears that every other interrupt was ignored?

Too much code for a more detailed review…

Have you traced your code in the debugger to make sure it is doing what you expect?

You can check some of the rotary encoder code in the CodeShare section.

@ Mike,

I’ll try to detail the interrupt handler method when I get back to my desk. It’s similar to the codeshare. I have tried stepping through the code and it seems to behave and respond the way it is programmed to. Somehow the inputs get messed up

…come to think of it, is it necessary to qualify my state array as Volatile?(as with low level C code for an ISR). I’m going to look that up too. Update: http://www.cesarafonso.pt/2011/09/everything-is-volatile-net-micro.html?m=1

In RotationInterrupt where you wait for StateCount == 2, looks like it skips some transitions? It is hard to tell exactly what is going on without being able to run the code, but it looks like you are comparing transitions this way:

Transitions
1 to 2
3 to 4
5 to 6
etc.

If that is what is happening then you are missing some transitions and getting into an invalid state.

The comparison should be:
1 to 2
2 to 3
3 to 4
etc

Something like this maybe?

private uint lastState = 10000;
		private void RotationInterrupt(uint data1, uint data2, DateTime time)//Data1 Is Pin Data 2 Is Pin.Read()
		{
			//Debug.Print("Pin A: " + PinA.Read().ToString() + " || " + "Pin B: " + PinB.Read().ToString());

			uint currentstate;

			if (RotationEventHandler == null)//Check to see if user has added an external event handler
			{
				throw new Exception("You must define a handler in your main code. Ex: RotaryEncoder.RotationEventHanlder+=DoSomething;");
			}
			
			

			//There are 4 states/conditions that can occur:
			if (data1 == (uint)PinA.Id && data2 == 0)//Pin A Goes LOW
			{
				currentstate = 1;
			}
			else if (data1 == (uint)PinA.Id && data2 == 1)//Pin A Goes HIGH
			{
				currentstate = 2;
			}
			else if (data1 == (uint)PinB.Id && data2 == 0)  //Pin B Goes LOW
			{
				currentstate = 3;
			}
			else if (data1 == (uint)PinB.Id && data2 == 1) //Pin B Goes HIGH
			{
				currentstate = 4;
			}

			if (lastState == 10000)
			{
				//We do need to skip the very first transition because we have nothing to compare it with
				lastState = currentstate;
				return;
			}



			if ((lastState == 1 && currentstate == 3) || (lastState == 2 && currentstate == 4)) //|| (State[0] ==1 && State[2] == 4))
			{
				//do counterclockwise
				RotationEventHandler(COUNTERCLOCKWISE, 0, DateTime.Now);
			}

			else if ((lastState == 3 && currentstate == 1) || (lastState == 4 && currentstate == 2)) //|| (State[0] ==3 && State[2] == 2) )
			{
				//do clockwise
				RotationEventHandler(CLOCKWISE, 0, DateTime.Now);
			}
			else
			{
				//Bad things have happened, do a reset
				lastState = 10000;
				Debug.Print("Error in Encoder: State0: " + lastState.ToString() + " || State1: " + currentstate.ToString());
			}
			lastState = currentstate;
		}

This is probably unnecessary too…

[quote] while (true)
{
Thread.Sleep(50);
}
}[/quote]
Replace with

@ Brett -

In this example I think that would be better.

For other reasons, I’ve tried that and I couldn’t figure out how to wake it back up if I need to break out of that loop. I have another method that polls a parameter to change state the current state. When the thread sleep is set to infinite it totally stops the state machine.

Sorry, it can’t stop the state machine - it stops that particular thread, and as long as your state machine is outside that thread, you should be fine. If that code you showed is honestly the last thing in main() then there should be no issue putting that thread to sleep. Interrupts should still be handled.

@ stevepx -

I am basically doing the same thing, but I think your nomenclature of LastState and currentState make it more legible so I’ve adopted that.

Originally, I only capture 2 states because that’s all that’s needed to detect a direction increment for these encoders. Additionally, I don’t act until I have two sequential states recorded. Once I have two states, I can act on it. I use SkipFirstInterrupt to throw away the other two interrupts that will fire as part of the pulses from a detent. I only wanted one event per click, not two.

I was really focused on why I was missing some states and getting incorrect results. :wall: It still concerns me and I would like to understand what’s really causing this. The hardware in the switch is fixed and dictates the pulses.

One detent(click) produces only two state combinations:

Clockwise
1,3,2,4

Counter-Clockwise:
3,1,4,2

Here’s a shot from my scope of the encoder in action(it’s consistent and correct)
[url]http://i.imgur.com/5xSFKwt.jpg[/url]

I’ve been playing around and maybe I need to capture ALL the states and then determine how to proceed. Live with the errors and handle it. Something like average the results over a click. For example, in a case where I get 2 clockwise and one bad result, I’ll send a clockwise command. Here’s my modified version that’s working MUCH better!

        private void RotationInterrupt(uint data1, uint data2, DateTime time)//Data1 Is Pin Data 2 Is Pin.Read()
        {
            //Debug.Print("Pin A: " + PinA.Read().ToString() + " || " + "Pin B: " + PinB.Read().ToString());

            uint currentState= 0;
            int resultCount = 0;

            if (RotationEventHandler == null)//Check to see if user has added an external event handler
            {
                throw new Exception("You must define a handler in your main code. Ex: RotaryEncoder.RotationEventHanlder+=DoSomething;");
            }



            //There are 4 states/conditions that can occur:
            if (data1 == (uint)PinA.Id && data2 == 0)//Pin A Goes LOW
            {
                currentState = 1;
            }
            else if (data1 == (uint)PinA.Id && data2 == 1)//Pin A Goes HIGH
            {
                currentState = 2;
            }
            else if (data1 == (uint)PinB.Id && data2 == 0)  //Pin B Goes LOW
            {
                currentState = 3;
            }
            else if (data1 == (uint)PinB.Id && data2 == 1) //Pin B Goes HIGH
            {
                currentState = 4;
            }
            else
            {
                throw new Exception("Not Detected State!!");
            }

            if (LastState == 10000)
            {
                //We do need to skip the very first transition because we have nothing to compare it with
                //currentState = lastState;
                LastState = currentState;
                return;
            }

            {
                StateCount = 0; //reset the statecount
                
                {
                    if ((LastState == 1 && currentState == 3) || (LastState == 2 && currentState == 4) || (LastState == 3 && currentState == 2) || (LastState == 4 && currentState == 1) )
                    {
                        //do counterclockwise
                        CounterClockwiseCount++;
                        //RotationEventHandler(COUNTERCLOCKWISE, 0, DateTime.Now);
                        //LastState = 10000;
                    }

                    else if ((LastState == 3 && currentState == 1) || (LastState == 4 && currentState == 2) || (LastState == 1 && currentState == 4) || (LastState == 2 && currentState == 3) )  
                    {
                        //do clockwise
                        ClockwiseCount++;
                        //RotationEventHandler(CLOCKWISE, 0, DateTime.Now);
                        //LastState = 10000;
                    }
                    else
                    {
                        //Bad things have happened, do a reset
                        //Debug.Print("----->Error in Encoder: Last State: " + LastState.ToString() + " || Current State: " + currentState.ToString());
                        ErrorCount++;
                        LastState = 10000;
                    }
                    
                }
                
                
            }
            LastState = currentState;
            
            //this is the logic to yield an actual event
            resultCount = ErrorCount + ClockwiseCount + CounterClockwiseCount;
            
            if (resultCount > 3)
            {
                if (ClockwiseCount > CounterClockwiseCount)
                {
                    RotationEventHandler(CLOCKWISE, 0, DateTime.Now);
                }
                else if (CounterClockwiseCount > ClockwiseCount)
                {
                    RotationEventHandler(COUNTERCLOCKWISE, 0, DateTime.Now);
                }
                else {
                    //no results cannot be determined
                }
                ClockwiseCount = 0;
                CounterClockwiseCount = 0;
                ErrorCount = 0;

            }

        }

This is working much better and I can give the encoder a good spin and it will spit out my results with much more consistency. For now, I can move on comfortably. Thanks all for chiming in and helping to work around this.

That said, I’m still curious why this “error” happens.

Digging up an old thread and finding the same issues… getting odd reads. Again, I’m confident the hardware is giving the proper signals, but I’m getting some bizarre results. I want to preface that I understand if I do half of a detent rotation or something weird i get a bad result, but with a normal “click” clockwise or counter-clockwise, I do get a correct signal.

I’m tried to rewrite the interrupt routine; In this case, I wait for the 4 interrupts and try to detect some type of pattern. At first, this works, but after about 10-20 rotations, it’s starts to get weird. The pattern will be something like:
Pin A Low
Pin A High
Pin B Low
Pin B High
or
Pin B High
Pin B Low
Pin A Low
Pin A High

Unexpected Results.

Any ideas? Can the interrupt queue get messed up? Can I prevent this?

Here’s my current code:

        private void RotationInterrupt(uint data1, uint data2, DateTime time)//Data1 Is Pin Data 2 Is Pin.Read()
        {
            //Debug.Print("Pin A: " + PinA.Read().ToString() + " || " + "Pin B: " + PinB.Read().ToString());

            uint currentState= 0;
            //int resultCount = 0;

            if (RotationEventHandler == null)//Check to see if user has added an external event handler
            {
                throw new Exception("You must define a handler in your main code. Ex: RotaryEncoder.RotationEventHanlder+=DoSomething;");
            }


            currentState = data1 << 1 | data2;

            states[StateCount] = currentState;
            StateCount++; //INCREMENT STATECOUNT
            /* testing error
            if (RotaryError)
            {
                StateCount = 0;
                Array.Clear(states, 0, 4);
                RotaryError = false;
                return;
            }
            */
            if (StateCount < 4)
            {
                return;
            }

            byte result = FindPattern();

            if (result == CLOCKWISE)
            {
                RotationEventHandler(CLOCKWISE, 0, DateTime.Now);
            }
            else if (result == COUNTERCLOCKWISE)
            {
                RotationEventHandler(COUNTERCLOCKWISE, 0, DateTime.Now);
            }
            else
            {
                RotaryError = true;
            }


            StateCount = 0;
            Array.Clear(states, 0, 4);
    }

I’ve implemented a find pattern function which compares part the 4 states to the known pattern of the encoder It’s pretty loose, but still fails.

        private byte FindPattern()
        {
            int patternPositionCW=0;
            int patternPositionCCW=0;
            int patternStart=0;
            int i=0;
            for (i=0; i < 4; i++)
            {
                if (states[0] == BasePattern[i])
                {
                    patternStart = i;
                }
            }
            patternPositionCW = (patternStart==3)? 0 : patternStart+1;
            patternPositionCCW = (patternStart==0)? 3 : patternStart-1;
            if (states[1] == BasePattern[patternPositionCW])
            {
                return CLOCKWISE;
            }
            else if (states[1] == BasePattern[patternPositionCCW])
            {
                return COUNTERCLOCKWISE;
            }
            
            return 3;
            
        }

and excerpt from the constructor:

            PinA = new InterruptPort(pinA, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);//Pins are pulled high.
            PinB = new InterruptPort(pinB, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);//Interrupt Needed here.

            PinA.OnInterrupt += RotationInterrupt;
            PinB.OnInterrupt += RotationInterrupt;

            PinAHigh = (uint)PinA.Id << 1 | 1;
            PinALow = (uint)PinA.Id << 1;
            PinBHigh = (uint)PinB.Id << 1 | 1;
            PinBLow = (uint)PinB.Id << 1;
            BasePattern[0] = PinALow;
            BasePattern[1] = PinBLow;
            BasePattern[2] = PinAHigh;
            BasePattern[3] = PinBHigh;

Your code seems overly complex just to handle rotary encoding. In reality all you want to know is that a pulse was detect and it’s direction.

By interrupting on A going LOW (or HIGH) and reading B you can determine the direction and fire a pulse detection event.

Checking the state either high or low seems to be way more than you need.

Your thread sleep of 100mS seems too long. Most cheap encoder switches are about 10-20mS of bounce and you have a cap and resistor on the encoder inputs that should handle this part. Try without this and just do a basic interrupt on A going LOW and read B to know direction and then fire you event to suit. Then when you turn 1 click you can see what the software detects.

@ Dave McLaughlin -

I want the code to be clean and elegant…it does feel a bit “off” right now…I remember there was a reason the method you suggest wasn’t acceptable: Anyway I quicly re-wrote to detect Pin A edge low and reading Pin B’s state in the Interrupt to determine direction:

        private void RotationInterrupt(uint data1, uint data2, DateTime time)//Data1 Is Pin Data 2 Is Pin.Read()
        {

            if (PinB.Read() == true)
            {
                RotationEventHandler(CLOCKWISE, 0, DateTime.Now);
            }
            else
            {
                RotationEventHandler(COUNTERCLOCKWISE, 0, DateTime.Now);
            }
        }

And excerpt from the constructor/class:

            
//global class members
private InterruptPort PinA = null;
private InputPort PinB = null;

//in constructor
PinA = new InterruptPort(pinA, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);//Pins are pulled high.
PinB = new InputPort(pinB, false, Port.ResistorMode.Disabled);

PinA.OnInterrupt += RotationInterrupt;

This works, but I still have some false reads, about 10%-25% of the time depending on a moderate speed of rotation…I’m wondering if this is because by the time the interrupt can run, Pin B’s state would have changed. The higher the speed of rotation, the more unreliable the encoder results become. It’s probably 50% when I spin a full rotation/second. Useless at this point. I think this stemmed the longer code to try to capture all the pulses and match to some type of pattern.

Any ideas on how to improve this?