Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Instrument Snapshot: How to Acquire and Render Screen-shots from Older Test Equipment

4.89/5 (13 votes)
16 Dec 2016CPOL10 min read 66.2K   1.9K  
An HPGL renderer and demo application for use in data acquisition

Introduction

I have more than once had the occasion to work with older model test equipment. Sometimes, engineers and technicians prefer the response of an older analog model over that of a newer piece of gear. Perhaps an organization has an investment in a wide range of legacy equipment and a limited budget for new. Much of that older equipment was designed to export graphical screen data to a pen plotter. Data plots would be printed and added to engineering notebooks along with design notes and computations.

Background

Getting screen capture data in a format that is readily incorporated into electronic documentation is fairly easy with new test equipment but can be a challenge with the older models. That was the situation I found myself in years ago when I was employed in the task of documenting the performance of various filters. I wanted to be able to to easily capture and save screen-shots from a Network Analyzer, specifically an HP8751A, but I couldn't find a software package to accomplish the task to my satisfaction. Thus, I set out to write something that would extract a screen-shot from this device with only a single button click.

Figure1

Figure 1: HP8751A Network Analyzer

Acquiring a screen-shot from an older piece of test equipment is usually a two step process: first request and receive the data, and second render the data into an image. Both of these objectives are more easily stated than accomplished but with some time and patience I figured out how to get the HP8751A to serve up the goods. Here's how I did it.

The Tools of the Trade

In order to work with instrumentation in an engineering capacity, it's good to have VISA. No, not the aesthetically pleasing piece of plastic[^], or the document stamp. VISA or Virtual Instrument Software Architecture[^] is a convenient standardized abstraction layer between your application and the many hardware interfaces and instrumentation buses in use today. Various test development products and environments such a NI LabVIEW (TM) are built on top of VISA.

VISA can usually be obtained for free from the vendor that you purchased equipment or an interface card from. I have used both Agilent (now Keysight) and National Instruments VISA drivers over the years. The installations include various development tools, but the following are the ones I find most useful.

  • Agilent IO Monitor and/or NI SPY (now called NIO Trace [^]) - check out The Spy Among Us -- Debugging I/O[^] for a nice write-up on how you can use these tools.
  • NI Visa Interactive Control and/or Agilent Interactive IO - These tools provide a way to send commands to instruments "on the fly" and are helpful for testing command sequences or seeing how an instrument will respond to a string of commands.

Playing Around

There are two ways to get a screen shot out of the HP8751A, one of them is to plot to file; If you do this; the Hewlett Packard Graphics Language (HPGL) defining the screen is written to a diskette that has been inserted into the drive, it may then be transfered it to a workstation via sneakernet [^]. The second way is to plot to a plotter connected to the General Purpose Interface Bus (GPIB) [^]. Once the Plotter draws the screen on to a sheet of paper, the image may be transferred to the workstation using the wooden table method [^]. The plot to file / sneakernet method was OK in a pinch but I wanted something more direct. Ditto for the second method (not to mention that I didn't even have a plotter in the lab.)

Perhaps there was a third way. "What if I could plot the screen to a workstation instead of a plotter?" I thought. I tested the idea by configuring the instrument to plot to the computer's GPIB address, fired up IO Monitor and pressed the PLOT button. All I got on the monitor was "OS" or something to that effect. I tried some different things without luck and then, with a little searching, found that "OS" was a request for output status. My instrument was attempting to handshake with a plotter and it wasn't going to divulge any HPGL unless it knew that there was a plotter connected and ready to receive the data! After looking around some more, I found that someone had already written a plotter emulator [^] and made it freely available.

I tried the plotter emulator and succeeded in retrieving an instrument initiated plot from my device. I then wanted to be able to initiate the plot from my workstation which I couldn't do with this application so I studied the code and took note of how the author treated the hand shake sequence in order to fool the HP8751A into thinking there was a genuine plotter on the other end. I experimented by firing up IO Monitor and sending the appropriate responses to status requests from the instrument and viola, I started to get chunks of HPGL data!

The result of all of this playing around was that I determined a sequence of events that needed to take place in order to make a host-initiated plot request from this instrument.

  • Open a Virtual Instrument Interface session for my virtual plotter (the workstation).
  • Configure the HP8751A to be the bus controller.
  • Set my virtual plotter's address to the one where the instrument expected to see a plotter.
  • Send the "PLOT" command.
  • Pass control of the bus from the workstation to the HP8751A.
  • Respond to status requests while buffering HPGL data.
  • On completion, reset the bus and return the instrument to default operation.

After coding it up and testing the process several times, I was confident that I had accomplished Step 1 of the two step process for acquiring a screen shot from an older piece of test equipment. Now I needed to work on step 2 rendering HPGL to an image.

The author of the plotter emulator, Mr. Miles, used a special library that he developed to render graphics. I could have gone this route but I had already found an HPGL parser (written in VB6) that was included in a code example in a sub folder of the Agilent VISA installation directory on my workstation. I liked how that the example used Windows GDI to draw the HPGL objects and, wanting to learn GDI, I translated this module into C and enhanced it to support a wider range of HPGL than it initially did.

Soon I had a small application that I could use to acquire and render a screen shot from the HP8751A Network Analyzer, but there were many other models of Network and Spectrum analyzer in the Lab and so whenever I had some spare time I played with them and figured out how to get plots out of each one.

Instrument Snap Shot Rises Again

Figure2

Figure 2: TDS420 Oscilloscope

Recently, the engineering group where I now work acquired two nice Tektronix TDS 420, 4 channel oscilloscopes. I wanted to be able to capture the screen of this model but the off-the-shelf software we have for that purpose wouldn't work with this older scope. So I dusted off my old snap shot and modified it to work with the TDS 420. While reviewing the code base, I realized I could improve upon the original design significantly with the application of code encapsulation patterns. I also wanted to give the user access to all of the plot rendering parameters - something the original application didn't do.

Figure3

Figure 3: Instrument Snapshot Demo

This demo is the fruit of that redesign effort. It does not support very many instruments, since I have only had access to so many pieces of gear. It is however, fairly easy to add code support for other fine old pieces of test equipment. It also should be fairly easy to take parts of this code and reuse them in other projects.

Under the Hood

The demo consists of several design elements:

  • A dialog and associated code to facilitate selection of, and communication with, a piece of test equipment.
  • A plot rendering module to convert instrument HPGL data to an image.
  • My Property Grid [^] to facilitate user access to the plot rendering module parameters.
  • A module to make reading and writing text based configuration or INI files a snap.
  • A GDI+ wrapper that provides a very simple C friendly interface to the GDI+ file format image encoders; in order that bitmaps might be saved to several popular image formats.
  • Various other goodies to play with.

It would be beyond the scope of this article to go into detail on each one of these elements; however, for the inquiring coder, here are some previews of what lies inside.

Test Equipment Communication

Here is an example of how I declare a virtual instrument to represent a communication point with an actual piece of test equipment:

C++
static VINSTRUMENT gInstrument; ///< pointer to virtual instrument

And connecting to that piece of equipment:

C++
// Set the virtual instrument properties for this connection
LPSTR supportedModels[] = { "TDS 420", "8711A",
"8751A", "8753C", "8753D", "4396A", NULL };

gInstrument.supportedModels = supportedModels;
gInstrument.supportedModelsCount = NELEMS(supportedModels) + 1;
gInstrument.instrumentClass = "Supported Instrument";

// Show browse dialog object to get instrument address and vi.
DlgConnect_Show(ghInstance, hwnd, &gInstrument);

// Verify connection
if (gInstrument.isConnected)
{
     //Do something

Then communicating with that piece of equipment:

C++
//Get current settings
WriteLine(gInstrument.vi, "HARDC?");
LPSTR strRes = Read(gInstrument.vi, MAX_PATH);
if (!IsEmptyString(strRes))
{
     //Do something

Converting HPGL To An Image

The following demonstrates the basic operation of the rendering engine using just the default properties. Here I declare a Plotter object to hold configuration settings, HPGL data and a graphical image:

C++
static LPPLOTTER glpScreenPlotter; ///< pointer to plotter object

Now I initialize it:

C++
glpScreenPlotter = New_Plotter();

Convert HPGL to an Image:

C++
// Assign the the desired output size
glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

//Set the data source to the buffer of HPGL commands  acquired from the instrument
glpScreenPlotter->plotCommandData = PlotData;

//Plot to image
HBITMAP hbmp = Plotter_Plot(glpScreenPlotter);

Note: It is unnecessary to delete the HBITMAP returned from Plotter_Plot(); it's resources are managed each time that the method is called and it is destroyed with a call to Plotter_Destroy() as I do in the demo's WM_CLOSE handler.

C++
static VOID Main_OnClose(HWND hwnd)
{
    Plotter_Destroy(glpScreenPlotter);
    EndDialog(hwnd, 0);
}

Read and Write Config Files

I just use an ini file wrapper. Here I read from a file:

C++
LPINIFILE lpFile = New_IniFile(filename);

//Plot Offset
lpp->offset_x = IniFile_ReadInteger(lpFile, _T("Plot Offset"), _T("x"), lpp->offset_x);
lpp->offset_y = IniFile_ReadInteger(lpFile, _T("Plot Offset"), _T("y"), lpp->offset_y);

//
// Read more stuff
//

IniFile_Destroy(lpFile); //All done so clean up

Now I write to a file:

C++
LPINIFILE lpFile = New_IniFile(filename);

//Plot Offset
IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("x"), lpp->offset_x);
IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("y"), lpp->offset_y);

//
// Write more stuff
//

IniFile_Destroy(lpFile); //All done so clean up

Write Images in Various File Formats

  • New_ImageFile(): Loads a subset of the GDI+ API and provides a pointer to the resources
  • ImageFile_Destroy(): Unloads GDI+ and frees resources
C++
static VOID MnuSavePlot_Click(HWND hwnd)
{
    OPENFILENAME ofn;
    TCHAR szFileName[MAX_PATH] = {0};

    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Png Image (*.png)\0*.png\0Gif Image 
    (*.gif)\0*.gif\0JPeg Image (*.jpg)\0*.jpg\0Tiff Image 
    (*.tif)\0*.tif\0Bitmap Image (*.bmp)\0*.bmp\0All Files (*.*)\0*.*\0";
    ofn.lpstrTitle = "Save an Image File";
    ofn.lpstrFile = szFileName;
    ofn.lpstrInitialDir = gPlotDirectory;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "png";

    if(GetSaveFileName(&ofn))
    {
        DoEvents(); // refresh screen

        // If the file name is not an empty string open it for saving.
        if (0 != _tcsncmp(szFileName, _T(""), NELEMS(szFileName)))
        {
             HBITMAP hbmp = Static_GetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP);

            if(NULL != hbmp)
            { 
                LPIMAGEFILE lpi = New_ImageFile();
                if(NULL != lpi)
                {
                    // file type selected in the dialog box.
                    // NOTE that the FilterIndex property is one-based.
                    switch (ofn.nFilterIndex)
                    {
                        case 5: //bmp
                        {
                            ImageFile_SaveBMP(lpi, hbmp, szFileName);
                        }
                        case 4: //tiff
                        {
                            BOOL fCompressLZW = TRUE;
                            ImageFile_SaveTIFF(lpi, hbmp, fCompressLZW, szFileName);
                        }
                            break;
                        case 3: //jpg
                        {
                            UINT uQuality = 100;
                            ImageFile_SaveJPEG(lpi, hbmp, uQuality, szFileName);
                        }
                            break;
                        case 2: //gif
                        {
                            ImageFile_SaveGIF(lpi, hbmp, szFileName);
                        }
                            break;
                        case 1: //png
                            //fallthrough
                        default:
                        {
                            ImageFile_SavePNG(lpi, hbmp, szFileName);
                        }
                    }
                    ImageFile_Destroy(lpi);
                }//if(NULL != lpi)
            }
        } 
    }
}

Using the Demo

The demo will not run unless you have VISA [^] installed on your computer. You will also need to install 488.2 GPIB support [^].

The demo currently will request a plot from the following pieces of equipment:

  • Tektronix TDS 420 4 channel oscilloscope
  • HP 8711A RF Network Analyzer
  • HP 8751A 8751A Baseband, IF and RF Network Analyzer
  • HP 8753C Network Analyzer
  • HP 8753D Network Analyzer, 30 kHz to 3 GHz
  • HP 4396A RF Network/Spectrum Analyzer

In addition to this, I added an import feature so that HPGL data files can be opened and rendered in the application. This allows you to play around with the rendering engine even if you don't have one of the above pieces of test equipment. There should be a screen-shot from the TDS 420, "test.plt", included in the demo zip file.

The demo also comes with a help file that covers basic operation.

Final Comments

I documented this source with Doxygen [^] comments for those that might find it helpful or useful. Your feedback is appreciated.

History

  • February, 2, 2015: Version 1.0.0.0
  • March, 6, 2015: Version 1.1.0.0 - Eric Moberg added support for the following:
    • Tektronix TDS 640A, TDS 460A, TDS 754C, TDS 754D, TDS 644B, and TDS 740 oscilloscopes.
  • March, 13, 2015: Version 1.2.0.0 - Fixed bug that caused some Tektronics scope screen shot data to not fully transfer to the Application.
  • December, 16, 2016: Version 1.3.0.0 - Improved scaling of device plots.  Added support for devices that export rastor image screen shots.  Added support for the following devices:
    • HP Agilent 4395A, N9010A, E4445A, DSO80204B, and 8753ES

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)