LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

DAQmx Start/Stop/TaskControl behavior

Hello,

 

I have a little DAQmx application I wrote in C for basic data acquisiton tasks. It works as it should, no issues so far, but I was curious if I could learn more about how the API functions DAQmxStartTask, DAQmxStopTask, and DAQmxTaskControl behave under certain circumstances.

 

One of the questions I have is what happens if the user accidentally calls DAQmxStartTask/DAQmxStopTask while the same task is already running/stopping? In my case, it seems to do nothing the second time it's called and the task continues/stops the same. Is this how DAQmxStartTask/DAQmxStopTask is written to behave or did I just get lucky? 

 

Another thing I wonder is if using DAQmxTaskControl makes a considerable difference in a use case along the lines of:

 

Version A:

// got some code here to properly define and initiate the task

DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Commit);

DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Start);

 

// vs

 

Version B:

// got some code here to properly define and initiate the task

DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Commit);

DAQmxStartTask(taskHandle);

 

and of course the same could be asked for DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Stop); vs DAQmxStopTask(taskHandle); given that DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Commit); is called first in both versions.

 

Thanks!

0 Kudos
Message 1 of 18
(1,343 Views)

@vygr0 wrote:

Hello,

 

I have a little DAQmx application I wrote in C for basic data acquisiton tasks. It works as it should, no issues so far, but I was curious if I could learn more about how the API functions DAQmxStartTask, DAQmxStopTask, and DAQmxTaskControl behave under certain circumstances.

 

One of the questions I have is what happens if the user accidentally calls DAQmxStartTask/DAQmxStopTask while the same task is already running/stopping? In my case, it seems to do nothing the second time it's called and the task continues/stops the same. Is this how DAQmxStartTask/DAQmxStopTask is written to behave or did I just get lucky? 

 

Another thing I wonder is if using DAQmxTaskControl makes a considerable difference in a use case along the lines of:

 

Version A:

// got some code here to properly define and initiate the task

DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Commit);

DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Start);

 

// vs

 

Version B:

// got some code here to properly define and initiate the task

DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Commit);

DAQmxStartTask(taskHandle);

 

and of course the same could be asked for DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Stop); vs DAQmxStopTask(taskHandle); given that DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Commit); is called first in both versions.

 

Thanks!


It makes a large difference. 

 

You can optomize DAQ-mx task execution by creating and commiting the tasks initailly prior to entering the main loop. Like the model shown below

 

 

 

JB_2-1691878010009.png

Is the LabVIEW version of your first example. This is a Finite Acquisition Task.

 

As shown: because the Task is explicitly transitioned to the Commited State before the loop, the Task is merely implicitly transitioned to the Running State, Runs, and returns to the Commited State inside the DAQmx Read.vi.

 

If the DAQmx Control Task.vi is omitted, the Task enters the loop in the Verified State.  When DAQmx Read.vi is called the Task is implicitly Reserved (obtain Exclusive Lock on all resources) And implicitly Commited (makes any routings, powers up ADC(s) sets Gains, resets Filters etc...) before any transition to the Run State can occur.   THEN the Task MUST be returned to the last State it was Explicitly Transioned to before the DAQmx Read.vi can complete.  That really means that the loop iteration time takes a huge hit but, you simply CANNOT break the Contract DAQmx Read() and Write() correctly implement to return Handles out in the same state they were passed in.

 

 

The DAQmx help file has some very good information that Sean N explained in detail in this Post. About Implicit vs. explicit State Transitions.  Applying this information to your application can prevent serious hair pulling when Tasks return to a lower state because of how implied state transitions work.

 

Back to your first Example.  Explictly Starting a Running Task should throw an error. The Task is simply not in a State where it can be Transitioned to the Run State.  It already in the Run State. Calling a Read/Write and implicitly Transitioning the Task to the Run State is a different kettle of fish.  Either way your code needs to prevent multiple access points to the single Task Handle.  (I can smell the code stench from here.)  Explicitly Stopping a Task that is not in a running State is a no-op and DAQmx Stop Task.vi will simply check that the Task Handle is Valid.  Stopping Task with a DESTROYED Task Handle is not a good idea and will return the runtime error you deserve and need to fix your bug.


"Should be" isn't "Is" -Jay
Message 2 of 18
(1,306 Views)

Awesome info from Jay!  I just want to further emphasize something -- his example is very specifically meant for finite sampling. 

 

The state transition stuff he made use of helps to minimize the "dead time" of overhead required to stop and then restart a task.  As shown, each iteration of the loop would complete one full run of the finite task.  (In real life, you should consider whether you'd need to wire up a larger non-default value for the timeout.  The 10 second default is very often much more than enough though.)

 

If you were setting up for continuous sampling, then you should explicitly call DAQmx Start before entering the reading loop.  (My habit and style has been to do the same thing explicitly with finite tasks too, but I suspect Jay's method is just a little bit more efficient.)  You should then also explicitly declare the # samples to read - the rule of thumb is about 1/10 the sample rate, i.e., about 1/10 second worth.

 

Jay's advice was spot on, just be sure to realize that it's specifically for finite sampling tasks.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 3 of 18
(1,260 Views)

This is very preliminary, but I followed Jay's link to the discussion about explicit and implicit transitions, and tried out hastily modified versions of his benchmark code at the end of the thread.  Note that the benchmark is for performing single sample acquisition using on-demand (aka software-timed) mode.  This makes for a better benchmark than hw-timed finite sampling mode, where much of the measured time may simply be idle time waiting for the finite buffer to be filled.

 

I only compared New Task with Commit and New Task RAW.  Results:

 

with Commit:  ~45 msec for 1000 "Read" iterations

with Reserve: ~90 msec for 1000 "Read" iterations

RAW:         ~3200  msec for 1000 "Read" iteration.

 

So the "overhead" in RAW mode, with no explicit state transitions before starting the task, is roughly 70x as large as when an explicit Commit is performed first.  That's pretty significant!

 

I also tried putting explicit calls to Start & Stop around the reads inside the loop, since I personally make those explicit calls habitually.  I didn't notice any real difference in the speeds with the explicit Starts and Stops.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 4 of 18
(1,242 Views)

Thanks Kevin.   Those Benchmarks were written many versions of DAQmx ago.  I am aware if a few changes that have been made to the driver since they were originally posted. 

 

I'll make one additional observation about your reported benchmark findings:  DAQ ASSISTANT uses the "RAW" method you described. 😉 

 

As for Continuous Read Tasks I will always use a Queued Message Handler.    I do NOT like the shipping example since it lacks a State Machine to encapsulate all the Actions to be performed on the DAQmx Task. The consumer loop will have the Task Handle on a Shift Register or Feedback Node and have the following State Cases

  • Initialize: Verifies the Task Channels exist
  • Configure: Verifies The rest of the Task is sane
  • Optional Control Task <State>
  • Start: transitions to Run and self enqueues Read
  • Read: obtains a <200mSec chunk of Data from the DAQmx Transfer Buffer [if you are really GOOD this also Logs the Data to the TDMS File using DAQmx Logging] and always self enqueues another Read. 
  • Stop: ALWAYS ENQUEUED AT OPPOSITE END! stops the Task and Flushes the Queue.
  • Exit: Destroys the Task and Exits the Consumer Loop
  • Some people claim an Error Case is useful for catching developer errors...Personally I prefer not to write bugs and omit this Case, exit on Error True and hand the Error to the caller in a post loop action

For Continuous Output Tasks we need to consider optimizing the whole shebang depending on Regeneration,  data source, Device Buffer and User interactions.   I have never found a one size fits all use cases approach. 


"Should be" isn't "Is" -Jay
Message 5 of 18
(1,230 Views)

I wanted to follow up on one other item from Kevin.

  • "I also tried putting explicit calls to Start & Stop around the reads inside the loop, since I personally make those explicit calls habitually.  I didn't notice any real difference in the speeds with the explicit Starts and Stops."

Earlier I mentioned that Starting a Started Task will cause an Error.

 

Yet, Kevin's benchmark did not throw one! WHY?  Well the Start Task call explicitly forced a forward State Transition and the Read call IMPLICITLY asked for the same state transition.  That is not the same as calling Start Task on a Running Task.  Again, the minutiae is buried in the DAQmx and LabVIEW help files, look around the explanations of the Autostart input to the Read call.

 


"Should be" isn't "Is" -Jay
Message 6 of 18
(1,201 Views)

Wow, great info from both of you! I'm not sure I understood all of it but it seems I forgot to share some necessary info about my application.

 

My application is a continuous acquisition application. Thanks to the explanations from both of you, I understand how crucial it is to commit the task via DAQmxTaskControl before entering the reading loop for performance. I'm still not 100% clear on whether to run 

 

DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Start);

 

or

 

DAQmxStartTask(taskHandle);

 

to start the task after committing it outside the loop for better performance. Just to be clear, I don't call DAQmxStartTask if I have already called DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Start); to start the task. At least in my application, DAQmxTaskControl(taskHandle, DAQmx_Val_Task_Start); seems to also start the task without me having to call DAQmxStartTask.

 

I see that Kevin suggests explicitly calling DAQmxStartTask before entering the loop is a good idea. I believe I would still commit via DAQmxTaskControl before entering the loop, then also explicitly call DAQmxStartTask to start the task in this case? I guess Sean_N's post which Jay shared explains that start should also be called explicitly in a task performing repeated reads:

 


Start—If your application repeatedly performs read or write operations, explicitly start a task. Starting the task reserves the resources that the task uses, programs some of the settings for these resources, and begins to perform the specified operation. By explicitly starting the task, these operations are performed once, not each time the read or write operation is performed. This process can considerably decrease the time required to perform each read or write operation. For example, if your application repeatedly performs single-sample, software-timed read operations, the time required for each read operation can dramatically decrease if you explicitly start the task before repeatedly performing these read operations.


Based on all this info, I think "Version B" in my original post is the best for me in my continuous acquisition task?

 

Thanks!

0 Kudos
Message 7 of 18
(1,166 Views)

My take:

 

Most of the discussion was about making it more efficient to repeatedly stop and restart a task.  If you're simply running a continuous acquisition that you'll only start once and later (maybe much later) stop only once, then it neither helps nor hurts to Commit prior to the Start.  I typically don't bother.  The real gains come when you need to rapidly stop and restart a Finite acquisition.

 

I would also make an explicit call to DAQmxStartTask() before the read loop rather than attempt using DAQmxTaskControl().  I do all my programming in LabVIEW, and there the "DAQmx Control Task.vi" function does not allow me to declare "Start".  The only defined behaviors are: verify, reserve, commit, unreserve, abort.  Since I honestly don't know whether your DAQmxTaskControl() API function performs task start the same way or not, I can only recommend the more familiar DAQmxStartTask().

 

Jay may well advocate that you don't even bother with an explicit call to a Start function -- just let the auto-start behavior built into the Read function do its thing.  I personally never got into the habit of relying on auto-start behaviors.  In fact, I've kinda migrated the other way, and many times advocate things that aren't necessary b/c I think they help to show clarity of intent.  For example, when calling a DAQmx Read function, even if you want the default behavior that comes when you don't specify how many samples you want to read, I advocate specifying the number explicitly anyway.  To show that you are doing it intentionally and didn't merely forget to override the default.

 

But I also wouldn't say it's wrong to rely on automatic or default behavior when you clearly understand it.  Sometimes I do, sometimes I don't, no specific overriding philosophy.  A lot of it's just whatever habit I happened to establish once upon a time. 

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 8 of 18
(1,151 Views)

What wonderful timing!  I'm currently wrestling with using a USB-6212 to be imbedded in a little controller to run a Stepper, a couple of tone signals (400 Hz for 0.3 s or 500 Hz for 0.15 s), a number of DI "sensors" and DO "actuators".  I just tried to make a flexible "beeper" where I decide which of two tones (see previous sentence), realized as a Digital Waveform of either 600 points at 4 kHz (400 Hz tone) or 300 points at 4 kHz (500 Hz tone).  I could get one Tone out nicely, but the second one was notably shorter.

 

These are my first experience generating programmed (flexible) finite "Beeps".  I'd not gotten "into the weeds" of DAQmx Tasks, but will print out and study the explanations from Jay and Kevin.  Thanks for teaching an Old Dog some New Trix.

 

Bob Schor

0 Kudos
Message 9 of 18
(1,136 Views)

@Kevin_Price wrote:

My take:

 

Most of the discussion was about making it more efficient to repeatedly stop and restart a task.  If you're simply running a continuous acquisition that you'll only start once and later (maybe much later) stop only once, then it neither helps nor hurts to Commit prior to the Start.  I typically don't bother.  The real gains come when you need to rapidly stop and restart a Finite acquisition.


My application does start and stop multiple times depending on user input but of course the user typically won't start/stop rapidly. It's a continuous task that makes use of everyNsamplesEvent (which fires quite often, meaning that I read() quite often). I think the main take away for me was that it seems to make a difference whether I use the DAQmxTaskControl or DAQmxStartTask to start the task, as Sean_N explains in his post Jay shared (which you also recommend). Also that the "Version B" in my original post is the way to go in my application.

 

Thanks everyone for your answers!

0 Kudos
Message 10 of 18
(1,120 Views)