Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Instability of continuously acquired signals #590

Open
muyichun opened this issue May 28, 2024 · 11 comments
Open

Instability of continuously acquired signals #590

muyichun opened this issue May 28, 2024 · 11 comments

Comments

@muyichun
Copy link

muyichun commented May 28, 2024

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

No tasks being tracked yet.
@zhindes
Copy link
Collaborator

zhindes commented May 28, 2024

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=""):

@muyichun
Copy link
Author

muyichun commented May 28, 2024

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!

@zhindes
Copy link
Collaborator

zhindes commented May 28, 2024

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)

@muyichun
Copy link
Author

image

It still doesn't seem to work.

@zhindes
Copy link
Collaborator

zhindes commented May 28, 2024

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

@muyichun
Copy link
Author

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.

@zhindes
Copy link
Collaborator

zhindes commented May 29, 2024

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?

@muyichun
Copy link
Author

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.

@zhindes
Copy link
Collaborator

zhindes commented May 30, 2024

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?

@RealBrandonChen
Copy link

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!

@zhindes
Copy link
Collaborator

zhindes commented Aug 7, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants