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.
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
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.
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:
static VINSTRUMENT gInstrument;
And connecting to that piece of equipment:
LPSTR supportedModels[] = { "TDS 420", "8711A",
"8751A", "8753C", "8753D", "4396A", NULL };
gInstrument.supportedModels = supportedModels;
gInstrument.supportedModelsCount = NELEMS(supportedModels) + 1;
gInstrument.instrumentClass = "Supported Instrument";
DlgConnect_Show(ghInstance, hwnd, &gInstrument);
if (gInstrument.isConnected)
{
Then communicating with that piece of equipment:
WriteLine(gInstrument.vi, "HARDC?");
LPSTR strRes = Read(gInstrument.vi, MAX_PATH);
if (!IsEmptyString(strRes))
{
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:
static LPPLOTTER glpScreenPlotter;
Now I initialize it:
glpScreenPlotter = New_Plotter();
Convert HPGL to an Image:
glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);
glpScreenPlotter->plotCommandData = PlotData;
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.
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:
LPINIFILE lpFile = New_IniFile(filename);
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);
IniFile_Destroy(lpFile);
Now I write to a file:
LPINIFILE lpFile = New_IniFile(filename);
IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("x"), lpp->offset_x);
IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("y"), lpp->offset_y);
IniFile_Destroy(lpFile);
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
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();
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)
{
switch (ofn.nFilterIndex)
{
case 5: {
ImageFile_SaveBMP(lpi, hbmp, szFileName);
}
case 4: {
BOOL fCompressLZW = TRUE;
ImageFile_SaveTIFF(lpi, hbmp, fCompressLZW, szFileName);
}
break;
case 3: {
UINT uQuality = 100;
ImageFile_SaveJPEG(lpi, hbmp, uQuality, szFileName);
}
break;
case 2: {
ImageFile_SaveGIF(lpi, hbmp, szFileName);
}
break;
case 1: default:
{
ImageFile_SavePNG(lpi, hbmp, szFileName);
}
}
ImageFile_Destroy(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