馬宇,葉衛東
(北京航空航天大學,北京 100191)
虛擬儀器是自動測試系統的基礎,測試軟件是虛擬儀器的核心。目前虛擬儀器和測試軟件常見的開發平臺包括:NI公司的LabVIEW,LabWindows / CVI,C#,C++和Java等。LabVIEW和LabWindows具有豐富的圖形化測試控件庫;C#便于開發Windows圖形界面程序;C,C++和Java是目前使用最廣的編程語言。
在實際的產品測試開發中,這些平臺或編程語言也存在一些局限。比如,LabVIEW圖形化語言不便于代碼管理和維護,LabWindows / CVI使用面向過程的C語言,模塊化開發需要大量的編程技巧,開發效率較低,手工管理內存容易導致軟件缺陷(如緩沖區溢出)。設計功能較復雜的測試軟件時,靜態語言在不重新編譯的情況下難以對軟件的功能進行動態配置。
Python是面向對象的高級編程語言,動態類型、自動內存管理、解釋執行、原生跨平臺,可拓展性極強,具有豐富的開源庫,能快速實現應用程序所需的各種功能[1]。Python在儀器編程方面已有少量應用[2],主要障礙是大量儀器沒有提供Python的編程接口。SWIG(Simple Wrapper and Interface Generator)是跨語言接口轉換工具,支持Python/Perl/ PHP等動態腳本語言與C,C++,C#,Java等靜態編譯型語言之間的接口轉換[3],Python中的很多拓展庫實際上來自SWIG對C庫的封裝。
本文采用Python設計和開發自動測試軟件,提出將SWIG用于儀器驅動的跨語言、跨平臺封裝,彌補Python在儀器編程方面的短板,希望能夠促進Python在虛擬儀器和自動測試領域的推廣和應用。
整個測試系統由測試計算機、測試服務器和控制主機組成,通過交換機組網,如圖1所示。測試計算機采用RS-232,GPIB或PXI等測試總線連接測試資源(模塊化儀器或可程控的臺式儀器),通過儀器驅動程序控制儀器設備;測試服務器運行數據庫和測試應用軟件;控制主機實現人機交互,對整個測試系統進行控制。

圖1 系統硬件組成
可根據測試需求,調整和縮放系統規模,如將三者合為一體,即以傳統的單機方式運行整個測試系統。
用Python開發自動測試軟件,其基本層次結構如圖2所示。

圖2 系統軟件層次結構
最上層為測試應用層,負責測試用例執行、數據存儲和分析、測試報告生成等具體的測試業務。應用層之下為儀器驅動層,在Python儀器驅動模塊中封裝和調用底層硬件的API,對測試資源進行配置和管理。
對于支持NI-VISA和NI-IVI標準驅動的測試設備,Python中的開源拓展庫pyvisa和pyivi分別提供了二者的API封裝,可以直接調用已封裝好的與具體儀器無關的函數接口或可互換類驅動接口[4]。
但實際的測試需求往往豐富多變,因為各種原因,測試系統還會使用很多缺少VISA或IVI驅動支持的測試儀器,如各種總線通訊接口卡、數據采集卡、多功能復合儀器,以及一些自研或定制的非標準設備,有時也包括一些暫時無法升級的老舊測試設備。這類不被VISA或IVI驅動支持的測試資源,常被稱作專有設備或非標設備。
將Python作為測試開發平臺的主要技術障礙,就是如何在Python中對這類專有設備進行編程控制。
儀器驅動通常采用C或C++編寫,一般會以C語言動態鏈接庫的形式發布,并提供頭文件、庫文件等供二次開發。常見的儀器驅動程序(或SDK)中包含的文件類型及作用,如表1所示。

表1 非標儀器設備驅動程序的常見結構
用C,C++調用專有儀器驅動的API函數時,一般只需要正確設置編譯器的鏈接路徑和鏈接方式。在C#中,需要用declare語句對API函數的參數和返回值類型等進行聲明,之后方可調用儀器API進行編程。有些廠商提供了LabVIEW,C#等環境的驅動接口聲明代碼(或SDK),以簡化編程工作。
Python語言在虛擬儀器開發中使用比例比較低,大多數儀器廠商并沒有為儀器提供Python SDK支持。Python不支持指針操作,完全使用引用類型表示變量、參數等(傳遞內存地址而不是值拷貝),編譯成中間二進制字節碼后通過解釋器解釋運行。Python與C語言雖然語句類似,但在數據類型、內存操作、設計模式、運行方式等方面存在很大的差異,Python無法簡單直接地調用針對C語言編程而設計的儀器驅動。
本文對在Python中跨語言調用儀器驅動程序,進行了技術研究和方案驗證。
Python本身是開放、可拓展的,除了可使用大量的第三方開源拓展庫,還可自行編寫Python拓展模塊。借助于一些開源庫,可以實現在Python腳本中調用外部DLL動態鏈接庫中的C程序。
1)ctypes庫,使用方式與C#中非托管方式調用DLL類似,手工編寫接口代碼、聲明每個API函數的參數和返回值類型。該方案簡單易行,但工作量大、難以自動化處理,適合API函數較少的情況。
2)libcffi庫提供了比ctypes更友好的編程接口,用更少的代碼可完成同樣的功能。
3)用CPython將外部DLL庫封裝為原生的Python拓展模塊。該方案需要編寫C代碼,將儀器驅動頭文件中定義的各種API函數、數據類型等轉換為相應的Python對象。由于需要了解Python解釋器的底層實現機制,工作量和開發難度都很大。
4)SWIG可自動解析C或C++的代碼和頭文件,提取API函數的參數類型、返回值類型,自動生成CPython接口轉換代碼。該方案通用性和自動化程度較高,只要熟悉SWIG的配置語法,無需手工編寫底層的轉換代碼,即可快速批量地進行API封裝。Python中的pyivi模塊,實際上就是NI-IVI的SWIG封裝。
在以上4種方案中,可能并不存在絕對的最佳方案。自動測試系統經常搭配使用模塊化儀器、臺式儀器以及其他程控測試資源,并根據測試需求靈活地增加或置換儀器。在選擇驅動封裝方案時,建議根據驅動API的數量和復雜度,結合開發人員對相關工具的熟悉程度,使用ctypes,libcffi或SWIG。
本文所設計的測試系統中使用了較多的非標儀器,API數量和類型都比較豐富,因此使用SWIG對儀器驅動進行封裝,通過自動化處理提高開發效率。
本文以某公司的多功能數據采集模塊PXI-nuDAQ2206為例(以下簡稱DAQ2206),簡要介紹將其廠商專有驅動用SWIG轉換為Python拓展模塊的關鍵步驟。
DAQ2206為PXI模塊儀器,測試資源包括64個AD通道,2個DA通道,2個定時/計數器和24個IO口。在工業控制、自動測試中使用多功能復合測試設備,可以提高資源密度、減小設備體積。但此類多功能復合設備,往往缺少VISA和IVI驅動的支持。
公司提供了專有驅動包D2K_DASK,支持包括DAQ2206在內的多種模塊化儀器。用SWIG對其進行接口封裝的核心工作是按照SWIG庫的配置語法,在拓展名為*.i的API接口描述文件中對特殊的API參數類型進行聲明,以便于SWIG能夠正確地進行類型轉換和封裝。示例如下(片段):
%module D2KDASK
%include"D2K_DASK.h"
%include"typemaps.i"
%apply int *OUTPUT { BOOLEAN *Stopped,U32 *AccessCnt};
%apply double *OUTPUT { F64 *voltage };
首先用%include指令包含驅動API的頭文件。一般情況下SWIG能自動識別大部分函數原型、變量和常量[5],并將其轉換為相應的Python對象。Python采用動態數據類型和自動內存管理,無法通過指針直接操作內存,所以儀器驅動中經常使用的指針類型的參數通常需要特殊處理。
儀器API大多將操作的狀態碼作為返回值,但由于C / C++函數不支持多返回值,為了輸出額外的數據,一般會使用指針作為參數、間接地繞開這一限制。以函數D2K_AI_AsyncCheck為例,其函數原型為I16 D2K_AI_AsyncCheck (U16 CardNumber,BOOLEAN *Stopped,U32*AccessCnt)。其中,指針類型的參數BOOLEAN*Stopped和U32 *AccessCnt,被用于輸出數據采集狀態和已采集數據的個數。該API函數實際上并不關心這些形參的初始值,只是單向地將輸出數據寫入指針所指向的內存。
借助于SWIG的指針類型處理模塊typemaps.i,通過指令%apply int *OUTPUT { BOOLEAN *Stopped,U32 *AccessCnt },可將對應的參數聲明為Python整數類型,參數用途為OUTPUT。
編寫好SWIG接口文件后,調用swig命令可自動生成接口的包裝代碼(如D2KDASK_wrap.c),將其編譯為動態鏈接庫(Windows下還需要修改拓展名為*.pyd),即得到儀器驅動的Python拓展模塊。在Python中可直接import導入儀器驅動拓展模塊。受益于Python語法簡單、多返回值、動態類型、自動內存管理等特性,無需繁瑣的定義、聲明和底層操作,可以簡潔、自然地調用驅動模塊中的資源,如:
>>>from D2K_DASK import D2K_AI_AsyncCheck
>>>status,stop,count=D2K_AI_AsyncCheck(0)
>>>status,stop,count
(0,1,50)
以上只是使用SWIG封裝儀器驅動的簡單示例。除typemaps.i外,SWIG還提供了windows.i,cpointer.i,carrays.i,cstring.i,cmalloc.i,cdata.i等拓展庫,能夠處理Windows編程中使用的各種頭文件,在Python腳本中操作函數指針、數組、字符串、結構體和聯合體等C語言數據類型,通過malloc動態申請和釋放內存,直接對內存進行不受保護地讀寫。SWIG大大豐富和擴充了Python的底層編程能力,基本能滿足用Python進行儀器編程的需求。
將儀器驅動封裝為Python模塊后,還可參考IVI可互換類驅動的實現機制,利用Python面向對象的特性,將具體的底層API操作封裝在類內部,對外抽象出與儀器無關的高級操作接口,逐步將測試軟件與底層儀器API解耦,提高儀器的可互換性。
設計和開發測試系統時,有時需要在測試軟件中集中管理和操作連接到多臺測試計算機的儀器資源。可能的原因包括:計算機接口類型和數量有限,儀器設備空間分布較廣,系統中不同的測試設備所要求的軟件運行環境無法統一等。隨著計算機軟硬件平臺不斷升級,測試設備會逐漸過時,在需要對老舊的測試系統進行升級維護時,上述問題可能會更加突出[6]。將儀器設備接入到多臺測試計算機后,傳統的測試開發平臺或編程語言往往難以用比較簡單的方式,解決在分布式、跨平臺的環境下,對儀器驅動進行遠程操作和遠程調試等問題。
Python具有數量眾多且功能強大的網絡編程庫。其中,RPyC(Remote Python Call)庫采用對象代理(Object Proxying)技術,可以像操作本地對象一樣操作遠程主機上的Python模塊和程序。Python作為弱類型的動態語言,允許在運行時修改和替換對象,該技術被稱為“猴子補丁”(MonkeyPatch),可用于在不改變源碼的情況下、對軟件功能進行追加或變更。
結合RPyC庫和“猴子補丁”,通過本地測試軟件中的代理對象,可通過RPyC庫,透明地操作遠程計算機所連接的測試設備,如圖3所示。

圖3 實現遠程、跨平臺調用儀器驅動
>>>import rpyc
>>>server_ip=′192.168.1.22′
>>>server=rpyc.classic.connect(host=server_ip,port=18812)
>>>D2K_AI_AsyncCheck=server.D2K_DASK.D2K_AI_AsyncCheck
>>>status,stop,count=D2K_AI_AsyncCheck(0)
在實現遠程調用的同時,以上方案還支持跨平臺操作,即遠程計算機和本地計算機可分別使用不同類型的操作系統(Windows/Linux/Unix等),不同版本的Python,SWIG。
使用pyvisa,pyivi以及SWIG封裝的Python拓展調用儀器驅動,結合RPyC和“猴子補丁”,可將單機測試軟件無縫遷移到分布式、跨平臺的網絡環境中。該方案能非常好地解決測試軟件設計中遠程調試、遠程操作以及運行環境無法統一的問題,為老舊測試設備的聯網升級改造、多臺測試機的組網運行,提供了一種簡單易行的技術方案。
Python腳本在運行時首先會被編譯為中間字節碼,再通過解釋器解釋執行,執行過程中解釋器會進行大量的類型檢查、自省等操作,導致Python代碼的運行效率和實時性表現較差[7]。虛擬儀器程序和自動測試軟件,對運行性能和實時性往往有較高的要求。調用經SWIG封裝的儀器驅動模塊時,隱含的類型轉換、數據拷貝等跨語言調用,必然會引入一定的封裝延遲。因此,有必要定量測量和研究經SWIG封裝后的Python儀器驅動模塊的運行效率。
本文選取了DAQ2206的5個API函數,分別用C語言直接調用和用Python調用經SWIG封裝后的驅動模塊,統計執行耗時、計算封裝開銷。
在C程序中精確測量時間的原理是:北橋提供了高精度性能計數器,調用QueryPerformanceCounter和QueryPerformanceFrequency這兩個API可分別獲取其計數值和計數頻率。在被測函數前后插樁、獲取計數差值,除以計數頻率,即得到函數的執行耗時。

表2 測試環境
嵌入式控制器 cPCI-2510計數頻率測得約為1.46 MHz(硬件決定),相當于計時分辨力可達1 μs以上。測試次數1000次,數據統計如表3所示。

表3 性能測試數據 μs
可以看出,SWIG接口轉換引入的封裝耗時約為2~15 μs。Register_Card,Release_Card的延遲相對其他API較高,但由于只在初始化階段和程序退出時調用,封裝開銷對性能的影響基本可忽略。其他3個API函數的封裝開銷為2~3 μs,與API本身的執行時間沒有明顯關聯。受操作系統任務調度影響,Windows軟件的實時性指標往往只能達到10 ms左右,因此微秒級的調用開銷一般不會對測試任務產生嚴重影響。
在對程序性能要求非常嚴格的場合,不建議非常頻繁(如每秒數千次以上)地調用SWIG封裝后的API函數(如AI_VoltScale)。此時,可以將最耗時的底層關鍵代碼用C語言實現,一并編譯、封裝到Python儀器驅動拓展模塊中,作為整體進行調用,這樣既可以使用Python語言進行高效率的開發,也不會由于SWIG封裝和Python解釋運行而導致軟件整體的實時性受到破壞。
在100 Mbps局域網環境下,遠程調用驅動API會再引入約2 ms的傳輸延遲,可能對軟件的執行效率產生一定的影響。因此,遠程調用一般更適用于軟件調試、低速數據采集等實時性要求相對較低的場景。
測試軟件是虛擬儀器和自動測試系統的核心,傳統的測試開發平臺使用中存在較大的局限性。本文通過SWIG將儀器驅動程序轉換為Python拓展模塊,彌補了Python在底層編程方面的不足,解決了用Python進行儀器編程的主要障礙。
受益于Python的自動內存管理、動態類型、面向對象以及豐富的拓展庫,用Python開發測試軟件,可提高編程效率,降低在分布式、跨平臺的環境下設計和開發測試軟件的難度,縮短復雜測控系統的開發時間,一定程度上也有助于提高儀器的可互換性。在對軟件實時性、儀器操作性能等有較高要求的場合,可采用C語言和Python混合編程,在軟件開發效率和運行效率之間,取得比較好的平衡。
[1] 丁未.將工業與科技世界的運行統一在Python語言的開源框架中[J].中國儀器儀表,2013(08):23-25.
[2] Hughes J M.真實世界的Python儀器監控 : Real world instrumentation with Python:數據采集與控制系統自動化[M].北京:電子工業出版社,2013.
[3] Beazley D M.SWIG: An Easy to Use Tool for Integrating Scripting Languages with C and C++[C]// Usenix Tcl/tk Workshop,1996.
[4] 黃建軍,李宥謀,劉婧,等.基于Python語言的自動化測試系統的設計與實現[J].現代電子技術,2017,40(4):39-43.
[5] Beazley D M.Automated scientific software scripting with SWIG[J].Future Generation Computer Systems,2003,19(5):599-609.
[6] Weltzin C,Schlonsky S.Reducing obsolescence of Linux-based ATEs with virtualization[J].Instrumentation & Measurement Magazine IEEE,2011,14(4):1-3.
[7] 范浩杰.面向Python程序源代碼的分析與編譯優化研究[D].北京:北京信息科技大學,2015.