Features


An lostream Class for the GPIB

Bradford T. Taylor


Bradford Taylor is a senior software engineer specializing in digital and analog cellular phone systems test software and equipment at IFR Systems, Inc. in Wichita, Kansas. He received a BS in Electrical Engineering from Kansas State University in 1972 and a MS in Electrical Engineering from Wichita State University in 1978. He has been involved in writing software for test equipment since 1973. Besides designing software, he has taught Calculus, Statistics, and programming at a local community college. Brad can be reached by voice at (316) 522-4981 ext. 231 or by e-mail at bradford.taylor@ifrsys.com.

Introduction

Programming on the General Purpose Interface Bus (GPIB) is a challenge. Anyone who has spent time with automated test equipment has likely had to develop a remote test suite for an instrument using a PC and the GPIB Bus. Traditionally, test instrument programming on the PC has involved the use of a commercial GPIB card, a GPIB interface library, and a lot of misery.

To simplify the design of GPIB application programs, I have evolved a set of GPIB stream classes that treats GPIB devices as objects in the spirit of C++ iostreams, as in

gout << ":MEAS:FREQ?" << endl;
gin >> result;
In this article I give a description of these classes and show how to use them.

GPIB Background

Another name for GPIB is the IEEE 488.2 bus. The IEEE 488 standard was developed as a standard digital interface for programmable instruments. In 1987, the IEEE 488 standards body systematically reviewed existing industry practices related to IEEE 488 device-dependent message syntax structures. This review spawned the creation of two standards, ANSI/IEEE Std 488.1-1987 and ANSI/IEEE Std 488.2-1987. These standards specified a set of codes and formats to be used by devices connected via the IEEE 488 bus.

In a nutshell, the GPIB bus is a bidirectional eight-bit data bus with three interface signal lines and five general interface management lines. The three interface signal lines are used to handshake the data lines from a talker to a listener or controller to one or more listeners. The five interface signal lines are used to manage an orderly flow of information across the interface.

Recently, programmable instruments on the GPIB have become more software intensive. Due in part to the Standard Commands for Programmable Instruments (SCPI) Consortium, instrument vocabularies and capabilities have grown exponentially. Rapid processing requirements call for GPIB interface routines written in responsive languages. Product release activities such as validation and regression testing have become both complex and tedious.

Easing GPIB Programming

Given the increasing complexity of GPIB programming and testing, I decided that I needed a set of friendly operations that could be easily incorporated into test applications. Vendor libraries do provide support functions, but these libraries usually contain so many routines that I find their general usage intimidating. Also, GPIB devices normally converse in ASCII text strings requiring explicit conversions to and from integer or floating-point elements. My civilized solution was to create reusable GPIB streams based around the library functions. The simplicity of

cout << "Hello, world" << endl;
for the GPIB world was too much of a temptation to ignore. Letting C++ hide all the dirty work, such as conversions, all a programmer need do is include a special header file in his application and link with the appropriate libraries.

Implementation Overview

I've implemented the GPIB stream classes using Borland C++ under MS-DOS. Listing 1, gpibio.h, defines C++ classes for the following:

(A header file, decl.h, is supplied with our National Instrument NI-488.2(TM) software driver to define prototypes, variables and constants used in their library. This file is on the code disk.)

The stream class, gstream (Listing 1) , is a scaled-down subset of the "standard" Iostream class. This class provides the usual stream buffer position pointers and support functions. I've made the underflow and overflow functions virtual so they may be redefined by any specific hardware interface routines.

I've divided the input and output classes into gpibin and gpibout. Each of these classes inherit from the gpibio and gstream classes. Originally I hadn't intended to use multiple inheritance, but given the provided library functions, the class design seemed to lend itself to this approach. The gpibio class provides the interface between the I/O classes and the GPIB interface board driver class.

Listing 1 also shows prototypes for manipulators. I've included manipulators mostly to support integer conversions, since programmable instruments can return numeric strings in various number bases, including base two. I've provided endl and flush manipulators to complete output operations and disregard unwanted input.

The input stream class also includes a support routine for waiting on device service requests and performing serial polls. Serial polling is the method by which GPIB handles device interrupts.

Generic Stream Class

The gstream class (Listing 1) consists of a group of protected character pointers, a constructor, destructor, and support functions. I've made the character pointers protected here because I want to restrict access to the first inheriting class. These character pointers manage access to the input and output streams. The constructor initializes these pointers to a null, to indicate to the support routines that no stream buffer has been allocated. The destructor, in turn, checks if a stream buffer has been allocated and releases this storage.

The overflow function's purpose is to flush characters from an existing buffer to a physical device, or if a buffer does not exist, to create one in-memory for temporary writing of characters. The underflow function's purpose is to completely fill an in-memory buffer with characters from a physical device; if no buffer exists, this function must first create one. These functions are declared pure virtual in the base class since their implementation depends upon board- and driver-specific features. I show implementations of these functions for the NI-488.2 software driver later in this article.

The stream output functions sput and sputc work as string-oriented routines. sput accepts a string argument and checks if there's room in the output buffer to append the string. If there's not enough room, sput calls overflow to empty the buffer (or allocate one if it does not exist). If the string is still too large for the buffer, sput attempts to resize the buffer. If the resize succeeds, sput copies the string into the buffer; if the resize fails, sput prints an error message to cerr. sputc outputs one character by creating a temporary string of one character and calling sput.

The string input functions gets, sbumpc, and sgetc are built around the underflow function. Each of these functions checks if the input buffer is empty and, if so, calls underflow to fill it from the physical device. The input function then transfers a string or character (as appropriate) to the location specified by its argument. gets transfers the entire input stream to the destination string and sets the stream pointer to indicate an empty state. sbumpc returns one character and advances the stream pointer by one. sgetc returns a character, but does not advance the stream pointer.

GPIB I/O and Board Classes

Designing the parent gpibio class functions (Listing 2) turned out to be a little harder than the rest of the GPIB classes. Since I use the National Instruments (NI) AT-GPIB/TNT hardware and software, I have the capability of addressing multiple GPIB boards, each of which can address multiple GPIB devices. The NI-supplied input and output routines require programs to use device handles — similar to file handles — when accessing devices. To use an interface board, the program must also provide a board handle. I wanted the class library user to be able to open a device by merely creating an instance, as in:

gpibin gin(5,0) // dev 5/board 0
gpibout gout(5,0)
This bit of code would acquire board and device handles, which would then be kept as members of the given classes.

My initial attempt at creating these objects caused an inefficiency due to the trickle effect of parent class constructor execution. Each time an instance was declared, the program would execute the gpibio constructor to open a GPIB board and then execute the child constructor to open the device. This amounted to wasted effort, since each GPIB board only needed to be opened once. To eliminate redundant opens, I allow each open interface board to have its own driver class instance. Listing 1 shows two static members in the gpibio class, rep_cnt[2] and dvr[2], which track board usage by maintaining a repetition count and a driver object for each board.

The constructor for gpibio initializes the input/output format radix to 10 and records the provided board index. The constructor then checks the repetition counter to see if a driver object has been allocated for this board. If not, the constructor creates a board driver instance and records it. The destructor, in turn, decrements the repetition counter and deletes the driver instance if the count has gone to zero.

The gpibdvr class constructor uses NI library routines to acquire a board handle and configure the board to my needs. The constructor sets some default behavior for the board, enabling the serial auto poll function (more on this later), disabling the EOI assertion at the end of each write, and setting EOI assertion on the newline character, 0x0A. EOI is a dedicated interface line which the GPIB can assert (set to a true state) to indicate the end of a multibyte transmission. Configuring the EOI to be true only with a newline character assists in the stream design. For example, this configuration allows me to set the time of day on a device by writing

gout << ":SYST:TIME " << h << ',' << m << ',' << s << endl;
without the EOI going true until the endl indication goes to the device.

The gpibdvr class also contains an open_device function which is called by child classes. This routine uses an NI library function to acquire the GPIB device handle. Note that this routine is intolerant of failing to get the device handle. My philosophy is if you can't access the device, then you can't continue.

I've overloaded the unary NOT (!) operator as a quick way of testing for I/O failures. Functions that call device read or write operation also record the success status, which can then be tested in the operator!( ) function to detect either an error or time-out occurrence. So you can write

if(!(cout << "..."))
   cerr << "Output Error." <<endl;

GPIB Output Class

The GPIB output class, gpibout, is defined in Listing 1. The member functions are implemented in file gpibout.cpp, available on this month's code disk and online sources, but I show some of the functions here as well. gpibout contains a constructor, overloaded << operators, and the virtual overflow and underflow functions. The constructor is simple:

gpibout::gpibout(int d, int b): gpibio(b)
{
   device = dvr[b]->open_device(d);
}
All it does is acquire the device handle and record its value. The overflow function uses this handle to perform the physical write operation.

Like any good output stream, I wanted my GPIB streams to provide the usual conversion operators. The string and character operators use sput and sputc from the parent gstream class. sput is shown as follows:

// Implementation of sput function void
// gstream::sput(char *s) {
    int len = strlen(s);
// If it's too long, flush and reallocate
// more room
if(pptr_+len+1 >= epptr_)
{
     overflow();
     // See if it will fit at all
     if(len > (int)(epptr_ - pbase_))
     {
         char *newarea = new char [len+1];
         if(newarea == NULL)
         {
             cerr <<
                 "Not enough memory" <<
                    "for output"
             << s << endl;
             return;
         }
         else
             delete pbase_;
         pbase_ = newarea;
         pptr_ = pbase_;
         epptr_ = base_ + len + 1;
    }
}
// Copy string into output stream
strcpy(pptr_,s);
pptr_ += len; }
The remaining conversion routines use the compiler-provided conversion functions ltoa and gcvt, for long-string and double-string conversions respectively. For example, the double output operator is:

gpibout & gpibout::operator<<(double _d)
{
     char string[32];
     return *this<<gcvt(_d, 5, string);
}
The unusual stream operator function in the bunch is shown as follows:

gpibout & operator<< (gpibout & (*_f)(gpibout &));
This operator references a pointer to function, which provides the hook to implement manipulators such as endl. All this operator function has to do is call the pointed to function. The manipulator function and C++ do the rest.

As discussed previously, overflow declares a virtual function whose instantiations must ensure the presence of an output stream buffer and flush its contents to the physical device. As shown in Listing 3, my version of overflow does this by checking if the output base pointer, pbase, is a null. If the pointer is null, then overflow allocates a new instance of 128 bytes for the output stream. If the stream had been previously allocated (pointer not null), then overflow assumes it is full and needs to be output to the device using a library write function. overflow then indicates that the stream is logically empty by setting the "put" pointer to the base of the put stream buffer.

GPIB Input Class

Like the output class, class gpibin is defined in Listing 1 and contains a constructor, overloaded >> operators, and the virtual overflow and underflow functions.

As with the output class, all the gpibin constructor does is acquire and record the device handle. The underflow function needs this handle to fill the input stream by reading from the physical device.

The input class conversion operators are pretty much like output class operators, except that they work in the opposite direction. The string and character operators use the gets and sbumpc functions found in the gstream class. gets is implemented as follows:

// Implementation of gets
void gstream::gets(char *s)
{
    if(gptr_ >= egptr_)
      underflow();
    // copy the remaining
    // stream into the string
    strcpy(s,gptr_);
    // update get pointer
    gptr_ = egptr_;
}
The input operator for double is shown below:

// gin >> double
gpibin & gpibin::operator >> (double & d)
{
    char c;
    // make sure we are
    // pointing at a valid character
    do
      c = sbumpc();
    while(!isdigit(c) &&
         c != '-'    &&
         c != '+'    &&
         c != '.'    &&
         c != 'e'    &&
         c != 'E');
    // Convert input string to a
    // double via strtod which
    // advances the char pointer
    d = strtod(gptr_-1,&gptr_);
    return *this;
}
Contrary to the standard Iostreams approach, I took a different tack on integer and floating-point conversion. In the classical stream class, the conversion routines usually skip whitespace characters (space, tab, etc.) before employing the string-to-number conversion. However, with GPIB input, a device may return anything — spaces, commas, semicolons, or whatever — to delimit fields on input. For this reason, my conversion operators skip whatever doesn't constitute a number (not just whitespace). Once the operators find an acceptable numeric string beginning they call the compiler-provided strtol and strtod functions to perform the conversions.

The input class also provides a pointer to function stream operator for access to the manipulator functions:

gpibin & gpibin::operator>>(gpibin &(*_f)(gpibin &))
{
return _f(*this):
}
underflow declares a virtual function whose instantiation must guarantee the presence of an input stream buffer and then fill the buffer from the physical device. If underflow determines that no buffer yet exists, it must allocate one and setup the gstream "get" pointers accordingly. At first I was uncertain how long to make the input stream. GPIB responses are typically short, but some instruments may return complete oscilloscope or analyzer trace information which could be quite lengthy. I found that by using an NI library read function to access the GPIB device, I could determine if the data transfer had completed by checking the EOI status. I could then incrementally increase the input stream buffer size until the transfer was complete. This approach enables the input stream buffer to customize its length to suit the instrument. My implementation of underflow for the NI board is shown in Listing 4.

After completing the input transfer, underflow strips off any trailing newline characters that might confuse the operator functions. It then sets the get pointers to indicate a "not-empty" condition and marks the current end of the input stream. underflow returns the first character in the stream, as required by the gstream class functions.

The srqwait function provided with the input class allows the controller function to handle device requests for service. A GPIB device requests service by issuing a Service Request (SRQ) on the GPIB. The SRQ sets a status bit on the interface card, which the controlling program can poll by calling the appropriate library function. srqwait acts as a wrapper around an NI library function (ibwait) which waits for an SRQ for a predetermined time, then times out if one does not occur. If an SRQ occurs, srqwait accesses the serial poll status by calling the NI ibrsp function. This function is shown below:

// do the wait for SRQ
int gpibin::srqwait()
{
   char status;
   int result;
   ::ibwait(device,TIMO|RQS);
   if(ibsta &(TIMO|ERR))
      return (-1); // Timed out or
                 // device error
   // Get Serial poll information
   ::ibrsp(device,&status);
   result = status;
   // Return the last status
   return (result&255);
}

Manipulators

The most common reason for including manipulators in stream classes is to make up for shortcomings in the stream input and output notations. My main motivation was that the conversion routines require a radix. I could have defaulted this radix to base ten, but with manipulators, I can change the radix on the fly. For example, if an instrument returns hexadecimal values, I can write

cin >> hex >> answer >> dec;
to have the conversion performed in the correct number base. The radix conversion manipulators are simple:

// output base 10
gpibout & dec(gpibout&x)
{
    x,radix = 10;
    return(x);
}
endl in this implementation inserts a newline character ('\n') and flushes the output, which in this case also triggers a GPIB EOI. The full list of manipulators I've included is as follows:

I've included a flush manipulator for both input and output stream operations. In my experience, some instruments will attach units on the end of a response, such as MHZ or KHZ. An access such as

gout << "query1?" << endl.
gin >> a_float; // returns "1.03 MHZ"
gout << "query2?" << endl;
gin >> a_string;
will leave the stream pointers hanging on the MHZ portion of the input stream and then the next string access will unknowingly pick up the units instead of getting the response to query2. Although flush is not an elegant solution, it cleans out the unwanted characters:

gout << "query1?" << endl;
gin >> a_float >> flush;
gout << "query2?" << endl;
gin >> a_string;

An Example

Listing 5, gpib. cpp, illustrates the appeal of using streams in a GPIB application. This example assumes that some frequency measuring device is attached to the bus, such as a spectrum analyzer or frequency counter, and has the GPIB address of 5.

The program creates an output instance, gout, and an input instance, gin, to access the instrument at address 5. The program's first action is to ask for the identity string from the GPIB instrument, using the IEEE 488.2 command *idn?. The answer is displayed in cout. The ! operator in the if statement verifies that the GPIB device is indeed on line and is able to listen and talk. Once this has been successfully determined, the program transfers the PC's time of day to the instrument using the gettime function and the automatic conversion capability of the output stream. The instrument will then provide time-stamps with its readings, which can be used to determine its efficiency. Finally, the program uses a for loop to request and display ten readings from the instrument, including the time it was measured.

Summary

The notational convenience of GPIB oriented Iostreams for test applications simplifies the task of creating instrument test routines. I have presented a simple implementation of a stream class that uses stream management, operator overloading, and manipulators to provide an easy-to-use package. Test engineers can readily integrate these streams into their C++ applications for using or testing GPIB instruments.

References

[1] ANSI/IEEE Standard 488.1-1987, IEEE Standard Digital Interface for Programmable Instrumentation. ANSI/IEEE documents may be ordered from IEEE through any IEEE member or by calling 1-800-678-IEEE. If you call, you must have an IEEE membership number available.

[2] ANSI/IEEE Standard 488.2-1987, IEEE Standard Codes, Formats, Protocols, and Common Commands.

[3] SCPI Consortium. SCPI Syntax & Style, Volume 1, 1995. SCPI Consortium, 8380 Hercules Drive, Suite P3, La Mesa, CA, 91942.

[4] National Instruments. NI-488.2(TM) Function Reference Manual for Dos/Windows. National Instruments Corp., 6504 Bridge Point Parkway, Austin, TX 78730-5039.

[5] P.J. Plauger. "Introduction to Iostreams," The C Users Journal April: 1994.

[6] P.J. Plauger. "The Header <ios>," The C Users Journal, May: 1994.

[7] P.J. Plauger. "The Header <streambuf>," The C Users Journal June: 1994.