09-19-2022 10:42 AM
I have 2 DAQmx devices, one is used for Analog Output (AO), the other as an Analog Input (AI). Users should be able to send an array of voltages to the AO devices and read back the response with the AI devices. The catch is that while one array is being sent, a user should be able to send another array to a different channel, without interrupting other output channels. I already solved this by creating my own buffer and writing to the AO once a second.
My problem is how to synchronize the data from the AO to when the AI reads the response. The AI is always measuring all channels and spitting out data. The channels that have no data written to it on the corresponsing AO channels simply have the data discarded. What is a good way to know exactly when the AO started writing actual data and so to start recording data from the AI? Consider that it is not known beforehand when the user applies the voltage. (If it was, it would be simple, just write 0V until the ramp should start).
Here an illustration:
One idea was to use the "Current Read/Write Position", or just the iteration count of the loops. This worked for my first version. But now I want to change the voltage range of the AI channels. This requires the task to be stopped and started, resetting the "Current Read Position". So that's out. Then, I tried to just count the samples that are written and read. I.e. if I start writing a second array after 10,000 samples have been written, I can start reading at 10,000 samples (since the sample rate is the same). But changing the range takes a small amount of time and the write and read start to desync.
Here an example what I mean. The amount of samples written and read are exactly the same in the beginning. Then I start to change the range (lots of times for this examples) and the number of samples read desyncs from the write loop.
So my question is: what is the best way to know exactly when the AO starts to write (the dashed arrow in the above illustration).
I've included my prototype VI.
09-19-2022 11:20 AM
I don't have time to check your code at the moment, but if I understand correctly you have a free running AO task and a free running AI task, correct? If so, I'd recommend sharing clocks between them to guarantee that you're synced between tasks down to the individual sample.
This also means that you would likely want to restart your AI task when you restart your AO task, and use your current method to sync up the two. Would that work?
You could manually create the clock for your system using a counter channel. You could then start/stop the counter channel manually as much as you'd like, and you'd still maintain sample-level accuracy between all of the channels. It might be a little more straightforward to think about than trying to route the sample clock from the AO task to the AI task (or vice versa), though it's basically the same thing under the hood.
09-19-2022 11:32 AM - edited 09-19-2022 11:39 AM
[Edit, after seeing BertMcMahon's answer after I posted. What he suggested is normally the starting point I would also suggest for sync'ing AO and AI. And it remains a good idea as *part* of the solution here. But here's why I didn't offer it up as a full solution: because of the particular requirement to inject data onto a 2nd AO channel midstream, without interrupting the 1st AO channel, and then be able to know which AI sample corresponds to the beginning of this midstream injection. That's what takes us away from being able to establish hardware-level sync for different distinct channels with offset start & end times.]
Summary: you can't get there from here via queries to the read/write position and suchlike. There will be additional unknown and variable latency from the time you write AO data to a specific known write position in the task buffer and when it then gets transferred to the back of the (variable sized) line of the device's onboard FIFO buffer, and then works its way through to be generated as an actual signal.
But it seems like you *could* figure out what you need from the AI data itself. Considering that you're already discarding all 0 values, you just have to detect the samples where the AI data is sufficiently above your noise floor enough to keep it.
But if you need to know the difference between 0's to discard and 0's that are part of the new data set, you might need to do something like this (if you have spare channels): define a "sentinel" pair of AO / AI channels. Each time you start an AO write to one of your real channels, place a 1 sample blip on the sentinel channel to indicate "new data starting on one of the real channels". You could probably set the voltage level of the blip to correspond to which AO channel is starting. And if desired, you could do similarly for the last of the new samples, maybe make it a negative voltage output?
-Kevin P
09-19-2022 11:51 AM
Kevin got me thinking, there's something I'd like a little clarity on. Are you running multiple separate AO tasks, or one multichannel AO task? In my answer above I assumed a single AO task that would need to start and stop as needed, but if you have multiple tasks of which some start and some stop independently of each other then my answer won't work.
One addition to Kevin's great idea for a sentinal channel- you don't even need the sample to be a blip. Just add each channel together, and use numbers that are powers of 2 apart.
For example, you could use 0.1V for Channel 0 active, 0.2V for Channel 1 active, 0.4V for Channel 2 active, 0.8V for Channel 3 active, and so on. Then in your AI code you could analyze that channel to see both starts and stops of any given AO channel.
For example, if you read 0.3V on your Sentinel pair, you know that Channels 0 and 1 are active. If you read 0.9V, then you know Channels 0 and 3 are active, since no other combination of channels could get you 0.9 V.
09-19-2022 01:21 PM
Kudos for the powers of 2 idea as a great way to unambiguously encode which channels are active!
(Note: the 0.1V example would work for up to 6 active channels on typical 10V DAQ devices. Just read the sentinel, divide by 0.1V, round to *nearest* integer to stay immune to small amounts of signal noise, then see which bits of the integer are 1's instead of 0's. It didn't sound like you'd need more than 6 channels, but if you do, you could tweak your step size or perhaps use negative voltages as part of your encoding scheme.)
Q for the OP: which specific AO and AI devices are you using?
-Kevin P
09-20-2022 07:06 AM
Thanks for the replies. I am using an NI PCIe-6738 as Analog Output and an NI PCI-6225 as Analog Input.
The idea of using a 'sentinel' pair with powers of 2 in voltage as a check is very interesting! Unfortunately I'm already using all the available channels, so I cannot use that strategy.
At the moment I have 1 Analog Output task with all 32 channels. And 1 Analog Input task with 80 (64+16) channels (1 for voltage and 1 for current). The other 16 are used for additional sensors (not coupled to an analog output channel). So I don't really have any spare channels available.
09-20-2022 10:49 AM
As far as I know, that device can only have one hardware-timed AO task, which means you're using multichannel outputs. Thus, you'll have to start and stop the task each time you shift ranges, which makes me think that just sharing clocks and starting/stopping both tasks will be the simplest way to accomplish your goal.
If you're using software timed tasks, then your timing might not be as critical and you could just use system clock level stuff. What exactly are your timing requirements? Is this a super high speed thing or a 1 Hz task thing?
09-20-2022 04:47 PM
@Basjong53 wrote:
The idea of using a 'sentinel' pair with powers of 2 in voltage as a check is very interesting! Unfortunately I'm already using all the available channels, so I cannot use that strategy.
Actually, the 6225 would provide 2 additional AO channels that could be used for "sentinel" encoding. But with 32 channels to encode, you'd be right at the edge of the theoretical limits of the device (2^16 values for each channel, and each uses a 16-bit D/A). Probably not feasible in real life.
Further, splitting off the sentinel values to a separate task has another pitfall. Not the hardware sync -- that can be done the same way as the existing AO and AI tasks. It's more about a sort-of race condition between where new data gets written into the task buffer, and where DAQmx is working on transferring data from the task buffer to the device.
Let me describe the scenario that worries me and to help make things easier to picture, let's just suppose you always write in blocks that are 1 full buffer in size. That way every write will start at the beginning of the buffer. Meanwhile DAQmx has a pointer to track the location in the buffer that's next in line for transfer to the device. That pointer will keep circling around as data is continually being delivered. Now suppose you call DAQmx Write for one task when the pointer is approaching the end of the buffer. Most of your new data goes in immediately and will start being transferred very soon when the pointer wraps around. But then suppose that by the time you call DAQmx Write for the other task, the wrap around just happened. DAQmx won't let your new data "lap" the transfer pointer, so the old buffer will get generated 1 more time for this 2nd task.
-Kevin P
09-20-2022 05:00 PM
That last post of mine was more of a tangent. Back to some focus.
Consider the time period that you reconfigure your AI task for different voltage ranges. You aren't capturing any of the response data during that time -- is there really any reason to keep the AO task running then?
I think Bert's suggestion to configure them for hardware sync and then start/stop them *both* when you change ranges is part of the solution. The other part is to make the AO task non-regenerating. This will require your code to keep writing data to it regularly (even if it's the same data you fed it last iteration).
Then when it comes time to send out new data on one of the AO channels, you'll know how many samples you've previously written to the task, which also tells you which AI sample # the response should start at. Assuming proper hardware sync. (Shared sample clock, source of sample clock signal needs to start last.)
-Kevin P
09-20-2022 05:10 PM
I think he's already running in non-regeneration mode since he said he's writing to the AO buffer once a second, and the user can change voltages on the fly.