贴个英文FAQ-框图部分
时间:12-13
整理:3721RD
点击:
The Block Diagram
Is the while loop really a while loop?
<Alex Le Dain alexATicon-tech.com.au>
(Sep 2000)
No. Strictly speaking the while loop is a "do while" (C) or repeat until
(PASCAL) programming structure. In other words the loop always executes at
least once, and the Boolean test for continuation is done at the end of the
loop execution. With a true while loop in a text based program the test is
done prior to executing any of the commands contained within the loop.
To create a "real" while loop wire the output of your terminating condition
to a case structure surrounding the rest of the code within the loop.
(back)
How do I stop a for loop?
<Scott Hannahs sthATmagnet.fsu.edu, Mark Evans Info-LabVIEWATUltimateG.com>
(Sep 2000)
A for loop by definition executes the requisite number of times (N). If the
number of iterations is not known then you have to use a while loop.
Is there some religious prohibition against the while loop that precludes its
use? There are ugly, inelegant things you could do by putting a case
statement in the for loop to avoid doing anything after some number of
iterations but there is no reason to do this except to introduce wasteful,
inefficient, useless code. Use a while loop, you will like it.
An underappreciated feature of while loops is their ability to index arrays
or produce array outputs similar to for loops. To do this "enable indexing"
on the wire coming into or out of the while loop. Likewise you can disable
indexing on for loops to avoid the indexing feature.
(back)
What does a 0 msec wait function do in a while loop?
<Greg McKaskle>
(Timeless)
If you have multiple loops in your application that don't need to run as fast
as possible, then placing a 1ms delay will limit your loops to at most 1000
iterations per second. Without a delay, the loop rate can be in the millions
of iterations per second range depending on what is inside it. So that means
that your CPU has about 1/1000th as much work to do and can go off and tend
to other tasks. If your loop was already taking several ms, then the 1ms
delay is likely in parallel and it won't affect the loop speed noticeably. So
placing the delay in the loop can drop your CPU usage noticably and allow
time for the OS to do other work like send messages that your application may
be waiting for, etc.
But what does a wait of 0 ms do? Let's consider the LV execution system to be
a xerox copying machine. Everyone that has something to copy heads for the
copying machine and lines up. If you have never had the pleasure of waiting
at a copy machine, then consider any other time consuming task where people
wait in line. Everytime a LV diagram executes a wait function, it is like
releasing the copier and staying out of the line for some time delay. After
the delay has passed, you will get back in line to make your next copy. A
wait of 0 ms is like immediately going to the end of the line to let a
smaller copy-task take place. If nobody is in line behind you, you
immediately start your next copy task. If someone is in line, it lets them
take a turn.
This 0 ms wait is a pretty cool little trick to make LabVIEW loop parallelism
less chunky. It naturally adds some overhead because the loops have some
setup time, but when the loops are doing significant work it is tiny. Use it
whenever you think you need to, but beware that if some loops don't have any
delay, they are still going to hog the CPU, and the wait of 0 ms may turn
into much larger waits because the loops with no waits play by different
rules.
(back)
How do I execute a shell command via system exec?
<Alex Le Dain alexATicon-tech.com.au>
(Timeless)
The exec VI function can be used to send shell commands. Include the shell
call with the commands that are desired. For example under Windows operating
systems the call is to command.com. For example, the string to copy filea to
a drive would be
command "copy filea a:"
with the arguments enclosed in quotes. The exact shell call will depend on
the operating system.
(back)
What is a state machine?
<Alex Le Dain alexATicon-tech.com.au>
(Timeless)
The state machine is a convenient LabVIEW construct where a case structure is
contained with a while loop. Execution of particular cases in the structure
is determined by the output from the previous case (or in the instance of the
first execution) by the control selector input. The control of the order of
case execution is controlled through the use of a shift register. By wiring
the output from one case as the determinate to the subsequent case (wiring to
the shift register on the right hand side) and wiring the input to the case
selector from the left hand side shift register, operations can be ordered
according to which state is executed.
The state machine has the advantage of allowing several steps to be linked in
series so that each individual step can be executed (or debugged) easily. The
trap for the inexperienced programmer is the use of the state machine to step
in a haphazard fashion through the cases in the loop. This can lead to
complicated pathways through the state machine reminiscent of convoluted goto
statements in conventional text based languages. However state machines are a
valuable tool and can be used very effectively with this in mind.
(back)
How do I control the order of execution in a state machine?
<Alex Le Dain alexATicon-tech.com.au>
(Feb 2003)
There are three possible ways to order a LabVIEW state machine. The first and
easiest method (although NOT recommended) is to use an integer. The integer
output from one case is wired as the selector for the subsequent case. The
disadavantage of this method is that the numbers themselves are not very
helpful in tracking through the code and the case selector becomes somewhat
meaningless. Either of the following methods are preferred and there are pros
and cons for each of them. Debate (strong at times) arises on the list quite
frequently as to the best of these methods, but in practice either will work
very nicely.
The second method is to use a string as the case selector. With the correct
choice of string states, this has the advantage of labelling each case so
that your state machine becomes self documenting. A further advantage of this
method is that all cases need not be written in the first instance and it is
somewhat easier to include new states should the need arise (especially in
earlier versions of LabVIEW (< 6.0)). The disadvantage is that it is easy to
misspell a state and have the state machine buggy because of his. An easy way
to overcome this disadvantage is to include a default case that pops up a
warning including the spelling of the failed case.
The third method is to use an enumerated type as the case selector. In
preference this enum should be a typedef'ed so that changes to the states are
refleced easily and transparently when new states are added to the enum (via
the LabVIEW auto update from typedef setting). Note that there are reports
where this method causes problems in version of LabVIEW prior to 6.0, because
the enum auto updating did not correctly handle newly inserted cases. This
method has the advantage of the string method in code documentation, but
requires the use of a default case if not all the cases are required to be
written when the code is first created.
(back)
How do occurrences occur?
<Stepan Riha, stepanATnatinst.com>
(Sep 2000)
There seem to be misconceptions about how exactly occurrences work and what
exactly the "ignore previous" parameter on "Wait on Occurrence" (WoO) does.
The following tries to explain, in a long and winded way, how they
(functionally) behave:
How Occurrences are "generated": When a VI is loaded (!) each "Generate
Occurrence" function allocates exactly one unique occurrence. When the VI is
running and this function is called it simply returns this one occurrence --
no matter how many times it is called (try putting one in a loop and
examining its value using the probe, it will always have the same number). If
you stop the VI and run it again, you will get the same value; only removing
the VI from memory and loading it again will give you a "fresh" value.
How Wait On Occurrence (WoO) works: Each WoO functions "remembers" what
occurence it last waited on and what time it continued (because the occurence
fired or because of a timout). When a VI is loaded (!) each WoO is
initialized with a non existing occurrence. When a WoW is called and "ignore
previous" is FALSE there are
four potential cases:
1) The occurrence has *never* been set -> in this case WoO waits
2) The occurrence has been set since this WoO last executed -> WoO does not
wait
3) The occurrence has last been set before this WoO last executed and last
time this WoO was called it waited on the *same* occurrence -> WoO will wait
4) The occurrence has last been set before this WoO last executed but last
time this WoO was called it waited on a *different* occurrence -> WoO will
*not* wait!!!!
The first three cases are pretty clear, the last one may seem a bit strange.
It will only arise if you have a WoO inside a loop (or inside a *re-entrant*
VI in this loop) and it waits on *different* occurrences (out of an array,
for example) or if it is inside a *non re-entrant* VI and the VI is called
with different occurrences. These cases do generally not happen. The reason
the WoO behaves this way is due to its implementation. Each
occurrence "knows" the last time it has been fired, and each WoO remembers
the occurrence it was last called with and what time it fired (or timed out).
When WoO is called and "ignore previous" is FALSE, it will look at its input;
if the input is the same as last time, it will look at the time of the last
firing and wait depending on whether the time was later than last execution;
if the input is *not* the same as last time, it will simply look at the time
and wait depending on whether it has *ever* been fired.
A possible (implementation) problem: It appears that occurances are
"remembering" that they have been set during previous invocations of the
program. One would think that generating an occurance should create a clean
"non-set" occurance. This problem can be illustrated in a program that has
three parallel loops with an abortable wait in each using occurances. If the
program is stopped with the stop button things are fine. But if one waits
until one of the random stop conditions triggers the end of the loops
(generated from the other loops), the next time the program is run, the loops
will execute only once and not loop at all. (The random terminate condition
in the actual program is an error occuring in some piece of equipment.)
Either this is a bug, or we are completely wrong on the use of occurances.
Our example here is using the occurances in the "do not clear previous" mode.
We would think that we will not remember ccurances from previous runs of the
program since a new clear occurance should be created with the generate
occurance icon. In this instance we can not use the clear previous occurances
mode since we need a single occurance to stop multiple parallel loops.
The reason for the problem: The first time the occurrence is set because of
an error, the loops terminate, but the "stop button" loop is still running.
When you click on the stop button, the occurrence gets triggered again
(unneccessarily) and the program stops. The next time the VI runs, the WoO
will not wait because of this extra trigger; and since you'll trigger again
in the "stop button" loop, the VI won't work until it's reloaded from disk.
A solution to the problem: Due to this behavior of occurrences, it is clear
that one cannot use the "timed out" flag to determine when the occurrence
fired. You will have to maintain some global information about your state,
let's say in a global boolean called "FINISHED". At the beginning of the
program you would initialize it to false. If you have an error, set FINISHED
to true, and then trigger the occurrence. After the WoO see if FINISHED is
true (make sure you don't read the global until *after* WoO finished
executing), if FINISHED is false continue with the loop. In the "Stop
button" loop, also set FINISHED before you trigger the occurrence.
BTW, if you don't like globals, you could use a VI with an uninitialized
shift register (LabVIEW 2 global), but the effect would be the same.
Comments: One may think that occurrences are quirky, a pain to use and that
they
should be avoided. One might be right! Occurrences are very low level and you
often have to add functionality to them in order to use them effectively. In
other words, they are not for the faint of heart. Anything implemented with
occurences can be also implemented without them, but maybe not as efficently.
What occurrences do is to allow you to program synchronization in a way that
does not use polling, and is thus "cheaper" in processor time.
(back)
Why does an typecast enum output not change?
<Paul Sullivan PaulATSULLutions.com>
(May 2002)
If a typecast function is used to convert a number related, for example, to
the button pressed (in an array of buttons) to an enum type then a problem
observed in that the type cast doesn't seem to work. The button press is
changing the number on the input, but the enum output does not change. The
answer is that most likely the two inputs to the typecast function have
different representations, probably I32 and U16. The type cast is reading the
high order bits of the I32 (which aren't changing) and casting them into the
U16 enum output. The solution is to convert both to representations having
the same length (signed and unsigned mix fine), either by changing the
representation of the controls themselves or by using a convert function at
the input to the type cast.
(back)
How do I make cool looking icons smaller than the entire square?
<Don R. Wagner wagnerATsiliconlight.com, Albert Geven>
(May 2002)
First, draw the border for the icon in the B&W icon view. Be sure to align
the icon to the wiring terminals by using the Show Terminal check box. This
defines the icon outline. If you have no border drawn in the B&W view, it
defaults to the whole square (in my limited tests, you get a "no outline in
B&W view default will be used" dialog). The icon border will actually be the
largest of the drawn areas in the three icon views. So actually, you need
only draw a single dot on the B&W view to define your 256 colour drawing as
the border limit. Something must be drawn in the B&W view to get the
selection border for the icon on your diagrams to conform to the drawn
outline (is this a useful feature? seems like you could have nothing drawn in
this view by defaults and have it still work).
Second, Click on the 256 colour icon view (if that's what you use), and click
Copy from Black & White to get your border. Then finish drawing the icon to
your liking, link it up, and presto, small icon. Wires to border, transparent
outside, etc. You can even make irregularly shaped icons. The white area
outside will be transparent in your diagrams. Interestingly, if the border
you define is not contiguous, the inside of your icon will also be
transparent (you can see a wire go all the way to its connection point
(visible as a little cross in the "show terminals" icon view). You can
actually have multiple non-transparent areas in your icons if you wish. Very
cool! You could have a wire pass between two blobs to represent some subtle
transformation on the data if you really wanted to go all out, but this
actually requires drawing wires between the connection points on the icon to
make them look contiguous.
(back)
Are there any other icon tricks I should know?
<Don R. Wagner wagnerATsiliconlight.com, Albert Geven>
(May 2002)
Double clicking on some (any?) of the tool icons in the editing palette will
apply that tool to the entire icon square. For instance double click on the
dotted square selection tool will select the entire icon area (handy for
deleting the icon as a starting point in making small icons). Double clicking
on the bordered square will draw a border around the current icon in the
currently selected colour, without affecting the inner pixels, etc.
(back)
Why doesn't my FOR loop return a value?
<Jean-Pierre Drolet jean-pierre.droletATtr.cgocable.ca, Uwe Frenz
Uwe.frenzATgetemed.de, Alex Le Dain alexATicon-tech.com.au>
(Oct 2001)
As a good programming rule in LabVIEW, NEVER output a value from a for loop
with indexing disabled. When the loop does not execute (0 iteration) a non
indexing output stays undefined and will hold any garbage left there by
previous memory usage. This is because the wire output from the for loop has
no code or data source to get the value from when the loop does not execute.
Instead, use a shift register (SR) to output the value and at the left SR
enter a default value for the case when the loop does not execute. When the
for loop does not execute this default value of the left SR is passed to the
right SR. Similarly passing a refnum through a for loop that never executes
(0 iterations) destroys the reference. Either pass the reference using a SR
as described above or wire around the loop. Note that while loops always
execute at least once so the outputs are always defined.
(back)
What is a reentrant VI and what are the implications of reentrancy?
<David A. Moore David_A_MooreATMooreGoodIdeas.com, Greg McKaskle>
(Feb 2003)
First, consider three VIs, foo.vi, bar.vi, and sub.vi. Both foo.vi and bar.vi
call sub.vi.
If sub.vi is normal (not reentrant), then if foo.vi tries to call sub.vi but
sub.vi is busy servicing a call from bar.vi, then foo.vi has to wait. This
can be both a very GOOD thing and a very BAD thing depending on
circumstances. It's very GOOD when sub.vi controls access to something like a
serial port, where you only want one part of your program accessing it at a
time. It's very BAD when sub.vi controls something like ALL the serial ports,
because you may want foo.vi to be able to use one serial port while bar.vi is
busy using a different serial port. Another very bad circumstance is where
foo.vi is in a critical loop and bar.vi is not, yet because of the contention
for sub.vi, bar.vi can end up blocking foo.vi.
If sub.vi is reentrant, then both foo.vi and bar.vi can call sub.vi at the
same time. In order for this to work, each call to sub.vi needs to have its
own "data space" which is all the internal storage sub.vi uses in order to
execute its code.
Now at this point I need to point out a distinction between LabVIEW and most
other languages. LabVIEW doesn't want to allocate a data space on the fly,
because for LabVIEW that would slow down performance. LabVIEW allocates all
the VI data spaces it needs when VIs are being loaded. Except when you use VI
Server to call VIs dynamically, all the loading happens before any VIs
execute. Therefore, for each place that sub.vi appears on foo.vi's block
diagram, a copy of sub.vi's data space gets embedded in foo.vi's data space,
assuming sub.vi is reentrant. If sub.vi isn't reentrant, it just has its one
data space allocated that each call will use in turn.
In most (all?) other languages, reentrant functions allocate their data
spaces on the fly, so there's no storage that goes with each place that a
particular function is called.
How does LabVIEW's unusual implementation affect us in practical terms? There
are really two ways:
1. If you use uninitialized shift registers to store information, then you
can get two different behaviors depending on the reentrancy of your VI. For a
non-reentrant VI, you get a data sharing function that lets you move large
quantities of information between parallel loops without making copies of it.
For a reentrant VI, you get a reusable storage function that can keep
independent copies each place you use it. There is a PowerPoint presentation
and some example code related to uninitialized shift registers at:
http://www.mooregoodideas.com/Downloads/Downloads.htm#ChangeDetector.
2. The second implication is that you can't do recursion (functions that call
themselves) easily in LabVIEW. In most languages, if a function is reentrant
then it's OK for it to call itself. In LabVIEW, that would require that the
data storage for sub.vi would include a copy of the data storage for sub.vi
which would ... to infinity. You can do recursion in LabVIEW if you use VI
Server to have a VI call itself dynamically, but as I said, allocating data
spaces on the fly is inherently slow. In LabVIEW, it's best to convert
recursive algorithms to their iterative equivalents, which I hear is
mathematically proven to always be possible. In the iterative version, you'll
end up changing the sizes of arrays at each iteration, which is also one of
the slower operations in LabVIEW, but is not nearly as slow as dynamic VI
calls.
And Greg McKaskle added:
To expand on this, reentrant means that more than one execution is allowed to
take place at the same time. In other languages, it is more a situation than
a setting. You never mark a C function as allowing or disallowing reentrancy,
it is either safe to do so or a source of bugs. In LV it is a setting, and
many times its setting doesn't affect the correctness of a VI, but in some
cases, it can be a source of bugs. It depends on what the VI does.
The setting in LV determines two major attributes about how a VI executes.
First is access. With reentrancy turned off, only one call to the subVI can
be active at a time. When the current call finishes, the next one can begin.
The subVI calls queue up while the VI is busy. For functions that execute
quickly, this is normally fine and reentrancy doesn't affect much.
If you have a function that uses TCP to talk to another computer and waits
for responses, these waits also affect the other subVI calls that are queued
up. So if you have an operation that can occur in parallel and doesn't
consume the CPU, you can make the VI reentrant and the multiple subVI calls
don't enter a queue, and multiple VIs can talk TCP and wait for responses at
once. This allows the wait time of one subVI to be used as work time in
another and increases overall performance.
On the otherhand, given a VI that reads a global modifies it and writes it
back, a reentrant subVI means that more than one subVI call at a time can be
modifying the global -- a race condition which will cause incorrect answers.
Lots of real-world devices also get confused when more than one subVI tries
to control them at a time. So when trying to protect a global resource, the
one of the tools, and frequently the easiest to use is to simply make sure
that the access goes through a non-reentrant VI.
The second attribute is data side-effects. If a VI has unconnected controls
or uninitialized shift registers on its diagram, then it remembers some
amount of information from call to call. A good example of this is a PID or a
filter. Data from previous calls affect the result of the next call. For
these sorts of VIs, if they are reentrant, then each call gets its own place
to store the previous call's state information. If made non-reentrant, there
will be only one storage location for all calls to share, so the data will
get all jumbled, likely causing an incorrect answer.
(back)
What are some of the pitfalls of the event structure?
<Alex Le Dain alexATicon-tech.com.au>
(Feb 2003)
In LabVIEW v6.1 a new structure was introduced: the Event Structure. This
structure allows the user more power over the UI, but at the same time with
the new power comes the potential to lock a LabVIEW application hard. Below
are a compilation of tips for using the UI Event Structure:
0. Read the LabVIEW Help on "Loops and Case Structures, Case Sequence and
Event Structures, Event Structures, Caveats and Recommendations when Using
Events in LabvIEW"
1. Only place a single event structure within a while loop. In reality the
while loop and the event structure are intimately linked so there should only
ever be one event structure per loop.
2. Never place an event structure within an event structure. It is better to
solve issues where you might want to do this with some thought and perhaps a
second while loop in parallel.
3. It is possible to have more than one event structure while loop
combination on the same block diagram. There are valid reasons why you might
want to do this: eg to have some events handled without pausing the while
loop (Lock Panel Until Handler Completes = False) and others to wait until
the handler completes (= True). The advice here is to separate out those
events that lock the handler and those that do not lock the handler into
separate while loops.
4. Whether the panel is locked until the handler completes is set
individually for EACH case in the event structure.
5. It is advisable to notify the user (ie with a message popup or mouse busy)
when tasks that lock the panel take a long time to execute (> 0.5 s). Another
way is to set some busy status message visible as the first step and then
hide this message when the case exits.
6. Boolean switches and their state require some thought when used within the
event structure. With the default button style ("latch when released")
LabVIEW reads the state of the boolean and only after reading does the button
change back to it's other state. If the boolean is the "switched" type then
TWO events are generated when the boolean is switched (ie F->T and T->F). If
a single case of the event structure is used for "value chaged" on a boolean
that is of the "switched" type, make sure that the code is only executed when
the true event is processed (ie with a true/false case selector). This is
most relevant where to use a boolean as a local variable then the boolean
type must be of the "switched" type.
(back)
Why does the event structure behave as it does?
<Greg McKaskle>
(Feb 2003)
LV BE (Before Events) had the panel and diagram behaving as asynchronously as
possible WRT one another. A control periodically handled UI events from the
user and dropped the value into its terminal. Independently, the diagram
takes the current value when it is needed and does its work. There is no
synchronization between when the UI event is handled, when the value in the
terminal changes, and when the diagram reads it. It is often a very simple
way of writing your code and mimics how most hardware works. So why do events?
The primary reason for the events feature is to allow synchronization between
the UI and the diagram. First off, the diagram gets notification of a value
change. It is guaranteed not to miss user changes or to burn up the CPU
looking for them. In addition to the notification, the diagram gets a chance
to respond, to affect the rest of the UI, before the rest of the user input
is evaluated. Maybe this part needs an example.
Lets look at polled radio buttons. In theory, your diagram code polls fast
enough to see each change in the radio buttons so that it can make sure that
the previous button is set to FALSE. But when the user is faster than the
diagram and presses multiple buttons, what order did they press them in?
There has to be a fixup step to break the tie and pop out all but one button
to FALSE regardless of the order the user clicked them.
With events, when the user clicks on a button, the panel is locked and will
not process the next user click until the diagram finishes. This allows the
diagram to see the button changes one at a time in the same order as the user
presses.
Another example, perhaps a better one, is a panel with three action buttons:
Save, Acquire, and Quit. The order that the presses are responded to is
important, so the polling diagram has to "guess" whether to Save and then
Acquire or Acquire, then Save. The Event Structure knows the order and the
code in it is synchronized with the UI allowing for a better, more friendly
UI.
Getting back on topic, the event structure introduces the synchronization,
but the downside is that as with all synchronization, it allows for deadlocks
when not used in the right way. As already discovered, nesting event
structures is almost never the right thing to do, at least not yet.
As noted earlier, it is possible to leave the Event Structures nested, but
turn off the UI locking. This appears to solve the problem, but it isn't how
I would do it. I think a much better solution is to combine the diagrams of
two structures into one. The value change for hidden controls will not
happen, but it doesn't hurt to have it in the list of things to watch for.
Another option is to place them in parallel loops. This will let the first
structure finish and go back to sleep.
(back)
Why doesn't the event structure register local variable changes?
<Greg McKaskle>
(Feb 2003)
>The main reason for not sending events for programmatic value changes is to
avoid feedback.
A happens. In responding to A, you update B and C. If responding to B or C
results in changing A, then you have feedback and your code will behave like
a dog chasing its tail. Sometimes the feedback will die out because the value
set will match what is already there, but often it will continue indefinitely
in a new type of infinite loop.
One solution to this is what is called User Events. You define what data they
carry and when they fire. Then you either have the value change and the set
local both fire the user event, or you can combine the event diagram to
handle both and just fire the event when writing to the locals. Today, you
can accomplish this with the queued state machine using the state machine to
do all the common work and just having the event structure pass things to the
queue.
(back)
Why is it necessary to lock the panel after an event fires?
<Greg McKaskle>
(Feb 2003)
Given the synchronized mechanism of events, it is pretty easy to repost
events to another queue or turn off locking and synchronization. If the event
structure isn't synchronized, it would be impossible for the diagram to add
it and become synchronized with the UI, so it is at least necessary for event
diagrams to be able to lock the panel.
Should it be the default?
In our opinion, yes. When responding to an event, it is pretty common to
enable/disable or show/hide some other part of the display. Until you finish
doing this, it is wrong for LV to process user clicks on those controls, and
LV doesn't know which controls you are changing until you are finished.
Additionally, it isn't the best idea, but what happens when the event handler
does something expensive like write lots of stuff to a database inside the
event structure? If the UI is locked, then the user's events don't do much,
ideally the mouse is made to spin and this works the same as a C program. The
user is waiting for the computer, and the UI more or less tells the user to
be patient.
If the UI isn't locked, the user can change other things, but you can't
execute another frame of your event structure until this one is finished.
This is a node. It must finish and propagate data before it can run again,
and the loop it is probably in can't go to the next iteration until it
completes. You would have low level clicks being interpretted with the
current state of the controls before the diagram has a chance to respond.
This is sometimes the case, so it is possible to turn off event handling on
the cases where you know that you may take awhile and you do not affect the
state of the UI.
If you need to respond to the events in parallel, you can make a parallel
loop, add an event handler for the other controls, and handle them there
while this one churns away. That will work fine and it is IMO clear from the
diagram what is synchronized and what is parallel. Taken to an extreme, each
control has its own loop and this approach stinks, but it is a valid
architecture. Note that for this to work well, you need to turn off the UI
lock or have a node to release it.
Another way of doing expensive tasks is to have the Event Structure do the
minimum amount necessary before unlocking -- treat them like interrupts. Have
the event structure repost expensive operations to a parallel loop or fire up
an asynchronous dynamic VI. Now your event structure is free to handle
events, your UI is live, LV is still a nice parallel-friendly language, and
your diagram just needs to keep track of what parallel tasks it has going on.
In the end, I'm not sure I can convince you, but if you continue to
experiment with the different architectures that can be built using the Event
Structure, I think you will come to agree that it normally doesn't matter
whether it is locked or not. There are times where it is really nice that it
is locked, and occasionally you may turn off locking so that the user can do
additional UI things up to the point where synchronization is necessary
again. For correctness, we decided that locking should be the default.
I'd suggest reading the article on devzone sooner or later. It is in the
Developer Insights/LabVIEW Guru section, and it will help start you down the
right path.
(back)
What does an "Insane Object" mean? Should I report this as a bug to NI or do
they already know about it?
<Stephan Mercer stephan.mercerATni.com>
(Feb 2003)
Firstly, there is no need to necessarily report this to NI, as they do know
about insane objects. Secondly they are NOT just bugs, insane objects can be
generated as part of the LabVIEW verification process.
The insane object message is what LabVIEW puts in a dialog when one of the
objects on the diagram does not meet its "checksum". In other words, this
object isn't in a state we expect it to be in. Most of the time these errors
are not fatal -- we simply put the object in the state we do expect. But it
raises questions about how the object got into that bad state and what might
have been done with that object between the last time we checked and it was
good and the time it became insane.
The insane object messages are something we work on with each version of LV.
But as it is a generic error message that can apply to anything from a simple
cosmetic to the front panel itself, you'll still see them in any version of
LV that has a bug -- a fact of life unfortunately for the foreseeable future.
If you get such a message, it is good to check the known bugs of LV at ni.com
and if the particular insanity in your dialog is not listed, report the
insanity to NI technical support.
The cryptic nature of the message can be deciphered as follows:
Insane object at FPHP+44 in "name.vi": {dsitem} 0x400: Panel (FPSC)
* FPHP -- this will be either FP or BD for "front panel heap" or "block
diagram heap"
* 44 -- this is a number indicating which object
* name.vi -- which VI had the insanity
* {dsitem} 0x400 -- really only meaningful if you know how our internals
work; I'll skip it here
* Panel (FPSC) -- the type of object that has problems. The four letter codes
are usually descriptive (COSM for a cosmetic or simple drawn part, SGNL for a
signal aka wire)
Most of the time, deleting the offending object and recreating it from
scratch is sufficient to fix your VI and allow you to continue working.
(back)
What are LabVIEW 2 style globals, functional globals, and uninitialised shift
registers (USR's)?
<Stephan Mercer stephan.mercerATni.com>
(Feb 2003)
LabVIEW 2 style globals and functional globals refer to the same code
construct in LV. Most people in the mailing list refer to these structures as
LV2 globals because this was the version of LV when they were introduced.
Functional globals is how NI refer to these structures because they can be
more than just global variables in that they can contain code and data.
Uninitialised shift registers (USR's) are the LV code primitives used in
making these functional global variables work.
A functional global is created by placing a shift register upon a while loop
and wiring a boolean constant to the condition terminal such that the loop
executes a single time. When the shift register is not wired with an input
from the left hand side (ie it is uninitialised) LV retains the memory in use
for that vi between calls. This means the the vi stores the value of the
shift register variable between calls (executions) in the code. Extensions
can be done by adding cases within the loop, eg one for read and one for
write with a 'mode' enumerated type selecting which case is executed when the
vi is called. These globals can contain as many cases as needed and because
these are very similar to state machines, they can be made to execute several
cases at each call to add increased functionality.
Of course all recent versions of LV have global variables, accessed from the
structures palette, so why do people still refer to them or even use them?
Well, there are a couple of good reasons:
1. More efficient memory storage. Beacuse the vi retains the same data space
regardless of it use in the code, only a single call to the memory manager is
made. If arrays are stored in the USR, and replace array element primitives
used, then the memory space is efficiently used/accessed wherever the vi is
called.
2. Built in data space protection. One problem that can happen with normal
globals in LV, occurs if you try to write a value to the global in several
places in your code, updates to the value are unpredictable with respect to
time. This means that changes to the global can be subject to race
conditions. This is especially true if you read/write to the global where a
race condition could occur between the read and the write of the variable.
So how does it work? In the LV execution system, a single vi, even if called
from multiple places in the LV code, can only be executed exclusively in each
place. In other words, the vi will not execute until a previous execution is
complete. With a functional global, because it is a vi, LV determines that
the functional global can only be executed once. Therefore updating of the
shift registers is protected by the LV execution system. This mechanism of a
vi 'protects' the data preventing any race condition. It should also be noted
that USR's only retain this ability to share data across the code when they
are not reentrant. If the vi is reentrant then the USR still stores the data,
but this is only stored between calls of the vi in that place in the code -
another call to the vi in another section of the code would have its own
dataspace and its own saved USR values. It should be stressed that the
reentrant vi would not transfer data from one section of the code to another
and by definition not be a functional global.
(back)
Is the while loop really a while loop?
<Alex Le Dain alexATicon-tech.com.au>
(Sep 2000)
No. Strictly speaking the while loop is a "do while" (C) or repeat until
(PASCAL) programming structure. In other words the loop always executes at
least once, and the Boolean test for continuation is done at the end of the
loop execution. With a true while loop in a text based program the test is
done prior to executing any of the commands contained within the loop.
To create a "real" while loop wire the output of your terminating condition
to a case structure surrounding the rest of the code within the loop.
(back)
How do I stop a for loop?
<Scott Hannahs sthATmagnet.fsu.edu, Mark Evans Info-LabVIEWATUltimateG.com>
(Sep 2000)
A for loop by definition executes the requisite number of times (N). If the
number of iterations is not known then you have to use a while loop.
Is there some religious prohibition against the while loop that precludes its
use? There are ugly, inelegant things you could do by putting a case
statement in the for loop to avoid doing anything after some number of
iterations but there is no reason to do this except to introduce wasteful,
inefficient, useless code. Use a while loop, you will like it.
An underappreciated feature of while loops is their ability to index arrays
or produce array outputs similar to for loops. To do this "enable indexing"
on the wire coming into or out of the while loop. Likewise you can disable
indexing on for loops to avoid the indexing feature.
(back)
What does a 0 msec wait function do in a while loop?
<Greg McKaskle>
(Timeless)
If you have multiple loops in your application that don't need to run as fast
as possible, then placing a 1ms delay will limit your loops to at most 1000
iterations per second. Without a delay, the loop rate can be in the millions
of iterations per second range depending on what is inside it. So that means
that your CPU has about 1/1000th as much work to do and can go off and tend
to other tasks. If your loop was already taking several ms, then the 1ms
delay is likely in parallel and it won't affect the loop speed noticeably. So
placing the delay in the loop can drop your CPU usage noticably and allow
time for the OS to do other work like send messages that your application may
be waiting for, etc.
But what does a wait of 0 ms do? Let's consider the LV execution system to be
a xerox copying machine. Everyone that has something to copy heads for the
copying machine and lines up. If you have never had the pleasure of waiting
at a copy machine, then consider any other time consuming task where people
wait in line. Everytime a LV diagram executes a wait function, it is like
releasing the copier and staying out of the line for some time delay. After
the delay has passed, you will get back in line to make your next copy. A
wait of 0 ms is like immediately going to the end of the line to let a
smaller copy-task take place. If nobody is in line behind you, you
immediately start your next copy task. If someone is in line, it lets them
take a turn.
This 0 ms wait is a pretty cool little trick to make LabVIEW loop parallelism
less chunky. It naturally adds some overhead because the loops have some
setup time, but when the loops are doing significant work it is tiny. Use it
whenever you think you need to, but beware that if some loops don't have any
delay, they are still going to hog the CPU, and the wait of 0 ms may turn
into much larger waits because the loops with no waits play by different
rules.
(back)
How do I execute a shell command via system exec?
<Alex Le Dain alexATicon-tech.com.au>
(Timeless)
The exec VI function can be used to send shell commands. Include the shell
call with the commands that are desired. For example under Windows operating
systems the call is to command.com. For example, the string to copy filea to
a drive would be
command "copy filea a:"
with the arguments enclosed in quotes. The exact shell call will depend on
the operating system.
(back)
What is a state machine?
<Alex Le Dain alexATicon-tech.com.au>
(Timeless)
The state machine is a convenient LabVIEW construct where a case structure is
contained with a while loop. Execution of particular cases in the structure
is determined by the output from the previous case (or in the instance of the
first execution) by the control selector input. The control of the order of
case execution is controlled through the use of a shift register. By wiring
the output from one case as the determinate to the subsequent case (wiring to
the shift register on the right hand side) and wiring the input to the case
selector from the left hand side shift register, operations can be ordered
according to which state is executed.
The state machine has the advantage of allowing several steps to be linked in
series so that each individual step can be executed (or debugged) easily. The
trap for the inexperienced programmer is the use of the state machine to step
in a haphazard fashion through the cases in the loop. This can lead to
complicated pathways through the state machine reminiscent of convoluted goto
statements in conventional text based languages. However state machines are a
valuable tool and can be used very effectively with this in mind.
(back)
How do I control the order of execution in a state machine?
<Alex Le Dain alexATicon-tech.com.au>
(Feb 2003)
There are three possible ways to order a LabVIEW state machine. The first and
easiest method (although NOT recommended) is to use an integer. The integer
output from one case is wired as the selector for the subsequent case. The
disadavantage of this method is that the numbers themselves are not very
helpful in tracking through the code and the case selector becomes somewhat
meaningless. Either of the following methods are preferred and there are pros
and cons for each of them. Debate (strong at times) arises on the list quite
frequently as to the best of these methods, but in practice either will work
very nicely.
The second method is to use a string as the case selector. With the correct
choice of string states, this has the advantage of labelling each case so
that your state machine becomes self documenting. A further advantage of this
method is that all cases need not be written in the first instance and it is
somewhat easier to include new states should the need arise (especially in
earlier versions of LabVIEW (< 6.0)). The disadvantage is that it is easy to
misspell a state and have the state machine buggy because of his. An easy way
to overcome this disadvantage is to include a default case that pops up a
warning including the spelling of the failed case.
The third method is to use an enumerated type as the case selector. In
preference this enum should be a typedef'ed so that changes to the states are
refleced easily and transparently when new states are added to the enum (via
the LabVIEW auto update from typedef setting). Note that there are reports
where this method causes problems in version of LabVIEW prior to 6.0, because
the enum auto updating did not correctly handle newly inserted cases. This
method has the advantage of the string method in code documentation, but
requires the use of a default case if not all the cases are required to be
written when the code is first created.
(back)
How do occurrences occur?
<Stepan Riha, stepanATnatinst.com>
(Sep 2000)
There seem to be misconceptions about how exactly occurrences work and what
exactly the "ignore previous" parameter on "Wait on Occurrence" (WoO) does.
The following tries to explain, in a long and winded way, how they
(functionally) behave:
How Occurrences are "generated": When a VI is loaded (!) each "Generate
Occurrence" function allocates exactly one unique occurrence. When the VI is
running and this function is called it simply returns this one occurrence --
no matter how many times it is called (try putting one in a loop and
examining its value using the probe, it will always have the same number). If
you stop the VI and run it again, you will get the same value; only removing
the VI from memory and loading it again will give you a "fresh" value.
How Wait On Occurrence (WoO) works: Each WoO functions "remembers" what
occurence it last waited on and what time it continued (because the occurence
fired or because of a timout). When a VI is loaded (!) each WoO is
initialized with a non existing occurrence. When a WoW is called and "ignore
previous" is FALSE there are
four potential cases:
1) The occurrence has *never* been set -> in this case WoO waits
2) The occurrence has been set since this WoO last executed -> WoO does not
wait
3) The occurrence has last been set before this WoO last executed and last
time this WoO was called it waited on the *same* occurrence -> WoO will wait
4) The occurrence has last been set before this WoO last executed but last
time this WoO was called it waited on a *different* occurrence -> WoO will
*not* wait!!!!
The first three cases are pretty clear, the last one may seem a bit strange.
It will only arise if you have a WoO inside a loop (or inside a *re-entrant*
VI in this loop) and it waits on *different* occurrences (out of an array,
for example) or if it is inside a *non re-entrant* VI and the VI is called
with different occurrences. These cases do generally not happen. The reason
the WoO behaves this way is due to its implementation. Each
occurrence "knows" the last time it has been fired, and each WoO remembers
the occurrence it was last called with and what time it fired (or timed out).
When WoO is called and "ignore previous" is FALSE, it will look at its input;
if the input is the same as last time, it will look at the time of the last
firing and wait depending on whether the time was later than last execution;
if the input is *not* the same as last time, it will simply look at the time
and wait depending on whether it has *ever* been fired.
A possible (implementation) problem: It appears that occurances are
"remembering" that they have been set during previous invocations of the
program. One would think that generating an occurance should create a clean
"non-set" occurance. This problem can be illustrated in a program that has
three parallel loops with an abortable wait in each using occurances. If the
program is stopped with the stop button things are fine. But if one waits
until one of the random stop conditions triggers the end of the loops
(generated from the other loops), the next time the program is run, the loops
will execute only once and not loop at all. (The random terminate condition
in the actual program is an error occuring in some piece of equipment.)
Either this is a bug, or we are completely wrong on the use of occurances.
Our example here is using the occurances in the "do not clear previous" mode.
We would think that we will not remember ccurances from previous runs of the
program since a new clear occurance should be created with the generate
occurance icon. In this instance we can not use the clear previous occurances
mode since we need a single occurance to stop multiple parallel loops.
The reason for the problem: The first time the occurrence is set because of
an error, the loops terminate, but the "stop button" loop is still running.
When you click on the stop button, the occurrence gets triggered again
(unneccessarily) and the program stops. The next time the VI runs, the WoO
will not wait because of this extra trigger; and since you'll trigger again
in the "stop button" loop, the VI won't work until it's reloaded from disk.
A solution to the problem: Due to this behavior of occurrences, it is clear
that one cannot use the "timed out" flag to determine when the occurrence
fired. You will have to maintain some global information about your state,
let's say in a global boolean called "FINISHED". At the beginning of the
program you would initialize it to false. If you have an error, set FINISHED
to true, and then trigger the occurrence. After the WoO see if FINISHED is
true (make sure you don't read the global until *after* WoO finished
executing), if FINISHED is false continue with the loop. In the "Stop
button" loop, also set FINISHED before you trigger the occurrence.
BTW, if you don't like globals, you could use a VI with an uninitialized
shift register (LabVIEW 2 global), but the effect would be the same.
Comments: One may think that occurrences are quirky, a pain to use and that
they
should be avoided. One might be right! Occurrences are very low level and you
often have to add functionality to them in order to use them effectively. In
other words, they are not for the faint of heart. Anything implemented with
occurences can be also implemented without them, but maybe not as efficently.
What occurrences do is to allow you to program synchronization in a way that
does not use polling, and is thus "cheaper" in processor time.
(back)
Why does an typecast enum output not change?
<Paul Sullivan PaulATSULLutions.com>
(May 2002)
If a typecast function is used to convert a number related, for example, to
the button pressed (in an array of buttons) to an enum type then a problem
observed in that the type cast doesn't seem to work. The button press is
changing the number on the input, but the enum output does not change. The
answer is that most likely the two inputs to the typecast function have
different representations, probably I32 and U16. The type cast is reading the
high order bits of the I32 (which aren't changing) and casting them into the
U16 enum output. The solution is to convert both to representations having
the same length (signed and unsigned mix fine), either by changing the
representation of the controls themselves or by using a convert function at
the input to the type cast.
(back)
How do I make cool looking icons smaller than the entire square?
<Don R. Wagner wagnerATsiliconlight.com, Albert Geven>
(May 2002)
First, draw the border for the icon in the B&W icon view. Be sure to align
the icon to the wiring terminals by using the Show Terminal check box. This
defines the icon outline. If you have no border drawn in the B&W view, it
defaults to the whole square (in my limited tests, you get a "no outline in
B&W view default will be used" dialog). The icon border will actually be the
largest of the drawn areas in the three icon views. So actually, you need
only draw a single dot on the B&W view to define your 256 colour drawing as
the border limit. Something must be drawn in the B&W view to get the
selection border for the icon on your diagrams to conform to the drawn
outline (is this a useful feature? seems like you could have nothing drawn in
this view by defaults and have it still work).
Second, Click on the 256 colour icon view (if that's what you use), and click
Copy from Black & White to get your border. Then finish drawing the icon to
your liking, link it up, and presto, small icon. Wires to border, transparent
outside, etc. You can even make irregularly shaped icons. The white area
outside will be transparent in your diagrams. Interestingly, if the border
you define is not contiguous, the inside of your icon will also be
transparent (you can see a wire go all the way to its connection point
(visible as a little cross in the "show terminals" icon view). You can
actually have multiple non-transparent areas in your icons if you wish. Very
cool! You could have a wire pass between two blobs to represent some subtle
transformation on the data if you really wanted to go all out, but this
actually requires drawing wires between the connection points on the icon to
make them look contiguous.
(back)
Are there any other icon tricks I should know?
<Don R. Wagner wagnerATsiliconlight.com, Albert Geven>
(May 2002)
Double clicking on some (any?) of the tool icons in the editing palette will
apply that tool to the entire icon square. For instance double click on the
dotted square selection tool will select the entire icon area (handy for
deleting the icon as a starting point in making small icons). Double clicking
on the bordered square will draw a border around the current icon in the
currently selected colour, without affecting the inner pixels, etc.
(back)
Why doesn't my FOR loop return a value?
<Jean-Pierre Drolet jean-pierre.droletATtr.cgocable.ca, Uwe Frenz
Uwe.frenzATgetemed.de, Alex Le Dain alexATicon-tech.com.au>
(Oct 2001)
As a good programming rule in LabVIEW, NEVER output a value from a for loop
with indexing disabled. When the loop does not execute (0 iteration) a non
indexing output stays undefined and will hold any garbage left there by
previous memory usage. This is because the wire output from the for loop has
no code or data source to get the value from when the loop does not execute.
Instead, use a shift register (SR) to output the value and at the left SR
enter a default value for the case when the loop does not execute. When the
for loop does not execute this default value of the left SR is passed to the
right SR. Similarly passing a refnum through a for loop that never executes
(0 iterations) destroys the reference. Either pass the reference using a SR
as described above or wire around the loop. Note that while loops always
execute at least once so the outputs are always defined.
(back)
What is a reentrant VI and what are the implications of reentrancy?
<David A. Moore David_A_MooreATMooreGoodIdeas.com, Greg McKaskle>
(Feb 2003)
First, consider three VIs, foo.vi, bar.vi, and sub.vi. Both foo.vi and bar.vi
call sub.vi.
If sub.vi is normal (not reentrant), then if foo.vi tries to call sub.vi but
sub.vi is busy servicing a call from bar.vi, then foo.vi has to wait. This
can be both a very GOOD thing and a very BAD thing depending on
circumstances. It's very GOOD when sub.vi controls access to something like a
serial port, where you only want one part of your program accessing it at a
time. It's very BAD when sub.vi controls something like ALL the serial ports,
because you may want foo.vi to be able to use one serial port while bar.vi is
busy using a different serial port. Another very bad circumstance is where
foo.vi is in a critical loop and bar.vi is not, yet because of the contention
for sub.vi, bar.vi can end up blocking foo.vi.
If sub.vi is reentrant, then both foo.vi and bar.vi can call sub.vi at the
same time. In order for this to work, each call to sub.vi needs to have its
own "data space" which is all the internal storage sub.vi uses in order to
execute its code.
Now at this point I need to point out a distinction between LabVIEW and most
other languages. LabVIEW doesn't want to allocate a data space on the fly,
because for LabVIEW that would slow down performance. LabVIEW allocates all
the VI data spaces it needs when VIs are being loaded. Except when you use VI
Server to call VIs dynamically, all the loading happens before any VIs
execute. Therefore, for each place that sub.vi appears on foo.vi's block
diagram, a copy of sub.vi's data space gets embedded in foo.vi's data space,
assuming sub.vi is reentrant. If sub.vi isn't reentrant, it just has its one
data space allocated that each call will use in turn.
In most (all?) other languages, reentrant functions allocate their data
spaces on the fly, so there's no storage that goes with each place that a
particular function is called.
How does LabVIEW's unusual implementation affect us in practical terms? There
are really two ways:
1. If you use uninitialized shift registers to store information, then you
can get two different behaviors depending on the reentrancy of your VI. For a
non-reentrant VI, you get a data sharing function that lets you move large
quantities of information between parallel loops without making copies of it.
For a reentrant VI, you get a reusable storage function that can keep
independent copies each place you use it. There is a PowerPoint presentation
and some example code related to uninitialized shift registers at:
http://www.mooregoodideas.com/Downloads/Downloads.htm#ChangeDetector.
2. The second implication is that you can't do recursion (functions that call
themselves) easily in LabVIEW. In most languages, if a function is reentrant
then it's OK for it to call itself. In LabVIEW, that would require that the
data storage for sub.vi would include a copy of the data storage for sub.vi
which would ... to infinity. You can do recursion in LabVIEW if you use VI
Server to have a VI call itself dynamically, but as I said, allocating data
spaces on the fly is inherently slow. In LabVIEW, it's best to convert
recursive algorithms to their iterative equivalents, which I hear is
mathematically proven to always be possible. In the iterative version, you'll
end up changing the sizes of arrays at each iteration, which is also one of
the slower operations in LabVIEW, but is not nearly as slow as dynamic VI
calls.
And Greg McKaskle added:
To expand on this, reentrant means that more than one execution is allowed to
take place at the same time. In other languages, it is more a situation than
a setting. You never mark a C function as allowing or disallowing reentrancy,
it is either safe to do so or a source of bugs. In LV it is a setting, and
many times its setting doesn't affect the correctness of a VI, but in some
cases, it can be a source of bugs. It depends on what the VI does.
The setting in LV determines two major attributes about how a VI executes.
First is access. With reentrancy turned off, only one call to the subVI can
be active at a time. When the current call finishes, the next one can begin.
The subVI calls queue up while the VI is busy. For functions that execute
quickly, this is normally fine and reentrancy doesn't affect much.
If you have a function that uses TCP to talk to another computer and waits
for responses, these waits also affect the other subVI calls that are queued
up. So if you have an operation that can occur in parallel and doesn't
consume the CPU, you can make the VI reentrant and the multiple subVI calls
don't enter a queue, and multiple VIs can talk TCP and wait for responses at
once. This allows the wait time of one subVI to be used as work time in
another and increases overall performance.
On the otherhand, given a VI that reads a global modifies it and writes it
back, a reentrant subVI means that more than one subVI call at a time can be
modifying the global -- a race condition which will cause incorrect answers.
Lots of real-world devices also get confused when more than one subVI tries
to control them at a time. So when trying to protect a global resource, the
one of the tools, and frequently the easiest to use is to simply make sure
that the access goes through a non-reentrant VI.
The second attribute is data side-effects. If a VI has unconnected controls
or uninitialized shift registers on its diagram, then it remembers some
amount of information from call to call. A good example of this is a PID or a
filter. Data from previous calls affect the result of the next call. For
these sorts of VIs, if they are reentrant, then each call gets its own place
to store the previous call's state information. If made non-reentrant, there
will be only one storage location for all calls to share, so the data will
get all jumbled, likely causing an incorrect answer.
(back)
What are some of the pitfalls of the event structure?
<Alex Le Dain alexATicon-tech.com.au>
(Feb 2003)
In LabVIEW v6.1 a new structure was introduced: the Event Structure. This
structure allows the user more power over the UI, but at the same time with
the new power comes the potential to lock a LabVIEW application hard. Below
are a compilation of tips for using the UI Event Structure:
0. Read the LabVIEW Help on "Loops and Case Structures, Case Sequence and
Event Structures, Event Structures, Caveats and Recommendations when Using
Events in LabvIEW"
1. Only place a single event structure within a while loop. In reality the
while loop and the event structure are intimately linked so there should only
ever be one event structure per loop.
2. Never place an event structure within an event structure. It is better to
solve issues where you might want to do this with some thought and perhaps a
second while loop in parallel.
3. It is possible to have more than one event structure while loop
combination on the same block diagram. There are valid reasons why you might
want to do this: eg to have some events handled without pausing the while
loop (Lock Panel Until Handler Completes = False) and others to wait until
the handler completes (= True). The advice here is to separate out those
events that lock the handler and those that do not lock the handler into
separate while loops.
4. Whether the panel is locked until the handler completes is set
individually for EACH case in the event structure.
5. It is advisable to notify the user (ie with a message popup or mouse busy)
when tasks that lock the panel take a long time to execute (> 0.5 s). Another
way is to set some busy status message visible as the first step and then
hide this message when the case exits.
6. Boolean switches and their state require some thought when used within the
event structure. With the default button style ("latch when released")
LabVIEW reads the state of the boolean and only after reading does the button
change back to it's other state. If the boolean is the "switched" type then
TWO events are generated when the boolean is switched (ie F->T and T->F). If
a single case of the event structure is used for "value chaged" on a boolean
that is of the "switched" type, make sure that the code is only executed when
the true event is processed (ie with a true/false case selector). This is
most relevant where to use a boolean as a local variable then the boolean
type must be of the "switched" type.
(back)
Why does the event structure behave as it does?
<Greg McKaskle>
(Feb 2003)
LV BE (Before Events) had the panel and diagram behaving as asynchronously as
possible WRT one another. A control periodically handled UI events from the
user and dropped the value into its terminal. Independently, the diagram
takes the current value when it is needed and does its work. There is no
synchronization between when the UI event is handled, when the value in the
terminal changes, and when the diagram reads it. It is often a very simple
way of writing your code and mimics how most hardware works. So why do events?
The primary reason for the events feature is to allow synchronization between
the UI and the diagram. First off, the diagram gets notification of a value
change. It is guaranteed not to miss user changes or to burn up the CPU
looking for them. In addition to the notification, the diagram gets a chance
to respond, to affect the rest of the UI, before the rest of the user input
is evaluated. Maybe this part needs an example.
Lets look at polled radio buttons. In theory, your diagram code polls fast
enough to see each change in the radio buttons so that it can make sure that
the previous button is set to FALSE. But when the user is faster than the
diagram and presses multiple buttons, what order did they press them in?
There has to be a fixup step to break the tie and pop out all but one button
to FALSE regardless of the order the user clicked them.
With events, when the user clicks on a button, the panel is locked and will
not process the next user click until the diagram finishes. This allows the
diagram to see the button changes one at a time in the same order as the user
presses.
Another example, perhaps a better one, is a panel with three action buttons:
Save, Acquire, and Quit. The order that the presses are responded to is
important, so the polling diagram has to "guess" whether to Save and then
Acquire or Acquire, then Save. The Event Structure knows the order and the
code in it is synchronized with the UI allowing for a better, more friendly
UI.
Getting back on topic, the event structure introduces the synchronization,
but the downside is that as with all synchronization, it allows for deadlocks
when not used in the right way. As already discovered, nesting event
structures is almost never the right thing to do, at least not yet.
As noted earlier, it is possible to leave the Event Structures nested, but
turn off the UI locking. This appears to solve the problem, but it isn't how
I would do it. I think a much better solution is to combine the diagrams of
two structures into one. The value change for hidden controls will not
happen, but it doesn't hurt to have it in the list of things to watch for.
Another option is to place them in parallel loops. This will let the first
structure finish and go back to sleep.
(back)
Why doesn't the event structure register local variable changes?
<Greg McKaskle>
(Feb 2003)
>The main reason for not sending events for programmatic value changes is to
avoid feedback.
A happens. In responding to A, you update B and C. If responding to B or C
results in changing A, then you have feedback and your code will behave like
a dog chasing its tail. Sometimes the feedback will die out because the value
set will match what is already there, but often it will continue indefinitely
in a new type of infinite loop.
One solution to this is what is called User Events. You define what data they
carry and when they fire. Then you either have the value change and the set
local both fire the user event, or you can combine the event diagram to
handle both and just fire the event when writing to the locals. Today, you
can accomplish this with the queued state machine using the state machine to
do all the common work and just having the event structure pass things to the
queue.
(back)
Why is it necessary to lock the panel after an event fires?
<Greg McKaskle>
(Feb 2003)
Given the synchronized mechanism of events, it is pretty easy to repost
events to another queue or turn off locking and synchronization. If the event
structure isn't synchronized, it would be impossible for the diagram to add
it and become synchronized with the UI, so it is at least necessary for event
diagrams to be able to lock the panel.
Should it be the default?
In our opinion, yes. When responding to an event, it is pretty common to
enable/disable or show/hide some other part of the display. Until you finish
doing this, it is wrong for LV to process user clicks on those controls, and
LV doesn't know which controls you are changing until you are finished.
Additionally, it isn't the best idea, but what happens when the event handler
does something expensive like write lots of stuff to a database inside the
event structure? If the UI is locked, then the user's events don't do much,
ideally the mouse is made to spin and this works the same as a C program. The
user is waiting for the computer, and the UI more or less tells the user to
be patient.
If the UI isn't locked, the user can change other things, but you can't
execute another frame of your event structure until this one is finished.
This is a node. It must finish and propagate data before it can run again,
and the loop it is probably in can't go to the next iteration until it
completes. You would have low level clicks being interpretted with the
current state of the controls before the diagram has a chance to respond.
This is sometimes the case, so it is possible to turn off event handling on
the cases where you know that you may take awhile and you do not affect the
state of the UI.
If you need to respond to the events in parallel, you can make a parallel
loop, add an event handler for the other controls, and handle them there
while this one churns away. That will work fine and it is IMO clear from the
diagram what is synchronized and what is parallel. Taken to an extreme, each
control has its own loop and this approach stinks, but it is a valid
architecture. Note that for this to work well, you need to turn off the UI
lock or have a node to release it.
Another way of doing expensive tasks is to have the Event Structure do the
minimum amount necessary before unlocking -- treat them like interrupts. Have
the event structure repost expensive operations to a parallel loop or fire up
an asynchronous dynamic VI. Now your event structure is free to handle
events, your UI is live, LV is still a nice parallel-friendly language, and
your diagram just needs to keep track of what parallel tasks it has going on.
In the end, I'm not sure I can convince you, but if you continue to
experiment with the different architectures that can be built using the Event
Structure, I think you will come to agree that it normally doesn't matter
whether it is locked or not. There are times where it is really nice that it
is locked, and occasionally you may turn off locking so that the user can do
additional UI things up to the point where synchronization is necessary
again. For correctness, we decided that locking should be the default.
I'd suggest reading the article on devzone sooner or later. It is in the
Developer Insights/LabVIEW Guru section, and it will help start you down the
right path.
(back)
What does an "Insane Object" mean? Should I report this as a bug to NI or do
they already know about it?
<Stephan Mercer stephan.mercerATni.com>
(Feb 2003)
Firstly, there is no need to necessarily report this to NI, as they do know
about insane objects. Secondly they are NOT just bugs, insane objects can be
generated as part of the LabVIEW verification process.
The insane object message is what LabVIEW puts in a dialog when one of the
objects on the diagram does not meet its "checksum". In other words, this
object isn't in a state we expect it to be in. Most of the time these errors
are not fatal -- we simply put the object in the state we do expect. But it
raises questions about how the object got into that bad state and what might
have been done with that object between the last time we checked and it was
good and the time it became insane.
The insane object messages are something we work on with each version of LV.
But as it is a generic error message that can apply to anything from a simple
cosmetic to the front panel itself, you'll still see them in any version of
LV that has a bug -- a fact of life unfortunately for the foreseeable future.
If you get such a message, it is good to check the known bugs of LV at ni.com
and if the particular insanity in your dialog is not listed, report the
insanity to NI technical support.
The cryptic nature of the message can be deciphered as follows:
Insane object at FPHP+44 in "name.vi": {dsitem} 0x400: Panel (FPSC)
* FPHP -- this will be either FP or BD for "front panel heap" or "block
diagram heap"
* 44 -- this is a number indicating which object
* name.vi -- which VI had the insanity
* {dsitem} 0x400 -- really only meaningful if you know how our internals
work; I'll skip it here
* Panel (FPSC) -- the type of object that has problems. The four letter codes
are usually descriptive (COSM for a cosmetic or simple drawn part, SGNL for a
signal aka wire)
Most of the time, deleting the offending object and recreating it from
scratch is sufficient to fix your VI and allow you to continue working.
(back)
What are LabVIEW 2 style globals, functional globals, and uninitialised shift
registers (USR's)?
<Stephan Mercer stephan.mercerATni.com>
(Feb 2003)
LabVIEW 2 style globals and functional globals refer to the same code
construct in LV. Most people in the mailing list refer to these structures as
LV2 globals because this was the version of LV when they were introduced.
Functional globals is how NI refer to these structures because they can be
more than just global variables in that they can contain code and data.
Uninitialised shift registers (USR's) are the LV code primitives used in
making these functional global variables work.
A functional global is created by placing a shift register upon a while loop
and wiring a boolean constant to the condition terminal such that the loop
executes a single time. When the shift register is not wired with an input
from the left hand side (ie it is uninitialised) LV retains the memory in use
for that vi between calls. This means the the vi stores the value of the
shift register variable between calls (executions) in the code. Extensions
can be done by adding cases within the loop, eg one for read and one for
write with a 'mode' enumerated type selecting which case is executed when the
vi is called. These globals can contain as many cases as needed and because
these are very similar to state machines, they can be made to execute several
cases at each call to add increased functionality.
Of course all recent versions of LV have global variables, accessed from the
structures palette, so why do people still refer to them or even use them?
Well, there are a couple of good reasons:
1. More efficient memory storage. Beacuse the vi retains the same data space
regardless of it use in the code, only a single call to the memory manager is
made. If arrays are stored in the USR, and replace array element primitives
used, then the memory space is efficiently used/accessed wherever the vi is
called.
2. Built in data space protection. One problem that can happen with normal
globals in LV, occurs if you try to write a value to the global in several
places in your code, updates to the value are unpredictable with respect to
time. This means that changes to the global can be subject to race
conditions. This is especially true if you read/write to the global where a
race condition could occur between the read and the write of the variable.
So how does it work? In the LV execution system, a single vi, even if called
from multiple places in the LV code, can only be executed exclusively in each
place. In other words, the vi will not execute until a previous execution is
complete. With a functional global, because it is a vi, LV determines that
the functional global can only be executed once. Therefore updating of the
shift registers is protected by the LV execution system. This mechanism of a
vi 'protects' the data preventing any race condition. It should also be noted
that USR's only retain this ability to share data across the code when they
are not reentrant. If the vi is reentrant then the USR still stores the data,
but this is only stored between calls of the vi in that place in the code -
another call to the vi in another section of the code would have its own
dataspace and its own saved USR values. It should be stressed that the
reentrant vi would not transfer data from one section of the code to another
and by definition not be a functional global.
(back)