Advanced Scripting in Perl, Python and Tcl

Publish Date: Sep 28, 2012 | 6 Ratings | 3.00 out of 5 |  PDF

Overview

The ability to re-use existing code while migrating to a new test software framework can save developers both time and money.  Both NI TestStand test management software and NI LabVIEW give users the ability to directly call and integrate with code written in a variety of languages such as Perl, Python and Tcl.

This article examines some of the more advanced scripting language concepts such as communicating with data acquisition devices and instruments. In particular, we will look at how to use Perl, Python and Tcl to call functions in DLLs, perform Data Acquisition using NI-DAQmx and communicate with instruments using NI-VISA.

This article is Part 3 of a three-part series on calling scripting languages from TestStand with a focus on the needs of engineers and scientists.

Table of Contents

  1. Calling C-Style DLLs from Scripts
  2. Data Acquisition in Scripts Using NI-DAQmx
  3. Instrument Control in Scripts Using VISA
  4. Next Steps

1. Calling C-Style DLLs from Scripts

The first section will describe how to call a function in a simple C-Style DLL from Perl, Python and Tcl.

The C-Style DLL

We created a simple DLL using LabWindows/CVI that exports a single function called addNumbers. This function uses the cdecl calling convention and has the following four parameters and return value:

Prototype: int addNumbers (int x, int y, int* sum, char* string);

Parameter Type Details
x (in) int (by value) First Addend
y (in) int (by value) Second Addend
sum (out) int * (by reference) Output: Sum of x and y
string (out) char * (by reference) Output: “Hello World”
Return value int 0

 

CVI Code: addNumbers
int addNumbers (int x, int y, int* sum, char *string)
{
       *sum = x+y;
       strcpy (string, "Hello World");
       return 0;
}

 

You can download the precompiled DLL here: simpledll.dll

Caveats:

  • Perl – Calling Convention: In order to call a function that uses the cdecl calling convention, you must specify the calling convention when you load the function.
    For more on calling conventions, please refer to KnowledgeBase 2RCKK3TL: Calling Conventions in LabWindows/CVI.
  • Perl – Packing: Since numbers can be represented in multiple ways, in order to pass it to the DLL function, the number has to be stored in the same format as the DLL expects. In order to pass an integer pointer, you have to ‘pack’ it as an Int32 (‘i’) before calling the function. Packing in Perl basically tells the interpreter how to represent the data in memory.
    For a tutorial on Packing in Perl, refer to PerlMonks: Pack/Unpack Tutorial.
  • Python – ctypes: Since numbers can be represented in multiple ways, in order to pass it to the DLL function, the number has to be stored in the same format as the DLL expects. In order to pass an integer pointer, Python has a library called ‘ctypes’ that includes types like c_int to facilitate passing integer pointers.
    For more information on the ctypes library, refer to The Python Standard Library: ctypes – A foreign function library for Python.
  • Tcl – Yet Another DLL Caller: The example scripts below use a utility from the Tcl Wiki called ‘Yet Another DLL Caller’ that facilitates calling DLL functions from Tcl. This package contains a DLL as well as a Tcl file that must be included in your source code before you can use it.
    For more information on this utility, refer to Tcl Wiki: Yet another dll caller.

 

Calling the DLL from Scripts

The following code snippets demonstrate how to call a DLL from Perl, Python and Tcl.
Note: You can download any of the script files by clicking on their filename above the code snippets.

Perl: call_simple_dll.pl
#!/usr/bin/perl -w
use strict;

#include Win32::API to call DLLs
use Win32::API;

my $x = 5;
my $y = 8;
#First pack $sum as an Int32 since we have to pass this by reference
my $sum = pack('i', 0);
my $string = " " x 20;

#Function Prototype: int addNumbers (int x, int y, int *sum, char *string)
#Load the Function
#The third argument here 'IIPP' refers to the types of the paramters,
#i.e., two integers (I) followed by two pointers(P)
#The fourth argument here 'I' refers to the return type, i.e., an integer.
my $function = Win32::API->new('SimpleDLL','addNumbers','IIPP','I', '_cdecl');

#Call the Function
my $return = $function->Call($x,$y,$sum,$string);

#Unpack the sum back to a regular perl number
my $sumUnpacked = unpack('i', $sum);

#Print Results
print "Sum: ", $sumUnpacked, "\n";
print "String: ", $string, "\n";
print "ReturnValue: ", $return, "\n";


Python: call_simple_dll.py
#!/usr/bin/env python

#The ctypes library includes datatypes for passing data to DLLs
#For instance, c_int to pass integer pointers
from ctypes import *

#Load the DLL
dll = cdll.LoadLibrary("SimpleDLL.dll")

x = 5
y = 3

#Create sum as a c style integer
sum = c_int(0)
string = create_string_buffer("", 20)

#Function Prototype: int addNumbers (int x, int y, int *sum, char *string)

#Call the Function
#Pass pointers using the byref keyword
returnValue = dll.addNumbers(x, y, byref(sum), byref(string))

#Print Results
print "Sum: ", sum, "\n"
print "String: ", repr(string.raw), "\n"
print "returnValue: ", returnValue, "\n"

 

Tcl: call_simple_dll.tcl
#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

#Import YetAnotherDLLCaller to facilitate calling DLL functions
#Make sure your script directory contains dll_tcl.tcl and dll.dll from http://wiki.tcl.tk/12264
source dll_tcl.tcl

#Load the DLL
if {[catch {::dll::load SimpleDLL.dll -> simpleDLL}]} {
    error "SimpleDLL library not found!"
}

#Function Prototype: int addNumbers (int x, int y, int *sum, char *string)

#Import the Function
#Importing involves simply specifying the function prototype
::simpleDLL::cmd "int addNumbers(int, int, int *, char *)"   

set x 3
set y 5
set myString [::dll::buffer 20]

#Call the Function
#Pass arguments without the $ to pass by reference
set returnValue [::simpleDLL::addNumbers $x $y sum $myString]

#Print Results
puts "Sum: $sum"
puts "String: $myString"
puts "ReturnValue: $returnValue"

 

Back to Top

2. Data Acquisition in Scripts Using NI-DAQmx

NI-DAQmx is the high-performance, multithreaded, data acquisition (DAQ) driver software at the heart of NI measurement services software.

Installing the NI-DAQmx driver also installs and registers several DLLs. One such DLL is nicaiu.dll which is located at:
<Windows>\System32\nicaiu.dll

This particular DLL contains the function calls that are part of the NI-DAQmx C API.  Script developers can use these functions to perform data acquisition using National Instruments DAQ devices.

You can find help on all of the NI-DAQmx C API functions in the NI-DAQmx C Reference Help which installs to:
Start»Programs»National Instruments»NI-DAQ»NI-DAQmx C Reference Help.

Getting Started

  • Installing the NI-DAQmx Driver: In order to use the NI-DAQmx functions, you must have NI-DAQmx installed. This is a free download from http://ni.com/support.
  • Using an NI-DAQmx Compatible Device: In order to perform data acquisition, you also need a device to read from. If you have a device, the following examples refer to the device as ‘dev1’. If you don’t already have a device, you can create and use a simulated device, and make sure that its alias is set to ‘dev1’. For instructions on creating a simulated device, refer to:
    Developer Zone Tutorial: NI-DAQmx Simulated Devices

The Functions We Will Use

The following is a list of NI-DAQmx functions that are used will use in the following example scripts:

  • DAQmxCreateTask
    Prototype:
    int32 DAQmxCreateTask (const char taskName[], TaskHandle *taskHandle);
    Purpose: Creates a task.
  • DAQmxCreateAIVoltageChan
    Prototype:
    int32 DAQmxCreateAIVoltageChan (TaskHandle taskHandle, const char physicalChannel[], const char nameToAssignToChannel[], int32 terminalConfig, float64 minVal, float64 maxVal, int32 units, const char customScaleName[]);
    Purpose: Creates channel(s) to measure voltage and adds the channel(s) to the task you specify with taskHandle.
  • DAQmxReadAnalogScalarF64
    Prototype:
    int32 DAQmxReadAnalogScalarF64 (TaskHandle taskHandle, float64 timeout, float64 *value, bool32 *reserved);
    Purpose: Reads a single floating-point sample from a task that contains a single analog input channel.
  • DAQmxClearTask
    Prototype:
    int32 DAQmxClearTask (TaskHandle taskHandle);
    Purpose: Clears the task. Before clearing, this function stops the task, if necessary, and releases any resources reserved by the task.

Note: All DAQmx functions return 0 on success. This example checks for zero at each step and reports an error for non-zero return values.

Detailed help on these functions can be found in the NI-DAQmx C Reference Help.

Calling the NI-DAQmx DLL

Perl: calling_daq_dll.pl

#!/usr/bin/perl -w
use strict;
use Win32::API;

#Steps:
# 1. Create a task.
# 2. Create an analog input voltage channel.
# 3. Read a scalar value from the channel.
# 4. Call the Clear Task function to clear the task.
# 5. Display an error if any.

#Note: All DAQmx functions return 0 on success.
#This example checks for 0 at each step and reports an error for non-zero return values

##############################
#Create a Task
##############################
my $inTaskHandle = pack('i', 0); #DAQmx TaskHandle
my $taskName = "AITask"; #Task Name

print "Creating a Task\n";

#Function Prototype: int32 DAQmxCreateTask (const char taskName[], TaskHandle *taskHandle);
my $functionDAQmxCreateTask = Win32::API->new('nicaiu','DAQmxCreateTask','PP','I');

#First create the task and get a handle to the task that will be used for all subsequenct operations
my $return = $functionDAQmxCreateTask->Call($taskName, $inTaskHandle);
die ("Could not Create Task. Error: ", $return, "\n" ) unless ($return == 0);

my $outTaskHandle = unpack('i', $inTaskHandle);

print "TaskName: ", $taskName, "\n";
print "TaskHandle: ", $outTaskHandle, "\n";
print "ReturnValue: ", $return, "\n\n";

##############################
#Add an AI Channel to the Task
##############################
my $physicalChannel = "dev1/ai0"; #Physical Channel: AI0 on Dev1
my $channelName = "ai0";
my $terminalConfig = -1; #DAQmx_Val_Cfg_Default constant from NIDAQmx.h
my $minVal = -10.0;
my $maxVal = 10.0;
my $units = 10348; #DAQmx_Val_Volts constant from NIDAQmx.h
my $customScaleName = "";

print "Adding an AI Channel to the Task\n";

#Function Prototype: int32 DAQmxCreateAIVoltageChan (TaskHandle taskHandle, const char physicalChannel[],
#const char nameToAssignToChannel[], int32 terminalConfig, float64 minVal, float64 maxVal,
#int32 units, const char customScaleName[])
my $functionDAQmxCreateAIVoltageChan = Win32::API->new('nicaiu', 'DAQmxCreateAIVoltageChan', 'IPPIDDIP', 'I');

#Add an analog input channel to the task and specify/configure the channel to read on:
#Specify dev1/ai0 as the channel, use the default terminal configuration and specify that the units is Volts
$return = $functionDAQmxCreateAIVoltageChan->Call($outTaskHandle, $physicalChannel, $channelName, $terminalConfig, $minVal, $maxVal, $units, $customScaleName);
die ("Could not add Channel. Error: ", $return, "\n" ) unless ($return == 0);

print "ReturnValue: ", $return, "\n\n";

##############################
#Read a scalar value
##############################
my $timeout = 10.0;
my $inValue = pack('d', 0.0);
my $reserved = 0;

print "Reading a scalar value from the channel\n";

#Function Prototype: int32 DAQmxReadAnalogScalarF64 (TaskHandle taskHandle, float64 timeout, float64 *value, bool32 *reserved);
my $functionDAQmxReadAnalogScalarF64 = Win32::API->new('nicaiu', 'DAQmxReadAnalogScalarF64', 'IDPP', 'I');

#Next, use the taskHandle to read a single scalar value on the channel specified earlier
$return = $functionDAQmxReadAnalogScalarF64->Call($outTaskHandle, $timeout, $inValue, $reserved);
die ("Could not read Value. Error: ", $return, "\n" ) unless ($return == 0);

my $outValue = unpack('d', $inValue);
print "Value: ", $outValue, "\n";
print "ReturnValue: ", $return, "\n\n";

##############################
#Clear the Task
##############################
print "Clearing the task\n";

#Function Prototype: int32 DAQmxClearTask (TaskHandle taskHandle);
my $functionDAQmxClearTask = Win32::API->new ('nicaiu', "DAQmxClearTask", 'I', 'I');
#Finally, clear the task to release its resources
$return = $functionDAQmxClearTask->Call($outTaskHandle);
die ("Could not clear Task. Error: ", $return, "\n" ) unless ($return == 0);

print "ReturnValue: ", $return, "\n";


Python: calling_daq_dll.py

#!/usr/bin/env python
import sys

from ctypes import *

dll = windll.LoadLibrary("nicaiu.dll")

#Steps:
# 1. Create a task.
# 2. Create an analog input voltage channel.
# 3. Read a scalar value from the channel.
# 4. Call the Clear Task function to clear the task.
# 5. Display an error if any.

#Note: All DAQmx functions return 0 on success.
#This example checks for 0 at each step and reports an error for non-zero return values

##############################
#Create a Task
##############################

taskHandle = c_int(0) #DAQmx TaskHandle
taskName = "AITask" #Task Name

print "Creating a Task"

#First create the task and get a handle to the task that will be used for all subsequenct operations
#Function Prototype: int32 DAQmxCreateTask (const char taskName[], TaskHandle *taskHandle);
returnValue = dll.DAQmxCreateTask(taskName, byref(taskHandle))
if returnValue != 0:
print >> sys.stderr, "Could not Create Task. Error: ", returnValue, "\n"
sys.exit(returnValue)

print "TaskName: ", taskName
print "taskHandle: ", taskHandle
print "returnValue: ", returnValue, "\n"

###############################
##Add an AI Channel to the Task
###############################
physicalChannel = "dev1/ai0" #Physical Channel: AI0 on Dev1
channelName = "ai0"
terminalConfig = -1 #DAQmx_Val_Cfg_Default constant from NIDAQmx.h
minVal = c_double(-10.0)
maxVal = c_double(10.0)
units = 10348 #DAQmx_Val_Volts constant from NIDAQmx.h
customScaleName = None

print "Adding an AI Channel to the Task"

#Add an analog input channel to the task and specify/configure the channel to read on:
#Specify dev1/ai0 as the channel, use the default terminal configuration and specify that the units is Volts
#Function Prototype: int32 DAQmxCreateAIVoltageChan (TaskHandle taskHandle, const char physicalChannel[],
#const char nameToAssignToChannel[], int32 terminalConfig, float64 minVal, float64 maxVal,
#int32 units, const char customScaleName[])
returnValue = dll.DAQmxCreateAIVoltageChan(taskHandle, physicalChannel, channelName, terminalConfig, minVal, maxVal, units, customScaleName)
if returnValue != 0:
print >> sys.stderr, "Could not add Channel. Error: ", returnValue, "\n"
sys.exit(returnValue)

print "ReturnValue: ", returnValue, "\n"

###############################
##Read a scalar value
###############################
timeout = c_double(10.0)
value = c_double(0)
reserved = None

print "Reading a scalar value from the channel"

#Next, use the taskHandle to read a single scalar value on the channel specified earlier
#Function Prototype: int32 DAQmxReadAnalogScalarF64 (TaskHandle taskHandle, float64 timeout, float64 *value, bool32 *reserved);
returnValue = dll.DAQmxReadAnalogScalarF64(taskHandle, timeout, byref(value), reserved)
if returnValue != 0:
print >> sys.stderr, "Could not read Value. Error: " ,returnValue, "\n"
sys.exit(returnValue)

print "Value: ", value
print "ReturnValue: ", returnValue, "\n"

##############################
##Clear the Task
##############################
print "Clearing the task"

#Finally, clear the task to release its resources
#Function Prototype: int32 DAQmxClearTask (TaskHandle taskHandle);
returnValue = dll.DAQmxClearTask(taskHandle)
if returnValue != 0:
print >> sys.stderr, "Could not clear Task. Error: " ,returnValue, "\n"
sys.exit(returnValue)

print "ReturnValue: ", returnValue, "\n"

 

 

Tcl: Calling_daq_dll.tcl

#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

source dll_tcl.tcl

if {[catch {::dll::load nicaiu.dll -> daqDLL}]} {
error "SimpleDLL library not found!"
}

#Steps:
# 1. Create a task.
# 2. Create an analog input voltage channel.
# 3. Read a scalar value from the channel.
# 4. Call the Clear Task function to clear the task.
# 5. Display an error if any.

#Note: All DAQmx functions return 0 on success.
#This example checks for 0 at each step and reports an error for non-zero return values

##############################
#Create a Task
##############################
set taskName "AITask" ;#DAQmx TaskHandle
set taskHandle 0 ;#Task Name

puts "Creating a Task"

#Function Prototype: int32 DAQmxCreateTask (const char taskName[], TaskHandle *taskHandle);
::daqDLL::cmd "int DAQmxCreateTask(char *, int *)" ;# &library

#First create the task and get a handle to the task that will be used for all subsequenct operations
set returnValue [::daqDLL::DAQmxCreateTask $taskName taskHandle]
if {$returnValue != 0} {
puts stderr "Could not Create Task. Error: $returnValue \n"
return returnValue
}

puts "TaskName: $taskName"
puts "TaskHandle: $taskHandle"
puts "ReturnValue: $returnValue \n"

###############################
##Add an AI Channel to the Task
###############################
set physicalChannel "dev1/ai0" ;#Physical Channel: AI0 on Dev1
set channelName "ai0"
set terminalConfig -1 ;#DAQmx_Val_Cfg_Default constant from NIDAQmx.h
set minVal -10.0
set maxVal 10.0
set units 10348 ;#DAQmx_Val_Volts constant from NIDAQmx.h
set customScaleName [::dll::buffer 0]

puts "Adding an AI Channel to the Task"

#Function Prototype: int32 DAQmxCreateAIVoltageChan (TaskHandle taskHandle, const char physicalChannel[],
#const char nameToAssignToChannel[], int32 terminalConfig, float64 minVal, float64 maxVal,
#int32 units, const char customScaleName[])
::daqDLL::cmd "int DAQmxCreateAIVoltageChan (int, char *, char *, int, double, double, int, char *)"

#Add an analog input channel to the task and specify/configure the channel to read on:
#Specify dev1/ai0 as the channel, use the default terminal configuration and specify that the units is Volts
set returnValue [::daqDLL::DAQmxCreateAIVoltageChan $taskHandle $physicalChannel $channelName $terminalConfig $minVal $maxVal $units $customScaleName]
if {$returnValue != 0} {
puts stderr "Could not add Channel. Error: $returnValue \n"
return returnValue
}

puts "ReturnValue: $returnValue \n";

###############################
##Read a scalar value
###############################
set timeout 10.0
set value 0.0
set reserved 0

puts "Reading a scalar value from the channel"

#Function Prototype: int32 DAQmxReadAnalogScalarF64 (TaskHandle taskHandle, float64 timeout, float64 *value, bool32 *reserved);
::daqDLL::cmd "int DAQmxReadAnalogScalarF64 (int, double, double *, int *)"

#Next, use the taskHandle to read a single scalar value on the channel specified earlier
set returnValue [::daqDLL::DAQmxReadAnalogScalarF64 $taskHandle $timeout value 0]
if {$returnValue != 0} {
puts stderr "Could not read Value. Error: $returnValue \n"
return returnValue
}

puts "Value: $value";
puts "ReturnValue: $returnValue \n";

##############################
##Clear the Task
##############################
puts "Clearing the task"

#Function Prototype: int32 DAQmxClearTask (TaskHandle taskHandle);
::daqDLL::cmd "int DAQmxClearTask (int)"

#Finally, clear the task to release its resources
set returnValue [::daqDLL::DAQmxClearTask $taskHandle]
if {$returnValue != 0} {
puts stderr "Could not clear Task. Error: $returnValue \n"
return returnValue
}

puts "ReturnValue: $returnValue \n";

 

Back to Top

3. Instrument Control in Scripts Using VISA

National Instruments Virtual Instrument Software Architecture (VISA) is a standard for configuring, programming, and troubleshooting instrumentation systems comprising GPIB, VXI, PXI, Serial, Ethernet, and/or USB interfaces. VISA provides the programming interface between the hardware and development environments.

Installing the NI-DAQmx driver also installs and registers several DLLs. One such DLL is visa32.dll which is located at:
<Windows>\System32\visa32.dll

This particular DLL contains the function calls that are part of the VISA C API, and developers can use these functions to perform instrument control.

You can find help on all of the VISA C API functions in the NI-VISA Help which installs to:
Start»Programs»National Instruments»VISA»Documentation»NI-VISA Help.

Getting Started

  • Installing the NI-VISA Driver: In order to use the VISA functions, you must have VISA installed. This is a free download from http://ni.com/support.
  • Using the NI-VISA Device: In order to perform instrument control, you also need a device to communicate with. In the following example, we communicate via a GPIB Board to an Instrument Simulator on address 2. The example can easily be modified for serial communication as well.

The Functions We Will Use

The following is a list of NI-VISA functions we will use in our example:

  • viOpenDefaultRM
    Prototype:
    ViStatus viOpenDefaultRM(ViPSession sesn)
    Purpose:
    This function returns a session to the Default Resource Manager resource.
  • viOpen
    Prototype:
    ViStatus viOpen(ViSession sesn, ViRsrc rsrcName, ViAccessMode accessMode, ViUInt32 openTimeout, ViPSession vi)
    Purpose: Opens a session to the specified resource.
  •  viSetAttribute
    Prototype:
    ViStatus viSetAttribute(ViObject vi, ViAttr attribute, ViAttrState attrState)
    Purpose: Sets the state of an attribute.
  • viWrite
    Prototype:
    ViStatus viWrite(ViSession vi, ViBuf buf, ViUInt32 count, ViPUInt32 retCount)
    Purpose:
    Writes data to device or interface synchronously.
  • viRead
    Prototype: ViStatus viRead(ViSession vi, ViPBuf buf, ViUInt32 count, ViPUInt32 retCount)
    Purpose: Reads data from device or interface synchronously.
  • viClose
    Prototype:
    ViStatus viClose(ViObject vi)
    Purpose:
    Closes the specified session, event, or find list.

Note: All VISA functions return 0 on success. This example checks for zero at each step and reports an error for non-zero return values.

Detailed help on these functions can be found in the NI-VISA Help.

Calling the NI-VISA DLL

Perl: calling_visa_dll.py

#!/usr/bin/perl -w
use strict;
use Win32::API;

#Steps:
# 1. Open Resource Manager
# 2. Open VISA Session to an Instrument
# 3. Set Timeout Attribute
# 4. Write the Identification Query Using viWrite
# 5. Try to Read a Response With viRead
# 6. Close the VISA Session & Resource Manager
# 7. Display an error if any.

#Note: All VISA functions return 0 on success.
#This example checks for 0 at each step and reports an error for non-zero return values

##############################
#Open Resource Manager
##############################
my $inResourceManagerHandle = pack('i', 0); #Resource Manager Handle

print "Opening Resource Manager\n";

#Function Prototype: ViStatus viOpenDefaultRM(ViPSession sesn)
my $functionViOpenDefaultRM = Win32::API->new('visa32','viOpenDefaultRM','P','I');

#First get the resource manager handle. This will be used next to get a handle to the instrument
my $return = $functionViOpenDefaultRM->Call($inResourceManagerHandle);
die ("Could not open Resource Manager. Error: ", $return, "\n" ) unless ($return == 0);

my $outResourceManagerHandle = unpack('i', $inResourceManagerHandle);

print "ResourceManagerHandle: ", $outResourceManagerHandle, "\n";
print "ReturnValue: ", $return, "\n\n";

##############################
#Open Resource Session
##############################
my $inSessionHandle = pack('i', 0); #Instrument Handle
my $VI_NULL = 0; #VI_NULL constant from visa.h
my $resourceName = "GPIB0::2::INSTR"; #Resource: GPIB Device 0, Primary Adress 2

print "Opening Resource Session\n";

#Function Prototype: ViStatus viOpen(ViSession sesn, ViRsrc rsrcName, ViAccessMode accessMode, ViUInt32 openTimeout, ViPSession vi)
my $functionViOpen = Win32::API->new('visa32','viOpen','IPIIP','I');

#Open a VISA session to a device on GPIB Device 0 at Primary Address 2 and retreive a handle to this session
#This session handle will be used for subsequent operations
$return = $functionViOpen->Call($outResourceManagerHandle,$resourceName,$VI_NULL,$VI_NULL,$inSessionHandle);
die ("Could not open Resource Session. Error: ", $return, "\n" ) unless ($return == 0);

my $outSessionHandle = unpack('i', $inSessionHandle);

print "SessionHandle: ", $outSessionHandle, "\n";
print "ReturnValue: ", $return, "\n\n";

##############################
#Set Timeout Attribute
##############################
my $VI_ATTR_TMO_VALUE = 1073676314; #Timeout Attribute Constant from visa.h
my $timeout = 5000; #Timeout: 5000ms

print "Setting Timeout Attribute\n";

#Function Prototype: ViStatus viSetAttribute(ViObject vi, ViAttr attribute, ViAttrState attrState)
my $functionViSetAttribute = Win32::API->new('visa32','viSetAttribute','III','I');

#Set timeout value to 5000 milliseconds (5 seconds)
$return = $functionViSetAttribute->Call($outSessionHandle, $VI_ATTR_TMO_VALUE, $timeout);
die ("Could not set Timeout Attribute. Error: ", $return, "\n" ) unless ($return == 0);

print "ReturnValue: ", $return, "\n\n";

##############################
#Write "*IDN?" to Device
##############################
my $bufferToWrite = "*IDN?"; #*IDN? typically tells devices to return identification
my $bytesToWrite = length($bufferToWrite) + 1;
my $inBytesWritten = pack('i', 0);

print "Writing *IDN? to Device\n";

#Function Prototype: ViStatus viWrite(ViSession vi, ViBuf buf, ViUInt32 count, ViPUInt32 retCount)
my $functionViWrite = Win32::API->new('visa32','viWrite','IPIP','I');

#Use this session handle to write *IDN? command to the instrument asking for the its identification.
$return = $functionViWrite->Call($outSessionHandle, $bufferToWrite, $bytesToWrite, $inBytesWritten);
die ("Could not write *IDN? to Device. Error: ", $return, "\n" ) unless ($return == 0);

my $outBytesWritten = unpack('i', $inBytesWritten);

print "Bytes Written: ", $outBytesWritten, "\n";
print "ReturnValue: ", $return, "\n\n";

##############################
#Read Response from Device
##############################
my $response = " " x 128;
my $bytesToRead = 128;
my $inBytesRead = pack('i', 0);

print "Reading Response from Device\n";

#Function Prototype: ViStatus viRead(ViSession vi, ViPBuf buf, ViUInt32 count, ViPUInt32 retCount)
my $functionViRead = Win32::API->new('visa32','viRead','IPIP','I');

#Attempt to read back a response from the device to the identification query that was sent.
$return = $functionViRead->Call($outSessionHandle, $response, $bytesToRead, $inBytesRead);
die ("Could not Read Response from Device. Error: ", $return, "\n" ) unless ($return == 0);

my $outBytesRead = unpack('i', $inBytesRead);

print "Bytes Read: ", $outBytesWritten, "\n";
print "Response: ", $response, "\n";
print "ReturnValue: ", $return, "\n\n";

##############################
#Close Sessions
##############################
print "Closing Resource Session\n";

#Finally, close the instrument session and the resource manager to free resources

#Function Prototype: ViStatus viClose(ViObject vi)
my $functionViClose = Win32::API->new('visa32','viClose','I','I');

$return = $functionViClose->Call($outSessionHandle);
die ("Could not close Resource Session. Error: ", $return, "\n" ) unless ($return == 0);

print "ReturnValue: ", $return, "\n\n";

print "Closing Resource Manager\n";

$return = $functionViClose->Call($outResourceManagerHandle);
die ("Could not close Resource Manager. Error: ", $return, "\n" ) unless ($return == 0);

print "ReturnValue: ", $return, "\n\n";

 

Python: calling_visa_dll.py

#!/usr/bin/env python
import sys

from ctypes import *
dll = windll.LoadLibrary("visa32.dll")

#Steps:
# 1. Open Resource Manager
# 2. Open VISA Session to an Instrument
# 3. Set Timeout Attribute
# 4. Write the Identification Query Using viWrite
# 5. Try to Read a Response With viRead
# 6. Close the VISA Session & Resource Manager
# 7. Display an error if any.

#Note: All VISA functions return 0 on success.
#This example checks for 0 at each step and reports an error for non-zero return values

##############################
#Open Resource Manager
##############################
resourceManagerHandle = c_int(0) ##Resource Manager Handle

print "Opening Resource Manager"

#First get the resource manager handle. This will be used next to get a handle to the instrument
#Function Prototype: ViStatus viOpenDefaultRM(ViPSession sesn)
returnValue = dll.viOpenDefaultRM(byref(resourceManagerHandle))

if returnValue != 0:
print >> sys.stderr, "Could not open Resource Manager. Error: ", returnValue
sys.exit(returnValue)

print "ResourceManagerHandle: ", resourceManagerHandle
print "ReturnValue: ", returnValue, "\n"

##############################
#Open Resource Session
##############################
sessionHandle = c_int(0) #Instrument Handle
VI_NULL = 0 #VI_NULL constant from visa.h
resourceName = "GPIB0::2::INSTR" #Resource: GPIB Device 0, Primary Adress 2

print "Opening Resource Session"

#Open a VISA session to a device on GPIB Device 0 at Primary Address 2 and retreive a handle to this session
#This session handle will be used for subsequent operations
#Function Prototype: ViStatus viOpen(ViSession sesn, ViRsrc rsrcName, ViAccessMode accessMode, ViUInt32 openTimeout, ViPSession vi)
returnValue = dll.viOpen(resourceManagerHandle,resourceName,VI_NULL,VI_NULL,byref(sessionHandle))
if returnValue != 0:
print >> sys.stderr, "Could not open Resource Session. Error: ", returnValue
sys.exit(returnValue)

print "SessionHandle: ", sessionHandle
print "ReturnValue: ", returnValue, "\n";

##############################
#Set Timeout Attribute
##############################
VI_ATTR_TMO_VALUE = 1073676314; #Timeout Attribute Constant from visa.h
timeout = 5000; #Timeout: 5000ms

print "Setting Timeout Attribute";

#Set timeout value to 5000 milliseconds (5 seconds)
#Function Prototype: ViStatus viSetAttribute(ViObject vi, ViAttr attribute, ViAttrState attrState)
returnValue = dll.viSetAttribute(sessionHandle, VI_ATTR_TMO_VALUE, timeout);
if returnValue != 0:
print >> sys.stderr, "Could not set Timeout Attribute. Error: ", returnValue
sys.exit(returnValue)

print "ReturnValue: ", returnValue, "\n"

##############################
#Write "*IDN?" to Device
##############################
bufferToWrite = "*IDN?" #*IDN? typically tells devices to return identification
bytesToWrite = len(bufferToWrite) + 1
bytesWritten = c_int(0)

print "Writing *IDN? to Device"

#Use this session handle to write *IDN? command to the instrument asking for the its identification.
#Function Prototype: ViStatus viWrite(ViSession vi, ViBuf buf, ViUInt32 count, ViPUInt32 retCount)
returnValue = dll.viWrite(sessionHandle, bufferToWrite, bytesToWrite, byref(bytesWritten))
if returnValue != 0:
print >> sys.stderr, "Could not write *IDN? to Device. Error: ", returnValue
sys.exit(returnValue)

print "Bytes Written: ", bytesWritten
print "ReturnValue: ", returnValue, "\n";

##############################
#Read Response from Device
##############################
response = create_string_buffer("", 128)
bytesToRead = 128
bytesRead = c_int(0)

print "Reading Response from Device";

#Attempt to read back a response from the device to the identification query that was sent.
#Function Prototype: ViStatus viRead(ViSession vi, ViPBuf buf, ViUInt32 count, ViPUInt32 retCount)
returnValue = dll.viRead(sessionHandle, byref(response), bytesToRead, byref(bytesRead))
if returnValue != 0:
print >> sys.stderr, "Could not Read Response from Device. Error: ", returnValue
sys.exit(returnValue)

print "Bytes Read: ", bytesWritten;
print "Response: ", repr(response.raw);
print "ReturnValue: ", returnValue, "\n";

##############################
#Close Sessions
##############################
print "Closing Resource Session";

#Finally, close the instrument session and the resource manager to free resources

#Function Prototype: ViStatus viClose(ViObject vi)
returnValue = dll.viClose(sessionHandle)
if returnValue != 0:
print >> sys.stderr, "Could not close Resource Session. Error: ", returnValue
sys.exit(returnValue)

print "ReturnValue: ", returnValue, "\n";

print "Closing Resource Manager";

returnValue = dll.viClose(resourceManagerHandle)
if returnValue != 0:
print >> sys.stderr, "Could not close Resource Manager. Error: ", returnValue
sys.exit(returnValue)

print "ReturnValue: ", returnValue, "\n";

 

Tcl: calling_visa_dll.tcl

#!/bin/sh
# -*- tcl -*-
# The next line is executed by /bin/sh, but not tcl \
exec tclsh "$0" ${1+"$@"}

source dll_tcl.tcl

if {[catch {::dll::load visa32.dll -> visaDLL}]} {
error "SimpleDLL library not found!"
}

#Steps:
# 1. Open Resource Manager
# 2. Open VISA Session to an Instrument
# 3. Set Timeout Attribute
# 4. Write the Identification Query Using viWrite
# 5. Try to Read a Response With viRead
# 6. Close the VISA Session & Resource Manager
# 7. Display an error if any.

#Note: All VISA functions return 0 on success.
#This example checks for 0 at each step and reports an error for non-zero return values

##############################
#Open Resource Manager
##############################
set resourceManagerHandle 0 ;#Resource Manager Handle

puts "Opening Resource Manager"

#Function Prototype: ViStatus viOpenDefaultRM(ViPSession sesn)
::visaDLL::cmd "int viOpenDefaultRM(int *)"
#First get the resource manager handle. This will be used next to get a handle to the instrument
set returnValue [::visaDLL::viOpenDefaultRM resourceManagerHandle]

if {$returnValue != 0} {
puts stderr "Could not open Resource Manager. Error: $returnValue \n"
return returnValue
}

puts "ResourceManagerHandle: $resourceManagerHandle"
puts "ReturnValue: $returnValue \n"

##############################
#Open Resource Session
##############################
set sessionHandle 0 ;#Instrument Handle
set VI_NULL 0 ;#VI_NULL constant from visa.h
set resourceName "GPIB0::2::INSTR" ;#Resource: GPIB Device 0, Primary Adress 2

puts "Opening Resource Session"

#Function Prototype: ViStatus viOpen(ViSession sesn, ViRsrc rsrcName, ViAccessMode accessMode, ViUInt32 openTimeout, ViPSession vi)
::visaDLL::cmd "int viOpen(int, char *, int, int, int *)"
#Open a VISA session to a device on GPIB Device 0 at Primary Address 2 and retreive a handle to this session
#This session handle will be used for subsequent operations
set returnValue [::visaDLL::viOpen $resourceManagerHandle $resourceName $VI_NULL $VI_NULL sessionHandle]

if {$returnValue != 0} {
puts stderr "Could not open Resource Session. Error: $returnValue \n"
return returnValue
}

puts "SessionHandle: $sessionHandle"
puts "ReturnValue: $returnValue \n"

##############################
#Set Timeout Attribute
##############################
set VI_ATTR_TMO_VALUE 1073676314 ;#Timeout Attribute Constant from visa.h
set timeout 5000 ;#Timeout: 5000ms

puts "Setting Timeout Attribute"

#Function Prototype: ViStatus viSetAttribute(ViObject vi, ViAttr attribute, ViAttrState attrState)
::visaDLL::cmd "int viSetAttribute(int, int, int)"
#Set timeout value to 5000 milliseconds (5 seconds)
set returnValue [::visaDLL::viSetAttribute $sessionHandle $VI_ATTR_TMO_VALUE $timeout]

if {$returnValue != 0} {
puts stderr "Could not set Timeout Attribute. Error: $returnValue \n"
return returnValue
}

puts "ReturnValue: $returnValue \n"

##############################
#Write "*IDN?" to Device
##############################
set bufferToWrite "*IDN?" ;#*IDN? typically tells devices to return identification
set bytesToWrite [expr {1 + [string length $bufferToWrite]}]
set bytesWritten 0

puts "Writing *IDN? to Device $bytesToWrite"

#Function Prototype: ViStatus viWrite(ViSession vi, ViBuf buf, ViUInt32 count, ViPUInt32 retCount)
::visaDLL::cmd "int viWrite(int, char *, int, int *)"
#Use this session handle to write *IDN? command to the instrument asking for the its identification.
set returnValue [::visaDLL::viWrite $sessionHandle $bufferToWrite $bytesToWrite bytesWritten]

if {$returnValue != 0} {
puts stderr "Could not write *IDN? to Device. Error: $returnValue \n"
return returnValue
}

puts "Bytes Written: $bytesWritten"
puts "ReturnValue: $returnValue \n"

##############################
#Read Response from Device
##############################
set response [::dll::buffer 128]
set bytesToRead 128
set bytesRead 0

puts "Reading Response from Device";

#Function Prototype: ViStatus viRead(ViSession vi, ViPBuf buf, ViUInt32 count, ViPUInt32 retCount)
::visaDLL::cmd "int viRead(int, char *, int, int *)"
#Attempt to read back a response from the device to the identification query that was sent.
set returnValue [::visaDLL::viRead $sessionHandle $response $bytesToRead bytesRead]

if {$returnValue != 0} {
puts stderr "Could not Read Response from Device. Error: $returnValue \n"
return returnValue
}

puts "Bytes Read $bytesWritten"
puts "Response: $response"
puts "ReturnValue: $returnValue \n"

##############################
#Close Sessions
##############################
puts "Closing Resource Session"

#Finally, close the instrument session and the resource manager to free resources

#Function Prototype: ViStatus viClose(ViObject vi)
::visaDLL::cmd "int viClose(int)"
set returnValue [::visaDLL::viClose $sessionHandle]
if {$returnValue != 0} {
puts stderr "Could not close Resource Session. Error: $returnValue \n"
return returnValue
}

puts "ReturnValue: $returnValue \n"

puts "Closing Resource Manager"

set returnValue [::visaDLL::viClose $resourceManagerHandle]
if {$returnValue != 0} {
puts stderr "Could not close Resource Manager. Error: $returnValue \n"
return returnValue
}

puts "ReturnValue: $returnValue \n";

 

Back to Top

4. Next Steps

Now that you have learned the basics of data acquisition and instrument control in these three scripting languages, you are ready to create your own test automation software.  To see more resources on National Instruments products and scripting languages click on the links below.

Developer Zone Tutorial: Calling Scripting Languages from TestStand
Developer Zone Tutorial: Call Perl and Python Scripts from LabVIEW

Back to Top

Bookmark & Share



Ratings

Rate this document

Answered Your Question?
Yes No

Submit