LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

NI-DAQmx one sample delay between analog output and analog input on USB-6421

Solved!
Go to solution

I am using NI-DAQmx (Python) to control a single channel analog read/write operation. I have Ao0 directly connected to Ai1 and Ai2 in parallel and Ai8 and Ai9 connected to ground for differential reading. The issue I'm having is that when reading any single channel, my analog input lags behind my analog output signal by one sample regardless of sampling frequency. When I try to do a multi-channel read/write only the first analog input channel has this lag.

I am using the analog input sample clock to drive the write task timing and have connected the write task start trigger to the read task rising trigger signal's rising edge (see code below).

I can work around this problem by always recording an additional sample on my first analog input channel and throwing away the first sample, but that seems really clunky for such a simple task. Is there a better way around this?

with nidaqmx.Task() as read_task, nidaqmx.Task() as write_task:
    # Configure input channel (leader clock)
    read_task.ai_channels.add_ai_voltage_chan(input_channel,
                                              min_val=-5, max_val=5,
                                              terminal_config=nidaqmx.constants.TerminalConfiguration.DIFF)     # We must use differential mode to avoid connecting the negative "AO From DAQ" reference to the 24V ground                                                                                              # For this example, the pins will be ao0 as + and ao8 as - (vertical pairs)
   
    read_task.timing.cfg_samp_clk_timing(rate=sample_freq,
                                         sample_mode=nidaqmx.constants.AcquisitionType.FINITE,                  # we know the number of samples to collect
                                         samps_per_chan=num_samples)
   
    # Configure output channel (follower clock)
    write_task.ao_channels.add_ao_voltage_chan(output_channel,
                                               min_val=-5, max_val=5)
   
    write_task.timing.cfg_samp_clk_timing(rate=sample_freq,
                                          source="/{}/ai/SampleClock".format(device_name),                      # sample using the output clock as the leader
                                          sample_mode=nidaqmx.constants.AcquisitionType.FINITE,                 # we know the number of samples to collect
                                          samps_per_chan=num_samples)


   
    write_task.triggers.start_trigger.cfg_dig_edge_start_trig(read_task.triggers.start_trigger.term,Edge.RISING)            # make sure both tasks start at the same time
    write_task.write(sig, auto_start=False)
    write_task.start()
    input_data = read_task.read(num_samples)



0 Kudos
Message 1 of 10
(1,091 Views)

I'm curious why you don't do this using LabVIEW and DAQmx.  Also, what are the timing parameters, i.e. sampling rate (1 Hz? 1 kHz? 1 MHz?), what are the # of samples per acquisition (1, 1000?), and why are you using Finite sampling?  When you have DAQ tasks that need to run "related" to each other, seems to me DAQmx provides the functionality (and may even have Examples!) that you need.  But then, I've never used Python (and feel some anxieties around snakes) ...

 

Bob Schor

0 Kudos
Message 2 of 10
(1,068 Views)

To address your questions in order:

 

I really want to use a python or other programming suite to interface with the DAQ because I'll need to combine DAQ data with data from other hardware/software components later and a flexible language like python running everything in the background will (I think) make my life easier.

I've tried running with a sampling rate ranging from 1Hz to 10kHz and consistently get a delay of exactly one sample between the analog input relative to the analog output. I've tried varying the number of samples from ~10 to ~1000 and haven't had any effect there either. I'm using finite sampling because I know how many samples I'm taking in advanced so I think that mode makes sense, but correct me if I'm wrong there.

The only thing that I can think of right now is that events happen in this order for each cycle of the analog input sampling clock:

1) Ai0 records its sample
2) Ao0 changes its state to match the desired voltage
3) all other analog in channels record their samples

 

The analog in channels are multiplexed on my DAQ, which could maybe introduce enough delay that by the time the first channel is done being read the analog output channel has had time to set to its new value. If that's the case I feel like there should be SOME way to correct for that by like adding a delay to the analog in sampling or making the analog in channels sample on the falling edge of the sampling clock or something.

0 Kudos
Message 3 of 10
(1,062 Views)

@Kentum wrote:


The only thing that I can think of right now is that events happen in this order for each cycle of the analog input sampling clock:

1) Ai0 records its sample
2) Ao0 changes its state to match the desired voltage
3) all other analog in channels record their samples


Not really. 1) and 2) happen pretty much exactly at the same time. BUT!

There is no quantum entanglement between the AO pin and the AI pin so the transfer between the analog output to the analog input is not instantaneous even if you could make the connecting wire 0mm long. And to make things worse, the Digital to Analog Converter needs some time to produce the analog voltage at its output, then it passes through an analog amplifier and then through some PCB traces to the IO connector, then through your wire to the analog input, from there through some PCB traces to the multiplexer, and from there to the analog input of the Analog to Digital Converter. This is together in the range of several microseconds, but by that time the analog input has long sampled its input and only sees the changed voltage at the next sample. Electrical signals travel at roughly 200'000 km/s through copper wires/traces. Yes that is almost the speed of light, but it is not instantaneous. And the speed through electric circuitry like DAC and analog amplifiers is even slower.

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
Message 4 of 10
(1,025 Views)

Your explanation is what I was thinking though I could have worded my explanation better perhaps. In any case, I guess the one sample offset issue is consistent enough that I can just hard-code an extra 1 sample for the AI channel and just discard the first sample from the first AI channel. It's a bit annoying there's not a more polished way to get around this issue, but thank you for your help!

0 Kudos
Message 5 of 10
(1,021 Views)

It actually depends on your speed. For really fast sampling rates (which is not likely a concern with a USB-6421 although above a few 100kS/s it could start to be relevant) the delay through everything may be larger than what the sample interval is.

 

Also there are different types of ADC and DAC hardware. So called Delta-Sigma DAC and ADCs require for instance a certain amount of clock cycles for a specific signal to pass through the converter as they basically have internally multiple stages that convert a successively smaller error signal to get the desired result. But your typical measurement DAQ hardware does not use such converters. They are mainly employed for high resolution, high speed conversion as they are relatively cost effective at the cost of a higher signal delay. But for high quality audio that delay is often not important and for vibration analysis it can be compensated for as it is a fixed amount of clock cycles.

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
0 Kudos
Message 6 of 10
(999 Views)
Solution
Accepted by topic author Kentum

There are ways to control the timing, but I'm afraid I'm no help at all with the syntax needed to implement in Python.  Here's a method I prefer:

 

1. Use an onboard counter to generate the pulse train that both AO and AI will use as their sample clock.  By using a counter output, you get to control the pulse width, i.e., the delay from AO to AI.

 

2. Be sure to set polarity such that AO generates its sample on the leading edge while the AI task initiates its sample on the trailing edge.

 

3. With this method, it's crucial to start both AO and AI tasks before starting the counter output task.

 

4. Triggering is not needed.  The shared use of a common sample clock signal (along with proper start sequencing) is sufficient to sync the tasks.

 

5. You can also optionally configure the AI "convert clock" rate if you need to multiplex across multiple AI channels.  Because part of the sample period is "used up" before the trailing edge initiates AI, you may need a faster convert rate to squeeze all the conversions into the remaining portion of the sample period.

 

 

-Kevin P

ALERT! LabVIEW's subscription-only policy came to an end (finally!). Unfortunately, pricing favors the captured and committed over new adopters -- so tread carefully.
Message 7 of 10
(942 Views)

While what you write is indeed an option, doing that even in LabVIEW is definitely not for the faint at heart. Going to program your own advanced counter task to trigger and synchronize multiple other tasks is one of the ZEN Master tasks to attempt with DAQmx. Absolutely possible if your hardware has enough hardware resources for it but an exercise in tinkering, trial and error and persistence.

 

Doing it in Python is just an extra trouble, as there are far less examples in the wild to do similar things than there are for LabVIEW. And without examples you are going to have to study the low level docs for your hardware in quite some depth as well as half of the vast DAQmx API.

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
0 Kudos
Message 8 of 10
(889 Views)

Hello all. Sorry for the delay in getting this response out there. I had reached out to NI support (who were very helpful) around the same time I made the original forum post. In the end, the solution I like the most has been to modify the "delay_from_samp_clk_delay" parameter of the read task. Combined with using the AI sample clock for my write task, this effectively creates a phase delay between my read and write tasks, which are still synchronous. The code for task setup now looks like this:

with nidaqmx.Task() as read_task, nidaqmx.Task() as write_task:
            # Configure input channel (leader clock)
            read_task.ai_channels.add_ai_voltage_chan(input_channels,
                                                    min_val=input_bounds[0], max_val=input_bounds[1],
                                                    terminal_config=terminal_config)

            read_task.timing.cfg_samp_clk_timing(rate=sample_update_rate,
                                                sample_mode=nidaqmx.constants.AcquisitionType.FINITE,           # we know the number of samples to collect
                                                samps_per_chan=num_samples + 1)                                 # we have to add 1 because first AI channel has 1 sample delay
                                                                               
            read_task.timing.delay_from_samp_clk_delay_units = nidaqmx.constants.DigitalWidthUnits.SECONDS
            read_task.timing.delay_from_samp_clk_delay = 10e-6                                                  # add a 10 microsecond delay between start of sample clock and when we take samples

            # Configure output channel (follower clock)
            write_task.ao_channels.add_ao_voltage_chan(output_channels,
                                                    min_val=output_bounds[0], max_val=output_bounds[1])
            write_task.timing.cfg_samp_clk_timing(rate=sample_update_rate,
                                                source=f"/{self.device_name}/ai/SampleClock",                   # sample using the output clock as the leader
                                                sample_mode=nidaqmx.constants.AcquisitionType.FINITE,           # we know the number of samples to collect
                                                samps_per_chan=num_samples)

            write_task.triggers.start_trigger.cfg_dig_edge_start_trig(read_task.triggers.start_trigger.term)    # make sure both tasks start at the same time (rising edge default)

 

The important lines are:

read_task.timing.delay_from_samp_clk_delay_units = nidaqmx.constants.DigitalWidthUnits.SECONDS
read_task.timing.delay_from_samp_clk_delay = 10e-6
 
A quick breakdown of the pros and cons of some ideas I tried:
  • Add extra sample to every AI channel, then delete first entry from first AI channel and last entry from subsequent AI channels
    • Pros:
      • Works regardless of sampling frequency
    • Cons:
      • After shifting AI channels, the first AI channel is effectively sampled on a delay of 1 sample period from every other AI channel. For low sampling frequencies and/or high amounts of system transience on the time scale of 1 sample period, this can be significant
      • Saving an extra data point every task (not a huge amount of data, but still)
  • 'Dummy' AI channel that samples first to take up the slew time of the AO channels
    • Pros:
      • Maintains synchronization between all AI channels (no weird 1 channel being delayed)
      • Works regardless of sampling frequency
    • Cons:
      • Saving a whole extra sample set of data each task
      • Wastes an AI channel reading nothing
  • Using delay_from_samp_clk_delay
    • Pros:
      • Can tune the delay to your specific device/setup
      • You know exactly what the delay is between AO start and first AI channel
    • Cons:
      • Delay has to be tuned properly for this to work regardless of sampling frequency

 

0 Kudos
Message 9 of 10
(475 Views)

In the Python nidaqmx environment, I can accomplish a similar thing by simply assigning a delay between AI sample clock and the read task using:

read_task.timing.delay_from_samp_clk_delay

Because I use the AI sample clock for both the read and write tasks, this effectively phase-shifts the read task slightly while keeping both tasks synchronized nicely just as your method would.
0 Kudos
Message 10 of 10
(483 Views)