Low Frequency Encoder Interface

Hello NETMF BrainTrust,

I am currently developing a product around the USBizi-144. One block of this product includes a human-actuated linear encoder with a quadrature output and index. The maximum frequency is less than 1kHz when considering both the end-to-end (of the physical strip) average frequency as well as the pulse-to-pulse, “instantaneous” frequency.

With my current test setup I can see nice pulses as expected at the input pin of the USBizi. However, I miss as many as, 2/3rds of the pulses in some cases. Now I am looking for a potential software solution to rectify this problem.

I have read, in some depth, about using the available timers through the Register class. This seems feasible, but I have read in other forum topics that access to the VIC registers is not available using the Register class. Can anyone confirm that this is the case? Is it all of them? I really only need access to one Vectored Interrupt Address to interrupt on the transition of one of the quadrature pulses and check the state of the 90-degree out-of-phase signal as well as the index signal and set the DAC register value.

Would this be a viable solution? Or maybe I am complicating the situation too much? Is there a way to give the interrupt more priority from the managed software? I have also read some mention of some encoder support from GHI, that was about a year ago. Has there been any development in that regard?

If these options are not viable, I will go for the hardware approach using an 8051 to keep track of the encoder position while polling the 8051 over a COM or SPI connection. But, I’m really hoping I can solve this through software instead of having to make additional hardware changes and use additional development tools.

I have also read some about using the RLP approach but after, albeit, a small amount of research on that technique, I feel as though I maybe more comfortable going with the 8051 solution.

For reference, the code below is generally what I have been using for this block. Please forgive extra commented code as I’ve been adding/commenting as I’ve been trying to debug. Many thanks in advance for everyone’s time and consideration.



using System;
using System.IO;
using System.Text;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.IO;
using Microsoft.SPOT.Hardware;

using GHIElectronics.NETMF.IO;
using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.USBClient;


namespace USBizi_EncoderInterfaceAndMotorControl
{
    public class Program
    {
        static InterruptPort encChI;
        static InputPort encChB;
        static OutputPort stop, dir;
        static bool fwd, rev, init;
        static AnalogOut VoltLevel;
        static int iVoltLevel;
        static int aCount, bCount, iCount;

        public static void Main()
        {
            Debug.EnableGCMessages(false);
            
            InterruptPort encChA = new InterruptPort(USBizi.Pin.IO5, true, 
                Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeHigh);
            encChA.OnInterrupt += new NativeEventHandler(encChA_OnInterrupt);

            /*InterruptPort*/ encChI = new InterruptPort(USBizi.Pin.IO1, true,
                Port.ResistorMode.PullDown, Port.InterruptMode.InterruptEdgeHigh);
            encChI.OnInterrupt += new NativeEventHandler(encChI_OnInterrupt);
            
            encChB = new InputPort(USBizi.Pin.IO11, true, Port.ResistorMode.PullDown);
            //encChI = new InputPort(USBizi.Pin.IO1, true, Port.ResistorMode.PullDown);

            fwd = false;
            rev = false;
            init = false;

            stop = new OutputPort(USBizi.Pin.IO13, false);
            dir = new OutputPort(USBizi.Pin.IO17, false);

            VoltLevel = new AnalogOut(AnalogOut.Pin.Aout0);
            VoltLevel.SetLinearScale(0, 3300);
            iVoltLevel = 1450;
            VoltLevel.Set(iVoltLevel);

            aCount = 0;
            bCount = 0;
            iCount = 0;
                        
            Thread.Sleep(Timeout.Infinite);

        } // public static void Main()



        static void encChA_OnInterrupt(uint port, uint state, DateTime time)
        {
            bool stChB = encChB.Read();
            Debug.Print(stChB.ToString() + "-" + aCount);
            aCount++;

            if (encChI.Read() == true)
            {
                Debug.Print("----->Zero");
            }

            //Debug.Print(aCount.ToString());
                        
            //if (encChI.Read() == true)
            //{
            //    aCount = 0;
            //    Debug.Print("Zero");
            //    init = true;
            //    fwd = false;
            //    rev = false;
            //    VoltLevel.Set(1500);
            //} // if (encChI.Read() == true)
            /*else*/ if (stChB /*encChB.Read()*/ == true)
            {
                //Debug.Print("Reverse");
                if (!fwd)
                {
                    rev = true;
                    dir.Write(false);
                    //increase voltage
                    iVoltLevel = iVoltLevel + 6;
                    if (iVoltLevel > 2500) { iVoltLevel = 2500; }
                    VoltLevel.Set(iVoltLevel);
                    //if count gets to a certain level, delay then stop, or stop if endpoint 
                    //switches are triggered, to avoid motor burnout
                }
                else
                {
                    //decrease voltage
                    iVoltLevel = iVoltLevel - 6;
                    if (iVoltLevel < 1450) { iVoltLevel = 1450; }
                    VoltLevel.Set(iVoltLevel);
                }
            } // if (encChB.Read() == true)
            else if (stChB /*encChB.Read()*/ == false)
            {
                //Debug.Print("Forward");
                if (!rev)
                {
                    fwd = true;
                    dir.Write(true);
                    //increase voltage
                    iVoltLevel = iVoltLevel + 6;
                    if (iVoltLevel > 2500) { iVoltLevel = 2500; }
                    VoltLevel.Set(iVoltLevel);
                    //if count gets to a certain level, delay then stop, or stop if endpoint 
                    //switches are triggered, to avoid motor burnout
                }
                else
                {
                    //decrease voltage
                    iVoltLevel = iVoltLevel - 6;
                    if (iVoltLevel < 1450) { iVoltLevel = 1450; }
                    VoltLevel.Set(iVoltLevel);
                }
            } // if (encChB.Read() == false) 

        } // static void encChA_OnInterrupt(uint port, uint state, DateTime time)



        static void encChI_OnInterrupt(uint port, uint state, DateTime time)
        {
            Debug.Print("Zero-" + iCount.ToString() + "-FWD:" + fwd.ToString() + "-REV:" + rev.ToString());
            init = true;
            fwd = false;
            rev = false;
            VoltLevel.Set(1450);
            iCount++;
        } // static void encChI_OnInterrupt(uint port, uint state, DateTime time)



    } // public class Program
} // namespace USBizi_MotorControlTest

You have glitch filter enabled on interrupt port. Is this what you wanted?

Oh that would be amazing if it something as simple as disabling the glitch filter… I will give that a try.

…I’m not sure, if it is what I wanted or not, just seemed appropriate for a human-actuated interface. I don’t really see why I would have glitches from the encoder interface though. Out of curiousity, can you tell me the cut-off frequency used in the glitch filter?

Are you reading one encoder with that code there? I don’t know much about different encoders then those with only 2 channels (that would be A and B). Why is that interrupt on Channel I for? Can you point me to a datasheet?

BTW, if you really need the RLP solution, I can help you with that.

By enabling the glitch filter, you’re adding a delay to your interrupt, which might be just to big before you read out the other channel.

The encoder I have is created by me using three photointerrupters, so there is no datasheet. I do have it providing a quadrature output, the same as an A and B channel with one being 90 degrees out of phase to indicate direction. I also have an index signal to indicate a “home” or nominal position.

…essentially its one encoder with and A, B and INDEX signal.

Now with the glitch filter disabled for the interrupt port, it seems the result is, I’m no longer missing pulses (of the A channel interrupt) but the state of the channel B is incorrect at times. This is with the glitch filter both enabled and disabled.

I think a better understanding of how interrupts work in MF is necessary. :smiley:

When an interrupt occurs, the framework does its processing of the interrupt and then queues it for user processing. On a separate thread, the queued interrupt is then passed to the user.

There can be a few milliseconds from when the hardware interrupt occurred until the user actually sees it. The timestamp, which is passed to the user, is the actual time that the hardware interrupt occurred.

In your interrupt routine, you are reading the value of B when you get the A interrupt. That means you are using an older value of A with the current value of B. The current value of B may not be the same as it was when the A hardware interrupt occurred.

You should be processing interrupts on both A and B, and keeping track of their states.

1 Like

Excellent idea Mike… once getting all of the interrupt pulses consistently, I started rewriting the program to do just that as the latency to get the level of the other channel is inconsistent.

oh, and when posting code, my other tip would be to clean it up beforehand. Multitudes of IF statements etc hidden in comments make it challenging to follow and decode your logic. Comments are meant to be comments :slight_smile:

Duly noted Brett, thanks for the tip!

@ Gus: Many thanks, disabling the glitch filter was the clincher to accomplish what I needed.

EDIT: For future projects, do you know what the glitch filter cut-off frequency is?

There is a property to set it.

Do you want to share this driver on code share? :wink:

I could probably clean up the code and post it.

…I also have one more quick question:

I am using C# Express and when trying to deploy (without debugging) the firmware to the device, it doesn’t seem to update. This is using the Build>>Deploy Solution or Build>>Deploy menu items.

C# Express says “Build Started”, “Build Succeeded”, “Preparing to Deploy Assemblies to Device”, “Deploy Succeeded” or very similar. Then when testing, the device is still using the previous firmware, loaded by choosing Debug>>Start Debugging.

Any thoughts?

I use this daily and it works just fine. I am not sure why you see it different.