Random high time-overhead/delay in block mode

Post your .Net discussions here
Post Reply
mzeeshan
Newbie
Posts: 0
Joined: Wed Jun 28, 2017 7:26 am

Random high time-overhead/delay in block mode

Post by mzeeshan »

Hello,

I'm developing an application based on PicoScope 5242A. My application requires to get a block of data at fixed rate (10 Hz). So I setup a high precision timer and on its each tick I run the RunBlock command to request data. I fix the following values for PicoScope configuration:

Input signal frequency = 13.56 MHz
Resolution = 12-bits
Channels enabled = 2
Time-base = 2
Sample count = 185 (in order to gather approximately 10 sine waves in a single block)

The timer precisely ticks every 100 ms. The problem is that when I measure the time PicoScope takes after the RunBlock command until the values have been received in the PC buffers (using GetValues) is not only very random but also high. It usually takes as low as 3ms to as high as 230ms occasionally :shock: (checked through the code at the bottom).

Now as I read in the manual the expected overhead is tens of milliseconds but this is huge and bigger problem is its not constant. After getting a block at 10Hz, I need to perform data processing that must be finished well before the next block comes in. How can I get the blocks precisely at 10Hz always? Am I doing something wrong here? Please look into this problem.
Thanks. Here is the code I used to record the delay.

Code: Select all

while (true)
                {
                    _ready = false;
                    _callbackDelegate = BlockCallback;
                    stopwatchDataAcqDuration.Restart();
                    do
                    {
                        retry = false;
                        status = Imports.RunBlock(_handle, 0, (int)sampleCount, _timebase, out timeIndisposed, 0, _callbackDelegate, IntPtr.Zero);
                        if (status == (short)StatusCodes.PICO_POWER_SUPPLY_CONNECTED || status == (short)StatusCodes.PICO_POWER_SUPPLY_NOT_CONNECTED || status == (short)StatusCodes.PICO_POWER_SUPPLY_UNDERVOLTAGE)
                        {
                            status = Imports.ChangePowerSource(_handle, status);
                            retry = true;
                        }
                        else
                        {
                            //textMessage.AppendText("Run Block Called\n");
                        }
                    }
                    while (retry);
                    while (!_ready)
                    {
                        //Thread.Sleep(100);
                    }
                    
                    if (_ready)
                    {
                        short overflow;

                        status = Imports.GetValues(_handle, 0, ref sampleCount, 1, Imports.DownSamplingMode.None, 0, out overflow);
                        if (status == (short)StatusCodes.PICO_OK)
                        {
                           System.Diagnostics.Debug.Print(stopwatchDataAcqDuration.ElapsedMilliseconds.ToString());                           
                        }
                        else
                        {
                            //textMessage.AppendText("No Data\n");
                        }
                    }
                    else
                    {
                        //textMessage.AppendText("data collection aborted\n");
                    }
                }
            });

Gerry
PICO STAFF
PICO STAFF
Posts: 1145
Joined: Mon Aug 11, 2014 11:14 am

Re: Random high time-overhead/delay in block mode

Post by Gerry »

Hi mzeeshan,

There are 2 completely different mechanisms of data storage going on here. (a) the real-time capture of the data in the hardware PicoScope buffer, and (b) the non-real-time transfer of data from the hardware PicoScope buffer to the software buffers on your computer (i.e. the overview buffer in the driver, followed by the buffer you have defined in your code) on the execution of your getValues() request.

When capturing the data in hardware, in block mode, the start of the capture happens when the trigger has been fired due to the trigger condition being met. This will repeat at the rate that whatever you are triggering on occurs (as long as it is longer than the time it takes to capture the data in the hardware PicoScope buffer + the re-arm time, which will be explained next). Once a capture has finished and all of the data has been placed in the hardware buffer, the trigger can be set up to capture the next set of data. The time to set the trigger is the re-arm time and is the overhead of tens of milliseconds that you mentioned.

The variation in time that you are seeing is due mainly to the transfer of the data over USB (which varies depending upon what else is happening on the USB bus and what else the computer needs to do before getting data from USB). So, if you are seeing delays out to 230mS you need to establish what is causing these delays (is the USB port otherwise engaged, are you running through a USB hub that could be responding slowly for different reasons, is the CPU otherwise occupied so that your code has to wait a long time before it can run?) Assuming that you can get the delay down to tens of milliseconds, then your 100mS cycle includes the transfer time and only leaves you, what should be 60mS of that for you to capture the 185 samples, and store the data somewhere (so that it is not overwritten). So you need to make sure that you're total capture time and the time that it takes you to move the data is not longer. Then, in your code, you need to be getting the data into an adequate sized buffer quickly. The ultimate solution would be to have a large enough buffer to be able to just increment a write pointer, however if you have to move the data between buffers you will have to make sure that your buffer is large enough to hold the data being transferred from a previous capture while the new capture data is being written to it by the callback function.

Incidentally, you should not interfere with the execution of the callback function because this is how the driver is doing what you are trying to do, i.e. buffer the data, while prompting for it to be transferred over USB and then transferring it to your designated buffer.

Regards,

Gerry
Gerry
Technical Specialist

mzeeshan
Newbie
Posts: 0
Joined: Wed Jun 28, 2017 7:26 am

Re: Random high time-overhead/delay in block mode

Post by mzeeshan »

Hello Gerry,

Thank you for your detailed reply.

I have been working testing and finding a good solution for my problem before posting here again. I checked my hardware, there are no other USB devices attached. Picoscope is connected with USB 3.0 port. Also I closed all applications other than my development environment (VS 2015). So I was able to achieve an average delay of a few millisecond with exceptions of around 50-80ms once in a while.

So, I came up with a solution that looked well theoretically: a thread-safe fixed size queue. When a block arrives it should be added to the queue and also at the same time we should check if the queue length has exceed its size n, in which case it should be dequeued to maintain the size. The size n should be 3 or 4 so that the queue always contains the last few values.

Separately a precise timer ticks every 100ms and at the tick event we can dequeue the queue to get queue to get almost the latest value. (this overcomes the issue where all packets are normally coming very fast with a few occasionally taking longer).

However, after implementing this using a ConcurrentQueue I always end up with a FatalExecutionEngineError after several minutes.

To make sure that it was not a problem in my implementation I just tried a much simpler solution. After getting values in the buffers using GetValues, I just copy the received values to another buffer and then in the 100ms timer tick event, I just copy the values from that other buffer. (I lock the two parts to make them thread-safe). That simple thing works very well until a several minutes later I get the FatalExecutionEngineError again. I cannot find what is causing it. Please have a look at the following code. Its simple and should work but why am I having these memory issues, whenever I try to buffer the values.

Code: Select all

public void getBlockData()
        {
            uint status;

            //---------------------------------------------------------------------------------------------------------------------------------
            //*********************************               Initialize signal gen           *************************************************
            //---------------------------------------------------------------------------------------------------------------------------------

            try
            {
                startFreq = Convert.ToDouble("13560000");
                pkToPk = Convert.ToUInt32("2000") * 1000;
                offset = Convert.ToInt32("0");

            }
            catch
            {
                MessageBox.Show("Error with start frequency, offset and/or pktopk", "INVALID VALUES", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }


            stopFreq = startFreq;
            increment = 0;
            dwellTime = 0;
            sweeptype = Imports.SweepType.PS5000A_UP;

            Imports.WaveType wavetype = Imports.WaveType.PS5000A_SINE;


            if (wavetype == Imports.WaveType.PS5000A_DC_VOLTAGE)
            {
                pkToPk = 0;
            }



            status = Imports.SetSigGenBuiltInV2(_handle, offset, pkToPk, wavetype, startFreq, stopFreq, increment, dwellTime, sweeptype,
                                                    operations, shots, sweeps, triggertype, triggersource, extinthreshold);

            if (status != StatusCodes.PICO_OK)
            {
                MessageBox.Show("Error SetSigGenBuiltInV2 error code :" + status.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            //---------------------------------------------------------------------------------------------------------------------------------




            status = Imports.SetChannel(_handle, Imports.Channel.ChannelA, 1, Imports.Coupling.PS5000A_DC, Imports.Range.Range_10V, 0);
            status = Imports.SetChannel(_handle, Imports.Channel.ChannelB, 1, Imports.Coupling.PS5000A_DC, Imports.Range.Range_10V, 0);

            short enable = 1;
            uint delay = 0;
            short threshold = 1000;
            short auto = 0;

            status = Imports.SetSimpleTrigger(_handle, enable, Imports.Channel.ChannelA, threshold, Imports.ThresholdDirection.Rising, delay, auto);

            _ready = false;
            _callbackDelegate = BlockCallback;
            _channelCount = 4;
            string data;
            int x;

            bool retry;
            uint sampleCount = 185;
            PinnedArray[] minPinned = new PinnedArray[_channelCount];
            PinnedArray[] maxPinned = new PinnedArray[_channelCount];

            int timeIndisposed;
            short[] minBuffersChA = new short[sampleCount];
            short[] maxBuffersChA = new short[sampleCount];
            short[] minBuffersChB = new short[sampleCount];
            short[] maxBuffersChB = new short[sampleCount];
            bufferValueChA = new short[sampleCount];
            bufferValueChB = new short[sampleCount];

            minPinned[0] = new PinnedArray(minBuffersChA);
            maxPinned[0] = new PinnedArray(maxBuffersChA);
            status = Imports.SetDataBuffers(_handle, Imports.Channel.ChannelA, maxBuffersChA, minBuffersChA, (int)sampleCount, 0, Imports.RatioMode.None);
            status = Imports.SetDataBuffers(_handle, Imports.Channel.ChannelB, maxBuffersChB, minBuffersChB, (int)sampleCount, 0, Imports.RatioMode.None);

            int timeInterval;
            int maxSamples;
            while (Imports.GetTimebase(_handle, _timebase, (int)sampleCount, out timeInterval, out maxSamples, 0) != 0)
            {
                _timebase++;
            }
            _timebase = 2;
            /* Start it collecting, then wait for completion*/
            _ready = false;
            _callbackDelegate = BlockCallback;
            while (true)
            {
                do
                {
                    retry = false;
                    status = Imports.RunBlock(_handle, 0, (int)sampleCount, _timebase, out timeIndisposed, 0, _callbackDelegate, IntPtr.Zero);
                    if (status == (short)StatusCodes.PICO_POWER_SUPPLY_CONNECTED || status == (short)StatusCodes.PICO_POWER_SUPPLY_NOT_CONNECTED || status == (short)StatusCodes.PICO_POWER_SUPPLY_UNDERVOLTAGE)
                    {
                        status = Imports.ChangePowerSource(_handle, status);
                        retry = true;
                    }
                    else
                    {
                        //textMessage.AppendText("Run Block Called\n");
                    }
                }
                while (retry);

                //textMessage.AppendText("Waiting for Data\n");

                while (!_ready)
                {
                    //Thread.Sleep();
                }

                Imports.Stop(_handle);

                if (_ready)
                {
                    short overflow;
                    status = Imports.GetValues(_handle, 0, ref sampleCount, 1, Imports.DownSamplingMode.None, 0, out overflow);
                    if (status == (short)StatusCodes.PICO_OK)
                    {
                      
                        lock (lockForDataAcq)
                        {
                            Array.Copy(maxBuffersChA, bufferValueChA, maxBuffersChA.Length);
                            Array.Copy(maxBuffersChB, bufferValueChB, maxBuffersChB.Length);
                        }
                    }
                    else
                    {
                        //textMessage.AppendText("No Data\n");

                    }
                }
                else
                {
                    //textMessage.AppendText("data collection aborted\n");
                }

                Imports.Stop(_handle);
            }
            foreach (PinnedArray p in minPinned)
            {
                if (p != null)
                    p.Dispose();
            }
            foreach (PinnedArray p in maxPinned)
            {
                if (p != null)
                    p.Dispose();
            }

        }

private void OnTimedEvent(object sender, MicroLibrary.MicroTimerEventArgs timerEventArgs)
        {
            lock (lockForDataAcq)
            {
                Array.Copy(bufferValueChA, receivedDataChA, bufferValueChA.Length);
                Array.Copy(bufferValueChB, receivedDataChB, bufferValueChB.Length);
            }
            System.Diagnostics.Debug.Print(stopwatchDataAcqDuration.ElapsedMilliseconds.ToString() + " " + receivedDataChA[0].ToString() + " " + receivedDataChB[0].ToString());
        }
I think that's a fairly common application that I am trying to implement; getting a data at fixed interval. As my input signal is very fast (13.56MHz) and I'm only trying to get a few data points (185) in each packet at a fixed rate (100ms) that's much slower than the time required to sample these points, therefore I don't see a reason for this to be so difficult to achieve.

Could you please point out the problem here? Also it will really helpful me if you could suggest the correct way to do this without memory errors. Also do you think this is hard to achieve considering that I have to also perform some data processing on the packets after getting this correct?

Thank you.

Regards,

Zeeshan

mzeeshan
Newbie
Posts: 0
Joined: Wed Jun 28, 2017 7:26 am

Re: Random high time-overhead/delay in block mode

Post by mzeeshan »

Hello Gerry,

I have found why I have been having those memory errors after random intervals; because I missed setting up the pinned array for channel B in the code. A simple mistake but hard to find out. After the correction its been running well for over 1000 minutes now.

Thank you.

Post Reply