About Type Definitions

The types, they are a-changin’

LabVIEW beginners often either don’t know about type definitions, or don’t appreciate their value. This article will attempt to explain their use and how they can save you boatloads of time and effort.

Suppose you have a cluster of items that’s very handy to your project. For the sake of discussion, we’ll call it a channel, and assume this is a data acquisition project. A channel might have these items:

  • Channel Name
  • Active switch
  • Scale factor
  • Units

So, suppose you have an array of these to match the channels on your MIO DAQ board.

You might develop a series of VIs that deal with channels:

  • You need an editor, so the user can rename them, select which ones to use, and scale them;
  • You need a VI that takes an array of channels, and configures the hardware;
  • You need to print the setup, so a VI takes an array of channels and formats a page for printing;
  • You need to export the data to a spreadsheet, so another VI takes the array of channels and produces a spreadsheet.

and so on. You’ve defined the cluster and all these subVIs and it all works.

Now, though, you realize that you need to add another item to the channel, namely OFFSET. You have a new 4-20mA transducer which produces a 1-5V signal when you use the right resistor. So, instead of the single SCALE FACTOR (which assumes 0.0EU at 0.0 Volts), you implement the standard linear equation Y = mX + b, where X is the measured voltage, m is the SCALE FACTOR (slope), b is the OFFSET, and Y is the resulting Engineering Units (EU). How do you go about it?

The brute force way to go about it is to open up every instance of the cluster you used, and add an OFFSET term to the cluster. OUCH! In the example, you have 4-8 instances; that’s painful enough, but imagine if you had 50!

A slightly (slightly) more civilized way is to add the OFFSET term to one cluster somewhere, then copy the cluster, and paste it over all the old instances.

One of the good things about LabVIEW is that you can follow all the broken RUN arrows and figure out the places you missed. But is this really what computers are for? After all, LabVIEW knows all these things are broken, it knows WHY they are broken, but it’s YOU that has to chase them all down. So, do this a few dozen times and you might start wishing for a better way.

A Better Way

Enter the TYPEDEF, short for Type Definition. A Typedef is a “master” control. To use it, you ask for a new custom control (File | New | Custom Control). Here’s where you define the cluster you want for a channel. The first time you would use the four items mentioned initially. You then set the control menu to STRICT TYPEDEF, and SAVE the file with a name like CHANNEL.ctl. Now, every time you want to use it, you use the SELECT A CONTROL option on the panel palette and choose that file. What you get is a cluster that looks just like the one you made originally. Place one on your VI for exporting, another on the VI for printing, etc., etc. You can copy and paste them just like anything else.

So far, there’s nothing different. Where the value come in is when you need to change it. You may notice that you can’t change the cluster on your export VI. Drag something on top of it, and the something just sits there, on top. It doesn’t get put “into” the cluster. If yuo really do want to change it, you open the CTL file. You can do this thru the FILE menu, or notice that every instance has an OPEN TYPE DEF entry in the pop-up menu now.

If you open the CTL file, you can add the OFFSET term there, and SAVE it. When you do, EVERY INSTANCE of that control/indicator updates to follow! Open any of your VIs that used it, and it will have the OFFSET term in it! You’ve just changed 4-8 (or 40-80) controls in one whack. This is convenient for this example, but on a larger program, it is absolutely essential.

Another use for them is in ENUMs. Suppose you have an ENUM for units, with the options “PSI, kPA, mmHG, InH2O”. You can create that, and copy and paste it wherever you need it, but if you have to add something to the list, you’re in the same boat as the cluster, above. The answer is the same, make it a TYPEDEF, and use the TYPEDEF wherever you need to. Then when it’s changing time, all the instances update at once.

An Analog Clock (first version)

Sometimes all that digital stuff is just too bland.

If you want to display a time-of-day clock in LabVIEW, it takes three seconds to plop down a TIMESTAMP indicator, and 10 more seconds to enter ADVANCED EDITING mode, and skip the month, day and year, and format it the way you want. Piece of cake.

And it looks just exactly like every other digital clock out there: six digits, two colons, everybody knows what it means. And that’s fine for a lot of purposes.

But sometimes you like to decorate your work a little bit. If you’ve got space on a front panel, and a few cycles of execution time in your run loop, then consider an analog clock.

clock

The visual part of this is pretty simple: a custom control with a cluster of three gauges, all with their scales stretched to a full 360° and both ends of the scale at the top:

  • A gauge indicator in the back: single dark red needle, and the scale set to 0..12.
  • A gauge indicator with an INVISIBLE scale of 0..60, and a bright red needle for minutes.
  • A gauge indicator with an INVISIBLE scale of 0..60, and a gray needle for seconds.

The three indicators are sized identically and lined up identically, with transparent backgrounds, so the needles show through.

An analog is an analogy

You might think it’s straightforward to use this thing – just pass a cluster of hours, minutes, and seconds to the indicator, and you’re done.

You’d be wrong.

Consider what would happen: at 9:22:10, as shown above, the hour hand would be EXACTLY on the 9, the minute hand EXACTLY on the 22. Not exactly what you would expect from an analog clock. It’s even worse 35 minutes later: the minute hand would be on the 57, but the hour hand is still EXACTLY on the 9. It looks for all the world like it’s three minutes before nine, but it’s actually 9:57. If you’re going for the analog, then you’ve got to do better than that.

The gauges accept floating point numbers, so we’re in luck. If you think of how an analog clock works, it’s not the same as a digital clock (and you can quote me on that!). The second hand may move smoothly around the dial, or it may snap from one second to the next. We choose the snap idea, as it’s easier. But the minute hand definitely does NOT snap – it moves smoothly.

So we have to incorporate the position of the SECOND hand into the position of the MINUTE hand. Namely, we add SECONDS / 60 to the MINUTES value given, so that 6:30 will be halfway between 6 and 7, and 6:57 will be just before 7. Similarly, we have to incorporate the position of the MINUTE hand into the position of the HOURS hand so that 6:30:00 puts the hour hand at 6.5, halfway between 6 and 7.

Download

Click to download the example (about 12kB ZIP), containing the typedef indicator you can put on your panel, and a VI to do the above math for it. You can display the CURRENT time by leaving the TIME input unwired, or display a specific time by supplying a timestamp.

Enjoy.

State of the Machine

Handle command sequences for one or a thousand devices the same way.

In dealing with external devices, there are often command sequences that require coordination between the host computer and the device. For example, a recent project of mine involved a TCP connection to (gas concentration measurement) devices which require frequent calibration (using gases of known concentration). The sequence went like this:

  1. Send CAL command
  2. Wait (about 60 sec) until END reply
  3. Chan N = 1
  4. Send request for Chan N’s ZERO result
  5. Wait on Chan N’s ZERO result
  6. Send request for Chan N’s SPAN result
  7. Wait on Chan N’s SPAN result
  8. N = N + 1. If N <= #Chans, go to 4
  9. Reset Device to standby.

That is written in what I call linear style; it assumes you have nothing else to do in the meantime. If that’s the case, then it’s well enough to implement it in exactly the above way. But that’s never the case: you have user interface issues to attend to, if nothing else. So what’s the best way to handle it? A State Machine is a piece of code, with three distinguishing characteristics:

  1. It knows its own state, one of a finite number of predefined states.
  2. It can determine whether the conditions for entering a different state have been met, and if so, switch to that state.
  3. It is called often and performs the above steps quickly, and returns without waiting.

For our gas analyzer example, the states could be Sending CAL, Waiting for END, Requesting Zero, Waiting on Zero, Requesting Span, Waiting on Span, Next Channel, and Resetting Device. For most cases, an Idle state is also useful.

I often combine the state machine itself with other code that pertains to it. The “manager” has several functions, one of which is to service the state machine. Other manager functions might be to Initialize (connect), Shutdown (disconnect), and to initiate the start of a CAL sequence.

Suppose we’re in the Sending Cal state when we get a service call. We know the conditions for getting out of this state are having sent the command to CAL. So we send the command and switch states to Waiting for End. This service call lasted only long enough for use to generate the command string, and send it down the pipe, and we’re done. The CPU is free; the caller can do other things.

The next time we get a service call, we see that we’re in the Waiting for End state. The conditions for exiting that state are having received the END reply from the device. We check the TCP read to see if we have a reply but we do not wait for one. That would violate rule C. We either have a reply or we don’t. If we have the reply, we switch to the next state; if we don’t, we stay in the same state; but either way, we return quickly.

If we are in the Requesting Zero state, we send the command to ask for the zero value of a certain channel and enter the next state. The Waiting on Zero state checks for a reply, and so on and so on.

Benefits

So what is the point of all this? What benefits do we gain from this architecture?

  1. The CPU is not tied up handling the device. If you run the linear style, you’re stuck waiting for 60 seconds. You have to insert other code to handle user stuff like clicking buttons.
  2. The code is re-usable. Keep the state machine and its manager completely separate from the user interface stuff, and you’ll be able to use the same manager code the next time you deal with that device, even in a different project.
  3. You can add a second (third, fourth…) device to the mix, and use the same code! There’s a small change you have to make to go from one device to two. After that, going from two to three or three hundred is trivial!

Multiple Devices

To use the code to manage more than one device, you have to make it reentrant, using the VI PROPERTIES – EXECUTION page. Making it reentrant means two things:

  • The code can be interrupted, run from the beginning by a different caller, and then resumed by the first caller. We don’t care so much about that in our case.
  • Each caller has its own dataspace. In other words, if we’re called from one place in the main code, we have one set of local data (local variables, and uninitialized shift registers), and if we’re called from a second place, we work with a completely different set of data, This we care very much about, since it allows us to store our state, and anything else we want, in shift registers, but have them be different for each device.

The rules for reentrancy dictate that each time you place the VI on the diagram you create a new dataspace for it. In other words, if you called the INIT function from one place and the START CAL function from another, they are not dealing with the same data. That’s no help at all.

The solution is to have a “wrapper” VI, which has all the controls and indicators that your core VI (the reentrant one) does, plus another integer which determines which instance to call. In other words, if you want four devices, you have a wrapper VI with a case structure in it. The case structure has cases 0,1,2,3. Inside each case is a call to the reentrant state machine manager. All its inputs come from the wrapper’s inputs, all its outputs go to the wrapper’s outputs. An additional input selects which device to use. That way, the re-entrant VI is only on the diagram in four places. But you can call the wrapper from as many places as you need to.

An example will probably help clarify that. Click to download example (100 kB ZIP).

The Terminator

Make sure that quitting time is followed by happy hour.

Generally, you don’t do anything special in a LabVIEW program to quit; when it runs out of things to do, it terminates. (Quite clever, that). Your program has a loop waiting on the user to do something: when the QUIT button is clicked, the loop stops. If that is the last thing in your VI, then the VI terminates, and it’s left on the screen for you. Usually, that’s what you want; you quit because you’re ready to add the next feature, or because you decided that the purple font on the pink background was too gaudy, or because you need to find out why the temperature shows -1.33e+44 degrees.

But if you compile your program into an executable, the same thing happens (the main window hangs around), but that’s usually NOT what you want here.  The user pressed the QUIT button – why doesn’t it quit?  Most programs make their windows disappear when the user quits, and yours doesn’t do that.   You might understand that it really did quit and the window’s just hanging around waiting for you to close it, but unless you enjoy the tech support calls that result, you should consider doing something about it.

 

To get that window to close in an executable, you have to QUIT LABVIEW.  Find that function on the APPLICATION CONTROL palette and put it where it will execute when everything in your program is safely shut down.

The trouble is, it will ALSO quit LabVIEW when you’re in the development system.  That’s nice in the executable, as all your windows go away just like they’re supposed to. But in the development system, you don’t want to quit developing; you want to stop your program from running.

One easy way to satisfy both requirements is to use the ROOT FOLDER vi.  It has an output called NOT IN LIBRARY which connects to the QUIT? input on the QUIT LABVIEW function.  If you’re already using that VI to ascertain your root folder, then just remember the initial value of NOT IN LIBRARY.  Or call it again at quitting time; it’s small and fast.

That way, you close up the windows in an executable, but leave it ready in the development environment, and you don’t have to set conditionals, or remember to set a boolean flag in your code.  It works without change in either situation.

Where Am I?

Use a tool to find yourself.

When your program is large enough to have multiple folders to store files in, or read them from, then you have to know, or find, where those folders are.

Asking your users to create those folders will surely raise the ire of some, if not most of them. After all, the computer is capable of creating folders, the computer knows what folders are needed, why is this %!^#$^ box asking ME to do its job?

Complaining to the user about a folder not being found is in the same league; no matter how many times you print it in the instructions that they have to create the folders beforehand, folks will run your program and object to the complaint it gives them.

So, you put in things like CREATE FOLDER (”C:\MyWonderfulProgram\ConfigFiles”), and everything is all groovy, right?

Not exactly. Maybe I want to put this program on drive D:. Maybe I want to run it under OS X. Maybe I want to put it under C:\2009\March\Mediocre Programs. What do you do then?

One solution is to make all these folders relative to the root. And to do that, you have to find out where you are.

Download a Helper

I have used the following VI, for that purpose, since 1992.

Click to download Root Folder.vi

Basically, put it into a library (LLB). Call it early on when your main VI starts.
Call it with INIT = TRUE, once. When you INIT it, you can either pass the path of the calling VI (This VI’s Path) in the FILE palette, or leave the path input unwired. If you leave it unwired, it assumes that it’s in a library, and finds the path to the folder containing the library. If you provide a path, it assumes the path is to a VI in a library, and provides the path to the folder containing that library.
For our purposes, a library and an executable are the same thing, and this code works unmodified in either situation. Do not use it if it is not in a library.
After INITing it once, you can call it wherever you want to find out the root folder. You can then use BUILD PATH and create or open folders using the root folder and have them work anywhere.

Caution: Some operating systems (VISTA, in particular) will write-protect the APPLICATIONS directory, so that if the program tries to create a file or folder in that directory, it will be prevented from doings so, unless the app is run as an administrator. If you must run in that situation, consider a different method of locating the files you want to create / use.

Hybrid Data Files

Combine BINARY and DATALOG files for the best of both worlds.

In LabVIEW, there are three kinds of files:

  • TEXT files. Ordinary text, stored in human-readable form, with spaces and line feeds, etc.
  • BINARY files. Raw information stored as machine-readable information. A 32-bit integer is stored in 4 bytes. A double-precision number is stored in 8 bytes.
  • DATALOG files. Structured data, suitable for quick transfer between a memory structure and the disk file.

I have long used Datalog files for configuration data. They offer several advantages:

  • EXTREMELY simple reading/writing. No matter how complicated the data structure, you just open the file, write the structure to it, and close the file. No muss, no fuss. I have a project where the datalog file is a single record of maybe 200 k Bytes. It’s still all done with a single WRITE FILE call.
  • Being binary, users are reluctant to open it in their text editors and twiddle about. You won’t get a service call to figure out that the user set the serial port to COM1.76 and the alarm level to 0 ( But it was working perfectly yesterday! ).
  • Compactness. A DBL is 8 bytes. The value -12.3456 is 8 bytes, not even counting the delimiters separating it from it’s neighbors. If space is truly an issue, use SGL (4 bytes) instead of DBL.
  • If your panel is set to allow a number to vary between 10 and 20, then that’s what gets stored. You don’t have to check every number in the file to see if it’s in range. Since you wrote it there, it’s correct.
  • Format checking. Even if you use the standard file dialogs for choosing files (rather than a custom one), you can set it so that it will show only files of the correct type. The user has fewer wrong files to choose from, therefore the odds of a mistake are lessened.

There are a couple of disadvantages, though:

  • Rigid formatting. The thing that makes it so easy to read and write, turns around and complicates things when it’s time to revise the format. If you add or remove so much as a single item, or re-arrange the order of things, then the old files are not readable anymore. You can attempt to compensate for this by adding spare fields at the start, but if you make a change to the format, you will have to make an updater which reads the old files and writes new ones in their place, or else all the old files are worthless.
  • Non-portability. The tamper-resistance feature can be a disadvantage if the file must be available in other (non-LabVIEW) applications. For this reason, datalog format is best suited to files that have limited, well-defined uses.

Typically, a data-acquisition program, when used over a reasonable period of time, needs a configuration file to define which channels to use, what their scale factors are, their names, and units, etc., etc. The data recorded in “Run 107″ was recorded using “JOEs setup”, but the data recorded in “Run 108″ was recorded with “JOEs Other Setup”. So how do you keep the files paired? You don’t. You can try various naming schemes, but sooner or later, some mistake will leave the user with a missing or mismatched CONFIG and DATA file pair.

I avoid that whole scenario by including the config data structure inside the data file. Every data file contains the config used to record it. You just put the config cluster inside a large cluster that includes your data, and record that. There is no question about which scale factor was used on the flatistrat channel, because it’s recorded right there. It’s a bit wasteful in terms of disk space, but not terribly so.

Datalog + Binary = Hybrid

So, given all that, suppose you have a LOT of data to record. The config data is a small portion, and the data is huge. There’s a problem with the idea of a data file being a cluster containing the config structure and the data. And that problem is memory size. To write a cluster to a datalog file, you have to have the data all in one place. If your data is stored in some other place as it’s acquired, then writing the file means making a COPY of your huge data and putting into the file cluster before writing the file. That’s wasteful. And back in the days when 8 Megabytes was all the RAM a machine could hold, and my clients needed to record more, it wasn’t even POSSIBLE.

To solve those issues, I invented a hybrid file. That term is my own label; it is not an official LabVIEW term. The idea is that you write the config data as a DATALOG file, and close it. Then you open the SAME file as a binary file , skip past the datalog portion, and write binary data. You get the benefit of both worlds: it’s easy to read / write the config header with ordinary datalog operations, and it’s easy to read the binary data with binary operations. You can write the data as you need to; you don’t need to make copies. You can write more data than you have RAM for. You just have to remember that the file doesn’t start at offset zero. It’s perfect, right?

Almost.

You have to figure out where the start of binary data should be, and that’s not trivial. LabVIEW’s DATALOG files include their own header, and the structure and size of that header is not public information. However, you can make some deductions. Since the FILE DIALOG can discriminate between datalog files of different structure, the structure format has to be embedded in the file itself. So what I do is flatten the datalog portion to a string and get it’s size. I take the TYPE STRING (which is not really a string) that comes out of the FLATTEN function, and flatten that and get another size. I add those two sizes together, and round it UP to the next highest multiple of 4096 or so. That works for any size structure, as it includes an estimate for the Datalog header, as well as our own header.

When you read the file, you do the same thing, and compute the offset where the binary data starts.

One more gotcha. Occasionally, National Instruments changes the format used in Datalog files. Usually it’s a minor change, and usually LabVIEW handles it automatically. You may have seen the message “This file was recorded using an older version of LabVIEW, it must be updated to be read. Do you want to update it?”. All that is well and good if it’s a plain Datalog file, but if it’s a hybrid file, LabVIEW doesn’t know anything about the binary data you’ve stuck on the end. So it will open the datalog portion, and re-write a new datalog portion, truncating everything after that, including your data. Beware.

Still this method brings more benefits to the table that it brings problems, so consider it for your own projects.

Enjoy.

A Beginner's Guide to TCP-IP

Talk amongst yourselves.

I guess this Internet thing is here to stay, huh? It can be daunting at first, what with looking at the complexities of a web page and the realization that banks use it to launch nuclear missiles and the military can conduct a million two-dollar transactions in a day (or something like that…:-). If you grew up with RS-232 and GPIB as the ways for one box to talk to another, it might seem seriously complicated, what with clients and servers and HTTP and proxies and routers and ports and …

It’s not.

It’s not even close.

The truth is, it’s a whole lot simpler than either of those technologies, at least from a software point of view. And a whole lot more versatile, as well. There’s one analogy that brought it home for me:

TCP communications is just like using a telephone.

This is true in a lot of ways:

  • To make a TCP connection, you have to open a connection to a given IP address (dial a phone number).
  • To answer a connection, you have to be listening for one (have your ringer turned on),
  • Once the connection is established, the roles of client and server are established by convention, not by any property of the connection. Either end can send at any time (you can call the office of the U.S. President, but who is the client and who is the server is debatable).
  • A port is the final destination-specifying part of an address (like an extension).
  • Figuring out what you’re going to say and how you’re going to say it takes a whole lot more thought than establishing the connection itself.

How to Use TCP in Your Programs

First, decide how it is to be used. By convention, the “client” is the piece that initiates the call and requests information, and the “server” is the piece that answers the call and dispenses information. Note that this is a general guideline, not a rule: the connection works both ways. How does that relate to your situation? Which piece is going to initiate the call? Which piece will answer? Do you have control of both ends of the connection? If you don’t, you have to play by the rules already established by the other end (if you want to dial up a web server, you can; but what you get back is not under your control).

Next, establish how you’re going to obtain the server’s IP number for the client. If you have a local area network (LAN), you can just fix the address (192.168.xxx.xxx) and store a constant somewhere, or offer it as a user-configurable field. If your project is large-enough scale, you can register a domain name, and the DNS service will connect the name (www.culverson.com) to your IP number. That’s only useful if it’s supposed to be a publicly-available service though. For most small scale projects, make it a user-configurable field and be done with it.

You need to know several VIs, all in the PROTOCOLS: TCP palette:

  • TCP Open Connection
  • TCP Close Connection
  • TCP Write
  • TCP Read
  • TCP Create Listener
  • TCP Wait on Listener

For the client side, it’s extremely easy:

  1. Call TCP OPEN CONNECTION with the IP Number you want to connect to, the PORT number you want to connect to, and a suitable timeout. “Suitable” requires a bit of thought: if you’re dialing across the room on a clean network, 100 mSec might be adequate, but if you’re dialing across the world, then 10000 mSec might be more appropriate.
  2. Call TCP WRITE, as appropriate. For TCP WRITE, you have to provide the CONNECTION ID (obtained from TCP OPEN CONNECTION ), and a string to send. It doesn’t have to be ASCII, you can flatten anything into a string and send it. That is especially useful for sending binary data.
  3. Call TCP READ, as appropriate. For TCP READ, you have to provide the CONNECTION ID (obtained from TCP OPEN CONNECTION ), a MODE (more about that later), and a NUMBER OF BYTES TO READ (more about that later).
  4. When you’re done, call TCP CLOSE CONNECTION.

For the server side, it’s only a tad more complicated:

  1. Call TCP CREATE LISTENER. This gives you a LISTENER ID.
  2. In a WHILE loop, call TCP WAIT ON LISTENER, using the LISTENER ID you just got. Usually you use a timeout of -1 (never). You’ll get out of it later. This suspends the loop until a connection comes in; for that reason you have this in a separate loop from your main code.
  3. When a connection comes in, the WAIT ON LISTENER function returns to you without an error. You want to check the error code – when it returns WITH an error, you want to terminate your loop.
  4. If you have no error, then the CONNECTION ID out of WAIT ON LISTENER is valid. Call TCP READ and TCP WRITE, as appropriate.
  5. When you’re done with this connection, call TCP CLOSE CONNECTION on the CONNECTION ID, and repeat the loop, to wait on another connection to come in.
  6. When you’re ready to quit the program, or at least quit listening, call TCP CLOSE CONNECTION, but use the LISTENER ID as an input for that. That will cause WAIT ON LISTENER to terminate with an error, and your loop to stop.

For the MODE parameter to TCP READ, there are four choices, as detailed in the HELP for that function. I almost always use BUFFERED, so that I get either nothing, or the amount of bytes I requested. When talking to an instrument that sends Carriage Returns / Line Feeds, I will use CRLF mode, so that I get things a line at a time. I have never used the other two modes. I also usually use a timeout value of zero, so that it returns immediately, with either a whole message to deal with, or a timeout error. In any case, I have better things to do that wait around for the message to come in.

So, how many bytes do you request? Good question. There are two methods in common use, each appropriate to some circumstances. The first is to design a protocol that uses a fixed length. If all messages can be sent in 13 bytes, then use 13 as a message length, and you’re done. Anything that is shorter can be padded up to 13 bytes, and it’s OK. For other situations, that is not adequate. In one project I’ve done, some messages can be a single byte, others can be several thousand. It would be very wasteful to pad every message up to 4000 bytes, so a better scheme is needed. The answer is to send a fixed-size header, and then a variable-sized data portion. The header includes a number specifying the length of the data portion. So each receive operation must actually read twice; once to read a header, and once to read the payload. LabVIEW documentation suggests that it is better(faster) to combine the header and payload and do a single TCP WRITE than to do two separate TCP WRITEs for the header and payload. That means the OS handles it as one transmission, not two, which makes sense.

So, for my case, I designed a HEADER typedef (you are using typedefs, aren’t you?), consisting of two fields: a COMMAND field (1 byte) and a PAYLOAD SIZE field, a two byte integer (U16).

The COMMAND field contains an enumerated typedef, defining what type of message this is. The SIZE field specifies the payload to follow.

The receiving end can therefore use a common procedure to receive messages. First you read 3 bytes from the connection, and unflatten it into a header. Then you use the PACKET SIZE field of that header to determine how much more to read for the payload.

That’s all there is to it! Well, except for that pesky bit about what to say. You still need to decide what the protocol is going to be. That’s just another way of defining who’s going to say what when. Does the server answer with a “Hello”, or does the server expect the client to ask the question first? If you control both ends, it’s entirely up to you.

Enjoy.

What Time is it?

When you’re trying to make things in your program work faster, it’s important to remember a simple rule. On any given computer…

You cannot make the computer go faster. You can make it do less work.

Of course, to take advantage of that rule, you must be able to judge a given method of doing something, and compare that to a different way of getting the same result. In text-based languages, you at least have a hint: the ten-line solution is likely faster than the fifty-line solution. It’s not guaranteed, but if you have the source code to both methods, you can make a reasonable guess.

In LabVIEW, though, everything is a black box. The Fast-Fourier Transform is a single icon on your diagram, so is the function to add two numbers. You can’t judge a function’s speed by it’s icon (there’s an aphorism there somewhere).

Suppose your task is to convert an array of complex numbers to magnitudes. The mathematical rules are not that complicated: M= sqrt(R2+I2). That’s two array multiplies, one array addition and one array square root.

But wait: there’s a function in the complex arithmetic palette to do this for you: Complex to Polar (C to P). It takes an array of complex numbers, and gives an array of magnitudes and an array of angles. That’s what you want, right?

The true speed freaks among you will immediately recognize a problem: This function does MORE than you need: you also get the angles associated with the complex numbers. And if you remember your basic trigonometry, calculating the angle is the more complicated of the two rectangular-to-polar operations. There’s the arctangent to compute, and the edge cases to deal with.

So if you’re interested in the speed of getting this done, the question becomes which one is faster? It’s certainly easier to plunk the C-to-P function down and throw away the angle numbers it produces. But suppose the speed of programming it is less important that the speed of executing it. There’s this nagging suspicion that the ANGLE calculations might be a fly in the ointment. So how do you test this?

It sounds simple: Just remember the mSec timer before doing the operation, do the operation, remember the mSec timer after the operation, and figure the difference of the two timers. Version 1 shows this approach:

testtimingversion1

Looks simple enough. When you run a test like this, you generally should disregard the timing of the first run, because the compiler has just run and it is still shutting down as your program starts up. I always take three readings, to verify that this has settled down. And the answers I got are:

13, 14, and 12 for the Discrete method, and 0, 0, and 0 for the C to P method.

Well, isn’t THAT interesting?

Obviously, that has to raise some suspicion. We’re not measuring what we think we are. Although the code looks right, it can’t do it that fast, it just can’t. Even raising N to 10 million shows the C-to-P operation taking zero time. To see what’s going on, we add indicators to the results. Since we don’t want to measure the display time, we put those indicators OUTSIDE the timing chain:

testtimingversion2

We run it four more times (ignoring the first) and get:

19, 18, and 19 for the discrete case, and 7, 7, and 7 for the C-to-P case.

What does that tell us? Well, if we give LabVIEW some credit for being smart, then we might say that it doesn’t compute results that it’s not going to use. If there’s no wire on the output, then it doesn’t perform the function at all! This leads to the question: Is it smart enough to do that on for each terminal separately, or does it go all-or-nothing? Simply adding another indicator answers that question – it takes longer to compute magnitude AND angle, than it does to compute magnitude only. No surprise, but that does reinforce our conclusion that LabVIEW is smart enough to help out. It’s only computing what we need to use.

Note that this applies only to built-in (yellow) functions. That’s because the compiler can tell whether we’re using the results or not. This might not apply to library functions, because they don’t have such a direct connection to the compiler.

So, to answer our starting question: it is indeed faster to use the C-to-P function and disregard the angle outputs. But be careful, because the exact answer you get depends on the exact question you ask. The example above assumes you have the numbers in complex form already. Note that in version 3, the Complex-to-Real-and-Imaginary function has been pulled OUTSIDE the timing chain, meaning the discrete version stands a better chance of competing.

testtimingversion3

Here the timing results were:

10, 10, and 9 for the Discrete method, and 12, 12, and 12 for the C to P method.

That’s because the C-to-P method has to convert the real and imaginary parts to complex before using them.

Template

If you do a lot of that sort of work, you don’t want to construct the timing thing from scratch every time. I have a template VI that I use for such things, and it has extreme accuracy.

Over and above the example method shown, this VI has the following features:

  • It’s a template, not a VI. That means whenever you open it, it forgets its file name and you have to save it somewhere else. This keeps it clean for the next time. If you want to save a particular version, go ahead, but when you open the template again, it’s empty and ready.
  • It allows you to set the number of iterations, and provides an AVERAGE time per iteration. An operation that takes a microsecond can’t be timed with a millisecond timer, so you have to use 10000 operations or so. But an operation that takes a second can be measured with only one iteration.
  • It compensates for the loop-execution overhead. If your loop runs 10 million times, the loop itself takes time. That time is removed for you.
  • Since the example above proved that you MUST store the results somewhere to time something accurately, you might want to subtract out the storage time. In that case you can STORE (X[]) in the first loop and STORE (f(X[ ]) in the second. The cost of the actual storage is accounted for you.

Click to Download the Template (24 kB, unzipped, in LV 7.0 format).

Enjoy.

Hello, World

Hello world, indeed.

“Hello world” is traditionally the first program one writes when working with a new programming language. I suppose it’s just to prove to yourself that you can get all the way from concept through an editor/compiler/assembler/interpreter/linker/debugger chain to a visual display and have a sense of a wall broken down between you and the tools of this unknown language.

And so it is here, as I have never written for mass consumption before, despite having written technical materials for quite some time.

I hope to share with you some of the tips and tricks I have discovered and invented over the years, and maybe save you some time and effort in solving the problems and challenges that you face in your own programming work.

Most of my work has been in LabVIEW recently, so I suppose most of my posts will pertain to that environment. I am working on a large project that uses a PXI box in real-time, so there will be observations related to that environment, and tips related to working with large projects. However, I won’t preclude articles on other languages, so rummage around and you might find something useful.

    Affiliations

    • TI Alliance

    Calendar

    February 2012
    M T W T F S S
    « Mar    
     12345
    6789101112
    13141516171819
    20212223242526
    272829  

Testimonial  Contact Info

207-790-0949

1-877-676-8175 (toll-free)

Fax: 1-815-572-8269

General questions:

Sales@Culverson.com

Accounts/billing questions:

Accounts@Culverson.com


Logo