G30 rlp spi

Here is the display class:

using System.Threading;
using Microsoft.SPOT.Hardware;
namespace _510_Microcontroller
{

public class STU7066U 
{
    private OutputPort lcdRS;
    private OutputPort lcdE;
    private OutputPort lcdD4;
    private OutputPort lcdD5;
    private OutputPort lcdD6;
    private OutputPort lcdD7;
    private const byte DISP_ON = 0x0C;              //Turn visible LCD on
    private const byte CLR_DISP = 1;                //Clear display
    private const byte CUR_HOME = 2;                //Move cursor home and clear screen memory
    private const byte SET_CURSOR = 0x80;           //SET_CURSOR + X : Sets cursor position to X 

    private void WriteNibble(byte b)
    {
        lcdD7.Write((b & 0x8) != 0);
        lcdD6.Write((b & 0x4) != 0);
        lcdD5.Write((b & 0x2) != 0);
        lcdD4.Write((b & 0x1) != 0);

        lcdE.Write(true); lcdE.Write(false);        //Toggle the Enable Pin
        Thread.Sleep(1);
    }

    ///<summary>
    /// Sends an LCD command.
    /// </summary>
    private void SendCommand(byte command)
    {
        lcdRS.Write(false);                         //set LCD to data mode

        WriteNibble((byte)(command >> 4));
        WriteNibble(command);

        Thread.Sleep(2);

        lcdRS.Write(true);                          //set LCD to data mode  
    }

  
    public void initialize()
    {
        Thread.Sleep(100);
        lcdRS = new OutputPort(GHI.Pins.G30.Gpio.PC7,true);
        lcdE = new OutputPort(GHI.Pins.G30.Gpio.PC6, true);
        lcdD4 = new OutputPort(GHI.Pins.G30.Gpio.PB12, false);
        lcdD5 = new OutputPort(GHI.Pins.G30.Gpio.PB15, false);
        lcdD6 = new OutputPort(GHI.Pins.G30.Gpio.PB13, false);
        lcdD7 = new OutputPort(GHI.Pins.G30.Gpio.PB14, false);

        SendCommand(0x30); //Wake up
        Thread.Sleep(100);
        SendCommand(0x02); //Function Set 4bit mode 

        SendCommand(DISP_ON);
        SendCommand(CLR_DISP);

        Thread.Sleep(3);
    }



    /// <summary>
    /// Prints the passed in string to the screen at the current cursor position.
    /// </summary>
    /// <param name="value">The string to print.</param>
    public void Print(string value)
    {
        for (int i = 0; i < value.Length; i++)
            Print(value[i]);
    }

    /// <summary>
    /// Prints a character to the screen at the current cursor position.
    /// </summary>
    /// <param name="value">The character to display.</param>
    public void Print(char value)
    {
        WriteNibble((byte)(value >> 4));
        WriteNibble((byte)value);
    }

    /// <summary>
    /// Clears the screen.
    /// </summary>
    public void Clear()
    {
        SendCommand(CLR_DISP);
        Thread.Sleep(2);
    }

    /// <summary>
    /// Places the cursor at the top left of the screen.
    /// </summary>
    public void CursorHome()
    {
        SendCommand(CUR_HOME);
        Thread.Sleep(2);
    }

    /// <summary>
    /// Moves the cursor to given position.
    /// </summary>
    /// <param name="row">The new row.</param>
    /// <param name="column">The new column.</param>
    public virtual void SetCursorPosition(byte row, byte column)
    {
        byte[] row_offsets = new byte[4] { 0x00, 0x40, 0x14, 0x54 };
        SendCommand((byte)(SET_CURSOR | row_offsets[row] | column));
    }

}
}

Don’t choose some number of times it changes before you write. Then you will only write when a quota of changes is filled. Is that what was happening?

Instead keep track of what the last value written was. Now every time the counter is filled (call it two iterations or ten iterations or whatever (* Iterations of the loop, not of changes being detected *)) you check to see if the current reading is not equal to the last written value. This is the case in which you write… or if you can’t guarantee that the value was actually displayed just write it without the comparison.

The important factor being that it is based on a non-changing frequency. Even though you do not print every change when changes are rapid, the display will reflect the current position sooner than the human operator can detect lag.

In terms of the bug you found I will be sure to let you know if I can be of assistance in that matter.

@jwizard93 Ah thank you that makes much more sense than what I tried.

Edit: Bug fixed after adjusting the counter for every iteration and not every change. I guess the display couldn’t keep up even though specs are in ns in the data sheet.

@jwizard93 Reducing the display writes does not reduce the tracking error though.

Ahh that is unfortunate.

The theory was always that if you could read the device faster then you would get a more accurate read. If this is implemented correctly then you are reading the device faster, more often… but you still have to stop and print…

How many iterations before printing have you tried maximum?

After that maybe you should consider your back up plan. But even before that you should do what you know you should do as an engineer and figure out what are the numbers involved with your goal, and what are the numbers involved with your implementation.

It doesn’t have to be all that rigorous to get a good idea of what’s going on. Pick the man or woman around boasting the fastest spins on the encoder. Ask them to spin it while you watch the pin that blips each time a full go-round occurs. Set your requirement such that that speed is 80% of the maximum speed you will support.

And also move to two threads. One for reading one for printing.

The print function will magically go from:

WriteNibble
WasteTime
WriteNibble
WasteTime

to

WriteNibble
ReadSomeMore
WriteNibble
ReadSomeMore

Thread.Sleep() is an invitation to run other threads (This is why GHI says the argument in Thread.Sleep is the minimum time the thread will sleep)

@jwizard93 I don’t understand your last post about reading in between nibbles…maybe I am just not versed enough in prioritizing threads to grasp what you said.

I have tried 10 iterations and less, I will try more.

Basically the problem stems from the “fiddle factor,” in the above code. It is difficult to set a fiddle factor for all velocities…the max velocity that doesn’t screw up the tracking is based off how fast I can read through SPI to determine the correct fiddle factor and not miss a revolution in the calculation.

BackUp plan: Switch to G80 + upgrade encoder (next model coming out in July handles the multi turn tracking but may be subject to the same limitations because it will require a read to the single turn position register as well as a read to the “turn count” register).

BackBackUp plan: Use a version of NETMF 4.3 with RLP coded into the FW version to do the SPI writes.

BackBackBackedUp Plan: add friction to the shaft so that it can’t be spun more than a revolution in one fling (probably going to do this anyways as there is no reason for it to move so quickly, that is just how the old, non-digital version is set up).

@jwizard93 Printing every 20th iteration of the loop is the best it can do…very small amount of display lag introduced at this point

want to explain your “fiddle factor” comment and algorithm?

And how do you define display lag? Have you had multiple people look at the unit and operate it, because human perception has the chance to be a big variable

@Brett I define display lag as noticeable delay between movement of the knob its respective displayed position…only noticeable when delaying the print 20-25 iterations of the above for loop.

As for the fiddle factor - this is a single turn, 12-bit encoder so the position within one turn is read back from 0 to 4095 with AMT.GetPosition(). If one revolution is exceeded, the position starts back at zero. To overcome this, the above code looks for when the change in position is greater than said “fiddle factor” which is the number set to detect a change in position so large that it could only be from the position going from 4095 to 0 or vice versa. This effectively defines a max velocity at the given sampling rate of my code…if my SPI reads are too slow and the shaft has been flung (intentionally or by accident) over half a rev in one iteration of the loop, the position tracking is lost and the device must be re-calibrated. There is no closed loop feedback, everything relies on the encoder/G30 reads.

ok so noticeable means - some perceived slow reaction from the amount you turn the encoder to when it displays its status update? Or some perceived slow reaction that then allows it to get out of sync between the setting on the dial and the displayed value? Not that this probably matters really - seems you’ve found the limit you’re comfortable with

As for your fiddle factor - seems like you missed my request for you to describe the algorithm. When I look at the code, either the initial or the jwizard proposed change, it was unclear to me what it was attempting to do. When I took the time to look at the code and your explanation it became clearer - its catering for rotation past the zero recording point and trying to capture the direction based on only two data points, the current reading and the previous. You could also cater for that by maintaining the direction over multiple loop passes, and since you’re doing this “flat out” you know that even if it is four or five passes later, you know that your direction won’t change that quickly. Again it’s probably not going to make this work any quicker, but may address your reliability of calculation when you get someone whacking the encoder in the extreme.

What I meant with the threading stuff is this…

Currently you read, read, read, read, read… and then Display. Rinse and repeat.

If you look at your display code there is a Thread.Sleep(1) that will execute twice.

If this is your only thread… then all of this time will be spent doing nothing or doing behind the scenes NetMF stuff only.

If instead you had one thread for printing and one for displaying, then while you are sleeping in the DisplayThread, the Read thread can be reading. This will happen automatically. You don’t have to configure this to happen other than launching a thread for each task at the beginning of the program.

If your threads did not have Thread.Sleep() at all then it would go 20ms of reading -> 20ms of writing -> 20ms of reading -> 20ms of writing over and over again. (With fluctuations as Netmf will insert background tasks)

When you change the priority of write to less than that of read then you might get something like

20ms of write 10 times in a row -> 20ms of display

Okay, but now when you insert Thread.Sleep(X), what this does is tell the scheduler to pause this thread and look for another one to execute.

When eventually the scheduler thinks its a good time to go back to the original thread it checks to see if the sleep time has elapsed. If it hasn’t elapsed it goes back to try and find something to execute. This is why it is a minimum sleep time. It does not tell the scheduler when to come back to this thread, it tells it if it has been long enough when it comes back to check on it.

You do not know when it will be scheduled really… but you can tune it with Thread.Sleep() and Thread Priorities to get what you need.

//Thread 1
for(;;)
{
    MyTask();
}
//Thread 2
for(;;)
{
    MyTask();
    Thread.Sleep(0); // will stop execution and give control back to scheduler
}

The loop of Thread 1 will run over and over again for 20ms. The loop of Thread 2 will only execute once before Thread 1 runs for another 20ms straight.

I’m talking about this because I believe it is very important that you use multi-threading to eliminate downtime in your program. The advantage to this over actually writing out the exact order of what you want to happen is that the code will be cleaner and the various modules will not depend on each other.

Alright I will try to implement these suggestions Monday…

@Brett I am not exactly sure how to go about using multiple iterations to track the position, that seems tricky to tune for all speeds.

Threading I can definitely handle.

It could be as simple as instead of tracking just one previous iteration position (temP variable), track multiple values that you can use look back further and confirm direction

@jwizard93 tremendous improvement in trackability with the encoder read thread set to AboveNormal priority and the display print set to normal priority. Even with a sturdy power fling, tracking is not lost (:

There is still one small bug I can’t figure out. I have an 8-character display that I am writing messages like “20.00 dB” to. If the length of the number is less than 5 characters (such as “1.50 dB”) a space is added to the number to maintain the display positioning ("_1.50 dB"). Very rarely something will get out of whack where it the first digit is printed last like “0.00 dB2” which is “20.00 dB” being printed wrong. I thought this was stemming from the space not being added but if I put a break, it is still being added. The only way to stop messages from printing wrong once it starts is to call display.Clear(). I avoid doing this every display write as it causes a flicker instead of just updating the characters that are different. Could this still be a timing issue / writing to the display to fast? I am not sure how to programmatically detect when this happens (visual inspection is the only way) or I would just call display.Clear() during these instances.

That’s great. I’m glad there is improvement.

Can you post a link to the display’s datasheet?

this to me is more likely a positioning error in your code. display.clear just forces the cursor to a known position, where if you don’t clear before re-writing you need to move the cursor to the right spot, and I’d say sometimes that isn’t getting run correctly. Want to post your code too?

@Brett I think the cursor starts back at home after printing 8 characters…works for thousands of writes but occasionally that bug happens. I tried to account for every possible length that could be printed…it didn’t make a difference.

 public static void Main()
     {
            Thread thread1 = new Thread(encoderRead);
            Thread thread2 = new Thread(interpolation);

            thread1.Priority = ThreadPriority.AboveNormal;
            thread2.Priority = ThreadPriority.Normal;

             display.Clear();
             AMT.SetAMT203ZeroPosition();
             position = AMT.ReadAMT203Position();

             if (position == 0)
             {
                 thread1.Start();
                 thread2.Start();
             }
             else
             {
                 display.Clear();
                 display.Print("Re-Zero");
             }
     }

public static void interpolation()
    {
        for (; ; )
        { 
            if (counter == 1 | counter > 20)
            {

                if (counter >= 20)
                {
                    counter = 0;
                }

                int checkIndex = Array.IndexOf(TabsFinal, totalPos);

                if (checkIndex > -1)
                {
                    if (checkIndex == 0)
                    {
                        display.Print("Max Attn");
                    }
                    else
                    {
                        display.Print(Taba[checkIndex].ToString("F") + " dB");
                    }
                }
                else
                {
                    double pos2 = FindClosest.DoubleValue(TabsFinal, totalPos);
                    int index2 = Array.IndexOf(TabsFinal, pos2);

                    double pos1 = TabsFinal[index2 + 1];
                    double atn1 = Taba[index2 + 1];
                    double atn2 = Taba[index2];


                    targetAtn = (((atn2 - atn1) / (pos2 - pos1)) * (totalPos - pos1)) + atn1;

                    if (targetAtn > 71)
                    {
                        display.Print("Max Attn");

                    }
                    else
                    {
                        if (targetAtn.ToString("F").Length == 4)
                        {
                            display.Print(" " + targetAtn.ToString("F") + " dB");
                        }
                        else if (targetAtn.ToString("F").Length == 3)
                        {
                            display.Print("  " + targetAtn.ToString("F") + " dB");
                        }
                        else if (targetAtn.ToString("F").Length == 2)
                        {
                            display.Print("   " + targetAtn.ToString("F") + " dB");
                        }
                        else if (targetAtn.ToString("F").Length == 1)
                        {
                            display.Print("    " + targetAtn.ToString("F") + " dB");
                        }
                        else
                        {
                            display.Print(targetAtn.ToString("F") + " dB");
                        }
                    }
                }
            }
        }                     
    }
    public static void encoderRead()
    {

        for (; ; )
        {
            counter = counter + 1;
            try
            {
                temP = position;
                position = AMT.ReadAMT203Position();

                if (temP != position)
                {
                    fiddle = (double)(position) - (double)(temP);

                    if (fiddle > 2000)
                    {
                        turnPos = turnPos - 4095;
                    }
                    else if (fiddle < -2000)
                    {
                        turnPos = turnPos + 4095;
                    }
                    else { Thread.Sleep(0); }
                    totalPos = turnPos + (double)position;
                }
                else { Thread.Sleep(0); }
             
            }
            catch (Exception Ex)
            {
                display.Clear();
                display.Print("Re-zero");
            }
        }
    }

So a couple of comments. Where do you ever set the cursor position in that code? Does your display.print() do that by default? If so, that is where you need to look.

Second comment. We’ve been talking endlessly about speed. And then you show us a section of code that does repeated ToString() operations, which are about the worst things you could want to duplicate - although the subsequent string prints are going to hurt you anyway, there’s still little need to duplicate those. Do the ToString() once, and use it several times in the code.