Using VISA Formatted I/O in Instrument Driver Development
Overview
The purpose of this Developer Zone document is to show instrument driver developers how to perform various I/O tasks using the formatted I/O services in VISA. This document assumes a basic knowledge of string formatting and ANSI-C format specifiers. For more information on VISA format specifiers, refer to the NI-VISA Programmer Reference Manual.
VISA provides both formatted and buffered I/O capabilities for data transfers between devices that use message-based communication. The formatting capabilities include those specified by ANSI-C with extensions for common protocols used by instrumentation system. To perform I/O, use the viPrintf(), viScanf(), and viQueryf() service routines with the appropriate format strings.
This document illustrates four different categories of formatted I/O: Integers, Floating point numbers, Strings, and Data blocks. For each category, a description and a list of examples is shown. In each example, the focus is on the VISA I/O supported format specifiers that are most frequently used in driver development and on the different modifiers that work with each type of format code. To eliminate redundancy, error-checking routines on I/O operations have been omitted from all of the examples.
Table of Contents
Integer
Integer formatting is often found in driver development. Besides transferring the numeric values that the instrument reads, it may also represents the status codes (Boolean values) or error codes returned by the instrument. When writing integer values to or reading them from the instrument, you can use %d format code with length modifiers (h and l) and the array modifier (,). Short Integer— %hd
Use this modifier for short (16 bit) integers. These are typically used for holding test results and status codes.
Examples
This example shows how to scan a self test result (a 16 bit integer) returned from an instrument into a short integer.
/* Self Test */
…
ViInt16 testResult;
viPrintf (io, "*TST?\n");
viScanf (io, "%hd", testResult); /* read back the test result in short integer */
…
Long Integer— %ld, %d
Use this modifier for long (32 bit) integers. These are typically used for data value transfers and error codes.
Examples
This example shows how to scan an error code (a 32 bit integer) returned from an instrument into a 32 bit integer.
/* Error query */
…
ViInt32 errCode;
viPrintf(io, ":STAT:ERR?\n");
viScanf (io, "%d", &errCode); /* read back error code in integer */
…
This example shows how to format the sample count (a 32 bit integer) into the command string sent to an instrument.
/* Send Sample Count */
…
ViInt32 value = 5000;
viPrintf (io, ":SAMP:COUN %d;", value);
…
Floating Point
When writing floating point values to or reading them from the instrument, you can use %f or %e format codes with length modifiers (l and L) and the array modifier (,). Floating point values are important when programming a numeric value transfer.
Note: %f does not fully represent floating point value in the extreme cases. Use %e to represent the floating point in the extreme cases.
Double Float— %le
Use this modifier for double (64 bit) floats. These are typically used for data value transfers.
This example shows how to scan the vertical range (a 64 bit floating point number).
Examples
/* Query Vertical Range */
…
ViReal64 value;
viPrintf (io, ":CH1:SCA?\n");
viScanf (io, "%le", &value);
…
This example shows how to format the trigger delay (a 64 bit floating point number) into the command string sent to an instrument.
/* Send Trigger Delay */
…
ViReal64 value = 50.0;
viPrintf (io, ":TRIG:DEL %Le;", value);
…
Precision Specifier (.)
You can use the precision specifier to specify the number of precision digits when doing numeric transfer. This modifier sets the accuracy of the values.
Example
This example shows how to set the voltage resolution. The resolution is represented in a long double floating point (64 bits). The precision modifier .9 specifies that there are nine digits after the decimal point. In this case, 0.000000005 is sent to the instrument.
/* Set Resolution */
…
ViReal64 value = 0.0000000051;
viPrintf (io, "VOLT:RES %.9Le", value);
…
Array of Floating Point Values Specifier (,)
This modifier transfers an array of floating point values to or from an instrument. The count of the number of elements can be represented by a constant, asterisk (*) sign, or number (#) sign. The asterisk (*) sign indicates the count is the first argument on viPrintf(). The number (#) sign indicates that the count is the first argument on viScanf(). You can use the constant on both viPrintf() and viScanf().
Examples
/* Create User Defined Mask */
…
ViInt32 maskSize = 100;
ViReal64 interleaved[100];
…
/* defines points in the specified mask in the selected user coordinates and store the
points in the array */
…
viPrintf (io, ":MASK:MASK1:POINTS %*,Le", maskSize, interleaved);
…
The Create User Defined Mask example shows how to send an array of long double numbers to the instrument. The comma (,) indicates the parameter is an array and the asterisk (*) specifies the array size to be passed in from the argument.
/* Read Multi-Point */
…
ViInt32 readingCnt = 50;
ViReal64 readingArray[50];
viPrintf (io, "READ?\n");
viScanf (io, "%,#Le", &readingCnt, readingArray);
…
The Read Multi-Point example shows how to take multiple readings from an instrument. The comma (,) indicates the parameter is an array and the number (#) sign specifies the actual number of readings returns from the instrument.
/* Fetch Multi-Point */
…
ViReal64 readingArray[1000];
viScanf(io, "%,1000le", readingArray);
…
The Fetch Multi-Point example shows how to fetch multiple reading from an instrument. The comma (,) indicates the parameter is an array while the constant 1000 specifies the number of readings.
String
White Space Termination — %s
Characters are read from an instrument into the string until a white space character is read.
Example
/* Trigger Source Query */
…
ViChar rdBuffer[BUFFER_SIZE];
ViInt32 rdBufferSize = sizeof(rdBuffer);
viPrintf (io, ":TRIG:SOUR?\n");
viScanf (io, "%#s", &rdBufferSize, rdBuffer);
…
The Trigger Source Query example queries trigger source. This instrument returns a string. The maximum length of the string is specified in the format string with the number (#) sign. The argument rdBufferSize contains the maximum length on input, and it contains the actual number of bytes read on output.
END Termination—%t
Characters are read from an instrument into the string until the first END indicator is received. This will often be accompanied by the linefeed character (\n) but that is not always the case. Use %T to parse up to a linefeed instead of an END.
Example
/* Instrument Model Information */
…
ViChar moduleStr[BUFFER_SIZE];
ViInt32 modelNumber;
viPrintf (io, "*IDN?\n");
viScanf (io, "TEKTRONIX,TDS %ld,%t", &modelNumber, moduleStr);
…
The Instrument Model Information example queries the instrument model on a Tektronix instrument. The model number, a long integer (32 bits) is the part of the string between the first two characters “,” returned from the instrument. The format string %t specifies that the string reads from the device until END indicator is received. For instance, the instrument returns TEKTRONIX,TDS 210,0,CF:91.1CT FV:v1.16 TDS2CM:CMV:v1.04\n, the model number is 210, the module string is 0,CF:91.1CT FV:v1.16 TDS2CM:CMV:v1.04\n.
Other Terminators— %[^], %*[^]
Without the asterisk, characters are read from an instrument into the string until the character specified after ^ is read. With the asterisk, characters are discarded until the character specified after ^ is read.
Examples
/* Self Test */
…
ViChar testMessage[256];
viPrintf (io, "TST\n");
viScanf (io, "%256[^\n]", testMessage);
…
The Self Test example is an example of how to perform a self-test. In this case, the format string %256[^\n] specifies the maximum field width of the string as 256 and terminates with a line feed (LF) character.
/* Error Query */
…
ViInt32 errCode;
ViChar errMessage[MAX_SIZE];
viPrintf(io, ":STAT:ERR?\n");
viScanf (io, "%ld,\"%[^\"]\"", &errCode, errMessage);
…
The Error Query example shows how to query an error. The instrument returns an integer (32 bits) as error code and a message that terminates with a double-quote (“). The message is in quotes.
/* Instrument Manufacturer */
…
ViChar rdBuffer[256];
viQueryf (io, "*IDN?", "%256[^,]%*T", rdBuffer);
…
The Instrument Manufacturer example shows how to query for the instrument manufacturer. The manufacturer name is the first part of the string, up to character “,”, returned from the instrument. For instance, the instrument returns ROHDE&SCHWARZ,NRVD, 835430/066,V1.52 V1.40\n. The manufacturer name is ROHDE&SCHWARZ. The rest of the response is discarded.
/* Instrument Model Information */
…
ViChar rdBuffer[256];
viQueryf (io, "*IDN?", "%*[^,],%256[^,]%*T", rdBuffer);
…
The Instrument Module Information example shows how to query for the instrument model. The model name is the part of the string between the first two characters “,” returned from the instrument. For instance, the instrument returns ROHDE&SCHWARZ,NRVD, 835430/066,V1.52 V1.40\n. The model name is NRVD. The format string %*[^,] discards the input up to character “,”. The final part of the response is also discarded.
/* Instrument Firmware Revision */
…
ViChar rdBuffer[256];
viQueryf (io, "ROM?", "%256[^\r]", rdBuffer);
…
The Instrument Firmware Revision example queries the instrument firmware revision. The firmware revision information is everything up to the carriage return (CR) character.
Data Blocks
IEEE-488.2 Binary Data—%b
When writing binary data to or reading it from the instrument, you can use %b, %B format codes with length modifiers (h, l, z, and Z). ASCII data is represented by signed integer values. The range of values depends on the byte width specified. One-byte-wide data ranges from -128 to +127. Two-byte-wide data ranges from -32768 to +32767. An example of an ASCII waveform data string follows:
CURVE<space>-110,-109,-110, -110, -109, -107, -109, -107, -106, -105, -103, -100, -97, -90, -84, -80
Example
/* Waveform Query */
…
ViInt32 totalPoints = MAX_DATA_PTS;
ViInt8 rdBuffer[MAX_DATA_PTS];
viQueryf( io, ":CURV?", "%#b", &totalPoints, rdBuffer);
…
The Waveform Query example queries a waveform. The data is in IEEE 488.2 <ARBITRARY BLOCK PROGRAM DATA> format. The number (#) sign specifies the data size. In absence of length modifiers, the data is assumed to be of byte-size elements.
Raw Binary Data— %y
When transferring raw binary data to or from an instrument, you can use %y format codes with length modifiers (h and l) and byte ordering modifiers ( !ob and !ol). Raw binary data can be represented by signed integer values or positive integer values. The range of the values depends on the specified byte width as shown in Table 1:
Table 1. Ranges of Values for Raw Binary Data
Byte Width | Signed Integer Range | Positive Integer Range |
1 | -128 to +127 | 0 to 255 |
2 | -32768 to +32767 | 0 to 65535 |
- Examples
/* Create Arbitrary Waveform */
…
ViInt32 wfmSize = WFM_SIZE;
ViUInt16 dataBuffer[WFM_SIZE]; /*contains waveform data */
dataBuffer[WFM_SIZE-1]|=0x1000; /* Add the end of waveform bit to the last point */
viPrintf (io, "STARTBIN 0 %d;%*!obhy", wfmSize, wfmSize, dataBuffer);
…
The Create Arbitrary Waveform example shows how to send a block of unsigned short integer (16 bits) in binary form to the instrument. The asterisk (*) specifies the block size to be passed in from the argument. Also, !ob specifies data is sent in standard (big endian) format. Use !ol to send data in little endian format.
/* Create FM Modulation Waveform */
…
ViInt16 dataBuffer[WFM_SIZE]; /*contains waveform data */
viPrintf (io, "%*ly", wfmSize, dataBuffer);
…
The Create FM Modulation Waveform example shows how to send a block of signed short integers (16 bits) in binary form to the instrument. The asterisk (*) specifies the block size to be passed in from the argument. Without the presence of a byte order modifier, data is sent in standard (big endian) format.
/* Waveform Preamble */
ViByte data[MAX_WAVEFORM_SIZE];
ViInt32 i, tmpCount, acqType;
ViReal64 xInc, xOrg, xRef,
yInc, yOrg, yRef,
*p2data = waveform;
checkErr( Ivi_SetAttributeViString(vi, VI_NULL, HPE1426A_ATTR_INST_WAVE_SOURCE, 0, channel));
viCheckErr( viQueryf(io, "WAV:PRE?", "%*[^,], %ld, %ld, %*[^,], %Lf, %Lf, %Lf, %Lf, %Lf, %Lf", &acqType, &tmpCount, &xInc, &xOrg, &xRef, &yInc, &yOrg, &yRef));
tmpCount = (acqType == 3) ? 2*tmpCount : tmpCount;
viCheckErr( viQueryf(io, "WAV:DAT?", "%#b", &tmpCount, data));
The Waveform Preamble example shows how to scan the preamble of waveform data returned from a scope, how to determine the number of data points in the waveform, and how to scan the array of raw binary data returned.
Minimizing Code Growth Within an IVI Instrument Driver
Reader Comments | Submit a comment »
Legal
This tutorial (this "tutorial") was developed by National Instruments ("NI"). Although technical support of this tutorial may be made available by National Instruments, the content in this tutorial may not be completely tested and verified, and NI does not guarantee its quality in any way or that NI will continue to support this content with each new revision of related products and drivers. THIS TUTORIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND AND SUBJECT TO CERTAIN RESTRICTIONS AS MORE SPECIFICALLY SET FORTH IN NI.COM'S TERMS OF USE (http://ni.com/legal/termsofuse/unitedstates/us/).
