From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

Multifunction DAQ

cancel
Showing results for 
Search instead for 
Did you mean: 

Acquire short pulses at fast repetitions using NI-6361

Solved!
Go to solution

Dear All. Currently I'm using the library nidaqmx for python 3.7.4 (Windows 10) in order to acquire a single analog input channel using an external trigger. I want to acquire short pulses of data (1000 samples) at high sampling frequency (2 MS/s) and with "fast" repetitions (for example with a period of 20 ms). Please see the following diagram:

 

 

diagram.png

 

The signal on channel 1 is the trigger signal. I want to perform the acquisition on the FALLING edge. The signal on channel 2 is the analog input and I want to acquire just the portion inside the red rectangle.

 

So far I have been able to achieve a smooth acquisition but only for t1 larger than 60 ms using the reference trigger function and a stream reader inside a loop for acquisitions with t2 of 0.5 ms. Please see my Python code attached:

 

import nidaqmx
import nidaqmx.constants as cst
import matplotlib.pyplot as plt
import numpy as np
from nidaqmx.stream_readers import AnalogSingleChannelReader
import warnings
warnings.filterwarnings("ignore") # Ignore all warnings

sample_time = 0.0005  # units = seconds
s_freq = 2000000
num_samples = int(sample_time*s_freq)
dt = 1/s_freq
diffTerminal = cst.TerminalConfiguration.RSE
Volts = cst.VoltageUnits.VOLTS
sampleMode = cst.AcquisitionType.FINITE
maxValue = 5
minValue = -5

task = nidaqmx.Task()

task.ai_channels.add_ai_voltage_chan("Dev1/ai0",name_to_assign_to_channel="mySignal", terminal_config=diffTerminal,
max_val=maxValue, min_val=minValue, units = Volts)

task.timing.cfg_samp_clk_timing(s_freq, sample_mode=sampleMode, samps_per_chan=num_samples)

task.ai_channels.ai_impedance = cst.Impedance1.FIFTY_OHMS

task.triggers.reference_trigger.cfg_dig_edge_ref_trig("PFI0",pretrigger_samples=100,trigger_edge=cst.Edge.FALLING)

data = np.zeros((num_samples,), dtype=np.float64)

comulativeData = np.empty((24*num_samples,0))

reader = AnalogSingleChannelReader(task.in_stream)

print('Wainting for trigger')
for ii in range(0,24):
    reader.read_many_sample(data, number_of_samples_per_channel=num_samples,timeout=30)
    comulativeData = np.append(comulativeData,data)

print('Acquisition Finished')
plt.figure()
plt.plot(comulativeData)
plt.show()

task.stop()
task.close()

I'm not an expert in data acquisition systems but I believe that want I want to do is not extremely difficult so probably I'm doing something wrong. At the time I'm acquiring pretrigger samples but that's not a mandatory requirement. I've tried using pause triggers but with no luck.

 

Ideally I would only call a read function every 24 pulses because after 24 pulses I have a large stop time in the analog signal. Can you help me speed up this process? If necessary I can change the programming language to C or Matlab.

 

Thank you in advance,

 

 

 

0 Kudos
Message 1 of 6
(2,788 Views)

My guess is that because the task is never explicitly started, there are tons of implicit DAQ task state transitions happening every time you call Read.  In other words, when you call Read, the task goes from Unverified to Running and then back from Running to Unverified (or maybe Verified).  See here:

 

http://zone.ni.com/reference/en-XX/help/370466AH-01/mxcncpts/taskstatemodel/

 

Anyway, before doing anything drastic, the first thing I'd try is calling task.commit() and then task.start() before you call your FOR loop.  (I'm not 100% sure about the syntax, but look for documentation on how to commit and then start your task using the Python API.)  Doing this should at least remove any driver state transitions that could be affecting your retrigger rate. 

 

After that, I would benchmark how long it takes to call this:

    comulativeData = np.append(comulativeData,data)

I'm not sure how well Python handles dynamically appending to arrays, but that operation could be pretty inefficient from a memory management perspective.  Generally, preallocating an array and then replacing its contents is much more efficient.  I don't know the Python syntax, but it shouldn't be hard to find.  Since you already know the size of the array since you know the number of samples per trigger and the number of triggers, preallocating an exact array size that can hold all of your samples should be straight forward.

Message 2 of 6
(2,743 Views)

Thank you for your comment. By replacing the dynamic allocated array with a preallocated one I was able to reduce the t1 to 30 ms. It's not perfect but It's better than what I had. The time to perform:

comulativeData[ii*num_samples:(ii+1)*num_samples] = data

is about 1 ms.

 

 

I had also added an explicit task commit before the for loop. When I add an explicit task.start() I get the following error:

 

Traceback (most recent call last):
nidaqmx.errors.DaqError: Attempted to read a sample beyond the final sample acquired. The acquisition has stopped, therefore the sample specified by the combination of position and offset will never be available.
Specify a position and offset which selects a sample up to, but not beyond, the final sample acquired. The final sample acquired can be determined by querying the total samples acquired after an acquisition has stopped.
Attempted to Read Sample: 2820249
Property: DAQmx_Read_RelativeTo
Corresponding Value: DAQmx_Val_CurrReadPos
Property: DAQmx_Read_Offset
Corresponding Value: 0

Task Name: _unnamedTask<0>

Status Code: -200278
Exception ignored in: <function Task.__del__ at 0x111AF078>

I suppose that after the calling of task.start() the NIDAQ starts acquiring samples without waiting for the first trigger to come. But I'm not sure of that explanation.

 

 

The complete code is:

 

import nidaqmx
import nidaqmx.constants as cst
import matplotlib.pyplot as plt
import numpy as np
from nidaqmx.stream_readers import AnalogSingleChannelReader
sample_time = 0.0005  # units = seconds
s_freq = 2000000
num_samples = int(sample_time*s_freq)
print(num_samples)

dt = 1/s_freq
diffTerminal = cst.TerminalConfiguration.RSE
Volts = cst.VoltageUnits.VOLTS
sampleMode = cst.AcquisitionType.FINITE
maxValue = 5
minValue = -5


task = nidaqmx.Task()

task.ai_channels.add_ai_voltage_chan("Dev1/ai0",name_to_assign_to_channel="mySignal", terminal_config=diffTerminal,
max_val=maxValue, min_val=minValue, units = Volts)

task.timing.cfg_samp_clk_timing(s_freq, sample_mode=sampleMode, samps_per_chan=num_samples)

task.ai_channels.ai_impedance = cst.Impedance1.FIFTY_OHMS

task.triggers.reference_trigger.cfg_dig_edge_ref_trig("PFI0",pretrigger_samples=100,trigger_edge=cst.Edge.FALLING)

data = np.zeros((num_samples,), dtype=np.float64)
comulativeData = np.zeros((24*num_samples,),dtype=np.float64)

reader = AnalogSingleChannelReader(task.in_stream)

task.control(cst.TaskMode.TASK_COMMIT)
#task.start()
print('Wainting for trigger')
for ii in range(0,24):
    reader.read_many_sample(data, number_of_samples_per_channel=num_samples,timeout=30)
    comulativeData[ii*num_samples:(ii+1)*num_samples] = data


task.stop()
task.close()
print('Acquisition Finished')
plt.figure()
plt.plot(comulativeData)
plt.show()

I will appreciate if anybody as some other suggestions that can boost up this script.

 

Thanks in advance,

 

 

0 Kudos
Message 3 of 6
(2,722 Views)
Solution
Accepted by topic author pvaz789

Previously (when you were not calling Start), each time you called Read, your task was implicitly starting and then stopping.  It was acquiring num_samples and then stopping.  Since you restarted the task implicitly each time, the task knew it only expected num_samples each time.  Since you only Read num_samples each time, you never read beyond the last sample in memory.

 

Now that you are calling Start, the Read will no longer implicitly start the task.  Since the task is still only configured to acquire num_samples when you configure your timing, it will collect num_samples on the first trigger and then not inherently expect to acquire any more samples.  Thus, when you call Read a second time, it complains about you trying to read a sample beyond the last sample acquired.  The solution is to set the AI Retriggerable property to TRUE.  This is a property on X Series devices like yours (63xx).  Here's some documentation on how to do it in LabVIEW.  I suspect it will be similar in Python:  http://www.ni.com/tutorial/5382/en/

 

Here's an explanation from that document:  "To create a retriggerable Task with an X Series device, use the Start:More:Retriggerable property shown in Figure 2. This property specifies whether a finite task resets and waits for another hardware start trigger after the task completes. When you set this property to TRUE, the device performs a finite acquisition or generation each time the start trigger occurs until the task stops. The device ignores a trigger if it is in the process of acquiring or generating signals."

 

Make sure you set this property before you Commit the task.  Doing this will tell the task to collect num_samples on every trigger so you won't get your error.  Rest assured that calling Start will NOT make the task start acquiring samples if you have configured a trigger, which you have.

Message 4 of 6
(2,712 Views)

Thank again a lot for your comment. It was very useful. I have changed my reference trigger for a start trigger with the retriggerable property on. I can now achieve t1 near 1 ms which is fast enough for my application. As a downside, I have lost the possibility to record pretrigger samples. In fact I'm not fully aware of the differences between a reference trigger and a start trigger. Do you know some document that can explain in detail the functioning of NIDaq and nidaqmx ?

 

This is the final code.

 

import nidaqmx
import nidaqmx.constants as cst
import matplotlib.pyplot as plt
import numpy as np
from nidaqmx.stream_readers import AnalogSingleChannelReader

sample_time = 0.0005  # units = seconds
s_freq = 2000000
num_samples = int(sample_time*s_freq)
print(num_samples)

dt = 1/s_freq
diffTerminal = cst.TerminalConfiguration.RSE
Volts = cst.VoltageUnits.VOLTS
sampleMode = cst.AcquisitionType.FINITE
maxValue = 5
minValue = -5


task = nidaqmx.Task()

task.ai_channels.add_ai_voltage_chan("Dev1/ai0",name_to_assign_to_channel="mySignal", terminal_config=diffTerminal,
max_val=maxValue, min_val=minValue, units = Volts)

task.timing.cfg_samp_clk_timing(s_freq, sample_mode=sampleMode, samps_per_chan=num_samples)

task.ai_channels.ai_impedance = cst.Impedance1.FIFTY_OHMS

task.triggers.start_trigger.cfg_dig_edge_start_trig("PFI0", trigger_edge=cst.Edge.FALLING)
task.triggers.start_trigger.retriggerable = True

data = np.zeros((num_samples,), dtype=np.float64)
comulativeData = np.zeros((24*num_samples,),dtype=np.float64)

reader = AnalogSingleChannelReader(task.in_stream)

task.control(cst.TaskMode.TASK_COMMIT)
task.start()
print('Wainting for trigger')

for ii in range(0,24):
    reader.read_many_sample(data, number_of_samples_per_channel=num_samples,timeout=30)
    comulativeData[ii*num_samples:(ii+1)*num_samples] = data


task.stop()
task.close()
print('Acquisition Finished')
plt.figure()
plt.plot(comulativeData)
plt.show()

 

I'm getting a small warning in the end of the code but it is not critical:

Warning 200010 occurred.

Finite acquisition or generation has been stopped before the requested number of samples were acquired or generated.
  error_buffer.value.decode("utf-8"), error_code))

Thank you again

 

 

0 Kudos
Message 5 of 6
(2,692 Views)

A start trigger is used to capture N samples starting on the arrival of the trigger.  A reference trigger is used to capture N samples, where you can specify how many of those samples are pretrigger samples.  In your code, you have used:

task.triggers.start_trigger.retriggerable = True

but I'm guessing that there is an equivalent property for reference triggers, which would allow you to keep gathering pretrigger samples.  I know that the property at least exists in the C API (http://zone.ni.com/reference/en-XX/help/370471AM-01/mxcprop/attr311d/), so double check the python API to make sure it's there.  It's probably "task.trigger.reference_trigger.retriggerable".

 

My favorite reference material is the X Series User Manual (just google for it, and you'll find it).  You can also use the NI-DAQmx Help (again, google for that) to get more API-specific help.

 

The reason that you're getting the warning at the end of your code is that there's no way to tell the DAQmx driver how many trigger events to expect.  By setting the task to retriggerable, it will expect to retrigger indefinitely.  After you've read in however many trigger events' worth of data you care about, if another trigger event comes in, the DAQ device will still acquire a record.  If you call Stop while that record is acquiring, it will throw that warning, which lets you know that the DAQ was acquiring data when you stopped it.  It is safe to ignore this warning.  Alternately, if you don't call Stop and just call Clear, you might not get the warning at all (just a guess).  This is a totally valid thing to do since you don't need to use the task further along in your code, and Clear implicitly Stops the DAQ task.

0 Kudos
Message 6 of 6
(2,677 Views)