仪器快照:如何从旧测试设备获取和渲染屏幕截图






4.89/5 (12投票s)
用于数据采集的 HPGL 渲染器和演示应用程序
引言
我曾不止一次地使用过老式测试设备。有时,工程师和技术人员更喜欢老式模拟设备而非新设备。也许一个组织对各种旧设备进行了投资,而新设备的预算有限。许多老式设备都设计用于将图形屏幕数据导出到绘图仪。数据图会被打印出来并添加到工程笔记本中,以及设计说明和计算结果。
背景
对于新测试设备而言,以易于整合到电子文档中的格式获取屏幕捕获数据相当容易,但对于旧型号设备而言,这可能是一个挑战。多年前,当我受雇于记录各种滤波器性能的任务时,我就遇到了这种情况。我希望能轻松地从网络分析仪(特别是HP8751A)捕获并保存屏幕截图,但我找不到一个能令我满意的软件包来完成这项任务。因此,我着手编写了一些东西,只需单击一下按钮即可从该设备中提取屏幕截图。
从旧测试设备获取屏幕截图通常是一个两步过程:首先请求并接收数据,其次将数据渲染成图像。这两个目标说起来容易做起来难,但经过一些时间和耐心,我弄清楚了如何让HP8751A提供所需的数据。我是这样做的。
行业工具
为了以工程能力处理仪器,拥有VISA是个好主意。不,不是那张美观的塑料卡[^],也不是文件印章。VISA,即虚拟仪器软件架构[^],是您的应用程序与当今使用的众多硬件接口和仪器总线之间方便的标准化抽象层。诸如NI LabVIEW (TM) 等各种测试开发产品和环境都是基于VISA构建的。
VISA通常可以从您购买设备或接口卡的供应商处免费获得。多年来,我使用过安捷伦(现为Keysight)和国家仪器(National Instruments)的VISA驱动程序。安装包中包含各种开发工具,但以下是我认为最有用的。
- Agilent IO Monitor 和/或 NI SPY (现在称为 NIO Trace [^]) - 查看 我们中的间谍 -- 调试 I/O[^],了解如何使用这些工具的精彩介绍。
- NI Visa Interactive Control 和/或 Agilent Interactive IO - 这些工具提供了一种“即时”向仪器发送命令的方法,有助于测试命令序列或查看仪器将如何响应一系列命令。
随意尝试
有两种方法可以从HP8751A中获取屏幕截图,其中一种是绘制到文件;如果这样做,定义屏幕的惠普图形语言(HPGL)会被写入插入驱动器的软盘中,然后可以通过跑腿网络[^]将其传输到工作站。第二种方法是连接到通用接口总线(GPIB)[^]的绘图仪进行绘制。一旦绘图仪将屏幕绘制到纸上,就可以使用木桌法[^]将图像传输到工作站。绘制到文件/跑腿网络的方法在紧急情况下还可以,但我想要更直接的方法。第二种方法也一样(更不用说我实验室里甚至没有绘图仪了)。
也许还有第三种方法。“如果我能将屏幕绘制到工作站而不是绘图仪呢?”我心想。我通过配置仪器将绘图发送到计算机的GPIB地址来测试这个想法,启动IO Monitor并按下PLOT按钮。我只在显示器上看到“OS”或类似的效果。我尝试了一些不同的东西,但都没有成功,然后,经过一番搜索,我发现“OS”是请求输出状态。我的仪器试图与绘图仪进行握手,除非它知道有绘图仪连接并准备好接收数据,否则它不会泄露任何HPGL!再找了一会儿,我发现有人已经编写了一个绘图仪模拟器[^]并免费提供。
我尝试了绘图仪模拟器,并成功从我的设备中检索到了由仪器发起的绘图。然后我想能够从我的工作站发起绘图,而这个应用程序无法做到这一点,所以我研究了代码,并记下了作者如何处理握手序列,以欺骗HP8751A,使其认为另一端连接着真正的绘图仪。我通过启动IO Monitor并向仪器发送状态请求的相应响应进行实验,结果,我开始收到大量的HPGL数据!
所有这些尝试的结果是,我确定了从该仪器发出主机启动绘图请求所需发生的一系列事件。
- 为我的虚拟绘图仪(工作站)打开一个虚拟仪器接口会话。
- 将HP8751A配置为总线控制器。
- 将我的虚拟绘图仪地址设置为仪器期望看到绘图仪的地址。
- 发送“PLOT”命令。
- 将总线控制权从工作站传递给HP8751A。
- 在缓存HPGL数据时响应状态请求。
- 完成后,重置总线并将仪器恢复为默认操作。
经过多次编码和测试,我确信我已经完成了从旧测试设备获取屏幕截图的两步过程中的第一步。现在我需要着手第二步,将HPGL渲染成图像。
绘图仪模拟器的作者Miles先生使用了他自己开发的特殊库来渲染图形。我本可以走这条路,但我已经在工作站上的Agilent VISA安装目录的子文件夹中的一个代码示例中找到了一个HPGL解析器(用VB6编写)。我喜欢该示例如何使用Windows GDI来绘制HPGL对象,并且为了学习GDI,我将该模块翻译成C语言并进行了增强,以支持比最初更广泛的HPGL。
很快我就有了一个小型应用程序,可以用来从HP8751A网络分析仪获取并渲染屏幕截图,但实验室里还有许多其他型号的网络分析仪和频谱分析仪,所以只要有空闲时间,我就会摆弄它们,并找出如何从每个设备中获取绘图的方法。
仪器快照再次崛起
最近,我目前工作的工程部门购买了两台不错的泰克TDS 420型四通道示波器。我希望能捕获该型号的屏幕,但我们现有的用于此目的的现成软件无法与这款旧示波器兼容。于是我翻出旧的快照程序,并对其进行修改以使其与TDS 420配合使用。在审查代码库时,我意识到通过应用代码封装模式,我可以显著改进原始设计。我还希望用户能够访问所有绘图渲染参数——这是原始应用程序所不具备的。
这个演示是重新设计工作的成果。它不支持很多仪器,因为我只能接触到这么多设备。然而,为其他优秀的旧测试设备添加代码支持相当容易。此外,将这部分代码用于其他项目也应该相当容易。
幕后
该演示由几个设计元素组成
- 一个对话框和相关代码,用于方便选择和通信测试设备。
- 一个绘图渲染模块,用于将仪器HPGL数据转换为图像。
- 我的属性网格[^],以便用户访问绘图渲染模块参数。
- 一个模块,让读写基于文本的配置文件或INI文件变得轻而易举。
- 一个GDI+包装器,为GDI+文件格式图像编码器提供了一个非常简单的C友好接口;以便位图可以保存为几种流行的图像格式。
- 各种其他好玩的东西。
本文的范围不适合详细介绍每一个元素;但是,对于好奇的程序员,这里有一些内部内容的预览。
测试设备通信
以下是我如何声明一个虚拟仪器以表示与实际测试设备的通信点。
static VINSTRUMENT gInstrument; ///< pointer to virtual instrument
然后连接到该设备
// 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
然后与该设备通信
//Get current settings
WriteLine(gInstrument.vi, "HARDC?");
LPSTR strRes = Read(gInstrument.vi, MAX_PATH);
if (!IsEmptyString(strRes))
{
//Do something
将HPGL转换为图像
以下演示了仅使用默认属性的渲染引擎的基本操作。这里我声明一个 `Plotter` 对象来保存配置设置、HPGL数据和图形图像。
static LPPLOTTER glpScreenPlotter; ///< pointer to plotter object
现在我初始化它
glpScreenPlotter = New_Plotter();
将HPGL转换为图像
// 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);
注意: 无需删除从Plotter_Plot()
返回的HBITMAP
;每次调用该方法时,其资源都会得到管理,并且在调用Plotter_Destroy()
时(如我在演示的WM_CLOSE
处理程序中所示)会被销毁。
static VOID Main_OnClose(HWND hwnd)
{
Plotter_Destroy(glpScreenPlotter);
EndDialog(hwnd, 0);
}
读取和写入配置文件
我只使用了一个ini文件包装器。这里我从文件中读取
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
现在我写入文件
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
以各种文件格式写入图像
New_ImageFile()
:加载GDI+ API的一个子集并提供指向资源的指针。ImageFile_Destroy()
:卸载GDI+并释放资源
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)
}
}
}
}
使用演示
除非您的计算机上安装了VISA [^],否则演示将无法运行。您还需要安装488.2 GPIB支持 [^]。
该演示目前将从以下设备请求绘图
- 泰克 TDS 420 四通道示波器
- 惠普 8711A 射频网络分析仪
- 惠普 8751A 8751A 基带、中频和射频网络分析仪
- 惠普 8753C 网络分析仪
- HP 8753D 网络分析仪,30 kHz 至 3 GHz
- 惠普 4396A 射频网络/频谱分析仪
除此之外,我还添加了一个导入功能,以便可以在应用程序中打开和渲染HPGL数据文件。这让您即使没有上述任何一个测试设备,也可以尝试渲染引擎。演示zip文件中应该包含一张来自TDS 420的屏幕截图,“test.plt”。
该演示还附带了一个涵盖基本操作的帮助文件。
最终评论
我使用 Doxygen [^] 注释记录了此源代码,以方便那些可能觉得它有帮助或有用的人。欢迎您的反馈。
历史
- 2015年2月2日:版本1.0.0.0
- 2015年3月6日:版本1.1.0.0 - Eric Moberg 增加了对以下设备的支持
- 泰克TDS 640A、TDS 460A、TDS 754C、TDS 754D、TDS 644B和TDS 740示波器。
- 2015年3月13日:版本1.2.0.0 - 修复了导致某些泰克示波器屏幕截图数据无法完全传输到应用程序的错误。
- 2016年12月16日:版本1.3.0.0 - 改进了设备图的缩放。增加了对导出光栅图像屏幕截图的设备的支持。增加了对以下设备的支持
- HP Agilent 4395A, N9010A, E4445A, DSO80204B, 和 8753ES