Skip to main content
Skip table of contents

UD Library User's Guide

The UD library is the Windows driver/library for the U3, U6 and UE9. UD is also referred to as LabJackUD and LJUD.

For other LabJack devices, go to the Software page.

Subsections

UD Library Installer Download

See our UD Library Software Installer Downloads.

Operation Basics

The general operation of the UD functions is as follows:

  • Open a LabJack U3, U6 or UE9.

  • Build a list of requests to perform (Add).

  • Execute the list (Go).

  • Read the result of each request (Get).

At the core, the UD driver only has 4 basic functions: OpenLabJack, AddRequest, Go, and GetResult.

Constants

There are many constants used to specify the desired device control actions. When programming in any language, it is recommended to have the header file handy so that constants can be copied and pasted into the code.

IOType

The first type of constant is an IOType, which is always passed in the IOType parameter of a function call. Typically the IOType is used to specify a particular type of I/O such as digital input or digital output. For example, the constant LJ_ioPUT_DAC is used to update the value of an analog output (DAC). There are also IOTypes used to specify configurations more generally. For example, the constant LJ_ioPUT_CONFIG is used to write device configurations such as the device local ID.

Channel Constant

The second type of constant is a Channel Constant, also called a Special Channel. These constants are always passed in the Channel parameter of a function call. These are typically used when a request is not specific to a particular channel, and go with the configuration IOTypes ( LJ_ioPUT_CONFIG or LJ_ioGET_CONFIG ). One example of a Special Channel is the constant LJ_chLOCALID, which is used to write or read the local ID of the device.

Value Constant

The third major type of constant used by the UD driver is a Value Constant. These constants are always passed in the Value parameter of a function call. One example of a Value Constant is the constant LJ_tmPWM8, which specifies a timer mode. This constant has a numeric value of 1, which could be passed instead, but using the constant LJ_tmPWM8 makes for programming code that is easier to read.

Error Codes

See our Error Codes page.

Pseudocode Example

The following pseudocode demonstrates a typical program flow.

Open a device

First, a call is done to open the device. The primary work done with this call is finding the desired device and creating a handle that points to the device for further function calls. In addition, opening the device performs various configuration and initialization actions, such as reading the calibration constants from the device:

//Use the following line to open the first found LabJack U6

//over USB and get a handle to the device.

//The general form of the open function is:

//OpenLabJack (DeviceType, ConnectionType, Address, FirstFound, *Handle)

//Open the first found LabJack U6 over USB.

lngErrorcode = OpenLabJack (LJ_dtU6, LJ_ctUSB, "1", TRUE, &lngHandle);

Build a list of requests

Second, a list of requests is built in the UD driver using AddRequest calls. This does not involve any low-level communication with the device, and thus the execution time is relatively instantaneous:

//Request that DAC0 be set to 2.5 volts.

//The general form of the AddRequest function is:

//AddRequest (Handle, IOType, Channel, Value, x1, UserData)

lngErrorcode = AddRequest (lngHandle, LJ_ioPUT_DAC, 0, 2.50, 0, 0);

//Request a single-ended read from AIN3.

lngErrorcode = AddRequest (lngHandle, LJ_ioGET_AIN, 3, 0, 0, 0);

Execute the requests

Third, the list of requests is processed and executed using a Go call. In this step, the driver determines which low-level commands must be executed to process all the requests, calls those low-level functions, and stores the results. This example consists of two requests, one analog input read and one analog output write, which can both be handled in a single low-level Feedback call:

//Execute the requests.

lngErrorcode = GoOne (lngHandle);

Get the results from the requests

Finally, GetResult calls are used to retrieve the results (errorcodes and values) that were stored by the driver during the Go call. This does not involve any low-level communication with the device, and thus the execution time is relatively instantaneous:

//Get the result of the DAC0 request just to check for an errorcode.

//The general form of the GetResult function is:

//GetResult (Handle, IOType, Channel, *Value)

lngErrorcode = GetResult (lngHandle, LJ_ioPUT_DAC, 0, 0);

//Get the AIN3 voltage. We pass the address to dblValue and the

//voltage will be returned in that variable.

lngErrorcode = GetResult (lngHandle, LJ_ioGET_AIN, 3, &dblValue);

Request Functions

The AddRequest/Go/GetResult method is often the most efficient. As shown above, multiple requests can be executed with a single Go() or GoOne() call, and the driver might be able to optimize the requests into fewer low-level calls. The other option is to use the eGet or ePut functions which combine the AddRequest/Go/GetResult into one call. The above code would then look like (assuming the U6 is already open):

//Set DAC0 to 2.5 volts.

//The general form of the ePut function is:

//ePut (Handle, IOType, Channel, Value, x1)

lngErrorcode = ePut (lngHandle, LJ_ioPUT_DAC, 0, 2.50, 0);

//Read AIN3.

//The general form of the eGet function is:

//eGet (Handle, IOType, Channel, *Value, x1)

lngErrorcode = eGet (lngHandle, LJ_ioGET_AIN, 3, &dblValue, 0);

In the case of the U6, the first example using add/go/get handles both the DAC command and AIN read in a single low-level call, while in the second example using ePut/eGet two low-level commands are used. Examples in the following documentation will use both the add/go/get method and the ePut/eGet method, and they are generally interchangeable. Device specific pseudocode is available in the respective device datasheet (U3/U6/UE9).

All the request and result functions always have 4 common parameters, and some of the functions have 2 extra parameters:

  • Handle – This is an input to all request/result functions that tells the function what LabJack it is talking to. The handle is obtained from the OpenLabJack function.

  • IOType – This is an input to all request/result functions that specifies what type of action is being done.

  • Channel – This is an input to all request/result functions that generally specifies which channel of I/O is being written/read, although with the config IOTypes special constants are passed for channel to specify what is being configured.

  • Value – This is an input or output to all request/result functions that is used to write or read the value for the item being operated on.

  • ×1 – This parameter is only used in some of the request/result functions, and is used when extra information is needed for certain IOTypes.

  • UserData – This parameter is only used in some of the request/result functions, and is data that is simply passed along with the request, and returned unmodified by the result. Can be used to store any sort of information with the request, to allow a generic parser to determine what should be done when the results are received.

Function Flexibility

The driver is designed to be flexible so that it can work with various different LabJacks with different capabilities. It is also designed to work with different development platforms with different capabilities. For this reason, many of the functions are repeated with different forms of parameters, although their internal functionality remains mostly the same. In this documentation, a group of functions will often be referred to by their shortest name. For example, a reference to Add or AddRequest most likely refers to any of the three variations: AddRequest(), AddRequestS() or AddRequestSS().

In the sample code, alternate functions (S or SS versions) can generally be substituted as desired, changing the parameter types accordingly. All samples here are written in pseudo-C.

Functions with an “S” or “SS” appended are provided for programming languages that can’t include the LabJackUD.h file and therefore can’t use the constants included. It is generally poor programming form to hardcode numbers into function calls, if for no other reason than it is hard to read. Functions with a single “S” replace the IOType parameter with a const char * which is a string. A string can then be passed with the name of the desired constant. Functions with a double “SS” replace both the IOType and Channel with strings. OpenLabJackS replaces both DeviceType and ConnectionType with strings since both take constants.

For example:

In C, where the LabJackUD.h file can be included and the constants used directly:

AddRequest(Handle, LJ_ioGET_CONFIG, LJ_chHARDWARE_VERSION,0,0,0);

The bad way (hard to read) when LabJackUD.h cannot be included:

AddRequest(Handle, 1001, 10, 0, 0, 0);

The better way when LabJackUD.h cannot be included, is to pass strings:

AddRequestSS(Handle, “LJ_ioGET_CONFIG”, “LJ_chHARDWARE_VERSION”,0,0,0);

Continuing on this vein, the function StringToConstant() is useful for error handling routines, or with the GetFirst/Next functions which do not take strings. The StringToConstant() function takes a string and returns the numeric constant. So, for example:

LJ_ERROR err;

err = AddRequestSS(Handle, “LJ_ioGET_CONFIG”, “LJ_chHARDWARE_VERSION”,0,0,0);

if (err == StringToConstant(“LJE_INVALID_DEVICE_TYPE”)) do some error handling..

Once again, this is much clearer than:

if (err == 2)

Multi-Threaded Operation

This driver is completely thread safe. With some very minor exceptions, all these functions can be called from multiple threads at the same time using the same LabJack handle and the driver will keep everything straight. Because of this Add, Go, and Get must be called from the same thread for a particular set of requests/results. Internally the list of requests and results are split by thread. This allows multiple threads to be used to make requests without accidentally getting data from one thread into another. If requests are added, and then results return LJE_NO_DATA_AVAILABLE or a similar error, chances are the requests and results are in different threads.

LabJack hardware can only process one command-response at a time. Due to this, calls to functions that perform LabJack communications can only run one at a time, and will block another thread’s call until all command-response have processed when using the same LabJack. These functions include Go, GoOne, ePut, eGet, eAIN, eDAC, etc.

The driver tracks which thread a request is made in by the thread ID. If a thread is killed and then a new one is created, it is possible for the new thread to have the same ID. Its not really a problem if Add is called first, but if Get is called on a new thread results could be returned from the thread that already ended.

As mentioned, the list of requests and results is kept on a thread-by-thread basis. Since the driver cannot tell when a thread has ended, the results are kept in memory for that thread regardless. This is not a problem in general as the driver will clean it all up when unloaded. When it can be a problem is in situations where threads are created and destroyed continuously. This will result in the slow consumption of memory as requests on old threads are left behind. Since each request only uses at most 64 bytes, and as mentioned, the ID’s will eventually get recycled, it will not be a huge memory loss. In general, even without this issue, it is strongly recommended to not create and destroy a lot of threads. It is terribly slow and inefficient. Use thread pools and other techniques to keep new thread creation to a minimum. That is what is done internally.

The one big exception to the thread safety of this driver is in the use of the Windows TerminateThread() function. As is warned in the MSDN documentation, using TerminateThread() will kill the thread without releasing any resources, and more importantly, releasing any synchronization objects. If TerminateThread() is used on a thread that is currently in the middle of a call to this driver, more than likely a synchronization object will be left open on the particular device and access to the device will be impossible until the application is restarted. On some devices, it can be worse. On devices that have interprocess synchronization, such as the U12, calling TerminateThread() may kill all access to the device through this driver no matter which process is using it and even if the application is restarted. Avoid using TerminateThread()! All device calls have a timeout, which defaults to 1 second, but can be changed. Make sure to wait at least as long as the timeout for the driver to finish.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.