News

Master Data Acquisition with NI DAQ Python

8 min read
hands typing on the keyboard, coding signs on the laptop screen, phone and glasses on the table

Broaden your toolkit beyond Labview by mastering Python control of National Instruments cards. In many laboratories, National Instruments acquisition cards, often referred to as DAQs, are staple devices. While Lab View is the go-to programming environment for these cards, it’s important to recognize that there are available libraries for integration with other programming languages. National Instruments offers a universal driver, NI-DAQmx, which is thoroughly documented for use with C programming.

The beauty of well-documented programming languages is that they open the door for development in other languages. For National Instruments hardware, the PyDAQmx project is a notable example. This project has adapted all functions to be Python compatible, essentially mirroring the original C code. However, when using PyDAQmx, remember to refer to the National Instruments documentation and tailor the code to fit Python’s syntax and capabilities. Delving into the NI DAQ Python ecosystem, let’s explore a practical example of configuring and utilizing it through a detailed setup.py walkthrough in the accompanying article.

Getting Started with Python and PyDAQmx

Understanding the unique specifications of each card is crucial as they vary and might not align with your current setup. For instance, if you’re aiming to read an analog input from your device, it’s essential to identify the specific number assigned to your card, which facilitates communication with it. Typically, this configuration is manageable through the National Instruments software, where you can set the number for your card.

import PyDAQmx as nidaq: 

t = nidaq.Task()
t.CreateAIVoltageChan("Dev1/ai0", None, nidaq.DAQmx_Val_Diff, 0, 10, nidaq.DAQmx_Val_Volts, None)
t.CfgSampClkTiming("", 1000, nidaq.DAQmx_Val_Rising, nidaq.DAQmx_Val_FiniteSamps, 5000)
t.StartTask()

In this concise code snippet, a new Task is established, named ‘t.’  This includes setting up an analog input channel for Dev1 at port ai0. Additionally, we’ve set up the timing internally to capture 1000 samples per second, aiming for a total of 5000 samples. To initiate sample acquisition, the task is activated. Comparing our implementation with the official documentation can be insightful for understanding how to effectively translate this setup into Python code.

PyDAQmx: Adapting Voltage Channel Syntax from C Docs

Initially, when reviewing the documentation for the CreateAIVoltageChan method, it’s important to recognize that its actual name differs; it’s listed as DAQmxCreateAIVoltageChan. A key observation is the omission of the DAQmx prefix in PyDAQmx. Following this, let’s examine the function’s arguments to understand their structure and usage.

int32 DAQmxCreateAIVoltageChan (
   TaskHandle taskHandle,
   const char physicalChannel[],
   const char nameToAssignToChannel[],
   int32 terminalConfig,
   float64 minVal,
   float64 maxVal,
   int32 units,
   const char customScaleName[]);

When using the task as a Python object with PyDAQmx, the function’s first argument is omitted, as PyDAQmx handles it automatically, as evident in the syntax t.CreateAIVoltageChan. For the remaining arguments, it’s crucial to match their types correctly.  For instance, where the function specifies a char, a String is passed, as done for the channel in this example, leaving it as None when not assigning a specific name. For the terminalConfig, various options are available in the documentation, such as DAQmx_Val_RSE and DAQmx_Val_Diff. PyDAQmx conveniently predefines these configurations, allowing their direct use, like nidaq.DAQmx_Val_Diff in the given example.

Analog Input Setup in PyDAQmx for NI Cards

Alt: people sitting at the table and typing on the laptops, two standing near them, coding signs above

In the setup process, PyDAQmx prompts for the definition of the analog input’s limits. It’s important to remember that DAQmx operates with various units, not exclusively volts, and these will be defined at a later stage.  The limits set must be in these specific units. By establishing these limits, the DAQ card can automatically adjust the gain for measurements, enhancing the effective resolution of the data captured. These values are then automatically converted to the specified type, as long as they are numerical.

A critical aspect of using an NI Card for measurements is the explicit definition of its units. For instance, if a thermocouple is connected to the analog input, you might need to measure in Kelvins instead of Volts, or you may have a different type of transducer attached. While setting up custom scales is a detailed process, it is often more straightforward to default to volts and perform any necessary unit conversions directly in our code. To this end, another built-in option of DAQmx is utilized, namely nidaq.DAQmx_Val_Volts. The final parameter is left as None, in accordance with the documentation’s guidance for scenarios where the scale is set to Volts.

Different libraries employ various methods for type conversion between Python and C. In the case of PyDAQmx, this process is efficiently managed behind the scenes, allowing for flexible type usage, such as using an integer where a float might be expected. However, it’s important to note that this ease of type conversion is not a universal feature across all libraries. Certain libraries necessitate the precise definition of specific types, highlighting the need for awareness of these differences in type handling.

Sample Clock and Data Read in PyDAQmx with Python

In the alternative approach, CfgSampClkTiming configures the sample acquisition clock. Refer to the documentation to understand each function argument. In summary, I set it to use an internal clock at 1000 samples per second, totaling 5000 samples over 5 seconds. The final line triggers the task.

Moving forward, the next phase involves reading the acquired data. Keep in mind that the acquisition takes 5 seconds, and since DAQmx functions are non-blocking, your program execution won’t pause at each operation. To read from the card, another method within the Task object is employed, requiring the use of numpy for this example to function properly.

import numpy as np

[...]

data = np.zeros((5000,), dtype=np.float64)
read = nidaq.int32()
t.ReadAnalogF64(5000, 5, nidaq.DAQmx_Val_GroupByChannel,
   data, len(data), nidaq.byref(read), None)
  • Reading from the NI DAQ follows a structure resembling traditional C code rather than the typical Python syntax. Notably, there is no use of “return” as seen in expressions like data = t.ReadAnalogF64(). Let’s break it down step by step, keeping in mind that we’re adopting an object-oriented approach and skipping the first argument, the task handler;
  • Initially, the number of data points per channel for reading is specified. If multiple channels are involved, it’s crucial to understand that this is not the total number of points but rather per channel. The timeout is then set in seconds, allowing the function to cease waiting if an insufficient number of data points is available;
  • Next, the method of grouping values in case of multiple channels is established. Since channels are read sequentially (Chan1_1 -> Chan2_1 -> Chan3_1 -> Chan1_2 -> Chan2_2 -> Chan3_2 -> Chan1_3 -> and so on), grouping by channel will return all measurements from Chan1, followed by Chan2, and so forth. This grouping aligns well with numpy’s reshape function, making it a preferred choice.

Advanced Data Handling in NI DAQ: Args & Memory

a side view of hands typing on the laptop keyboard, digital blue coding language above

Intriguingly, the ‘data’ is passed as an argument—an empty numpy array defined a few lines earlier. As per the documentation, it’s described as the array to read samples into, following a common C programming practice. The memory structure is constructed first, a numpy array with 5000 elements, to be overwritten by the read function. The subsequent argument denotes the actual samples’ count from each channel, correlating with the data array’s length.

The ultimate argument is the ‘read integer,’ also defined earlier, storing the total data points read per channel. Notably, a method called byref is employed when passing this integer, a standard approach when interfacing with C-written external libraries. By referencing the object’s memory location instead of the object itself, the function understands where the variable is stored, achieving the same outcome: holding the required information.

Now, unleash your creativity – plot, save, or manipulate your data as you wish. While I haven’t covered all the options in the read function, you can explore them in the documentation. The function’s complexity arises from accommodating various scenarios with minimal inputs. For instance, in continuous acquisition where you aim to download all available data points without prior knowledge of their quantity. It also handles situations involving an external trigger where the acquisition duration is uncertain.

Despite National Instruments cards not being initially designed for Python, they can be seamlessly integrated into diverse projects. The unified API across all cards simplifies code interchangeability without requiring alterations. Nevertheless, each card may exhibit distinct capabilities, such as acquisition rate or the number of simultaneous tasks it can manage.

Conclusion

The integration of Python with National Instruments cards offers a powerful, adaptable solution for diverse data acquisition needs. The flexibility of Python, coupled with the extensive capabilities of the NI DAQmx driver and PyDAQmx libraries, unleashes new possibilities for widespread applications. Whether controlling analog inputs, configuring timing, or analyzing data sets, these tools provide the keys to unlock high-level efficiency and flexibility in data acquisition. While there may be unique features or constraints inherent to individual cards, the common API ensures seamless interchangeability. Hence, the journey of mastering NI DAQ Python opens up new horizons for efficient, sophisticated data acquisition and management, even in the most complex settings.