Five Techniques for Better LabVIEW Code Peter Blume President peter.blume@bloomy.com This presentation is inspired by the customers and employees of Bloomy Controls.
Five techniques with examples Overview Introduction Five techniques with examples Specifications Data flow State machines Error handling Documentation Conclusion This presentation was motivated by some common mistakes we’ve seen repeatedly by the consulting and training customers of Bloomy Controls. The primary cause of the mistakes is the desire for instant gratification, by developing and running our applications as quickly as possible. In many ways, LabVIEW entices us to develop our applications quickly because of the graphical nature, and the many features that expedite programming tasks. However, LabVIEW is a programming language, and computer science practices commonly applied to conventional programming languages also apply to LabVIEW. This presentation illustrates several such practices. We will focus on LabVIEW programming and documentation techniques. My introduction discusses some undesirable tendencies, or bad habits, that I’ve observed with LabVIEW programmers, and how people justify them. The techniques involve Specifications, Data Flow, State Machines, Error Handling, and Documentation. I’m going to compare one or more good and bad LabVIEW programs of significant complexity, throughout this presentation.
LabVIEW facilitates fast development cycles Introduction Most LabVIEW applications begin with an industrial measurement and/or control challenge LabVIEW facilitates fast development cycles Easy to connect and control instrumentation Fast solutions to industrial challenges Instant gratification for developers LabVIEW developers adopt fast habits Sloppy wiring No documentation Computer science fundamentals are ignored Fast habits = bad habits Many developers choose LabVIEW because of how fast we can develop our test, measurement, and control applications. LabVIEW developers receive instant gratification by quickly connecting and controlling a wide variety of instrumentation, displaying the results, generating reports, etc. Many of us are confronted by a scientific or technical challenge in our business or industry, and we expect to develop some experiments, tests, or measurements using LabVIEW, and solve the problem the same day. What is the temperature gradient across this fuel cell membrane? How can we optimize the mass flow velocity of a chemical through a distillation column? Can we increase the throughput of our high volume production line? A good LabVIEW developer knows that she can wire up a few sensors, some PXI, SCXI or FieldPoint instruments, and throw together some LabVIEW code in short order. Many such industrial measurement and control challenges can be solved in a matter of days, sometimes hours. (How many of you apply conventional computer science techniques, such as specifications, pseudocode, flow charts, use cases, documentation, etc. to your LabVIEW applications?) Many LabVIEW developers ignore everything they learned about computer science.
LabVIEW is a graphical language Justifying Bad Habits LabVIEW is a graphical language Computer science fundamentals don’t apply Block diagram is self-documenting We don’t have time to write good code Intense time pressures LabVIEW is a secondary responsibility Requirements continuously change Equipment availability constraints I’m the only developer / end-user Nobody else needs to use or understand my programs Bad habits are justified in a variety of ways. Some believe that computer science fundamentals either don’t apply to graphical programming, or it’s not clear how to apply them. Others believe that LabVIEW is self-documenting because its graphical nature may resemble a flow chart. Other people will tell you they bought LabVIEW because they’re engineers, not programmers, and they’re exempt from computer science practices. Many customers work in a very dynamic environment with intense time pressures. They like to use their intense time pressures to justify bad LabVIEW programming habits. In a production environment there may be limited availability of the equipment in which to deploy and test your LabVIEW applications. Finally, some people justify their bad habits because they’re writing programs that only they will use.
Issues Resulting from Bad Habits Application requirements expand LabVIEW code becomes messy Inefficient Buggy Difficult to troubleshoot, expand, and maintain Not reusable Overall development time is increased (How many of you have ever written a LabVIEW application that never required any modifications, new features, or enhancements after it was first completed?) i.e. you wrote the application perfectly satisfying all the intended objectives (both explicit and implied), deployed it, and never had to make any further changes or enhancements? In practice, our application requirements expand. LabVIEW’s flexibility and capability to serve many different uses and departments within an organization – from laboratory R&D, design validation, manufacturing automation, production test, and reliability testing – and many different users – operators, technicians, engineers, scientists, managers – means that application requirements can expand just as rapidly as the developer can implement them. A simple laboratory experiment may evolve into much more complicated experiments involving many instruments. In some cases, a new product design is validated, and the same software is migrated into production test. Even worse, your application requirements may expand after you’ve forgotten how your program works, or after you’ve moved on to bigger and better things. (Bloomy Controls is often contracted by companies after a lead developer has left the company.) When application requirements expand, the diagram complexity increases, and depending on your programming style, it can become difficult to understand, maintain, troubleshoot, and modify your application. Because your code is messy, it is not reusable. Therefore, you’re always starting from a blank slate with each and every project. Overall development time is actually increased over the life of the project when fast (bad) tendencies are employed.
Bad Code Example What do you think of the LabVIEW program? Does it look like spaghetti? This is what happens when you have bad programming habits, and your application requirements expand. Why is this LabVIEW code bad? Examine: Specification, data flow, architecture, error handling, and documentation. Experienced LabVIEW developers refer to this diagram as “spaghetti.” It exhibits an inappropriate architecture, insufficient modularity, and a haphazard wiring scheme. Error handling is incomplete, and documentation including VI descriptions, is nonexistent. Specifically, the wiring was performed haphazardly, with data flowing left-to-right, right-to-left, up, down, and all around. Several local variables are improperly initialized outside the while loop in parallel with code that is reading the values from the corresponding control terminals within the while loop. Since there is no data dependency between the initialization operations and the while loop, the order of execution is not known. Therefore, these controls may not be correctly initialized before they are accessed within the looping structure. Several subVI icons contain text that was drawn free-hand, one unique trait that I would not consider common.
Good Code Example This LabVIEW program is actually far more complex than the last. It actually contains 5 asynchronous while loops, and a fairly sophisticated messaging scheme. What are a few things that strike you as improvements versus the bad code?
Let’s Write Better LabVIEW Code! Follow these five techniques More up-front time and effort Attention to detail Discipline Dramatically save time in the long run LabVIEW developers can improve their source code to best adapt to these short and long-term challenges by applying the techniques presented here. This will entail more up-front time and effort and attention to detail, but will provide dramatic improvements in the long-term maintainability of the application. These techniques are applicable to LabVIEW programs (versus text-based languages) and can be implemented quickly!
1. Write a Functional Specification Understand the application’s requirements Interview operators, developers, engineers, managers, bean counters Document the requirements Statement of high-level objectives Specific requirements for I/O, analysis, GUI Timetable and budget Design prototype screen shot Assign priorities to each requirement Test methodology The most common problem I see, is that people begin developing their LabVIEW applications without a full understanding of the requirements. This results in poor choices for the top-level architecture, incorrect data structures, inadequate error handling, etc. Moreover, it often results in wasted effort when significant portions of the application must be rewritten to accommodate new definitions of poorly understood requirements. Documenting the specifications and having them reviewed by all contributors ensures that the requirements are understood, agreed upon, and approved. Documenting the requirements should be the first step in any LabVIEW application development effort. Without a specification, it can be very difficult to determine when the application’s requirements have been satisfied. Verbally dictated specifications are most prone to misinterpretation and/or scope creep. This may result in frequent redevelopment, unnecessarily long development cycles, missed deadlines and budget overruns. Use a word processor to document the requirements. Have them reviewed by all parties involved, and agreed upon prior to starting the LabVIEW coding process.
Functional Specification (Continued) Avoid designing the system Don’t prolong specification phase by including too much detail such as how to implement the system Make the specification readily accessible Have all contributors review and approve Revise when requirements change Keep it somewhere that you and others can quickly reference it When you circulate a written specification, you’re often able to flush out assumptions and miscommunications. Revisions are usually necessary. Also, new requirements often arise after the project has started. Hence, the specification must be a living document. I keep an electronic copy of my specification in the root directory of the source code, and a hard copy in my project binder.
Simple Specification Example Most people agree that a specification is necessary for a large project, but many people may overlook the utility in a small project. This slide shows an example of a specification for a very simple functional test application that may only take a few days to implement. The moral of the story is, start with a specification for all projects, no matter how small they may appear in the beginning.
Bad Code Example When Bloomy Controls was contacted to debug this application, our first request was “please forward us the documentation for our review”. Not too surprisingly, there was no documentation ever written for this application. Even worse, the developer has long since left the company and is no longer available to answer questions.
Good Code Example What do you think of the LabVIEW program, of similar complexity? What are a few things that strike you as improvements versus the bad code?
Parallel paths are permitted and desirable 2. Use Proper Data Flow Data flow definition: “A block diagram node executes when all its inputs are available. When a node completes execution, it supplies data to its output terminals and passes the data to the next node in the dataflow path.” Parallel paths are permitted and desirable Efficiency is optimized when LabVIEW determines the execution order Readability is optimized when source terminals, wires, and destination terminals are visible Data flow is the fundamental principle of LabVIEW. Data flows along wires from source terminals to destination terminals. Programs execute most efficiently when data dependency determines the execution order. Hence, parallel data paths are desirable.
Local and global variables Coercions Sloppy wiring Data Flow Impediments Sequence structures Excessive nesting Local and global variables Coercions Sloppy wiring Excessive bends Overlapping wires with other objects Multidirectional data flow Sequence structures force the order of execution where data dependency doesn’t exist. Sequence structures are undesirable because they undermine data flow principals, and reduce the efficiency of LabVIEW’s compiler. Local and global variables also undermine data flow by duplicating terminals on the diagram. Each Read Local or Global variable makes a copy of the data in memory. Each Write Local or Global variable has the potential to overwrite a value written from another Write operation, resulting in a race condition. Hence, local and global variables are slow to access, memory consuming, and add complexity and potential misbehavior. These issues become increasingly important when the type of data is large or complex, or many variables are used. However, experienced LabVIEW programmers generally avoid them unless absolutely necessary. Coercions appear as gray dots at the junction of wires and nodes. They indicate that the data type carried by the wire is being converted, or upgraded to a different data type. This adds an extra operation, and creates an extra memory buffer. Similar to Read local and global variables, the additional overhead depends on the number of coercions, as well as the size and complexity of the data. Note that in many instances, a coercion is unavoidable, and the extra overhead may be negligible. Data flow appears sloppy if there are many unnecessary bends in the wires, if data flows in multiple directions, such as right to left as well as left to right, and if wires are routed underneath other objects or structures.
Simple Data Flow Example Impeded Unnecessary sequence Coercion Local variables Better data flow This is an oversimplified example of two equivalent diagrams. The top diagram uses a flat sequence structure to dictate the execution order. The flat sequence is a new feature of LabVIEW 7.0 that shows each frame side to side instead of stacked. This improves the readability versus legacy sequence structures. However, sequence structures are generally undesirable, unless it’s critical that the code executes in a specific order and no data dependency exists between the respective nodes. Also, notice that local variables are used to replicate the control terminals for Number 1 and Number 2 in the second frame of the sequence. This is also undesirable because unnecessary local variables increase memory usage, execution time, and complexity. Finally, the top diagram contains coercion dots because the control storing Number 2 has dissimilar data representation (single precision float) than the others. The bottom diagram allows LabVIEW to efficiently process parallel data paths.
Bad Code Example What data flow mistakes were made in this application? Error cluster not used with DAQ VIs. No data dependency between local variable initializations and while loop. There are (x) local and global variables, most of which aren’t necessary. Shift registers can be used to reduce local and global variables. Data flows left to right, right to left, up, down, and all around. Sloppy wiring is often the result of inadequate specification, inappropriate architecture, and expanded requirements. (No coercions and no sequence structures)
Bad Code – Zoom In on Wiring What data flow mistakes were made in this application? Error cluster not used with DAQ VIs. No data dependency between local variable initializations and while loop. There are (x) local and global variables, most of which aren’t necessary. Shift registers can be used to reduce local and global variables. Data flows left to right, right to left, up, down, and all around. Sloppy wiring is often the result of inadequate specification, inappropriate architecture, and expanded requirements. (No coercions and no sequence structures)
Data Flow Enhancements Artificial data dependency Use error cluster and/or task ID Shift registers Reduce local and global variables Clusters Reduce the overall number of wires Neat wiring Left-to-right data flow Straight wires Consistent data types Comments with “>” indicating data flow direction When the order of execution must be specified, an experienced LabVIEW developer will create artificial data dependency using an error cluster or other common data type, as an alternative to a sequence structure. Additionally, shift registers can be used to dramatically reduce the required number of local variables. Clusters can be used to combine multiple data elements, and thereby reduce the overall number of wires required. Finally, be sure to practice neat wiring practices. Use alignment tools to align nodes and straighten wires!!!
Good Code Example In the good code example, data flows strictly left to right, and occasionally up or down. All long wires contain comments with the direction of data flow indicated using “>” symbols. Shift registers are employed instead of local and global variables (although some locals are used where necessary). (If you’re passing data between two loops, you have to decide between local and global variables, and notifiers and queues. If the data will be displayed in an indicator, and no synchronization is required, then we simply use a local variable. However, if synchronization is required, then a notifier or queue is preferred.
3. Use a State Machine Top-Level Architecture Define application as a series of states Go to any state from any other state Easy to modify, maintain, and debug Self-documenting Scalable The diagram of all top-level VIs, except for very simple applications, should contain a state machine architecture. This provides the most flexibility and efficiency, while maintaining the natural data flow of LabVIEW.
Classic State Machine Is everyone familiar with a state machine? This is a classic state machine, as defined in the LabVIEW Basics hands-on course. It consists of a single case structure within a while loop. A shift register passes the next state as an integer data type wired to the condition terminal. The default case polls a menu of boolean controls. If a button is pressed, Search 1D Array returns the array element (cluster order number) of the first control that matches the True boolean constant, and passes this number to the shift register on the right border of the While loop. Upon the next iteration of the While loop, the state number is read from the shift register on the left border of the while loop and passes it to the condition terminal of the case structure to select the corresponding frame number. Hence, each button in the menu cluster corresponds to a frame of the case structure numbered in order of the cluster order number of the control within the cluster. It is important to note that the state machine is not limited in number of states by the number of boolean controls in the menu. Rather, any number of states may be defined, and called from any previous state. Furthermore, the state to execute next can be programmatically determined within the previous state. Therefore, state machines provide powerful flexibility.
State Machine Enhancements Use enumerated or string for case selector Poll user interface events in “No Event, Default” frame or in separate event structure in parallel loop Use intuitive state names Include “Initialize” and “Shutdown” states An enumerated or string case selector, with intuitive state names, serves to document the state machine. Avoid numeric selector types that display numbers in the case selector instead of text. “Initialize” and “Shutdown” frames are used for functions like initializing or closing instruments and hardware, setting control properties, and reading or writing to configuration files. Using “Initialize” and “Shutdown” states also reduces inefficient block diagram real estate that would otherwise contain these functions outside the state machine.
Enumerated State Machine Here is an enumerated state machine. Notice the intuitive state labels in the case structure selector, and in the enumerated constants.
Event-Driven State Machine States typically are derived directly from the steps of a test, measurement, or control sequence. However, consideration should be given to how long each state takes and how quickly the program needs to respond to GUI events. For example, if your VI needs to execute a Shutdown sequence within two seconds of the user clicking the Abort button, then the process should be divided in states with enough granularity such that no state takes more than two seconds. Queues may be used to store multiple user events and pass those events between parallel structures for processing. The diagram shown above is functionally equivalent to the previous state machine example. However, it uses a separate parallel event structure to optimize its ability to detect user interface events, and it stores the events in a queue for processing by the state machine. It has the advantage in that it can capture and process multiple user interface events.
4. Incorporate Proper Error Handling Trap and report any I/O related errors in all VIs I/O functions include DAQ, file I/O, instrument I/O, communication Trapping is facilitated by propagation of error cluster Reporting methods include dialog prompt or log to file Error handling consists of trapping and reporting any undesirable behavior that may cause the program to malfunction. We are primarily concerned with input/output errors, where LabVIEW is calling a device driver, operating system, or any application or resource external to the LabVIEW environment. Examples include querying an instrument that is not powered on or hung up, or reading or writing to a file or network path that does not exist, and so on. All such I/O functions contain the standard error-in and error-out clusters. These terminals must be wired and propagated among I/O functions and terminated with an appropriate error handler VI.
Error Trapping Any subVI or function call that performs an Input or Output operation must contain standard LabVIEW error input and error output type definition controls assigned to the lower left and lower right connector terminals, respectively. These functions must check the status of the error and not execute their input and/or output operation if the error status is true. If the function performs a DLL call, the call library node must be enclosed in the false frame of a case structure with the error cluster wired to the condition terminal. If the function calls a LabVIEW primitive that has error input and error output connector terminals (such as VISA functions), then the case structure may be omitted if the error cluster is propagated to these terminals, because the primitive will check the error status internally. However, it is preferable to have the I/O primitives inside a case structure to reduce overhead due to unnecessary calls to the primitive as well as any additional string parsing or other operations that the subVI may perform.
What’s Wrong With This Picture? The diagram above traps an error only if it is present upon completion of the last iteration of the While Loop. Any errors that are generated by the DAQmx Read VI or the Write File function before the user clicks the Stop button will be ignored. Therefore, many errors could go unnoticed!
Better Error Handling This diagram contains several enhancements. The error cluster is propagated between both DAQ and File I/O VIs, such that an error generated by one will cause all subsequent I/O operations to be bypassed. Additionally, the While Loop terminates immediately upon detecting an error and displays the error information in a dialog box through the Simple Error Handler.vi. Finally, if a warning occurs, it will be trapped and propagated by the shift register.
5. Document Your Source Code Assume every VI will be used and maintained by other developers Best time to document your source code is while you develop it By documentation, I’m referring to source code documentation.
LabVIEW Documentation Techniques Control labels Use succinct, intuitive labels Indicate units in parentheses or use free labels Enter descriptions or online help where further text is needed Icons Intuitive text or graphic 10-point small fonts Color-coding for icons of related subVIs The importance of control and indicator labels in documentation cannot be overstated.
Documentation Techniques (cont’d) Diagram Leave the background color white Set all control labels visible Liberally document with free labels Enter descriptions for every subVI
Bad Code Example None of the VIs in this application contain any VI descriptions, including Fault Reporter.vi. Furthermore, this subVI’s only indicator doesn’t have any label at all, consequently the context help window doesn’t have a label.
Good Code Example
Write a specification for all projects before you begin coding Summary Write a specification for all projects before you begin coding Use proper data flow Use a state machine top-level architecture Incorporate proper error handling Document your source code while you code
Techniques can be implemented quickly if you make them habits Conclusion Techniques improve: Readability Robustness Efficiency Maintainability Reusability Techniques can be implemented quickly if you make them habits Apply to every project, starting now Apply consistently throughout each project Dramatic reduction in overall time and effort
Test, measurement, automation, and control specialists since 1991 About Bloomy Controls Test, measurement, automation, and control specialists since 1991 Systems integration, software development, and training provider NI Select Integrator and Certified Training Center 3 Certified LabVIEW Architects 8 Certified LabVIEW Developers 1 Certified TestStand Architect 2 Certified TestStand Developers 8 Certified Professional Instructors Offices in Windsor, CT; Milford, MA; and Mahwah, NJ
Contact Bloomy Controls Email info@bloomy.com Write or visit CT, Western MA, Eastern NY: Eastern MA, RI, Northern New England: Greater NYC, NJ: 839 Marshall Phelps Rd. 100 Medway Rd., Ste 202 Windsor, CT 06095 Milford, MA 01757 Mahwah, NJ (860) 298-9925 (508) 902-0054 (201) 818-0117