Precision wait on non RTOS | LabJack
 

Precision wait on non RTOS

26 posts / 0 new
Last post
DSpen
DSpen's picture
Precision wait on non RTOS

Hello,

For part of my program, I need to do some waits/sleeps with high precision (~1ms). I'm unable to create a timerwait/sleep in c# on windows  due to time resolution being too low. Is there a way to use the u3-HV internal cock to make a precise wait (and at what resolution)? 

quick pseudo example:

if signal:

----wait(1ms)

----set DAC0 to 4v

----wait(2ms)

----set DAC0 to 0v

LabJack Support
LabJack Support's picture
The U3 has two separate wait

The U3 has two separate wait functions. The first "WaitShort" has a resolution of 128 us. The other is 16 ms, so I do not believe it will be much use to you. However, the U3 does not have the ability to respond to a trigger (if signal). You could watch for the signal in software and initial a pulse in response. That would have a 1-2 ms response time, but a pulse with 128 us resolution.

If you need finer resolution we need to use the timer and counter system. The U3 can use one timer to output a PWM and another timer to stop the first after a certain number of pulses. With this setup pulses can have resolution as low as 20.8 ns, the actual resolution is tied to the pulse frequency.

Relevent user's-guide pages:

DSpen
DSpen's picture
The WaitShort should be

The WaitShort should be giving me a resolution that's as acurate as I require. The actual wait period is going to be a fraction of the time between rising edges. I'm reading up on Period Measurement in 2.9.1.3 currently to use the u3 timer instead of windows for rising edge time.

However, you're saying the U3 doesn't have the ability to respond to a trigger...
I'd like to trigger this method whenever a rising edge occurs on AIN6, and interupt if another rising edge occurs during this method. Currently I'm using this approach:

 LJUD.eAIN(d.ljhandle, 6, 31, ref dblValue, 0, 0, 0, 0);
ain6 = dblValue;
if (ain6 > 2 && edge)  {//DAC events}

Is there a more efficient way to do this that you recommend? I'm also using lock(object) currently to stop this function when a rising edge occurs. Is there a better way to do this as well?

LabJack Support
LabJack Support's picture
Detecting and handling the

Detecting and handling the trigger will need to be done in software. In hardware the U3 does not have functionality like this, and only with our LabJack T7 can this be done on the device through onboard LUA scripting.

So in your software you will need to poll your U3's analog input channel, detect its trigger state (> 2.0 volts) and then set the U3's DAC or timer accordingly. With you code it already look like your are doing that effiicently. Locking will prevent that chunk of code in your function from running simultaneously (runs atomically). If you are only calling your trigger code in a loop in a single thread or using a slow C# timer that will not overlap then a lock is unecessary, otherwise it is good to have a lock to keep that code atomic.

DSpen
DSpen's picture
It's been a while since I've

It's been a while since I've last touched this project. I'm having trouble finding c# versions of some functions I used with python.

d.getFeeback(U3.WaitShort(Time = (elapsedTime /128) );

I'm getting an error showing that 'U3' does not contain a defenition for getFeedback, or waitshort. I've checked my assemblies/references and it looks like it should be working. I've found results for these within seconds for python, but not so much for .net.

I'm able to connect to the device with:

d= new U3(LJUD.CONNETION.USB, "0", true);

going over my object browser, I got no search results for feedback or waitshort under LabJack.LJM or LJUDDotNet.

LabJack Support
LabJack Support's picture
The .NET interface is built

The .NET interface is built around the UD driver which differs from LabJackPython's interface and its U3 class. To get familiar with C#, take a look at the C# examples and UD documentation:

https://labjack.com/support/software/examples/ud/dotnet
https://labjack.com/support/datasheets/u3/high-level-driver

In the UD driver you will want to use the LJ_ioPUT_WAIT IOType, which in C#/.NET is LJUD.IO.PUT_WAIT. This IOType is documented here:

https://labjack.com/support/datasheets/u3/high-level-driver/example-pseu...

A quick example:

LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_DAC, 0, 1.0, 0, 0);  //Set DAC0 to 1.0 V
LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_WAIT, 0, 2048, 0, 0);  //Delay for 2048 µs (delay resolution is 128 µs)
LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_DAC, 0, 2.0, 0, 0);  //Set DAC0 to 2.0 V
LJUD.GoOne(d.ljhandle); //Execute the requests.

For the U3 you will be using the LJUDDotNet .NET assembly. LabJack.LJM is for the LJM library which currently only supports the T7 and Digit.

DSpen
DSpen's picture
I have a few questions on how

I have a few questions on how it operates

1) If I tell DAC0 to wait, and then tell DAC1 to wait, will DAC1's wait command wait for DAC0's wait? (waiting in series, or waiting in parallel)

2) How can I check to see when/if the last request completes? Wanting this value for some unit tests.

3) the wait is on 128us increments, so to determine how many seconds to wait, I'd provide it with (seconds * 1,000,000 / 128), correct? 

LabJack Support
LabJack Support's picture
1. The DAC and waits are in a

1. The DAC and waits are in a series. For DAC and wait requests, they run in the order of your AddRequests.

2. When the GoOne call has returned, the command/response has completed. So the requests/command were sent to the U3 and the response from the U3 was received in the UD driver returning any read data (if applicable) and errors.

3. You pass the microseconds you want for the delay. Resolution is in 128 µs, and the driver will round down to the nearest 128 multiple with integer division. For example, 2000 µs is actually 1920 µs since 2000/128 = 15 with integer division, so the actual value is 1920. 256 µs will remain 256 µs since it is a multiple of 128. So math-wise: floor(seconds * 1,000,000 / 128) * 128 

DSpen
DSpen's picture
Thank you, my timer is

Thank you, my timer is working perfectly now.

Now, I'm thinking that my edge detection isn't sufficient if I'm not using Labjacks built in timer/counter that handles this. 

https://labjack.com/support/datasheets/u3/hardware-description/timers-co... and https://labjack.com/support/datasheets/u3/hardware-description/timers-co...

I kinda understand which timers to use, but not sure how. I don't see any examples for this in C# but I assume it would be something like addRequest(handle, TimerType, channel, return value,0,0).

It's kinda sloppy, but here's what I'm doing for edge detection currently:

while (true)

            {

                Console.WriteLine("loop start");

                reset = false;

                LJUD.eAIN(d.ljhandle, 7, 31, ref dblValue, 0, 0, 0, 0);

                if (dblValue < 2) //don't start during a hill, we want to wait for the first rising edge

                {

                    while (!reset)

                    {

                        LJUD.eAIN(d.ljhandle, 7, 31, ref dblValue, 0, 0, 0, 0);

                        if (dblValue >= 2) // we've found our first rising edge, enter main loop

                        {

                            valley = false;

                            while (!reset)

                            {

                                pt.Start(); //start timing the distance between rising edges

                                while (!reset)

                                {

                                    LJUD.eAIN(d.ljhandle, 7, 31, ref dblValue, 0, 0, 0, 0);

                                    if (dblValue < 2) //no longer on hill, set flag for valley

                                        valley = true;                     

                                    if (dblValue >= 2 && valley) //hit a rising edge

                                    {

                                        pt.Stop();

                                        elapsedTime = (int)pt.Duration;  //pre converted to micoseconds, used at 128us resolution

                                        if (!startDAC) //Make sure thread isn't in the middle of setting DACs

                                        {

                                            startDAC = true;

                                            valley = false;

                                            pt.Start(); //start timing next edge

                                        }

                                        else //pulse changed pace significantly, we need to recalculate, go back to first while loop

                                        {

                                            reset = true;

                                        }

                                    }

                                }

                            }

                        }

                    }

                }

            }

        }

    }

}

LabJack Support
LabJack Support's picture
Depending on the frequency of

Depending on the frequency of your signal, you can detect an edge with analog input readings. Keep in mind that at best when using command/response mode (eAIN) an analog input reading takes about 1 or 4 ms, and U3 timers are better suited for detecting edges.

Take a look at the .NET timer/counter example as it demonstrates how to configure the timers and counters. When configuring the mode, use the mode you want instead and the value (Timer Mode Descriptions describes the value setting for each mode). Then afterwards read the timer's reading as needed. Here's some general UD driver pseudocode and documentation as well:

https://labjack.com/support/datasheets/u3/high-level-driver/example-pseu...

DSpen
DSpen's picture
I followed the pseudo code

I followed the pseudo code and came up with the below. Once I get the below working correctly, I'd like to trigger my DAC events as soon as I have a new rising edge with an updated elapsed time. Would the most efficient way to do this be, looping eGet() and compare previous value with current value until it changes, and then provide the new value to my DAC thread?

ePut(lngHandle, LJ_ioPIN_CONFIGURATION_RESET,0,0,0,); //factory default pin config

//configure timers and counters

//set pin offset to 4, which causes the timers to start on FIO4

AddRequest(lngHandle, LJ_ioPUT_CONFIG, LJ_chTIMER_COUNTER_PIN_OFFSET, 4, 0,0);

//Enable a timer, using FIO4

AddRequest(lngHandle, LJ_ioPUT_CONFIG, LJ_chNUMBER_TIMERS_ENABLED, 1,0,0);

//configure timer0 as risingedge32

AddRequest(lngHandle, LJ_ioPUT_TIMER_MODE, 0, LJ_tmRISNGEDGES32,0,0);

//start timer

GoOne(lngHandle);

//Read most recent rising edge time from Timer0

eGet(lngHandle,LJ_ioGET_TIMER, 1, &ElapsedTime, 0)

LabJack Support
LabJack Support's picture
What you mentioned sounds

What you mentioned sounds good. Basically poll the timer (your eGet call in a loop) and check for the condition which will set the DAC to a new value.

DSpen
DSpen's picture
Thank you for the help,

Thank you for the help,

I'm having one last issue setting up the rising edge detection. The AddRequest for PUT_TIMER_MODE is giving me 2 errors, the 3rd parameter I'm passing should be for the channel (timer number), which I have as 1, which is showing as an invalid 32 bit signed integer. and my 4th paremeter, RISING_EDGE_32 showing as unable to convert to double. Am I missing a step in setting the timer mode?

            //set pin offset to 4, which causes the timers to start on FIO4

            LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_CONFIG, LJUD.CHANNEL.TIMER_COUNTER_PIN_OFFSET, 4, 0,0);

            //Enable a timer, using FIO4

            LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_CONFIG, LJUD.CHANNEL.NUMBER_TIMERS_ENABLED, 1,0,0);

            //configure timer0 as risingedgedetect32

            LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_TIMER_MODE, 1, LJUD.TIMERMODE.RISINGEDGES32, 0,0);

            //start timer

            LJUD.GoOne(d.ljhandle);

LabJack Support
LabJack Support's picture
Try a call like this instead:

Try a call like this instead:

LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_TIMER_MODE, 0, (double)LJUD.TIMERMODE.RISINGEDGES32, 0,0);

The third parameter is overloaded to be an integer or LJUD.CHANNEL. The fourth parameter is a double, so you just need to typecast the constant (LJUD.TIMERMODE.RISINGEDGES32) as a double. Also, timer numbers/channels start at 0, so if you enable 1 timer, timer 0 is available. If you enable 2 timers, timer 0 and timer 1 are available.

DSpen
DSpen's picture
That works. I was thinking i

That works. I was thinking i wouldn't need to typecast and I was doing something wrong.

So now that it's running, I'm getting a Timer Sharing Error at: LJUD.eGet(d.ljhandle, LJUD.IO.GET_TIMER, 1, ref elapsedTime, 0);

Here's my full setup:

using System;

using System.Threading;

using LabJack.LabJackUD;

namespace MarcProject

{

    class MainClass

    {

        public static U3 d;

        public static bool reset;

        public static double elapsedTime;

        public static volatile bool startDAC;

        public static double lastTime;

        static void Main(string[] args)

        {

            d = new U3(LJUD.CONNECTION.USB, "0", true);

            HiPerfTimer pt = new HiPerfTimer();

            reset = false;

            elapsedTime = 0;

            startDAC = false;

            lastTime = 0;

            LJUD.ePut(d.ljhandle, LJUD.IO.PIN_CONFIGURATION_RESET,0,0,0); //factory default pin config

            //configure timers and counters

            //set pin offset to 4, which causes the timers to start on FIO4

            LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_CONFIG, LJUD.CHANNEL.TIMER_COUNTER_PIN_OFFSET, 4, 0,0);

            //Enable a timer, using FIO4

            LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_CONFIG, LJUD.CHANNEL.NUMBER_TIMERS_ENABLED, 1,0,0);

            //configure timer0 as risingedge32

            LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_TIMER_MODE, 0, (double)LJUD.TIMERMODE.RISINGEDGES32, 0,0);

            //start timer

            LJUD.GoOne(d.ljhandle);

            Thread DacVoltageThread = new Thread(() =>

            {

                while (true)

                {

                    //wait until flagged to start

                    if (startDAC)

                    {

                        //DAC0

                        Console.WriteLine("elapsed: " + elapsedTime);

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_WAIT, 0, (elapsedTime / 3), 0, 0);

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_DAC, 0, 4, 0, 0);

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_WAIT, 0, (elapsedTime / 10), 0, 0);

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_DAC, 0, 0, 0, 0);

                        //DAC1

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_WAIT, 1, ((7*elapsedTime)/30), 0, 0);

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_DAC, 1, 4, 0, 0);

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_WAIT, 1, (elapsedTime / 10), 0, 0);

                        LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_DAC, 1, 0, 0, 0);

                        //execute requests

                        LJUD.GoOne(d.ljhandle);

                        Console.WriteLine("DAC cycle complete");

                        startDAC = false;

                    }                   

                }

            });

            DacVoltageThread.Start();

            while (!reset)

            {

                try

                {

                    LJUD.eGet(d.ljhandle, LJUD.IO.GET_TIMER, 1, ref elapsedTime, 0);

                    if (elapsedTime != lastTime)

                    {

                        if (!startDAC)

                        {

                            lastTime = elapsedTime;

                            startDAC = true;

                        }

                        else

                        {

                            reset = true;

                        }

                    }

                }

                catch (LabJackUDException e)

                {

                    Console.WriteLine(e);

                }

            }         

        }

    }

}

LabJack Support
LabJack Support's picture
You are trying to use timer 1

You are trying to use timer 1 which isn't enabled. Your code enables one timer, so only timer 0 (timer channel 0) is available and what you want to use, so something like this:

LJUD.eGet(d.ljhandle, LJUD.IO.GET_TIMER, 0, ref elapsedTime, 0);

DSpen
DSpen's picture
Ah, kept thinking of it as 1

Ah, kept thinking of it as 1 timer = timer 1. Seems to be functioning fairly well now, but I think my timer is detecting the wrong input.

So if I want to detect rising edges on FIO6, would I put the timer on to FIO5?

DSpen
DSpen's picture
I should have tried testing

I should have tried testing the timer on FIO6 before I posted, I figured it out. Working great now!

LabJack Support
LabJack Support's picture
If you want to detect with

If you want to detect with Timer 0 on FIO6, set the LJUD.CHANNEL.TIMER_COUNTER_PIN_OFFSET setting to 6. If you want it on FIO5, set it to 5. Currently you have it configured to 4, so your timer 0 is on FIO4. What FIO/EIO lines your timers and counters will be on with the TimerCounterPinOffset setting is discussed here:

https://labjack.com/support/datasheets/u3/hardware-description/timers-co... 

DSpen
DSpen's picture
https://www.youtube.com/watch
DSpen
DSpen's picture
I have a few more things to

I have a few more things to check:

The labjack seems to be functioning well in my thread, but I just want to verify something. My main method that is checking the rising edges does not pause when the thread is doing its waits between DACs, correct?

I'm thinking I'll need to put a counter for the amount of rising edges that have occurred on AIN6 (incase of equal pulse width). Looking at the pseudocode, it looks like the counter should be assigned to Timer Line + 1 to count the edges. So AIN7 in my case?

Also, the timer returns a value based on amount of clock cycles that have passed, which is at default, 48Mhz (48 million cycles per second?)  So i'd do something like micoSecondsToWait = elapsedTime/48 ?

Thank you again, you've been great help!

LabJack Support
LabJack Support's picture
1. Your DAC/wait GoOne call

1. Your DAC/wait GoOne call and timer read eGet call can block each other in different threads, so your rising edge read calls will pause/block for the dac/wait calls if they run concurrently and vice versa. Only one command/response on the U3 can run at a time, so the driver will run one GoOne/eGet call which performs its commands/responses to/from the U3 and block other concurrent calls in the other threads until the previous GoOne/eGet call finishes. There is a UD driver multi-threaded operation section in the U3 datasheet:

https://labjack.com/support/datasheets/u3/high-level-driver/overview/mul...

2. If the pin offset is 6 and you enable one timer and one counter, then yes your counter will be on FIO7/AIN7. The order is timers, then counters.

3. The rising edge (period measurement) timer is the number of clock cycles between the most recent pair of rising edges. The clock frequency is the configured TimerClockBase which is by default 48 MHz. So:

time between rising edges in microseconds = TimerValue/48

Which is basically the same as your microSecondsToWait.

DSpen
DSpen's picture
I'm a bit confused with the

I'm a bit confused with the timer/counter.  I've set up 1 timer and 1 counter. The timer is set to AIN6, which means the counter will be on AIN7. However my counter is only trying to count the edges on AIN7. Can I change this counter to work on the same pin as the timer (AIN6)? Or would I need to split my signal so it is connected both AIN6 and AIN7?

Here's my current counter/timer setup:

LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_CONFIG, LJUD.CHANNEL.TIMER_COUNTER_PIN_OFFSET, 6 ,0 ,0);

LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_CONFIG, LJUD.CHANNEL.NUMBER_TIMERS_ENABLED, 1,0,0);

LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_COUNTER_ENABLE, 1,1,0,0);

LJUD.AddRequest(d.ljhandle, LJUD.IO.PUT_TIMER_MODE, 0 ,(double)LJUD.TIMERMODE.RISINGEDGE32,0,0);

LabJack Support
LabJack Support's picture
Timer and counter pins cannot

Timer and counter pins cannot be shared. You will need to split your signal to AIN6/FIO6 and AIN7/FIO7, or connect your signal to either AIN6 or AIN7 and jumper AIN6 and AIN7, to get your timer and counter readings for your one signal.

DSpen
DSpen's picture
I've set up a jumper between

I've set up a jumper between ain6/7 so i'm able to get the counter and timer working off the same input!

Is there a way to change the counter mode to detect rising edges instead of falling edges?

If it only works with falling edges, what would be the most efficient way for me to count rising edges? Is looping to check for an increase in voltage using command/response my best option?

LabJack Support
LabJack Support's picture
The hardware counter counts

The hardware counter counts falling edges only:

https://labjack.com/support/datasheets/u3/hardware-description/timers-co...

If you configure a timer in firmware counter mode, that counts rising edges:

https://labjack.com/support/datasheets/u3/hardware-description/timers-co...

Make sure you keep the edge rate on all timers to less than 30k per second:

https://labjack.com/support/datasheets/u3/hardware-description/timers-co...