nidaqmx-python icon indicating copy to clipboard operation
nidaqmx-python copied to clipboard

Instability of continuously acquired signals

Open muyichun opened this issue 1 year ago • 11 comments

image

import os
import time
import h5py
import nidaqmx
import numpy as np
from nidaqmx.constants import AcquisitionType, Edge
def main():
    tmp_list = []
    data = np.zeros(400)
    with nidaqmx.Task() as task:
        def callback(task_handle, every_n_samples_event_type, number_of_samples, callback_data):
            # reader.read_many_sample(data, number_of_samples_per_channel=number_of_samples,timeout=0)
            # print(data[0])
            arr = task.read(number_of_samples_per_channel=number_of_samples)
            tmp_list.extend(arr)
            return 0
        task.ai_channels.add_ai_voltage_chan("Dev1/ai0")
        task.timing.cfg_samp_clk_timing(2e6, "", Edge.RISING, sample_mode=AcquisitionType.CONTINUOUS)
        task.triggers.start_trigger.cfg_dig_edge_start_trig("/Dev1/PFI0", Edge.RISING)
        task.register_every_n_samples_acquired_into_buffer_event(400, callback)
        task.start()
        input("Running task. Press Enter to stop.\n")
        task.stop()
        path = "D:/HGY_DATA/test_daq/xj1.h5"
        os.remove(path)
        with h5py.File(path, 'a') as file:
            dataset = file.create_dataset(
                'Imaging data_' + time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
                , shape=(0, 400)
                , maxshape=(None, 400)
                , dtype=np.float32
                , chunks=(1, 400))
            # 初始化创建字符串属性
            dataset.attrs['Display Time'] = 0
            # 追加写入新数据
            current_len = len(dataset)
            result_data = np.array(tmp_list).reshape((-1, 400))
            dataset.resize(current_len + result_data.shape[0], axis=0)
            dataset[current_len:] = result_data
    print("over")
if __name__ == "__main__":
    main()

Acquisition Card:NI PCIe-6361

Same environment, I use the Python API to continuously capture a standard sine wave from a signal generator. As a result, the captured data is not one cycle per line (the first amplitude value of each line is decreasing gradually), but it is normal if the data is captured using LabVIew. This question has been bothering me for days!!!

### Tasks

muyichun avatar May 28 '24 06:05 muyichun

Can you share a screenshot of your LV code and the values of any controls that are being passed into the DAQmx VIs? For what its worth, my hunch is that your channel config may be different. This code is hiding a lot of default parameter values, the one that might be at fault is the Terminal Configuration.

        task.ai_channels.add_ai_voltage_chan("Dev1/ai0")

The full declaration:

    def add_ai_voltage_chan(
            self, physical_channel, name_to_assign_to_channel="",
            terminal_config=TerminalConfiguration.DEFAULT, min_val=-5.0,
            max_val=5.0, units=VoltageUnits.VOLTS, custom_scale_name=""):

zhindes avatar May 28 '24 13:05 zhindes

image image image image

Thank you very much for your prompt reply!

Above is a screenshot of LabVIew, which is one that collects data and then saves it to disk. The sampling frequency is 2M/s and the signal generator 5000Hz. I did find the Python API manual, but still don't know what to do with it!

muyichun avatar May 28 '24 15:05 muyichun

The primary configuration difference I see is create channel, like I expected. Try this in your Python code:

        task.ai_channels.add_ai_voltage_chan("Dev1/ai0", terminal_config=TerminalConfiguration.DIFF, min_val=-10.0, max_val=10.0)

zhindes avatar May 28 '24 15:05 zhindes

image

It still doesn't seem to work.

muyichun avatar May 28 '24 16:05 muyichun

Oh, bah. I thought we were chasing an absolute accuracy issue. I see that those samples are the first point in a sine-wave, and you even said:

As a result, the captured data is not one cycle per line (the first amplitude value of each line is decreasing gradually), but it is normal if the data is captured using LabVIew.

My reading comprehension is poor today, I guess.

The other major thing that your VI is doing that Python is that it is a Finite Retriggered Acquisition. Try this:

        task.timing.cfg_samp_clk_timing(2e6, "", Edge.RISING, sample_mode=AcquisitionType.Finite, samps_per_chan=400)
        task.triggers.start_trigger.cfg_dig_edge_start_trig("/Dev1/PFI0", Edge.RISING)
        task.triggers.start_trigger.retriggerable = True

zhindes avatar May 28 '24 20:05 zhindes

Yes.It's too hard for me. Finite Retriggered Acquisition should be used, but how do you guarantee that he is infinitely acquiring? Right now it stops the task automatically after it has collected enough samples. I tried using callback function and writing in while loop respectively, it can't acquire infinitely.

muyichun avatar May 29 '24 06:05 muyichun

A finite retriggered acq shouldn't stop; it effectively behaves as a continuous acquisition. Can you share the most recent code that you have tried?

zhindes avatar May 29 '24 13:05 zhindes

image

import nidaqmx
from nidaqmx.constants import AcquisitionType, Edge, TerminalConfiguration, OverwriteMode
def main():
    with nidaqmx.Task() as task:
        def callback(task_handle, every_n_samples_event_type, number_of_samples, callback_data):
            arr = task.read(number_of_samples_per_channel=400)
            print(arr[0])
            return 0

        task.ai_channels.add_ai_voltage_chan("Dev1/ai0",terminal_config=TerminalConfiguration.DIFF, min_val=-10.0, max_val=10.0)
        task.timing.cfg_samp_clk_timing(2e6, "", Edge.RISING, sample_mode=AcquisitionType.FINITE, samps_per_chan=400*2000000)
        task.triggers.start_trigger.cfg_dig_edge_start_trig("/Dev1/PFI0", Edge.RISING)
        task.triggers.start_trigger.retriggerable = True
        task.register_every_n_samples_acquired_into_buffer_event(400, callback)
        task.start()
        input("Running task. Press Enter to stop.\n")
    print("over")

if __name__ == "__main__":
    main()

When I adjust the value of samps_per_chan, the first point of the sine function still changes when set very large.

My need is to keep getting the standard sine wave data point set over and over again, but the results are changing. It's too hard for me.

muyichun avatar May 30 '24 11:05 muyichun

samps_per_chan=400*2000000

That is acquiring a lot of data per trigger. The 400 samples callback will be invoked 2 million times per trigger. I am assuming 400 samples is a single cycle of the sine wave. If so, what you're seeing is time quantization errors from the clocks drifting between your acquisition device (the 6361) and whatever is generating the Sine Wave over a long period of time.

The clock of the DAQ device is a 100MHz oscillator that has up to 50ppm of error. So imagine your 6361 and your sinewave generation have 50ppm of error in opposite directions. The 6361 clock is actually 99.995MHz. In 400 seconds (400 * 2e6 samples acquired at 2e6 sample rate), we would expect 100M * 400 clock ticks. At 99.995MHz that will take 400.02 seconds. If your sinewave generation is using a similar clock, but with the opposite error, 100.005MHz, then those same number of clock ticks will take 399.98 seconds. That is .04 seconds of skew between the two clocks. When you're sampling 2M times per second, that drift is significant - .04 seconds is 80,000 samples.

I don't know where the PFI0 signal is coming from, but if its somehow referenced to that sine wave, then you're good.

When I adjust the value of samps_per_chan, the first point of the sine function still changes when set very large.

Correct. You want that to be a single cycle of your sine wave because of the drift I describe above. 400 samples, maybe?

zhindes avatar May 30 '24 13:05 zhindes

Hi,

Follow up this question, when I apply the code task.triggers.start_trigger.retriggerable = True for synchronization task from examples/analog_in/cont_voltage_acq_int_clk_dig_start_retrig.py, it always returns error:

nidaqmx.errors.DaqError: Specified property is not supported by the device or is not applicable to the task.

My device is USB-6210 and I checked it supports the start trigger. I wonder if it is the bug or that my device doesn't support the retrigger? Thank you!

RealBrandonChen avatar Aug 07 '24 17:08 RealBrandonChen

Sadly that device does not support retriggerable Analog Input. Sorry!

zhindes avatar Aug 07 '24 18:08 zhindes