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 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:
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.