LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Channel Wires with FOR loop

Solved!
Go to solution

Hi,

 

I want to acquire analog input from a sensor and save it to a file. To do this I am using producer-consumer WHILE loops. Inside the producer loop is a FOR loop within a FOR loop. The outer FOR loop is for the number of files I want to save. The inner FOR loop determines how many samples are saved. I then use Channel wires to stream the data between the WHILE loops.

 

I would like to save multiple files, each having 30 samples, and an index in its filename (1,2,3,....).  

 

The attached VI doesn't send the data across to the consumer loop until the outer FOR loop is exited. How do I resolve this issue?

 

My next step is to modify the code for Analog 2D DBL and use TDMS. 

 

Thanks,

lza

Download All
0 Kudos
Message 1 of 14
(532 Views)
Solution
Accepted by topic author laz_2331

In principle, you can run three Streams in parallel (as you have done, the Producer having an "Index" (I32) that "counts" the sample (counting from 1 to # of Files, the "N" of the For loop, plus a concatenated array (3 back-to-back) of A/D samples, plus another loop just carrying a "Stop" command, and running once in the outer While loop.

 

The Stream channel is distinguished by having support for "Sentinels", typically a Boolean that signals "All Done" or "Data Bad".  The key to using the Stream Sentinels (one called "element valid? (T)", the other "last element? (F)") is that if you write to one or both Sentinels on the Producer end, you must read from those Sentinels on the Consumer side.  You don't do this -- you only write "Last Element" to the Data Array Stream, which causes the Stream to stop working after the Consumer "see" the "Last Element".

 

Another problem is that if your outmost While Loop runs more than once, you'll end up using the same File Names when you start processing data from the second iteration of the Producer loop!  So if the For Loop goes "1, 2, 3" and the While Loop goes "1, 2, 3, ...", the Producer will create names "X1, X2, X3, X1, X2, X3, ...".

 

How do you fix this?

 

Before answering this, here's some advice about the Producer/Consumer process -- the Producer runs at a pace determined by the "Data", i.e. by the (timed) data acquisition.  Typically this does not take 100% of the CPU's time.  The Consumer, on the other hand, runs "as fast as it can consume the data", which is typically faster than the production of the data.  Because the two loops are not "connected" by a (regular) wire, but by a Channel Wire, the two run concurrently, with the Stream providing buffering for instances when the Consumer needs "a little more time" (such as when opening a data file -- writing is typically much faster than file look-up and creation).  So take the Wait (ms) out of the Consumer Loop, please -- it runs "as fast as" the Producer "feeds" it data.

 

Here's what you should do:  learn about State Machines, which can be a simple as a Case Statement inside a While Loop.  The Case Statement will have the names of the States (I'm oversimplifying ...). 

 

Here are the things (States) you want to do (sequentially):

  1. Initialize.  Set up your DAQ device. save the Task Wire in a Shift register that runs across the While and through the Case (so all States have access to it).
  2. Generate.  This has the nested For Loops.  This "Produces" data, and sends it on to the Consumer.
  3. Check for End of Loop (which is where you put the check for an Error and look at the value of Stop.
  4. Exit from Producer.

The key steps are 3 and 4.  If there is no Error, and the Stop button isn't pressed, you want to go back to Step 2 and keep generating, otherwise you go to Step 4.  In Step 4, you send one more Stream message, with "last element?" set to True, and "valid data?" set to False, and you wire True to the While Loop's Stop Terminal.  That stops the Producer.

 

On the Consumer side, put all the "Consuming code" (building the file name and writing the File) inside a Case Statement, with the Channel Reader "outside" the Case.  Wire the Data into the Case, and wire "last element?" to the Case Selector.  The code you just put into the Case Statement is the False case (you want to process everything except the final Sentinel value), and you leave the True case empty, but wire "last element?" to the Stop.  And did I say "Get rid of the Wait in the Consumer"?  [The Consumer can't run unless the Producer sends it data, and when the Producer sends its final "last element?" data, the Consumer can simply exit].

 

Whew.  Now fix your code.

 

Bob Schor 

 

So what do you do?  Replace the three Streams by a single Stream and use the Sentinels to tell the Consumer that the Producer has finished.  Note that you can make a cluster with "Sequence #" (the index of the For Loop +1) and "Triple Sample" (the output from the For Loop).

 

So how do you stop the loop(s)?  You need to send one more Stream element with "compatible data" (any I32 value for Sequence # and an empty 1D Array of Dbl for "A/D Data") through a Stream Writer with "element valid?" of False and "last element?" of True.  How do you do this?

 

Start by enclosing the nested For Loops in a Case Statement.  Y

Message 2 of 14
(497 Views)

I love that you are building your own application, and I also find it ironic that when you finish your next round of upgrades (multiple channels and TXT --> TDMS), you will have reproduced functionality in the shipping DAQmx example (examples\DAQmx\Analog Input\Voltage - Continuous Input.vi) which supports file spanning with drop of one DAQmx property node:

dsbNI_0-1727921310721.png


Follow Bob's guidance to develop a more robust application. Use FlexLogger Lite (Free) or the DAQmx example until you get your application working the way you want.

Doug
NI Sound and Vibration
0 Kudos
Message 3 of 14
(469 Views)

@Bob_Schor, please see the attached VI.

 

I am still figuring out how to convert this into a state machine for my overall task, which would include other functions and associated tasks. The attached VI makes use of channel wires to transmit AI samples read periodically once a user input is provided (Boolean switch). It appears to be doing what I expect it to, but I want to ensure I am not making any major slip-ups in channel wires (especially once stopping the loops).

 

Thanks,

lza

0 Kudos
Message 4 of 14
(389 Views)

There are several (other) issues with your code, but the basic question was "Channel Wires with FOR Loop".  I found a demo I wrote several years ago that talked about using a Messenger Channel Wire to create a Channel Message Handler, and also using a Stream Channel Wire to implement a Producer/Consumer design.

 

Turns out the Demo is more like a State Machine.  I'll try to work up a Channel Message Handler (BS Style) Demo over the weekend, maybe one that includes DAQmx (where you also need some advice).

 

Here is a Snippet of the Demo I called "Lenny of Pisa 2021".

Lenny of Pisa.png

This is a bit weird, as I'm using a Message-Handler's "Message" in place of a simpler "State" (I use the Channel Message Handler scheme all the time and automatically reached for a "Message" here).  You'll also notice there are two Boolean Controls -- "Next", which makes the State Machine run one State, and "Enable", which needs to be True for "Next" to work.  If you replace the top Event Loop with a While Loop that, say, "pushes" Next once a second, then "Enable" turns on and off the lower State Machine's "Next Fib" state.

 

The Next Fib state (not shown, but when you place this Snippet on a LabVIEW 2021 empty Block Diagram, should regenerate the entire routine) contains the Stream Writer "Producer" that sends the next Fibonacci number to the Stream-based "Consumer", the loop in the lower right.  One important feature of the Stream Channel lies in its Boolean inputs that let the Producer tell the Consumer two things -- the lower indicator (normally False) says "This is the last signal, there will be no more, so you can safely exit when you see this", so we wire it to the Consumer's "Stop" control.  The other, upper indicator (normally True) is "this is valid data".  This lets you safely stop the Consumer without having to send it more "real" data by setting the "valid data" input "False", making it a pure "stop" signal.

 

I'll try to cook up a simple Channel Message Handler that I use, maybe even involving DAQmx, but that will need to wait until next week.

 

Bob Schor

 

0 Kudos
Message 5 of 14
(327 Views)

@Bob Schor: Thank you for sharing the demo. Here is my beginner's look at it: The demo provides us with the next number in the sequence of Fibonacci numbers when asked to (by using the two Boolean switches: as long as the Enable switch is pressed, hitting the Next on the front screen would output one number next in the sequence; the initial two are 0 and 1). The top loop provides the overall control and acts as the producer loop for the while loop on the bottom left, which is the producer for the consumer on the bottom right. 

 

My questions here are about stopping the loops:

 

1. The "element valid?" input is set to 'F' and 'last element?' is set to 'T' once the STOP button is pressed. Wouldn't using only one of them be sufficient to stop the loops?

2. If the "element valid?" input is set to default 'T' and 'last element?' is set to 'T' such that the last element sentinel is also connected to STOP, would the consumer loop still process the last element (like saving or displaying) or would it terminate before? I am referring to my VI here where I have FOR loops that I would like to run for the number of files that need to be saved. But I increment that number by 1 since otherwise the data for the last iteration is not saved to file. 

3. What changes would you recommend for the data acquisition VI that I shared before?

 

Thanks,

lza

0 Kudos
Message 6 of 14
(280 Views)

In a Producer/Consumer design, the Producer "produces" data that it sends to the Consumer, and decides when to end the process (often because you pushed the Stop button that stops the Producer's While Loop).  At this point, the Producer knows that its work is done, no more data need to be sent to the Consumer;

 

When I learned about Producer/Consumer using LabVIEW, Channel Wires hadn't been invented -- we "did it with Queues".  In fact, NI ships with a Producer/Consumer Design Pattern (click on "New ...", then "From Templates" and choose Producer/Consumer) that shows one way for the Producer to signal the Consumer -- when the Producer exits, it releases the Queue, which causes an error in the Consumer's Dequeue function.  In my opinion, this is a Brute Force way to send a "signal" to your partner, one I never adopted.  Instead, I have the Producer leave the Queue intact when it exits, and send "one more signal" (called a "sentinel") to the Consumer that does not cause an error when it arrives, but can be detected as "unique" and interpreted as "time to quit".  An example of this -- when using Producer/Consumer with Analog sampling, the Producer gets 1000 samples from an A/D read and sends an I32 Array to the Consumer.  When it exits, it sends an empty Array.  The Consumer always gets "legal data" (namely an I32 Array).  The Array is tested with "Empty Array?" and if false, then there's data to process.  If True, however, you know that the Producer has exited (and won't send any more data through the Queue), so you, the Consumer, can exit and (safely) Release the Queue without causing any "error".

 

That's exactly what the Stream does.  When the Stream is active, you wire data into the Stream.  When you get to the State where you know you have no more data to send, you "send the sentinel" by sending one last element (which you really don't have, because you already decided there was no more "valid data", so you set "valid data?" to False, and "last element?" to True.

 

Note -- I'm answering this late at night on my laptop, and am not looking at my example code.  The key to the Producer/Consumer "synchronization" is that "For every Action (Producer), there is a corresponding Reaction (Consumer)".  Almost all the time, this is "Data goes from P --> C", except when you want to stop, then "Sentinel goes from P --> C" which you do by asserting "last element?" and denying "valid data?" -- the Consumer "knows to stop" when last element? becomes True, and the Consumer also knows to process the data when "valid data?" is True.

 

Bob Schor

0 Kudos
Message 7 of 14
(231 Views)

@Bob Schor: Thank you for your detailed response. Here is how the VI looks like now. If there are still corrections, please do let me know. Also, since data acquisition wasn't originally a part of the question, it's discussed in a different thread here

 

channel_wires_For_loop.png

0 Kudos
Message 8 of 14
(200 Views)

I'm assuming you attached a LabVIEW "snippet", saved from LabVIEW 2022.  My main LabVIEW installation is 2019 SP1 (32-bit), or LabVIEW 2021 SP1 (32-b9t).

 

What I'd suggest is that you build a small Project with your main Program and enough sub-VIs that it will compile.  Do a "Save for Previous Version" and specify LabVIEW 2019 (or 2021).  You'll get a Folder, which may have bits and pieces of C:\Program Files (x86)\National Instruments -- I don't need these folders, but the one in C;\Users\<Your Username>\Documents\LabVIEW Data\\<Project Folder> -- I need the Project Folder.  Note that if it is large (probably won't be), you can do a right-click "Send To:", "Compressed (zipped) Folder".

 

You can check that it contains a Project file that you can click and see your code and all the VIs it calls.  In particular, the picture you posted doesn't show me how you shut down the Producer/Consumer -- I assume you are using the Sentinel feature of the Stream Channel Wire ...

 

Bob Schor

 

Bob Schor

0 Kudos
Message 9 of 14
(169 Views)

Downconverted snippet

G# - Award winning reference based OOP for LV, for free! - Qestit VIPM GitHub

Qestit Systems
Certified-LabVIEW-Developer
0 Kudos
Message 10 of 14
(145 Views)