This package provides an interface to NI-DAQmx--- National Instruments' driver for their data acquisition boards. Their entire C header file was ported using Clang.jl, and a rudimentary higher-level API is provided for ease of use.
Similar functionality for the Python language is provided by PyDAQmx.
General
- Supports Windows and Linux
- NI-DAQmx Base is not supported
Linux specific
- Requires the latest DAQmx (currently 20.1)
- DAQmx on linux does not support USB DAQ devices
Windows
First download and install NI-DAQmx version
20.1 (or
19.6,
18.6;
or for Julia 0.6, 17.1.0;
or for Julia 0.5, 16.0.0;
or for Julia 0.4, 15.1.1;
or for Julia 0.3, 14.1.0,
14.0.0, or
9.6.0) from National
Instruments.
Linux
The package only supports DAQmx 20.1 on linux. Follow the instructions from this support doc.
Adding NIDAQ.jl
Then on the Julia command line:
]add NIDAQ
With no input arguments, the high-level getproperties
function can
be used to query the system:
julia> using NIDAQ
julia> getproperties()
Dict{String,Tuple{Any,Bool}} with 7 entries:
"DevNames" => (SubString{String}["Dev1"],false)
"GlobalChans" => (SubString{String}[""],false)
"NIDAQMajorVersion" => (0x00000010,false)
"NIDAQMinorVersion" => (0x00000000,false)
"NIDAQUpdateVersion" => (0x00000000,false)
"Scales" => (SubString{String}[""],false)
"Tasks" => (SubString{String}[""],false)
Returned is a dictionary of tuples, the first member indicating the property value and the second a boolean indicating whether the former is mutable.
getproperties
can also input a string containing the name of a data acquisition device:
julia> getproperties("Dev1")
Dict{String,Tuple{Any,Bool}} with 61 entries:
"AIBridgeRngs" => (Float64[],false)
"AICouplings" => (:Val_Transferred_From_Buffer,false)
"AICurrentIntExcitDiscreteVals" => (Float64[],false)
"AICurrentRngs" => (Float64[],false)
"AIDigFltrLowpassCutoffFreqDiscreteVals" => (Float64[],false)
"AIDigFltrLowpassCutoffFreqRangeVals" => (Float64[],false)
"AIFreqRngs" => (Float64[],false)
"AIGains" => (Float64[],false)
"AILowpassCutoffFreqDiscreteVals" => (Float64[],false)
"AILowpassCutoffFreqRangeVals" => (Float64[],false)
"AIMaxMultiChanRate" => (2.0e6,false)
"AIMaxSingleChanRate" => (2.0e6,false)
"AIMinRate" => (0.0232831,false)
"AIPhysicalChans" => (SubString{String}["Dev1/ai0","Dev1/ai1","Dev1/ai2",".
"AIResistanceRngs" => (Float64[],false)
"AISampModes" => (Symbol[:Val_FiniteSamps,:Val_ContSamps],false)
"AISupportedMeasTypes" => (Symbol[:Val_Current,:Val_Resistance,:Val_Strain_Gage.
"AITrigUsage" => (14,false)
"AIVoltageIntExcitDiscreteVals" => (Float64[],false)
"AIVoltageIntExcitRangeVals" => (Float64[],false)
"AIVoltageRngs" => ([-1.0,1.0,-2.0,2.0,-5.0,5.0,-10.0,10.0],false)
"AOCurrentRngs" => (Float64[],false)
"AOGains" => (Float64[],false)
"AOMaxRate" => (3.33333e6,false)
"AOMinRate" => (0.0232831,false)
"AOPhysicalChans" => (SubString{String}["Dev1/ao0","Dev1/ao1"],false)
"AOSampModes" => (Symbol[:Val_FiniteSamps,:Val_ContSamps],false)
"AOSupportedOutputTypes" => (Symbol[:Val_Voltage],false)
"AOTrigUsage" => (10,false)
"AOVoltageRngs" => ([-5.0,5.0,-10.0,10.0],false)
"AccessoryProductNums" => (UInt32[0x00000000],false)
"AccessoryProductTypes" => (SubString{String}[""],false)
"AccessorySerialNums" => (UInt32[0x00000000],false)
"BusType" => (:Val_USB,false)
"CIMaxSize" => (0x00000020,false)
"CIMaxTimebase" => (1.0e8,false)
"CIPhysicalChans" => (SubString{String}["Dev1/ctr0","Dev1/ctr1","Dev1/ctr2.
"CISampModes" => (Symbol[:Val_FiniteSamps,:Val_ContSamps],false)
"CISupportedMeasTypes" => (Symbol[:Val_CountEdges,:Val_Freq,:Val_Period,:Val_Tw.
"CITrigUsage" => (42,false)
"COMaxSize" => (0x00000020,false)
"COMaxTimebase" => (1.0e8,false)
"COPhysicalChans" => (SubString{String}["Dev1/ctr0","Dev1/ctr1","Dev1/ctr2.
"COSampModes" => (Symbol[:Val_FiniteSamps,:Val_ContSamps],false)
"COSupportedOutputTypes" => (Symbol[:Val_Pulse_Freq,:Val_Pulse_Ticks,:Val_Pulse_T.
"COTrigUsage" => (42,false)
"ChassisModuleDevNames" => (SubString{String}[""],false)
"DILines" => (SubString{String}["Dev1/port0/line0","Dev1/port0/lin.
"DIMaxRate" => (1.0e7,false)
"DIPorts" => (SubString{String}["Dev1/port0","Dev1/port1","Dev1/po.
"DITrigUsage" => (14,false)
"DOLines" => (SubString{String}["Dev1/port0/line0","Dev1/port0/lin.
"DOMaxRate" => (1.0e7,false)
"DOPorts" => (SubString{String}["Dev1/port0","Dev1/port1","Dev1/po.
"DOTrigUsage" => (10,false)
"NumDMAChans" => (0x00000000,false)
"ProductCategory" => (:Val_XSeriesDAQ,false)
"ProductNum" => (0x000075a1,false)
"ProductType" => (SubString{String}["USB-6366 (64 MS) (Mass Terminatio.
"SerialNum" => (0x01719e54,false)
"Terminals" => (SubString{String}["/Dev1/PFI0","/Dev1/PFI1","/Dev1/P.
One can index into the dictionary to get a list of channels:
julia> getproperties("Dev1")["AIPhysicalChans"]
(SubString{ASCIIString}["Dev1/ai0","Dev1/ai1","Dev1/ai2","Dev1/ai3","Dev1/ai4","Dev1/ai5","Dev1/ai6","Dev1/ai7"],false)
A bit simpler in this case though is to use another high-level function which returns just the string Array:
julia> analog_input_channels("Dev1")
8-element Array{String,1}:
"Dev1/ai0"
"Dev1/ai1"
"Dev1/ai2"
"Dev1/ai3"
"Dev1/ai4"
"Dev1/ai5"
"Dev1/ai6"
"Dev1/ai7"
To add, for example, analog input channels, use the high-level analog_input
function:
julia> t = analog_input("Dev1/ai0:1")
NIDAQ.AITask(Ptr{Nothing} @0x0000000025d18600)
julia> typeof(t)
NIDAQ.AITask (constructor with 3 methods)
julia> supertype(NIDAQ.AITask)
NIDAQ.Task
Two channels were added above using the :
notation. Additional
channels can be added later by inputing the returned Task
:
julia> analog_input(t, "Dev1/ai2")
getproperties
can also input a Task
:
julia> getproperties(t)
Dict{String,Tuple{Any,Bool}} with 5 entries:
"Devices" => (SubString{String}["Dev1"],false)
"Channels" => (SubString{String}["Dev1/ai0","Dev1/ai1","Dev1/ai2"],false)
"Name" => (SubString{String}["_unnamedTask<0>"],false)
"NumChans" => (0x00000003,false)
"NumDevices" => (0x00000001,false)
as well as a string containing the name of the channel:
julia> getproperties(t, "Dev1/ai0")
Dict{String,Tuple{Any,Bool}} with 52 entries:
"AccelUnits" => (:Val_g,false)
"AutoZeroMode" => (:Val_None,false)
"BridgeUnits" => (:Val_VoltsPerVolt,false)
"ChanCalDesc" => (SubString{String}[""],false)
"ChanCalOperatorName" => (SubString{String}[""],false)
"ChanCalPolyForwardCoeff" => (Float64[],false)
"ChanCalPolyReverseCoeff" => (Float64[],false)
"ChanCalScaleType" => (:Val_Table,false)
"ChanCalTablePreScaledVals" => (Float64[],false)
"ChanCalTableScaledVals" => (Float64[],false)
"ChanCalVerifAcqVals" => (Float64[],false)
"ChanCalVerifRefVals" => (Float64[],false)
"Coupling" => (:Val_DC,false)
"CurrentACRMSUnits" => (:Val_Amps,false)
"CurrentUnits" => (:Val_Amps,false)
"CustomScaleName" => (SubString{String}[""],false)
"DataXferMech" => (:Val_ProgrammedIO,false)
"DataXferReqCond" => (:Val_OnBrdMemNotEmpty,false)
"DevScalingCoeff" => ([0.000102924,0.000312673,5.87393e-14,-3.31855e-19],false)
"EddyCurrentProxProbeUnits" => (:Val_Meters,false)
"ForceUnits" => (:Val_Newtons,false)
"FreqUnits" => (:Val_Hz,false)
"Gain" => (1.0,false)
"InputSrc" => (SubString{String}["_external_channel"],false)
"LVDTUnits" => (:Val_Meters,false)
"LossyLSBRemovalCompressedSampSize" => (0x00000010,false)
"Max" => (10.0,false)
"MeasType" => (:Val_Voltage,false)
"Min" => (-10.0,false)
"PressureUnits" => (:Val_PoundsPerSquareInch,false)
"RVDTUnits" => (:Val_Degrees,false)
"RawDataCompressionType" => (:Val_None,false)
"RawSampJustification" => (:Val_RightJustified,false)
"RawSampSize" => (0x00000010,false)
"ResistanceUnits" => (:Val_Ohms,false)
"Resolution" => (16.0,false)
"ResolutionUnits" => (:Val_Bits,false)
"RngHigh" => (10.0,false)
"RngLow" => (-10.0,false)
"SoundPressureUnits" => (:Val_Pascals,false)
"StrainGageCfg" => (:Val_FullBridgeI,false)
"StrainUnits" => (:Val_Strain,false)
"TempUnits" => (:Val_DegC,false)
"TermCfg" => (:Val_Diff,false)
"ThrmcplCJCVal" => (25.0,false)
"TorqueUnits" => (:Val_NewtonMeters,false)
"UsbXferReqCount" => (0x00000004,false)
"UsbXferReqSize" => (0x00008000,false)
"VelocityUnits" => (:Val_MetersPerSecond,false)
"VoltageACRMSUnits" => (:Val_Volts,false)
"VoltageUnits" => (:Val_Volts,false)
"VoltagedBRef" => (1.0,false)
Use setproperty!
to change a mutable property:
julia> setproperty!(t, "Dev1/ai0", "Max", 5.0)
Once everything is configured, get some data using the read
function:
julia> start(t)
julia> read(t, 10)
10x3 Array{Float64,2}:
1.52407 -0.448835 0.381075
1.37546 -0.213537 0.305847
1.2363 -0.0268698 0.262826
1.109 0.118619 0.243117
0.995797 0.2311 0.240073
0.896695 0.315782 0.248004
0.811452 0.378752 0.262746
0.739429 0.424257 0.281893
0.679263 0.456223 0.302402
0.629672 0.477774 0.323473
julia> stop(t)
julia> clear(t)
read
can also return Int16
, Int32
, UInt16
, and UInt32
by specifying
those types as an additional argument:
julia> read(t, 10, Int16)
10×3 Array{Int16,2}:
-12619 -5351 -13973
-12618 -5350 -13973
-12620 -5350 -13973
-12619 -5350 -13974
-12618 -5351 -13972
-12618 -5348 -13974
-12619 -5350 -13973
-12619 -5350 -13973
-12619 -5350 -13972
-12620 -5350 -13973
Similar work flows exist for analog_output
, digital_input
,
and digital_output
. The high-level API also supports many counter
functions too, including count_edges
and generate_pulses
. For a
full list of convenience functions use the names
function in Julia Base:
julia> names(NIDAQ)
25-element Array{Symbol,1}:
:analog_output_channels
:digital_input_channels
:setproperty!
:line_to_line
:counter_input_channels
:counter_output_channels
:NIDAQ
:analog_voltage_input_ranges
:analog_current_input_ranges
:digital_input
:stop
:generate_pulses
:count_edges
:digital_output_channels
:analog_input
:channel_type
:analog_voltage_output_ranges
:analog_current_output_ranges
:devices
:digital_output
:getproperties
:quadrature_input
:analog_input_channels
:analog_output
:Bool32
:clear
NIDAQmx is a powerful interface, and while NIDAQ.jl provides wrappers
for all of it's functions, it only abstracts a few of them. If these
don't suit your needs you'll have to dive deep into src/functions_V*.jl
and src/constants_V*.jl
. Complete documentation of this low-level API
is here and
here.
One situation where the low-level API is needed is to specify continous output of pulses using a counter:
julia> t = generate_pulses("Dev1/ctr0")
NIDAQ.COTask(Ptr{Nothing} @0x00000000059d8790)
julia> fieldnames(typeof(t))
(:th,)
julia> NIDAQ.CfgImplicitTiming(t.th, NIDAQ.Val_ContSamps, UInt64(1))
0
Note that tasks consist of just a single field th
, and that this "task
handle" is what must be passed into many low-level routines.
Also, for brevity NIDAQ.jl strips the "DAQmx" prefix to all functions and constants in NI-DAQmx, and converts the latter to 32 bits. One must still take care to caste the other inputs appropriately though.
Install Clang.jl. If there are
build problems, make sure that llvm-config
is on your PATH
, and that
libclang
can be found, as described in the Clang.jl README. Clang
defaults to using a system installed version of LLVM. An alternative is
to set BUILD_LLVM_CLANG=1
in Make.user, and compile Julia from source.
Find NIDAQmx.h
, which usually lives in
C:\Program Files (x86)\National Instruments\NI-DAQ\DAQmx ANSI C Dev\include
.
Edit this header file as follows:
- For NI-DAQmx v19.6 in
NIDAQmx.h
change__int64 int64
tolong long int int64
andunsigned __int64 uInt64
tounsigned long long uInt64
. - For NI-DAQmx v9.6.0 in
NIDAQmx.h
changedefined(__linux__)
todefined(__linux__) || defined(__APPLE__)
.
Then run Clang to produce the corresponding Julia files:
julia> using Clang
julia> wc = init(; headers = ["NIDAQmx.h"],
output_file = "NIDAQmx.jl",
common_file = "common.jl",
clang_includes = vcat(CLANG_INCLUDE),
clang_args = map(x->"-I"*x, find_std_headers()),
header_wrapped = (root, current)->root == current,
header_library = x->"NIDAQmx",
clang_diagnostics = true)
julia> run(wc)
$ mv NIDAQmx.jl src/functions_V<version>.jl
$ mv common.jl src/constants_V<version>.jl
$ rm LibTemplate.jl ctypes.jl
Finally, the following manual edits are necessary:
- In
constants_V<version>.jl
- delete
const CVICALLBACK = CVICDECL
, - in NI-DAQmx v19.6 add
struct CVITime; lsb::uInt64; msb::int64; end
- in NI-DAQmx v17.1.0 comment out
const CVIAbsoluteTime = VOID
- change
const bool32 = uInt32
toconst bool32 = Bool32
. - in NI-DAQmx v15 to v18 comment out
using Compat
- delete
- In
functions_V<version>.jl
- in NI-DAQmx v18 and earlier, globally search for
Ptr
and replace withRef
, then globally search forCallbackRef
and replace withCallbackPtr
. - globally search for
Cstring
and replace withSafeCstring
- for Julia 0.7 support, replace
type
with_type
- in NI-DAQmx v18 and earlier, globally search for
Ben Arthur, [email protected]
Scientific Computing
Janelia Research Campus
Howard Hughes Medical Institute