09-08-2022 09:21 AM
I have a task that involves an analog output that causes an LED to flash on and off repeatedly in a particular pattern. I need AO because the intensity of the LED needs to be set via the DAQ. The flashing continues until user interruption. I could achieve what I describe by storing the waveform in the device buffer and cycling through it repeatedly in regen mode. However, I am stumped by an additional constraint. If the task ends with the LED beam on, I need to ramp down intensity gradually over about 100 ms. This must only happen at the end of the task and not during the on/off cycling. I can not predict predict when this needs to happen. I believe this may be hard or impossible to do using buffered acquisition (at least no way that does not involve an interruption or a latency); is this the case?
One solution I am contemplating is to add an optional RC circuit to the LED AO control line to filter the offset and so add a decay. I am thinking of gating the AO signal through this circuit using a transistor or a relay that I activate with a TTL signal when the user terminates the process. Is something like this the only option I have?
Solved! Go to Solution.
09-08-2022 12:07 PM
09-08-2022 12:15 PM
You can do it without *interruption*, but probably not without *latency*.
In older times, you couldn't write new data to a regenerating AO task but I was surprised to learn a few years ago that it had become possible. I don't really know when the support was added to DAQmx, I hadn't bothered trying for many years prior.
Anyway, you should be able to do 2 full-buffer writes back to back to accomplish what you need.
- write a full buffer that includes the ramp-down to zero from whatever was the last value in the original regenerating buffer. Pad with more zeros at the end if needed.
- write a second full buffer of nothing but zeros
- wait long enough to know that your ramp & zeros have worked through the task buffer and the device FIFO, then stop the task.
The amount of latency will be a combo of your task buffer size, where your generation task is within that buffer at the time you want to initiate the ramp-down, and the size & fill state of the device's onboard FIFO buffer (which is often configurable with DAQmx properties).
I've found that different devices use different methods to control the FIFO buffer. For some, it's a direct property to set the buffer size. For others, you get fuzzy control with the "data transfer request condition" which can be one of (less than full, half full, almost empty). There are yet other related properties that apply to USB-connected devices that I know less about.
-Kevin P
09-08-2022 02:38 PM
I'm using DAQmx via thin wrappers in MATLAB or maybe also NI's library in Python.
09-08-2022 02:43 PM
Anyway, you should be able to do 2 full-buffer writes back to back to accomplish what you need.
- write a full buffer that includes the ramp-down to zero from whatever was the last value in the original regenerating buffer. Pad with more zeros at the end if needed.
- write a second full buffer of nothing but zeros
Hmmm... So you are saying I can simply write to the buffer without stopping the task and this will play out what I need? That could well do what I need as I can keep the buffer small. I was planning on using a USB DAQ but I'd think again if it didn't work with one of these.
09-08-2022 10:26 PM
... So you are saying I can simply write to the buffer without stopping the task and this will play out what I need?
Yes. Here's a few more tidbits of info about DAQmx & buffer management:
1. The size of the task buffer is typically set by the # of samples you write to the task prior to starting it.
2. Continuous output tasks default to regeneration mode, treating the task buffer as circular, starting over from the beginning when it gets to the end.
3. DAQmx *also* manages the process of transferring data from the buffer down to the device, remembering where it left off. It knows which samples have been transferred (and are thus eligible for being overwritten from your app code) and which haven't (and are thus *ineligible*).
3. DAQmx will remember where you left off writing and subsequent writes will start at the next sample position. So if your initial write (prior to starting) sets the buffer size and fills the buffer, the next write will replace samples starting at the beginning of the buffer.
4. If your attempt to write samples would overwrite existing samples that had not yet been transferred to the device, DAQmx will try to wait for space to free up to allow the write to succeed.
This is why I suggest to write a whole buffer full that starts with a ramp down and pads with zeros if needed, followed by immediately writing another full buffer of zeros. The first write makes the ramp down start from the beginning of the buffer whenever the generation task gets back around to the beginning again. Because that first write fills the buffer, the second write will wait for space as it replaces the ramp with zeros (preventing you from wrapping around to start a 2nd ramp down).
Try to get that much of things functional and working, then come on back if you need to address latency issues. Those may require more advanced settings related to the device's onboard FIFO buffer and parameters that control the way DAQmx feeds the board with data, trading off latency with robustness.
-Kevin P
09-09-2022 03:01 AM
Thanks, I understand. I'll give it a shot when I have a chance and see how it goes.
09-09-2022 04:16 AM
Thanks, that's clear. It seems this ought to work if the two writes are placed in a queue. Otherwise something unexpected might happen. e.g. the FIFO ends up being is filled with the zeros instead of the ramp. I will try, though. Hopefully I get a chance to give this a shot during the middle of next week.
09-10-2022 06:15 AM
Successive writes *do* behave like a queue. The second write starts at the position where the first write left off, so all the samples from the first write will be transferred (and then generated) before any from the second write.
-Kevin P
09-15-2022 05:54 AM - edited 09-15-2022 06:46 AM
Ok, I'm trying this with a USB-6361 but writing to the buffer after the task has started does not alter what is played out of the analog output. I'm using MATLAB and accessing DAQmx via .NET --
% Build one cycle of a sine wave to play through the AO line (note the transpose)
waveform = sin(linspace(-pi,pi, sampleRate))';
% Instantiate the task and add a channel
task = NationalInstruments.DAQmx.Task;
task.AOChannels.CreateVoltageChannel('Dev1/AO0', 'myTask',-10, 10, AOVoltageUnits.Volts);
% Set channel properties
task.Stream.WriteRegenerationMode = WriteRegenerationMode.AllowRegeneration;
task.Timing.ConfigureSampleClock('',sampleRate, ...
SampleClockActiveEdge.Rising, ...
SampleQuantityMode.ContinuousSamples, ...
length(waveform))
% Write data to the buffer
taskWriter = AnalogSingleChannelWriter(task.Stream);
taskWriter.WriteMultiSample(false, waveform);
% Start the task. The waveform plays out of AO0 as expected.
task.Start;
If I now run taskWriter.WriteMultiSample(false, waveform*0.15); I see no change to the output unless I stop then restart the task. The write also blocks the MATLAB CLI for a second or two.
I also tried two successive writes and it seemed to have written to the buffer only the second one after I stopped/started.
I expect I am setting up the task incorrectly?