You must be logged in to reply.

Page 1 of 2 out of 17 messages.

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

Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
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);
                }
            }
Reply #1 — Posted 3yr ago
by Mike | Superhuman | 82,265 exp
Reply #1 — Posted 3yr ago
by Mike | Superhuman | 82,265 exp
@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.
Reply #2 — Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
Reply #2 — Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
@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.
Reply #3 — Posted 3yr ago (modified)
by Mike | Superhuman | 82,265 exp
Reply #3 — Posted 3yr ago (modified)
by Mike | Superhuman | 82,265 exp
@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.
Reply #4 — Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
Reply #4 — Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
@Mike

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

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
//        __   
//    |__|  |__|
//         __   
//     |__|  |__|
Reply #5 — Posted 3yr ago
by Mike | Superhuman | 82,265 exp
Reply #5 — Posted 3yr ago
by Mike | Superhuman | 82,265 exp
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.
Reply #6 — Posted 3yr ago
by Gismofx | Senior | 1,322 exp
Reply #6 — Posted 3yr ago
by Gismofx | Senior | 1,322 exp
@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.
Reply #7 — Posted 3yr ago (modified)
by Mike | Superhuman | 82,265 exp
Reply #7 — Posted 3yr ago (modified)
by Mike | Superhuman | 82,265 exp
@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.
Reply #8 — Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
Reply #8 — Posted 3yr ago (modified)
by Gismofx | Senior | 1,322 exp
@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
Reply #9 — Posted 3yr ago
by stevepx | Minor | 250 exp
Reply #9 — Posted 3yr ago
by stevepx | Minor | 250 exp
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;
		}

Page 1 of 2 out of 17 messages.

You must be logged in to reply.