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

    September 2010
    M T W T F S S
    « Mar    
     12345
    6789101112
    13141516171819
    20212223242526
    27282930  

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