Multifunction DAQ

cancel
Showing results for 
Search instead for 
Did you mean: 

DAQmx Sync Analog inputs

Solved!
Go to solution

Dear all,

 

I have a CDAQ 9189 with one 9215 card and three 9229 cards. So far I was using LabVIEW to collect continous data with syncronised tasks.
Now I want to use the DAQmx to do the same thing under Julia with the NIDAQ package (https://github.com/JaneliaSciComp/NIDAQ.jl), which has a long list of ccalls to the DAQmx API. I have not tried myself, but the same functionality is also in Python available.

 

So far I managed to collect the data and it works well, but I did not manage to synchronise the tasks.
What under LabVIEW works (see snippet), I don´t mange to make it work with the DAQmx API.


I looked at examples and online documentation and I found I need to use DAQmxGet/SetSampClkTimebaseSrc and DAQmxGet/SetSampClkTimebaseRate (from the LabVIEW code I did not find what function the property nodes are calling.)
When I call TimebaseSrc or TimebaseRate I get always a -200077 error (Requested value is not a supported value for this property.).

 

What I am doing wrong? Under LV I don´t get any error and it works even with simulated devices ...

 

Any help would be appreciated, even in Python would be fine.

 

Best,

Rubens

 

I am using:

NI-DAQmx 20.1

LabVIEW 2018 SP1
Windows 10

 

```
NIDAQ.DAQmxReserveNetworkDevice(dev_name,reinterpret(Bool32,NIDAQ.FALSE))

task1 = analog_input(dev_name * "Mod1/ai0", range = [-10, 10])
# the analog_input is a convenience function for:
#      CreateAIVoltageChan(t.th,
#                        Ref(codeunits(channel),1),
#                        Ref(codeunits(""), 1),
#                        terminal_config,
#                        range[1], range[2],
#                        Val_Volts,
#                        convert(Ptr{UInt8},C_NULL))

mod1_fs = 100_000
NIDAQ.CfgSampClkTiming(task1.th, convert(Ref{UInt8},b""), mod1_fs, NIDAQ.Val_Rising, NIDAQ.Val_ContSamps, mod1_fs * 2)

# th is taskhandle, # b"" is an UInt8 empty string

 

 

task2 = analog_input(dev_name * "Mod2/ai0:3", range = [-60, 60])
mod2_fs = 10_000
analog_input(task2, dev_name * "Mod3/ai0:3", range = [-60, 60])
analog_input(task2, dev_name * "Mod4/ai0:3", range = [-60, 60])
NIDAQ.CfgSampClkTiming(task2.th, convert(Ref{UInt8},b""), mod2_fs, NIDAQ.Val_Rising, NIDAQ.Val_ContSamps, mod2_fs * 2)

 

# trying to sync

rate = 0.
NIDAQ.DAQmxGetSampClkTimebaseRate(task1.th, Ref(rate)) # -> error -200077

 

src=convert(Ref{UInt8},b"")
NIDAQ.DAQmxGetSampClkTimebaseSrc(task.th, src, UInt32(1)) # -> error -200077

 

# however this works:
sz = NIDAQ.DAQmxGetDevAIVoltageRngs(Ref(codeunits(dev_name),1), convert(Ptr{Float64},C_NULL), UInt32(0)) # returns 2
data = zeros(sz)
NIDAQ.DAQmxGetDevAIVoltageRngs(Ref(codeunits(dev_name),1), Ref(data,1), UInt32(sz))

0 Kudos
Message 1 of 9
(2,170 Views)
Hi,
 
I also tried this, but no success:
 
data_rate = convert(Ptr{Float64},C_NULL)
NIDAQ.DAQmxGetSampClkTimebaseRate(task.th,data_rate)
 
data_rate = convert(Ptr{Float64},1)
NIDAQ.DAQmxGetSampClkTimebaseRate(task.th,data_rate)
 
data_rate = convert(Ptr{Float64},0)
NIDAQ.DAQmxGetSampClkTimebaseRate(task.th,data_rate)
 
Is the DAQmx driver sufficient or are other drivers involved? In the NI Max I have looked at the routings and the sync is supported...
 
Thanks,
Rubens
0 Kudos
Message 2 of 9
(2,103 Views)

I only program in LabVIEW and don't know any text API syntax in detail.

 

I think I see a conceptual problem though and it's a pretty subtle one.   There's an important difference between a Sample Clock (which you probably *should* be configuring) and a Sample Clock TIMEBASE (which you *are* configuring).

 

Here's the normal, default way things work when you don't go out of your way to specify these timing signals a different way. 

    The sample clock timebase is free running internal clock at something like 80 MHz or 100 MHz.  The timebase is always pulsing, it neither starts nor stops with your task.

    The sample clock starts and stops when your task starts and stops and *this* fact is why it's useful for sync.  It is derived by dividing down the timebase by some integer.

 

In your LabVIEW code, you configured your tasks to share the sample clock *timebase* which didn't really help you any because they were going to use the same chassis timebase *anyway*.  You mainly accomplished sync there with the trigger.  The so-called "Sync Pulse" was almost certainly unnecessary.

    You could have accomplished sync by sharing the sample clock itself, in which case you shouldn't need to configure a trigger or Sync Pulse.

 

What I can make of your text code only ever addresses the sample clock timebase, which doesn't help any for sync.  You should instead share the sample clock.  (Or you could configure for a shared start trigger and the same sample rate.)

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
0 Kudos
Message 3 of 9
(2,098 Views)

Kevin is right in the conceptual problem of sample clock timebase and sample clock. 9229 is also a DSA module that has its own oversample clock of 12.8 MHz.

 

From the screenshot of the block diagram, these module seems to be in the same cDAQ chassis. Is there any reason why they are not in the same DAQmx AI task? For cDAQ modules in the same cDAQ chassis or in multiple chassis synchronized by 9469 sync module/TSN network, they can be put in the same DAQmx AI task and DAQmx will take care of all the synchronization in the background.

 

 

 

0 Kudos
Message 4 of 9
(2,087 Views)

Thank you Kevin, I will then remove then the Get/Set Timebase. I actually thought that each task had its sample clock timebase and for this reason the cDAQ allows just 2 tasks.

 

Sorry, but I don´t understand when you say that I am setting the sample clock timebase and not the sample clock. In the snippet I create first the two tasks, then for each I set the sampling mode and rate with DAQmx Timing (Sample Clock).vi (DAQCfgSampClkTiming for the code) and then I do those unnecessary things with the timebase. Where am I setting the sample clock timebase?

 

@Jerry_X: I didn´t know about the 9469, I´ll keep it in mind in case in the future I have to sync two chassis. Thanks.

Yes, the cards are on the same chassis. I need two tasks because I sample with 10 kHz the NI9229 channels and with 100kHz the NI9215 channels.

 

That´s why I don´t use just one task or the same sample clock.

 

I tried to go ahead in the code now with the DAQmxGetStartTrigTerm, but unfortunately I am getting again the same  -200077 error.

I don´t think I am calling the function in a wrong way, since the DAQmxGetTaskName has similar input types and it works.
Can´t figure it out, why it doesn´t work with the code...
 
Thank you for your suggestions,
Rubens

 

0 Kudos
Message 5 of 9
(2,080 Views)

I see, in the screenshot both tasks are set to the same sample clock rate, I thought that was what you intended.

 

Okay, so the 3 9229 modules are in one task and the 1 9215 module is in a task by itself. 9215 is not a DSA module so it does not need a sync pulse, so we do not need it. All we need is to share the start trigger in your case. We should not have to set the sample clock timebase at all.

 

cDAQ has 3 AI timing engines. The driver picks an available timing engine when the AI task moves to reserve state. For DAQmxGetStartTrigTerm to succeed, we will have to transition the task to reserve.

 

However the error you are getting -200077, kErrorInvalidAttributeValue, just means that an attribute was set to an invalid value, so double check any attribute you are setting.

Message 6 of 9
(2,070 Views)

I hadn't caught onto the fact that you have a mix of Delta-Sigma and SAR devices and that changes things.

 

For long-term sync, problems can arise if you only sync the tasks' start times with a start trigger.  The DSA devices will be using one of their own free-running timebases to establish their sample clock while the SAR device will use a chassis timebase.  Over a period of time, these will drift relative to one another.

 

Like Jerry_X, I had also previously thought you wanted both tasks sampling at the same rate.   So now I need to largely change the advice I gave earlier.  

 

Now I know that sharing a sample clock isn't a good option, and in fact your best option is to share the sample clock timebase much as you were trying to do in the first place. 

     I'm pretty sure it'll be *necessary* to export the sample clock timebase from the task for the 9229's and import it for use by the 9215.  The 9215 task should then choose the integer timebase divisor that results in a sample rate as near as possible to your desired one.   Since the 9229's timebase is 12.8 MHz, a divisor of 128 should give you exactly the 100 kHz you want for the 9215.

 

You should start the 9215 task first so it'll be ready to receive the first sample clock generated when you start the 9229's task.

 

That takes care of the hardware side of sync, but you'll still need to deal with the filter delay inherent in the 9229's to establish full sync of your data streams.   Sorry, only time to mention things now, can't give a full tutorial.

 

 

-Kevin P

CAUTION! New LabVIEW adopters -- it's too late for me, but you *can* save yourself. The new subscription policy for LabVIEW puts NI's hand in your wallet for the rest of your working life. Are you sure you're *that* dedicated to LabVIEW? (Summary of my reasons in this post, part of a voluminous thread of mostly complaints starting here).
Message 7 of 9
(1,966 Views)

Thank you Jerry_X, I had set in my script the wrong fs for the NI9229 (although here in the forum the fs was valid, in my script I had 100_000, also one 0 too much).

Curiously, the DAQmxCfgSampClkTiming did not throw any error or warning (exit 0).

This works now:

NIDAQ.DAQmxTaskControl(task1.th,NIDAQ.DAQmx_Val_Task_Reserve)
NIDAQ.DAQmxTaskControl(task2.th,NIDAQ.DAQmx_Val_Task_Reserve)
#
sz = NIDAQ.DAQmxGetStartTrigTerm(task1.th, convert(Ptr{UInt8},C_NULL), UInt32(0))
data = zeros(UInt8,sz)
NIDAQ.DAQmxGetStartTrigTerm(task1.th, Ref(data,1), UInt32(sz))
NIDAQ.DAQmxCfgDigEdgeStartTrig(task2.th,Ref(data,1),NIDAQ.DAQmx_Val_Rising)

 

 

Thank you Kevin P, I wasn´t aware that the NI9229 is a SAR device. That´s probably why in the VI that I inherited, the sample clock timebase was used for sync.

 

I will try that asap I am back in office.

 

Thank you for pointing out the filter delay: in the datasheet I see that the delay is 40 * 5 / 512 / fs + 3.3µs. So, if I am correct for fs 10_000 S/s I have a delay of about 0.042ms. Therefore, the signal coming from the NI9215 sampled at 100_000 S/s (0.010ms) should be ahead of about 4 samples.

 

In my application the NI9229 is sampling at 10_000 S/s (0.1ms). Therefore, I believe that the delay of 1/4 of sample will not influence much my results. Anyway, I will put it in the todo list, thank you.

 

Best,

Rubens

0 Kudos
Message 8 of 9
(1,954 Views)
Solution
Accepted by topic author RubensR

Sorry for late reply. Below how I managed to sync the modules with julia.

It works under Windows and Linux (Ubuntu 20.04).

 

Thanks for all the support!

 

Best,

Rubens

 

# get the timebase source and rate
empty_string = convert(Ref{UInt8},b"") 
sz  = NIDAQ.DAQmxGetSampClkTimebaseSrc(task_master.th, empty_string, UInt32(0))
sz < 0 && error("Error at DAQmxGetSampClkTimebaseSrc, error code: $sz")

timebase_master_src=zeros(UInt8, sz)
NIDAQ.DAQmxGetSampClkTimebaseSrc(task_master.th, Ref(timebase_master_src,1), UInt32(sz))

timebase_master_rate = Ref{Float64}(0.)
NIDAQ.DAQmxGetSampClkTimebaseRate(task_master.th, timebase_master_rate)

# set the timebase source and rate
NIDAQ.DAQmxSetSampClkTimebaseSrc(task_slave.th,  timebase_master_src)
NIDAQ.DAQmxSetSampClkTimebaseRate(task_slave.th, timebase_master_rate[])


# set starting trigger (the slave task must start first!)
    
err = NIDAQ.DAQmxTaskControl(task_master.th, NIDAQ.DAQmx_Val_Task_Reserve) 
if err != 0
    @warn("The task was already reserved, trying to unreseve first, $err")
        
    NIDAQ.DAQmxTaskControl(task_master.th, NIDAQ.DAQmx_Val_Task_Abort)
    NIDAQ.DAQmxTaskControl(task_master.th, NIDAQ.DAQmx_Val_Task_Reserve)
end

sz = NIDAQ.DAQmxGetStartTrigTerm(task_master.th, convert(Ptr{UInt8},C_NULL), UInt32(0))
sz < 0 && error("Error at DAQmxGetStartTrigTerm, error code: $sz")

task_master_trig = zeros(UInt8,sz)
NIDAQ.DAQmxGetStartTrigTerm(task_master.th,   Ref(task_master_trig,1), UInt32(sz))
NIDAQ.DAQmxCfgDigEdgeStartTrig(task_slave.th, Ref(task_master_trig,1), NIDAQ.DAQmx_Val_Rising)

 

0 Kudos
Message 9 of 9
(1,837 Views)