Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Interrupts, Queue, Events, and Event Subscriber Class with Rotary Encoder

interrupt port Event Handlers Subscribers Rotary Encoder Grayscale lambda expression NativeEventHandler

Best Answer Paul Newton, 28 November 2013 - 06:10 PM

your interrupt fires twice since you've configured it so: Port.InterruptMode.InterruptEdgeBoth

 

Set the InterruptMode to InterruptEdgeHigh or -Low so it only fires once on the rising (or falling) edge, not on both

 

See also http://msdn.microsof...y/ee434400.aspx

 

I started to reply to the above and then did a U-Turn. (Normally you would want to have the interrupt on both edges to track every change in the encoder pattern.)

 

Building on what perpetualKid has said, if you just had a single edge interrupt on one of the pins, when the interrupt fired, you could use the state of the other pin to tell you the direction. This would make your code a lot simpler.

 

For example, interrupt on pin 1 rising edge, if pin 2 is low - its clockwise, if its high its anti-clockwise.

 

This would mean only one interrupt per complete cycle of the pulses from the encoder. (That might now mean not enough events.)

You would need to carefully select which pin to use for the interrupt, and which edge to use, to ensure the interrupt fires at the right part of the switch's travel. Then determine the state of the other pin for each direction.

 

If you then don't get enough events, try using both edges - but still only on the one pin.

 

Paul

Go to the full post


  • Please log in to reply
5 replies to this topic

#1 gismo

gismo

    Advanced Member

  • Members
  • PipPipPip
  • 110 posts

Posted 23 November 2013 - 06:51 PM

Hey Guys,

 

I have got a couple of questions regarding interrupts and events and then subscribing to events/interrupts created by the encoder class/driver. With specific regards to a 3 pin (A C B) rotary encoder with gray code output:

 

Specifically:

https://www.sparkfun...s/TW-700198.pdf
 

Some background, I found a rotary encoder driver on the forums and the output was not consistent due to what I believed was bounce in the encoder..so I've found a sufficient debounce circuit:

Posted Image

Now, I've wired up the encoder with a hardware debounce and I'm getting consistent and correct output from the rotation. Yay! But still a problem...the interrupt fires twice with each detented rotation/click. Because there are two 4 pulses per click. this makes sense to me...The driver works perfectly if I rotate the encoder between half way between the detents..But this is obviously not how I want to use the device. With each detent click, I only need one event fired. In efforts to optimize the code and not get a double event, is there a way to clear the interrupt queue or disable and re-enable the interrupt? Is that the right way to handle it?

 

That all being said, I'm really trying to wrap my head around the driver code and subscribers to the events created by the driver. I understand a basic interrupt and the handler, but I need to use the encoder for more several functions..this is where Subscriber come into play. I've spent hours googling, youtubing, MSDNing(the least useful resource IMO) researching this. I'm no c#.net expert and need an explanation on how this works, in understandable terms. Otherwise seems like black magic when stepping through the code..

 

I'll write out some abbreviated code:

namespace RotaryEncoderDriver
{
    public class RotaryEncoder
    {
        private static InterruptPort PinA = null;
        private static InterruptPort PinB = null;
     
        public event NativeEventHandler RotationEventHandler=null;
     
        public static byte CLOCKWISE = 0;
        public static byte COUNTERCLOCKWISE = 1;
 
        public struct ResultSet
        {
            public bool PinA;
            public bool PinB;
        }
 
        private bool InProcess;
        private ResultSet[] results = new ResultSet[2];
 
        /// <summary>
        /// This is the main constructor
        /// </summary>
        /// <param name="pinA"></param>
        /// <param name="pinB"></param>
        public RotaryEncoder(Cpu.Pin pinA, Cpu.Pin pinB)
        {
            InProcess = false;
            PinA = new InterruptPort(pinA, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
            PinB = new InterruptPort(pinB, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
 
         
            NativeEventHandler RotationEvent = (data1, data2, time) =>
            {
                NewNotaryResult(PinA.Read(), PinB.Read(), ref InProcess, ref results,RotationEventHandler);
            };//this line is black magic to me. How/Why does this work?
 
            PinA.OnInterrupt += RotationEvent; // attached event to interrupt. 
            PinB.OnInterrupt += RotationEvent;
 
 
        }
 
        /// <summary>
        /// Creates the event for subscribers
        /// </summary>
        /// <param name="type">CLOCKWISE or COUNTERCLOCKWISE uint value</param>
        private static void OnRaiseRotationEvent(uint type, NativeEventHandler handler)
        {
            // Event will be null if there are no subscribers
            if (handler != null)
            {
                handler(0, type, DateTime.Now);
            }
        } 
     
        /// <summary>
        /// Used to record a rotary result.  If rotary result is initial, we enter an "in process" state.
        /// If we are already in process, then the results will be processed and an event raised from another method.
        /// </summary>
        /// <param name="pinA">Tested result of Pin A on Encoder</param>
        /// <param name="pinB">Tested result of Pin B on Encoder</param>
        private static void NewNotaryResult(bool pinA, bool pinB, ref bool InProcess, ref ResultSet[] results, NativeEventHandler RotationEventHandler)
        {
            // Set the appropriate result items
            byte bytebool;
         
            if (InProcess)
            {
                bytebool=1;
            }
            else
            {
                bytebool=0;
            }
            results[bytebool].PinA = pinA;
            results[bytebool].PinB = pinB;
 
            // If already in process we need to process the rotation
            if (InProcess)
            {
                ProcessRotation(results, RotationEventHandler);
//This seems like a good place to clear the interrupts
 
 
            }
            // Toggle the InProcess
            InProcess = !InProcess;
        }
 
        /// <summary>
        /// Processes a rotation event and raises an event to public interface with result
        /// </summary>
        /// <param name="results"></param>
        private static void ProcessRotation(ResultSet[] results, NativeEventHandler RotationEventHandler)
        {
            // false result, cancel
            if (results[0].PinA == results[1].PinA && results[0].PinB == results[1].PinB)
                return;
 
            NativeEventHandler handler = RotationEventHandler;
            // if pin A high
            if (results[0].PinA == true)
            {
                //   if pin B went down then counter-clockwise
                if (results[1].PinB == false)
                    OnRaiseRotationEvent(RotaryEncoder.COUNTERCLOCKWISE, handler);
                //   else clockwise
                else
                    OnRaiseRotationEvent(RotaryEncoder.CLOCKWISE, handler);
            }
            // else pin A low
            else
            {
                //   if pin B went up then counter-clockwise
                if (results[1].PinB == true)
                    OnRaiseRotationEvent(RotaryEncoder.COUNTERCLOCKWISE, handler);
                //   else clockwise
                else
                    OnRaiseRotationEvent(RotaryEncoder.CLOCKWISE, handler);
            }
        }
 
 
    }
}

Main/Subscriber

public class Program    {        public static void Main()        {            RotaryEncoder RE = new RotaryEncoder(Pins.GPIO_PIN_D0, Pins.GPIO_PIN_D1);            Subscriber sb = new Subscriber(RE);//instantiate the subscriber                        Thread.Sleep(Timeout.Infinite);        }    }        public class Subscriber        {            public Subscriber(RotaryEncoder rotary)            {                rotary.RotationEventHandler += HandleRotation;            }            public static void HandleRotation(uint data1, uint data2, DateTime time)            {                Debug.Print("EVENT @ "+ time.TimeOfDay);                Debug.Print("Handle Rotation " + data2);            }        }    }

I hope this is clear and thanks in advance for reading through all this.



#2 perpetualKid

perpetualKid

    Member

  • Members
  • PipPip
  • 20 posts

Posted 28 November 2013 - 12:15 AM

your interrupt fires twice since you've configured it so: Port.InterruptMode.InterruptEdgeBoth

 

Set the InterruptMode to InterruptEdgeHigh or -Low so it only fires once on the rising (or falling) edge, not on both

 

See also http://msdn.microsof...y/ee434400.aspx



#3 Paul Newton

Paul Newton

    Advanced Member

  • Members
  • PipPipPip
  • 724 posts
  • LocationBerkshire, UK

Posted 28 November 2013 - 06:10 PM   Best Answer

your interrupt fires twice since you've configured it so: Port.InterruptMode.InterruptEdgeBoth

 

Set the InterruptMode to InterruptEdgeHigh or -Low so it only fires once on the rising (or falling) edge, not on both

 

See also http://msdn.microsof...y/ee434400.aspx

 

I started to reply to the above and then did a U-Turn. (Normally you would want to have the interrupt on both edges to track every change in the encoder pattern.)

 

Building on what perpetualKid has said, if you just had a single edge interrupt on one of the pins, when the interrupt fired, you could use the state of the other pin to tell you the direction. This would make your code a lot simpler.

 

For example, interrupt on pin 1 rising edge, if pin 2 is low - its clockwise, if its high its anti-clockwise.

 

This would mean only one interrupt per complete cycle of the pulses from the encoder. (That might now mean not enough events.)

You would need to carefully select which pin to use for the interrupt, and which edge to use, to ensure the interrupt fires at the right part of the switch's travel. Then determine the state of the other pin for each direction.

 

If you then don't get enough events, try using both edges - but still only on the one pin.

 

Paul



#4 gismo

gismo

    Advanced Member

  • Members
  • PipPipPip
  • 110 posts

Posted 11 December 2013 - 05:52 PM

your interrupt fires twice since you've configured it so: Port.InterruptMode.InterruptEdgeBoth

 

Set the InterruptMode to InterruptEdgeHigh or -Low so it only fires once on the rising (or falling) edge, not on both

 

See also http://msdn.microsof...y/ee434400.aspx

 

Changed both interrupts to InterruptEdgeHigh and I'm getting one event per detent! This is what I wanted! Thanks!

 

 

Now... Onto the next challenge. Subscribing and unsubscribing to events. Like similar minimalist controls, I want to use the push button to activate a "settings" interface after being depressed for a few seconds...say 3 seconds and then upon depress it will activate a settings interface. At this point I want to activate the subscription to the rotary encoder events. 

 

Where should I start? Should I create a "Settings" Class?

 

UPDATE:

I did a little more inspection and I was getting one event, but direction was inconsistent. What works was setting only ONE pin to InterruptEdgeBoth and I seem to be getting one event per click and consistent rotation directions.


Edited by gismo, 14 December 2013 - 03:58 AM.


#5 gismo

gismo

    Advanced Member

  • Members
  • PipPipPip
  • 110 posts

Posted 15 December 2013 - 11:30 PM

Further Update...I think I've drastically simplified the code based on my findings.

 

here are some snippets:

PinA = new InterruptPort(pinA, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);//changePinB = new InterruptPort(pinB, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptNone);
NativeEventHandler RotationEvent =
                delegate
                {
                    NewNotaryResult(PinA.Read(), PinB.Read(), ref InProcess, ref results, RotationEventHandler);
                };
private static void NewNotaryResult(bool pinA, bool pinB, ref bool InProcess, ref ResultSet[] results, NativeEventHandler RotationEventHandler)        {            //MODIFIED            //Debug.Print("Pin A: " + pinA.ToString());            //Debug.Print("Pin B: " + pinB.ToString());            NativeEventHandler handler = RotationEventHandler;            if (pinB)                OnRaiseRotationEvent(RotaryEncoderTEST.COUNTERCLOCKWISE, handler);            else                 OnRaiseRotationEvent(RotaryEncoderTEST.CLOCKWISE, handler);         }

What seems to work is if I only look at PinA interrupt edge high, PinB will either be High or Low depending on the rotation. Simple. I also changed the lambda expression for a delegate declaration for the subscriber because it seems more clear to me and I'm learning. This solution may not be perfect, but am looking for something as simple as possible at the moment.



#6 gismo

gismo

    Advanced Member

  • Members
  • PipPipPip
  • 110 posts

Posted 25 March 2014 - 01:40 PM

I've come a long way and resolved my problems by eliminating the PinA.Read() and PinB.Read along with the entire delegate(which I now understand). There are potentially delays between when the interrupt occurs and when the Read commands are executed which can cause inconsistent results.

 

I uncovered this when using the method discussed with Paul(above). You could get away with only one edge interrupt and then look at the state of the other pin and you know the direction. While this worked pretty well, it's not robust due to the Read commands.The best way is to use the Data parameters passed by the NativeEventHandler which are from the instant the interrupt occurs.

 

Looking back, it's obvious where all my problems came from, but happy how I arrived here.

 

I can post the final code if anyone is interested.

 

Thanks everyone.







Also tagged with one or more of these keywords: interrupt port, Event Handlers, Subscribers, Rotary Encoder, Grayscale, lambda expression, NativeEventHandler

0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.