12-19-2019 11:19 PM - edited 12-19-2019 11:20 PM
@johnson64 wrote:
The additional local variables are just for error searching purposes, but thanks. When everything is working as desired, I will delete the unnecessary ones.
So I guess what I'm trying to suggest is that beyond the "error searching purposes", in general (with new LabVIEW programmers) Local Variables are pretty overused.
There are a few reasons to use one (or else maybe they'd have been removed/never added?) but if you can use a wire instead, that's usually a better choice - even if it makes it a bit messy with wires everywhere.
If adding wires makes things messy, you could consider something like...
@johnson64 wrote:
Next time when I struggle I will upload the part of the program if this is more helpful.
Sounds good! 🙂
12-20-2019 03:18 AM - edited 12-20-2019 03:19 AM
So hello again,
So I guess what I'm trying to suggest is that beyond the "error searching purposes", in general (with new LabVIEW programmers) Local Variables are pretty overused.
There are a few reasons to use one (or else maybe they'd have been removed/never added?) but if you can use a wire instead, that's usually a better choice - even if it makes it a bit messy with wires everywhere.
so I should use wires instead of calling the local variable by its name? Why is that? Shouldn`t it be updated or is it sometimes not possible because of a loop for example?
I tried my best to apply the advice from GerdW but apparently I failed.
I don`t know why the notifier just reads one value and stops afterward.
My DAQx is not running while waiting for the next value too.
Can you please explain the mistakes I have done?
Thank u in advance!
Best regards,
johnson
P.S. The program I attached is just a part of the original program, but this is the part I`m struggling with. Hopefully, I included all the important stuff.
12-20-2019 03:38 AM - edited 12-20-2019 03:44 AM
Hi johnson,
apparently your message was trapped by the SPAM filter, but will appear later…
But I already wrote some answers on your VI:
No, you did not follow my suggestions: no error handling, unneeded sequence frames, unused terminals due to heavy overuse of local variables, coercion dots, not used AutoCleanup, …
Locals avoid to THINK DATAFLOW, the very basic principle in LabVIEW. (And they cause race conditions and data copies in memory…)
On the notifier problem:
I don't see the reason, but I see your "unconventional" use of the queue: Why do you enqueue in TRUE case, but dequeue in FALSE case? What's the point here???
Please cleanup your VI before posting again:
12-21-2019 06:41 AM - edited 12-21-2019 06:42 AM
Hi Johnson,
I'd like to emphasis the following point:
@GerdW wrote:
Hi johnson,
- no sequence frames needed once you use DATAFLOW!
regarding your question about local variables.
Perhaps the desire to strongly enforce an ordering is responsible for the use of the Flat Sequence Structure, but in LabVIEW we use "dataflow" to achieve the same result.
I'm not sure if you're familiar with this concept or not, but essentially the idea is that any given node/VI/structure cannot begin to execute until all of the inputs are available, and none of the outputs will become available until after the node/VI/structure finishes running.
This means that for example, you can connect error wires through a set of subVIs to ensure they run one after the other. It also means that if two separate nodes/VIs have no dependence on one-another, they can run "simultaneously" (at least in principle, in practice this will depend on the scheduling of your OS I believe, number of cores, etc) and you cannot predict which will start first.
Local Variables do not have other dependencies (no error terminals, just a single input or output) so they can be more difficult to order (terminals are the same (1 in or 1 out) but usually they can be wired in a way that does not produce a race condition). Local Variables also produce somewhat non-local effects - you can't clearly see where data came from, or what last changed it, and so on. With a wire you can simply follow the line around the screen to find the source (always only 1) and any other outputs (0..N) like indicators or further subVIs, etc.
If the desire springs solely from wanting to associate names with wires, you might find Labels useful. Right-clicking on a wire and choosing Visible Items > Label will allow you to specify a label for that wire, and so Context Help (Ctrl+H) will display the name when you hover over it, and the label will be placed over/near the wire (you can drag it around to place it as desired).
12-21-2019 08:03 AM
Hello, Freddi Johnson (that's a guess). I'm guessing that you are a BME student, as you are asking engineering questions about biological phenomena. I happen to be a biologist (a neurophysiologist, in fact) who has picked up a lot of engineering skills in the furtherance of my (and other's) research.
So when I look at your task, I see three "data streams" -- Time (as manifested by the 100 Hz Sampling Clock), Stimulus Intensity (not sure of the units, but it is a function of Time, changing every second = 100 Time "ticks"), and Wrist Angle (acquired from the Goniometer every Time "tick").
Time is a very important concept in LabVIEW, and there are special functions and data formats to deal with it. Conventional Programming Languages compete to do things "as fast as possible, certainly faster than <put some other language here>", but LabVIEW, being an Engineering Workbench (look at the last two letters of LabVIEW) wants to run at a speed appropriate to the task.
For collecting and analyzing data sampled at known, equal sampling intervals (e.g. 0.01 sec, or 100 Hz), LabVIEW has a special Data Structure called the Waveform, with three main Time/Data-related components: t0, a TimeStamp representing the date and time that the first point was acquired, dt, the time interval between the points, and Y, a 1D Array of Dbls representing the sampled Data Stream. DAQmx, the system that LabVIEW uses to interact with Hardware and control such things as Data Acquisition, can output sampled data as a Waveform (if a single Channel is being acquired) or as a 1D Array of Waveforms (if you are acquiring multiple Channels at the same sampling rate).
There are two "time intervals" in your task. One is the sampling rate, 100 Hz, from the Goniometer, which can be approached by a DAQmx Read of (say) 100 Samples at 100 Hz from an A/D channel. Well, look at that, if you do it this way, it will take exactly (or as accurately as your DAQ Device's hardware clock, the "gold standard" of Data Acquisition loops, can make it) 1 second, which "just happens" to be the time interval to change the stimulus parameter (which you just do in the same loop). [Note -- Data Flow means that if you have the Acquiring Code and the Stimulus Code in the same loop, and they don't share "data flow", i.e. some output from, say, the Acquisition Code isn't used as an input to the Stimulus Code, or vice versa, you can't say "which comes first". But achieving this is simple if you use the Error In/Out terminals found on most LabVIEW Functions -- simply connect Error Out from DAQmx Read (acquiring 100 samples) with the Error In on DAQmx Write (updating the Stimulus).]
But now there's a (minor) problem -- you have a Waveform from your DAQmx Read, but a single number (with no Time Information) from your code to generate the Stimulus. Simple, you build a "fake" Waveform that has the same t0 and dt as the Goniometer Data, and a Y (data) Array consisting of 100 identical samples of the (preceding) Stimulus Value (as it remained constant during the entire second). Now you have 2 compatible Waveforms that you can write to a data file.
Do you need to write the Time information to a file, as well? Actually, you don't, as it is embedded in the t0/dt elements of each Waveform.
Sometimes it is difficult to "step back" and look at your Task from a few feet away, where you can see the Big Picture and how to simplify your Task. The usual thing is to "go for the details", but it is usually better to think first, and carefully, about what you want to do (simultaneously continuously acquire goniometer and stimulus intensity measurements), and only later worrying about how you are going to do that.
Bob Schor
12-22-2019 03:26 AM
Hello everyone,
first of all thanks to GerdW, cbutcher, and Bob for your help! I appreciate it!
Please excuse my mistakes as I`m trying my best to get used to LabVIEW.
I tried to include the advice from GerdW into my program so that this version should be at least more tidy and easier to understand.
Nevertheless, I have a difficult time to build the program following your suggestions. My understanding of how the queue is working seems to be wrong. I tried to use the bulb to see the single steps of the program.
The bool output of “wait on notification” is true if the defined timeout value has been exceeded so that the second loop has to wait for a change of value. Until now I didn`t use a specific timeout value (= -1). As a result, the result was false and the goniometer was working. But if I want to change the value to something different than -1, I have no clue which value would be appropriate as a timeout. Could please explain, how this is working? I tried some values but always ending up in the false-case.
Also, is my way of using enqueue and dequeue correct? Until now I couldn`t write data to a file because of the described error.
@Bob: Indeed, I`m studying biomedical engineering.
Due to the fact, that I already invested so much time in figuring out how the queue and notifier are working, I will stick to this. If I keep failing, I will try your solution. I hope that it won`t be necessary as I`m running out of time. Nevertheless, thank you so much!
Best regards,
Johnson aka Freddi
12-22-2019 07:06 AM
Okay, so until now I can at least save data (angle and the notification value) like this:
First of all, I don`t know, how to get ride-off the zero in between the dequeued data. During the false-case I dequeue the obtained data, but what can/should I connect to the tunnel during the true-case. Maybe a "," as I use it to separate the different variables?
Do you know how to formate this table in a proper way? For example how to write the different dequeued data of the measured angle in one column among each other? And how could it be possible to write the stimulation intensity (100 times bc of 100 measurements regarding this intensity) next to the referring angle?
Once again, I have to thank you guys for helping me!
Best regards,
Johnson
12-22-2019 10:09 AM
Hi johnson,
@johnson64 wrote:
First of all, I don`t know, how to get ride-off the zero in between the dequeued data. During the false-case I dequeue the obtained data, but what can/should I connect to the tunnel during the true-case. Maybe a "," as I use it to separate the different variables?
That exactly is the problem: you are writing a 1D array to that autoindexing tunnel in the FALSE case - and in the TRUE case you are writing a 1D array containing "zero" to the very same tunnel. And now you wonder where those rows containing just "zero" are coming from?
Again: your use of the queue is silly: no need to write to the queue in the TRUE case, just to read it in the FALSE case!
You are still using a lot of locals, including race conditions (like "Save path"). Btw. there are also race conditions possible with global variables…
You are still don't do any error handling.
You are still using the "millisecond timer value", which (mostly) is nonsense…
Still duplicated code in case structures (like writing to global variable "L1 temp"/"L2 temp")…
12-22-2019 11:33 AM
Thank you for attaching your code. This is the first I'm learning that you are using VISA to collect data, so it isn't strictly timed (as I was assuming). Sadly, this somewhat complicates the data acquisition ...
I noticed that you are "playing" with concepts that you don't quite understand. I see what I think is supposed to be a Producer/Consumer design -- there's a Queue that is created and sent into a While Loop with a Dequeue function, clearly meant as a Consumer, but that's the only place the Queue wire is used! If you are not going to Enqueue anything, you don't need a Queue. One wonders whether you need a Notifier ...
Bob Schor
12-22-2019 05:29 PM - edited 12-22-2019 05:30 PM
Hello again,
That exactly is the problem: you are writing a 1D array to that autoindexing tunnel in the FALSE case - and in the TRUE case you are writing a 1D array containing "zero" to the very same tunnel. And now you wonder where those rows containing just "zero" are coming from?
I'm not wondering why the 0 are appearing as I'm writing them during the TRUE-case. The question is how to improve the design to not be forced to write to the tunnel (used in the FALSE-case to write the data to the file) during the TRUE-case as it is (as far as I know) not possible to write "nothing" to the tunnel during the TRUE-case.
Again: your use of the queue is silly: no need to write to the queue in the TRUE case, just to read it in the FALSE case!
As far as I understand queues, I have to enqueue the measurement data during the TRUE-case and dequeue/ read it during the FALSE-case. Not sure, whether I get the idea behind your sentence.
You are still using a lot of locals, including race conditions (like "Save path"). Btw. there are also race conditions possible with global variables…
You are still don't do any error handling.
You are still using the "millisecond timer value", which (mostly) is nonsense…
- Some locals are necessary due to the overall design of the rest of the program (Several programs in a CASE-Structure can get selected. Therefore, to not use the same code for every single case, some parts (i.e. the creation of the Save path) are excluded from the case structure to just generate the individual Save name for the single case. Therefore, I cannot delete all of them.
- I will inform myself about the concept of error handling.
- Please tell me, why the timer is nonsense. How to make sure this queue is waiting one second?
Thank you & Best regards,
Johnson