Multifunction DAQ

cancel
Showing results for 
Search instead for 
Did you mean: 

A USB-6221 (Visual C++ application, NIDAQmx 9.0 & Windows XP) works OK with slow computers, but doesn't work with fast computers.

Visual C++ software developed for a USB-6221 (NIDAQmx 9.0 & Windows XP) works OK with slow computers, but doesn't work with fast computers.  The software (sampling analog inputs at a few kHz) does work perfectly with a DAQPad-6016 with any computer.  What could be the problem? 

0 Kudos
Message 1 of 12
(4,964 Views)

Hi there,

 

I am sorry, I think I am a little confused on what you mean here. When you say that your application does not work on fast computers, what do you mean? Are you getting errors during execution, or is this a compatibility issue?

Regards,
Efrain G.
National Instruments
Visit http://www.ni.com/gettingstarted/ for step-by-step help in setting up your system.
0 Kudos
Message 2 of 12
(4,946 Views)

Hi Efrain,

 

    My name is Sean O'Leary and I took over the code from the previous developer for johnahughes, et. al. They asked me to provide more detail as to the specifics of the problem. As this is an URGENT issue which prevents us from shipping new products which customers are expecting immediately.

 

     First, to answer your question; we are not getting any "crash" or errors. But when we run the software on a "faster" PC with the USB-6221, the program stops responding and it takes minutes to respond to a single mouse-click. If you wait long enough, the software "works" but the data acquisition is tying up the resources so completely that it is functionally unusable.

 

    When the exact same software on the exact same PC is used with the DAQPad-6016 the program responds perfectly to user input and the data acquisition does not tie up the resources.

 

    Here is the code that initializes the DAQ Analog Input Channels and Clock Timing:

 

void Daq::Impl::initDaqTask()
{
  // Start the counter
  setCounterDivider(2);
  int ret = 0;
  ret = DAQmxCreateTask("",&daqTask);
  HandleDaqError(ret);

  ret = DAQmxCreateAIVoltageChan( daqTask, "/Dev1/ai0", "", DAQmx_Val_Cfg_Default, -10.0, 10.0, DAQmx_Val_Volts, 0 );
  HandleDaqError(ret);

  ret = DAQmxCreateAIVoltageChan( daqTask, "/Dev1/ai1", "", DAQmx_Val_Cfg_Default, -10.0, 10.0, DAQmx_Val_Volts, 0 );
  HandleDaqError(ret);

  ret = DAQmxCreateAIVoltageChan( daqTask, "/Dev1/ai2", "", DAQmx_Val_Cfg_Default, -10.0, 10.0, DAQmx_Val_Volts, 0 );
  HandleDaqError(ret);

  // AG - Mar 2010 - add Temperature, Volts and Amps inputs to auto conversion loop
  //                 for when daq is in external conversion clock mode
  ret = DAQmxCreateAIVoltageChan( daqTask, "/Dev1/ai4", "", DAQmx_Val_Cfg_Default, -5.0, 5.0, DAQmx_Val_Volts, 0 );
  HandleDaqError(ret);
  ret = DAQmxCreateAIVoltageChan( daqTask, "/Dev1/ai5", "", DAQmx_Val_Cfg_Default, -5.0, 5.0, DAQmx_Val_Volts, 0 );
  HandleDaqError(ret);
  ret = DAQmxCreateAIVoltageChan( daqTask, "/Dev1/ai6", "", DAQmx_Val_Cfg_Default, -5.0, 5.0, DAQmx_Val_Volts, 0 );
  HandleDaqError(ret);

  samplingMode( TIME, 1000.0 );
}

void Daq::Impl::setCounterDivider( int div )
{
  pCtr0.reset();
  pCtr0 = boost::shared_ptr<DaqCounter0>( new DaqCounter0(div, samplesPerChannel) );
}

void Daq::Impl::samplingMode( SamplingMode mode, double rate )
{
  // PFI6 is the one-shot output, 0 uses the onboard clock
  char* modeTxt = (mode == ENCODER) ? "/Dev1/PFI6" : 0;
  int ret = DAQmxCfgSampClkTiming( daqTask, modeTxt, rate, DAQmx_Val_Rising, DAQmx_Val_ContSamps, samplesPerChannel/** numberChannels*//*AG Apr 2010 1000*/ );
  HandleDaqError(ret);
  Daq::Impl::mode = mode;
}

 

    Heres the class that initializes Counter0:

 

class DaqCounter0
{
public:
  DaqCounter0( int divider = 2, int sampPerChan = 1000 )
  {
    int ret = 0;

	// create the output channel	
	ret = DAQmxCreateTask("",&task_);
    HandleDaqError(ret);

    // Configure the counter
    ret = DAQmxCreateCOPulseChanTicks( task_, "Dev1/ctr0", "", "/Dev1/PFI8", DAQmx_Val_Low, 0, divider, divider );
    HandleDaqError(ret);

    // We have to set the counter to output in pulse mode
    // Intuitivly this is the place where we would do this but...
    // for some unkonwn reason we have to first start and stop the counter and then
    // change to pulse mode
    // Must be a bug with the NI driver or software

    // Set the continous output mode
    ret = DAQmxCfgImplicitTiming( task_, DAQmx_Val_ContSamps, sampPerChan );
    HandleDaqError(ret);

    // Start the counter
    ret = DAQmxStartTask(task_);
    HandleDaqError(ret);

    // Stop the counter
    ret = DAQmxStopTask(task_);
    HandleDaqError(ret);

    // Change to pulse mode
    ret = DAQmxExportSignal( task_, DAQmx_Val_CounterOutputEvent, "/Dev1/Ctr0InternalOutput" );
    HandleDaqError(ret);
    ret = DAQmxSetExportedCtrOutEventOutputBehavior( task_, DAQmx_Val_Pulse );
    HandleDaqError(ret);

    // Start the counter again
    ret = DAQmxStartTask(task_);
    HandleDaqError(ret);

  }

  ~DaqCounter0( )
  {
    int ret = 0;

	// Stop the counter
    ret = DAQmxStopTask(task_);
    HandleDaqError(ret,false);

    // Clear the task
    DAQmxClearTask(task_);
    HandleDaqError(ret,false);

	// Stop the input task 
    HandleDaqError(DAQmxStopTask(readTask_) , false);
    // Clear the input task
    HandleDaqError(DAQmxClearTask(readTask_), false);

  }

private:
  TaskHandle task_;
  TaskHandle readTask_;
};

 

    I hope this sheds some light on something that you can recognize as an issue between the USB-6221 and the DAQPad-6016.

 

Thank you,

Sean O'Leary

0 Kudos
Message 3 of 12
(4,928 Views)

Hi Sean,

 

I am sorry that we are having this issue here. I have been doing some research to find if there are any known issues with the 6221 and DAQmx 9.0 to no avail. Have you tried using the task manager to monitor the memory usage while this program is running? I am assuming that you are using the same sampling rate in both cases. Is that correct?

Regards,
Efrain G.
National Instruments
Visit http://www.ni.com/gettingstarted/ for step-by-step help in setting up your system.
0 Kudos
Message 4 of 12
(4,900 Views)

Hi Efrain,

 

    There does seem to be a similar problem here: http://forums.ni.com/t5/Multifunction-DAQ/Timing-problem-latency/m-p/1091937

 

    Yes, we have looked at Windows Task Manager to see the resources and it is shows 95% CPU Usage and about 30MB Memory Usage running on one of the "faster" computers; with the program responding slowly or not at all to user actions. While on the "Slower" computers it is around 99% CPU Usage and around 45MB Memory Usage; with the computer responding instantly to user actions.

 

    Yes, the sampling rates and everything else, except the DAQ Boards, are the same for all scenarios.

 

Sean

0 Kudos
Message 5 of 12
(4,896 Views)

Hi Sean,

 

Here are some (admittedly scattered) questions:

 

Could you share some examples of a "slow" computer and a "fast" computer? Brand, model, CPU, chipset, etc.?

 

What is unresponsive? Your application, or the entire PC? Do other programs respond in a reasonable amount of time?

 

What is samplesPerChannel set to?

 

Does the program use DAQmxRegisterEveryNSamplesEvent()?

 

What does the code that reads data from the AI task look like?

 

Have you tried disabling the counter task to see the effect on the program's or PC's responsiveness?

 

Are the PC's input devices connected via USB or PS/2? If it's USB, are they on the same USB controller as the USB-6221?

 

If you break into your application with a debugger when the program is unresponsive, which function call is typically on the stack? DAQmxReadAnalogF64()? (If the entire PC is unresponsive and starting a debugger would take twenty minutes, then ignore this.)

 

Which XP service pack is on these PCs? Using USB M Series with XP RTM or XP SP1 is not recommended, and the DAQmx installer should print a warning about this.

 

It's probably unrelated to the responsiveness issue, but I have a hypothesis regarding why setting the counter to pulse mode didn't seem to work. This line:

DAQmxExportSignal( task_, DAQmx_Val_CounterOutputEvent, "/Dev1/Ctr0InternalOutput" )

tells DAQmx to route the counter output (which is /Dev1/Ctr0InternalOutput) to /Dev1/Ctr0InternalOutput. This doesn't route the signal anywhere. However, if you start the task with the default route, DAQmx routes /Dev1/Ctr0InternalOutput to /Dev1/PFI12 (M Series) or /Dev1/Ctr0Out (E Series), and then lazy uncommit keeps the signal routed the next time you start the task. You might be able to avoid the workaround if you pass "/Dev1/PFI12" (M Series) or "/Dev1/Ctr0Out" (E Series) to the export signal function, though I have not tried this.

 

Also, there are two ways to control the counter output terminal, which may be a bit confusing. Normally, the output terminal is controlled by CO.Pulse.Term (e.g. DAQmxSetCOPulseTerm()), but you can also control it with DAQmxExportSignal(). I think the reason is that export signal is available in counter input tasks as well; instead of configuring a pulse generation task, it is also possible to configure the counter for "count edges" (counter input) and export the counter output event.

 

Brad

---
Brad Keryan
NI R&D
0 Kudos
Message 6 of 12
(4,828 Views)

Hi Brad,

 

    Thank you for your detailed response and your questions. Hopefully answering them will shed some light on the problem.

 

Brad K: Could you share some examples of a "slow" computer and a "fast" computer? Brand, model, CPU, chipset, etc.?

Answer: John Hughes at Rotesco did some detail testing earlier in the month. Here are the details of his findings, including specs on the different computers used:

1.  The new Inco computer does work fine with the old data acquisition board.
2.  One possible clue about the problem might be:
With the rope speed set at about 500 feet per minute (speed varies a bit over time),
a)  All computers with both old and new DAQ boards showed correct rope footage.
b)  The new Inco laptop with the old DAQ worked well and showed rope speed of about 500 feet per minute, but with the new  DAQ had problems and showed rope speed of about 240 feet per minute.
c)  The new CF30 laptop with the new DAQ had problems and showed rope speed of about 240 to 270 feet per minute.
d)  Our old CF29 laptop with the new DAQ worked well (but I did noticed small problems only once in a while) and showed rope speed of about 390 to 450 feet per minute (definitely less than actual).
e)  Our old Toshiba laptop with the new DAQ worked well (so far I have not noticed any problems) and showed rope speed of about 500 feeet per minute.
f)  When there are big problems with the new laptops (most of the screen turns white) the only displays on the screen that seem to be working properly are the vertical scrolling screen and the updating printout of “xxx ft./min.”
f)  It seems strange that the only significant difference between the CF29 and CF30 is the processor type (although both are 1.6GHz), but show significant difference in the indicated rope speed and how well they work with the new DAQ.  The Toshiba (slowest computer) showed the correct rope speed and seems to work well with the new DAQ.
3.  The specs on the 4 computers that we have tested are:
a)  Inco new laptop computer (works with old DAQ but not new DAQ)
    XP version 2002 SP3 / Intel core 2 Duo 2.26 GHz / 791MHz 3.15 GB RAM / Phys. Addr. Ext.
b)  Bullivants new CF30 laptop computer (works with old  DAQ but not new DAQ)
    XP version 2002 SP3 / Intel core 2 Duo 1.6 GHz / 782MHz 1.87 GB RAM / Phys. Addr. Ext.
c)  Our old CF29 laptop computer (works with both DAQs, only occasional problem with new DAQ)
    XP version 2002 SP3 / Intel Pentium M 1.6 GHz / 589MHz 1.49 GB RAM
d)  Our old Toshiba laptop computer (seems to work well with both DAQs)
    XP version 2002 SP2 / Intel Pentium  1 GHz / 532MHz 256 MB RAM

Also, earlier this week John did some other tests, monitoring the CPU and Memory Usage through Windows Task Manager. Here are the results he sent me:
These are the results of the 4 tests with Async rate set to 100 Hz, and 2 test heads set up with the 2 consoles on the same moving rope (not sure of speed).

Slow computer with old DAQ
No problems,
during idle CPU=63, memory=54M ,
during calibr. record CPU=48-73, memory=52M,
during test record CPU=47-63, memory=59M

Slow computer with new DAQ
discrepancy with rope speed reading, otherwise no problems (but previous experience is that occasionally, the user interface does act up)
during idle CPU=86-98, memory=42M,
during calibr record CPU=87-98, memory=44M,
during test record CPU=89-97, memory=48M

Fast computer with old DAQ
No problems,
during idle CPU=55 (very steady),
memory=38M, during calibr record CPU=52-57,
memory=41M, during test record CPU=54-58, memory=47M

Fast computer with new DAQ
Big problems with user interface during test recording,
during idle CPU=81, memory=52M,
during calibr record CPU=89-94, memory=53M,
during test record CPU=97-99, memory=59M

 

Brad K: What is unresponsive? Your application, or the entire PC? Do other programs respond in a reasonable amount of time?
Answer: As far as I have tested, it is only the program itself, other programs do respond OK. 

 

Continued below...

0 Kudos
Message 7 of 12
(4,799 Views)

Brad K:What is samplesPerChannel set to?
Answer: 5000

 

Brad K:Does the program use DAQmxRegisterEveryNSamplesEvent()?
Answer: No.

 

Brad K:What does the code that reads data from the AI task look like?
Answer:

void Daq::Impl::threadfunc( )
{
  const float64 timeout = 0;
  boost::shared_array<int16> data( new int16[chunkSize] );
  int32 samplesPerChannelRead = 0;

  while( stopThread.isNonZeroReset() == 0 )
  {
    int32 ret = DAQmxReadBinaryI16( 	daqTask, 
					DAQmx_Val_Auto, 
					timeout, 
					DAQmx_Val_GroupByChannel, 
					data.get(), 
					chunkSize, 
					&samplesPerChannelRead, 
					0 );
    HandleDaqError(ret,false);

    if( samplesPerChannelRead != 0 )
    {
      // Get the last samples read, to fake poling the channels
      lmaAVal = data[1 * samplesPerChannelRead - 1];   // ai0
      lmaBVal = data[2 * samplesPerChannelRead - 1];   // ai1
         // lfVal = data[3 * samplesPerChannelRead - 1];      // ai2
         // AG - Mar 2010 add these 3 channels for monitoring
         temperatureSample = data[4 * samplesPerChannelRead - 1];    //ai4
         batteryVoltsSample = data[5 * samplesPerChannelRead - 1];   //ai5
         batteryAmpsSample = data[6 * samplesPerChannelRead - 1];    //ai6
      if (callback)
      {
        //LockType lock(mtx);
        callback->execute(data.get(), samplesPerChannelRead);
      }
    }
  }
}

 

Brad K:Have you tried disabling the counter task to see the effect on the program's or PC's responsiveness?
Answer: No

 

Brad K:Are the PC's input devices connected via USB or PS/2? If it's USB, are they on the same USB controller as the USB-6221?
Answer: In my case here at my shop. Yes, USB Mouse/Keyboard. I don't think so at Rotesco.

 

Brad K:If you break into your application with a debugger when the program is unresponsive, which function call is typically on the stack? DAQmxReadAnalogF64()? (If the entire PC is unresponsive and starting a debugger would take twenty minutes, then ignore this.)
Answer: Most often these two functions:

void RecordTraceView::ProcessDaqData()
{
  Interlocked::Decrement(&daqHandlerCounter_);

  size_t qSize = sampleQueue_->Size;

  if (qSize != 0)
  {
	  Sys::String *msg = Sys::String::Concat(S"Proc Samples: ", qSize.ToString());
    DWriteLine(msg);
    // Get all available samples.

    DoubleVector* lf = new DoubleVector;
    DoubleVector* lma = new DoubleVector;
    lf->Reserve(qSize);
    lma->Reserve(qSize);

    sampleQueue_->RemoveSamples(lf, lma);

    // Update controls. Some controls are always updated, other are updated
    // only when recording.

    scrollTracePanel_->AddSamples(lf, lma);
    scrollTracePanel_->Refresh();

    if (RecordState::Calibrating == recState_ || RecordState::Testing == recState_)
    {
      DAssert(totalTracePanel_->GetRecordSection() != 0);
      totalTracePanel_->AddSamples(lf, lma);
	  if (RecordState::Calibrating == recState_)
		  distCtrl_->IncSamplesTraversed(lf->Size, 1);
	  else
		  distCtrl_->IncSamplesTraversed(lf->Size, (MainFrame::Instance->lastForward ? 1 : -1));
	  samplesAcquired += lf->Size;
    }

    UpdateSpeedCtrl(lf->Size);

	if (!this->ChartingEnabled)	
		return;
    // Print samples to chart if requested
    // TODO: do we need all these checks? Is a certain one
    // sufficient?
    if (!ChartingManager::IsReadyToChart()) { return; }
    if (!ChartingManager::IsCharting()) { return; } 
    if (ChartingManager::AllSamplesSent) { return; }

    bool recording = (   RecordState::Calibrating == recState_ 
      || RecordState::Testing     == recState_);
    bool chartingSync = ChartingManager::ChartType == ChartingType::Sync;
    bool chartingAsync = ChartingManager::ChartType == ChartingType::Async;

    // Have to be charting asynchronously or charting 
    // synchronously and recording in order to print
    if (chartingAsync || (chartingSync && recording))
    {
      ChartingManager::ChartSamples(lf, lma, 
        scrollTracePanel_->LfGain, 
        scrollTracePanel_->LfZero, 
        scrollTracePanel_->LmaGain, 
        scrollTracePanel_->LmaZero);
    }
  }
  else
  {
    DWriteLine(S"Proc:Skipping");
  }
}

void RecordTraceView::execute(short* samples, size_t numSamples)
{
  IDaqUnit* daq = MainFrame::Instance->Daq;
  if (!daq)
    return;
  size_t prevQSize = sampleQueue_->Size;
  sampleQueue_->AddSamples(samples, numSamples);

  // To enhance performance, we only queue 1 daq handler at a time; i.e. we wait for
  // the last handler to start processing data before queuing another handler.
  // BeginInvoke starts the asynchrounous handler and returns immediately.

  if (daqHandlerCounter_ == 0)
  {
    Interlocked::Increment(&daqHandlerCounter_);
    BeginInvoke(daqHandler_);
  }
  else
  {
    DWriteLine(S"Skipped BeginInvoke Daq Handler");
  }
}

 

And less often, this one; which draws the scrolling "Chart Recorder" trace:

    void Draw(Graphics* g)
    {
      // Require at least two points to draw a line.
      if (pts_->Count < 2)
        return;

      // AG - Jan., 2010 - PruneSamples(); Gets executed every time samples are added
	  //PruneSamples();
      PruneTraceEvents();

      // Set clips so trace points outside the grid are not drawn.
      GraphicsState* gs = g->Save();
      g->SetClip(boundRect_);

      PointF pts[] = Decimate(pts_, boundRect_.Height, extents_.xmin, extents_.xmax);
      Matrix* tx = MakeWorldToClientTransform();
 
      if (pts->Count > 1)
      {
        Matrix* savedTx = g->Transform;
        g->Transform = tx; 
        Pen* pen = new Pen(Color::Yellow, 0);
        g->DrawLines(pen, pts);
        g->Transform = savedTx;
      }

      // Draw events. Yes, this could be done in separare TraceEventGraphic
      // class, but it not much work as of yet, so why bother?.

      SysCol::IEnumerator* it = events_->GetEnumerator();
      while (it->MoveNext())
      {
        TraceEvent* te = NextItem<TraceEvent>(it);
        
        // X point values are determined by the bound rect, not the trace
        // event. Trace event start and end are are sample (indexes). Must
        // convert to units first then transform to client. We draw events in
        // client coordinate so bounding rect points don't have to be
        // transformed from client to world then back to client.

        float startPos = te->Start * unitsPerSam_;
        float endPos = te->End * unitsPerSam_;
        PointF ePts[] = { PointF(startPos, 0), PointF(endPos, 0) };
        tx->TransformPoints(ePts);

        SolidBrush* brush = new SolidBrush(Color::FromArgb(90, Color::Red));
        g->FillRectangle(brush, boundRect_.Left, ePts[1].Y, boundRect_.Width, ePts[0].Y - ePts[1].Y);
      }
      
      g->Restore(gs);
    }

 

Brad K:Which XP service pack is on these PCs? Using USB M Series with XP RTM or XP SP1 is not recommended, and the DAQmx installer should print a warning about this.
Answer: SP2 and SP3
 

     I'm hoping something in here will give you an "Aha" moment! 😄
   Thank you for your interest.

  Sean O'Leary

0 Kudos
Message 8 of 12
(4,796 Views)

Hi Sean,

 

My best guess is that the problem is related to how often the application reads data from DAQmx. Specifying DAQmx_Val_Auto (-1) for the numSampsPerChan parameter tells DAQmx to read any samples that are available. On USB devices, this involves calling into the device's kernel driver and sending a message to the device. Combine this with the timeout of 0, and this means that Daq::Impl::threadfunc() is polling as fast as it can: constantly calling into the kernel and sending USB messages, with nothing to limit the frequency of these messages.

 

Here some ways to limit how often DAQmxRead is called:

  • Specify a non-zero timeout, so that when no data is available, DAQmxRead does not return until the timeout.
  • Specify a numSampsPerChan other than -1, so that the frequency of DAQmxRead calls is controlled by the sample rate. This also requires a timeout to be effective.
  • Instead of a background thread, it is also possible to use DAQmxRegisterEveryNSamplesEvent() to schedule a callback function based on the sample rate. This normally uses a background thread, but if you specify DAQmx_Val_SynchronousEventCallbacks, the callback is executed when your application processes Windows UI messages. Setting nSamples to a small number like 1 may still cause problems.
  • Call Sleep() or use a timer callback to limit the frequency of DAQmxRead calls. Using a non-zero timeout should have less latency than this approach.

Also, I'm not familiar with Inco laptops. I assume the CF30 and CF29 are Panasonic Toughbook laptops?

 

Brad

---
Brad Keryan
NI R&D
0 Kudos
Message 9 of 12
(4,789 Views)

Hi Brad,

 

    Thank you so much for responing today. I tried what you suggested and it seems to have addressed the issue very well on my test setup here. I will send the installation package to Rotesco for their testing tomorrow to confirm everything still work with all setups.

 

    Thank you again. I will let you know once the complete testing is done.

 

Sean

0 Kudos
Message 10 of 12
(4,782 Views)