At DMC, we write many applications where clients need to interact with and coordinate multiple pieces of test equipment. It can be challenging to write and maintain a LabVIEW application that does this, especially if you need to maintain backward compatibility with older test equipment, or you expect the underlying test equipment will be upgraded or changed.
How do you plan for these changes in your code? How do you structure your code to minimize changes when test equipment changes? One way to help is to implement a hardware abstraction layer.
In this blog, we will look at implementing a simple hardware abstraction layer for Digital Multi Meters using LabVIEW Object Oriented Programming. We’ll assume that you are familiar with how to create classes in LabVIEW and have a basic understanding of object-oriented programming. If you need a refresher, see the link here on using LVOOP.
What is a Hardware Abstraction Layer?
The hardware abstraction layer helps to separate the section of code that manages and coordinates different pieces of test equipment from the section of code that interacts with each piece of test equipment. For a more detailed explanation, check out our case study here about the benefits of a hardware abstraction layer. Hardware abstraction layers are also part of our Flex Framework suite of tools.
Why use a Hardware Abstraction Layer?
Consider the following example. You need to develop a test that takes voltage and resistance measurement of a device under test and determine if those measurements are valid. Your applications must support two different types of Digital Multi Meters (DMMs). Each DMM has a different communication interface, and each has a separate command for specifying which measurement to take.
The example below is one way you could implement this method. Each DMM is represented in its own class, DMM1 and DMM2, that apply different methods for each measurement type. Depending on which DMM type is selected, you call the appropriate method on the appropriate class. Consider what would happen to this code if you needed to support more DMM types. What would happen if you needed to take more voltage measurements? What if you needed to test the ‘Pass’ method without any hardware? This code will get messy in a hurry.
Implementing a hardware abstraction layer can simplify this codebase. To do this, we need to think about what is going on here. The test we are performing is only taking voltage and resistance measurements. Ideally, we would want a code that looks like the following:
Implementing the Hardware Abstraction Layer
The logic that performs our test utilizes a single DMM class and is independent of the type of DMM we are using. By using inheritance, we can implement a hardware abstraction layer to achieve this. Below is a picture of what the inheritance structure will look need to look like.
The base DMM class provides a set of methods that all calling code will depend on. These methods must be dynamic dispatch and must force child classes to override them. For each concrete DMM we have, we need to create a class that inherits from this base DMM class and overrides the dynamic dispatch methods.
Setting up our DMM Base Class
Create a new class called _DMMBase. The underscore at the beginning of the class name is a convention we use to indicate that this should be treated as an abstract class, meaning this class should have no non-dynamic dispatch methods and no method in this class should contain an actual implementation. Currently, there is no way to enforce these restrictions in LabVIEW, so it is up to the programmer to follow convention.
There are two dynamic dispatch methods a DMM must have – Measure Voltage and Measure Ohms. We want to require all child classes to override these two methods. To set up the "require override of dynamic dispatch method," right-click on the class and open its properties. Select the inheritance tab and check “Transfer all Must Override requirements to descendant classes.”
Create the two dynamic dispatch VIs in the _DMMBase class. There will be no concrete implementation contained in these VIs, but you will need to make sure that all the outputs are correctly wired to the connector pane. The connector pane is required to be the same for all child classes that override this VI.
Once you create these methods, reopen the class settings. Under the “Items Settings” tab, we need to set each method to “Require descendant classes to override this dynamic dispatch VI.”
Those are all of the requirements for the base class.
Setting up Child Classes
We need to create a new class for each type of DMM we need to support. As an example, let's create DMM 1. This class will inherit from _DMMBase and be a concrete implementation of a DMM. A concrete implementation is a class that holds implementations for the methods defined in the class it inherits from. To set up the inheritance, open DMM 1 class properties. Select the inheritance tab and click “Change Inheritance.” Select _DMMBase, then click okay.
Once you set up your new inheritance, you can right-click on the new class and select new VI for Override.
This brings up a window with a list of Vis that must be overridden by your child class. In this case, you can see the Measure Voltage and Measure Ohms methods we want to override.
After you implement the Measure Voltage and Measure Ohms VIs for each DMM, we can begin refactoring our code.
Using the Hardware Abstraction Layer
We can refactor our code so that our test logic calls _DMMBase class methods. We can then choose which concrete DMM to pass into those methods.
We still must select which DMM we are using for our test. However, at this point, the test logic is completely independent of the specific hardware. To further illustrate this point, we can refactor this code to wrap our test logic in a sub VI and pass our chosen DMM object in as an input.
If, in the future, we needed to add a new DMM (or a simulated one), there would be no need to change the ‘Run Test’ VI. All we would need to do is create a new class that inherits from _DMMbase and overrides the necessary methods.
A hardware abstraction layer minimizes the code changes required when the underlying hardware changes by separating the logic required to perform a test on a DUT from the logic required to interface with individual pieces of hardware. This method can improve the long-term maintainability of code. Programmers should consider this whenever interfacing with hardware.
We expect LabVIEW 2020 to introduce interfaces. An interface would replace the _DMMbase class and would functionally be the same. However, there are several corollaries an interface could provide, but more on this in the upcoming months.
Learn more about DMC's LabVIEW Programming Expertise. Contact us with any inquiries.