摘 要: 為了解決Simulink與VC++的數據交互問題,提出了采用C++語言S?函數實現兩種開發環境的網絡通信方法。首先介紹了S?函數以及基于S?函數的編程框架,其次介紹了UDP網絡程序設計模型。最后結合實例說明并驗證了該方法的有效性和實用性。
關鍵詞: S?函數; Simulink; VC++; UDP
中圖分類號: TN711?34 文獻標識碼: A 文章編號: 1004?373X(2013)13?0108?04
Research on network communication between Simulink and VC++ based on S?function
ZHOU Tao
(Xi’an Aircraft Industry (group) Company Ltd., AVIC, Xi’an 710089, China)
Abstract: In order to solve the problem of data interaction between the Simulink and Visual C++, a network communication method utilized the S?function of C++ language to realize the two developing environment. The S?function, the programming framework based on it and the design model of UDP network programming are introduced. the effectiveness and the practicability of this method are proved with examples.
Keywords: S?function; Simulink; VC++; UDP
0 引 言
Simulink是Matlab的重要組件之一,具有強大的系統建模和動態仿真能力,而VC++是Microsoft推出的可視化編程環境,具有豐富的Windows API,可以開發友好的人機界面,而且運行穩定高效。實際應用中經常會結合兩者的優勢,進行聯合仿真,其中必然存在兩種開發環境的網絡通信問題,本文采用S?函數(System Function,系統函數)構建一個單獨的通信模塊,通過C++的Socket通信來實現Simulink與VC++的數據交互,并結合實例對該方法進行了驗證。
1 S?函數簡介
S?函數是Simulink環境下的功能擴展機制,能有效提高和豐富Simulink的功能。S?函數提供了一種用Matlab、C、C++或者FORTRAN等多種計算機語言描述Simulink模塊的方法,將上述計算機編寫的S?函數用MEX工具編譯產生MEX文件。S?函數中用一種特定的回調語法,使用戶可以和Simulink Engine交互,此類交互和Simulink內置模塊與Engine間的交互極為相似。這種回調語法稱為S?函數API。
2 基于S?函數的編程框架
C++語言S?函數采用標準C++語言規范,并且產生可獨立運行的MEX文件。 C++語言的S?函數的編寫采用回調函數的方式, Simulink Engine在不同的仿真階段調用相應的回調函數,執行指定的任務。Matlab提供了S?函數的編程模板,指定了標準的回調方法,只需在其中加入特定的功能代碼即可。其基本框架和對應的回調方法如圖1所示。
<\\192.168.0.25\$d\8月\8-2\補\補!現代電子技術201313\Image\26t1.tif>
圖1 S?函數編程基本框架
3 UDP網絡程序設計模型
本文采用基于UDP的客戶機/服務器通信模式實現Simulink與VC++的數據交互,Simulink的S?函數模塊作為客戶機進行仿真數據的發送,VC++作為服務器接收數據并實時顯示。進行UDP通信時,客戶端與服務器端所使用的函數基本相同,其工作流程如下:
(1)使用WSAStartup ()函數檢查系統協議棧的安裝情況;
(2)使用socket()函數創建套接口;
(3)使用bind()函數將創建的套接口與本地地址綁定;
(4)使用sendto()函數發送數據,也可以使用redvform()函數接收數據;
(5)使用closesocket()函數關閉套接口;
(6)最后調用WSACleanup()函數,結束Windows socket API 的使用。
程序工作流程如圖2所示。
<\\192.168.0.25\$d\8月\8-2\補\補!現代電子技術201313\Image\26t2.tif>
圖2 UDP客戶機/服務器程序工作流程
4 驗證實例
此實例利用Simulink航空模型庫中的HL20自動著陸演示模型作為數據源,用來驗證Simulink和VC++之間的數據收發過程。首先,在Simulink的User? Defined Functions庫中選擇S?Function添加到模型中,將S?Function name 改為UDPclient,然后,新建一個名為UDPclient.cpp的C++文件,在其中利用C++語言按照S?函數編程模板進行UDP客戶端程序設計。將HL20演示模型進行適當的裁剪后,利用其輸出海拔高度,并接入S?函數模塊的輸入端口,再將輸出端口連接到Scope模塊。同時,在VC++中進行服務器端程序設計,并進行數據的實時顯示。最后,將Scope顯示的數據與VC++收到的數據進行對比,以驗證網絡通信的正確性。其模型框圖如圖3所示。
以下分別說明在S?函數模塊和VC++中如何實現客戶端和服務器端代碼。
<\\192.168.0.25\$d\8月\8-2\補\補!現代電子技術201313\Image\26t3.tif>
圖3 實例模型框圖
4.1 客戶端實現
首先,定義用于數據發送的結構體類型,同時,此結構體將用于服務器端用來接收數據,而且形式要完全一致,其具體格式如下:
typedef struct FlighData_tag{
double longitude; //經度
double latitude; //緯度
double altitude; //海拔高度
double phi; //橫滾
double theta; //俯仰
double psi; //偏航
}FlightData;
然后按照S?函數基本編程框架進行客戶端程序設計,具體步驟如下:
(1)在初始化回調方法中進行尺寸特性信息的設置,如I/O端口數目和端口寬度、采樣時間數目、工作向量數目等,主要代碼如下:
static void mdlInitializeSizes(SimStruct *S)
{
// 指定I/O端口
if (!ssSetNumInputPorts(S, 1)) return;
//設置輸入端口數目
ssSetInputPortWidth(S, 0, DYNAMICALLY_SIZED);
//設置輸入端口寬度
ssSetInputPortDirectFeedThrough(S, 0, 1);
//設置是否直接饋通
if (!ssSetNumOutputPorts(S,1)) return;
//設置輸入端口數目
ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED);
//設置輸出端口寬度
ssSetNumSampleTimes(S, 1); //設置采樣時間數目
ssSetNumPWork(S, 2); //設置指針工作向量數目
}
(2)在初始化采樣時間回調方法中設置采樣時間和偏置時間,主要代碼如下:
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S,0,INHERITED_SAMPLE_TIME);
//設置采樣時間
ssSetOffsetTime(S, 0, 0.0); //設置偏移量
}
(3)在仿真啟動回調方法中執行內存分配、設置用戶數據等在仿真開始時S函數需要初始化的相關操作,在仿真整個過程中只執行一次,主要代碼如下:
static void mdlStart(SimStruct *S)
{
WSADATA wsaData;
SOCKET *pSendSocket = new SOCKET; //客戶端套接口
*pSendSocket = INVALID_SOCKET;
sockaddr_in *pRecvAddr = new sockaddr_in;
//服務器端地址
unsigned short Port = 27015; //端口號
printf(\"Start socket communication, please wait...\n\");
//初始化套接口
WSAStartup(MAKEWORD(2, 2), wsaData);
//創建客戶端套接口用于發送數據
*pSendSocket = socket(AF_INET, SOCK_DGRAM, IPPRO TO_UDP);
//設置服務器端地址結構,并指定IP地址和端口號,假設遠 程終端地址為“192.168.1.1”
pRecvAddr?>sin_family = AF_INET;
pRecvAddr?>sin_port = htons(Port);
pRecvAddr?>sin_addr.s_addr = inet_addr(\"192.168.1.1\");
//設置指針工作向量數值
ssSetPWorkValue(S, 0, pSendSocket);
//設置指針工作向量的第一個元素為指向客戶端
套接口的指針
ssSetPWorkValue(S, 1, pRecvAddr);
//設置指針工作向量第二個元素為指向服務器端
地址的指針
}
(4)在主輸出回調方法中從輸入端口獲取數據,在此方法中通過指針訪問輸入信號,這樣輸入數組指針可以指向存儲器中的非相鄰單元。Simulink engine周期調用此方法將數據輸出到服務器端。主要代碼如下:
static void mdlOutputs(SimStruct *S, int_T tid)
{
//獲取I/O端口數據地址
InputRealPtrsType u = ssGetInputPortRealSignalPtrs(S,0);
//獲取輸入端口指針向量的指針
real_T *y = ssGetOutputPortRealSignal(S, 0);
//獲取輸出端口向量的指針
int InputNum = ssGetInputPortWidth(S, 0);
//獲取輸入端口的寬度,即指針向量元素個數
for(int i=0;i { y[i] = *u[i]; //依據輸入端口寬度,將輸入端口數據輸送到輸出端口 } FlightData SendBuf; //定義結構體變量 //通過指針工作向量獲取客戶端接口的指針 SOCKET *pSendSocket = static_cast //通過指針工作向量獲取指向服務器端地址的指針 sockaddr_in *pRecvAddr = static_cast //發送數據包到服務器端 SendBuf.altitude = y[0]; //作為示例僅發送高度數據 sendto(*pSendSocket,(char*)SendBuf,sizeof(FlightData,(SOCKADDR*)pRecvAddr, sizeof(sockaddr_in)); //向服務器端發送數據包 } (5)在仿真終止回調方法中,關閉套接口并釋放內存資源,主要代碼如下: static void mdlTerminate(SimStruct *S) { SOCKET *pSendSocket = static_cast sockaddr_in *pRecvAddr = static_cast //發送數據結束,關閉套接口 printf(\"Finished socket communication, Closing socket.\n\"); closesocket(*pSendSocket) ; //釋放資源并退出 WSACleanup(); delete pSendSocket; pSendSocket=NULL; delete pRecvAddr; pRecvAddr=NULL; } 經過以上步驟就完成了S?函數模塊客戶端程序,然后在Matlab的命令窗口輸入如下命令:MEX UDPclient.cpp, 如果程序沒有編譯錯誤,就會生成擴展名為mexw32的MEX文件,此文件可在Simulink環境中執行,用來將HL20自動著陸模型輸入的高度數據發送到服務器端,Scope模塊顯示輸出的數據,經測試S?函數模塊輸出的高度數據如圖4所示。 <\\192.168.0.25\$d\8月\8-2\補\補!現代電子技術201313\Image\26t4.tif> 圖4 S?函數模塊輸出的高度數據 4.2 服務器端實現 在VC++6.0開發環境下,建立基于對話框的MFC應用程序,在其中添加NTGraph控件用于高度數據的顯示,主要代碼模塊及功能如下: (1)首先,在類聲明文件中進行用于數據接收的結構體定義,此結構體應與客戶端定義的結構體完全一致,其次,聲明用于UDP服務器端的相關變量,并在構造函數中進行初始化。 (2)在OnInitDialog()函數中進行NTGraph控件的初始化,設置[X]軸和[Y]軸坐標以及其他顯示屬性。 (3)在OnCreate()函數進行定時器的設定和UDP網絡設置,主要代碼如下: int CTestDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) { …… SetTimer(0, 1, NULL); //設置定時器,用于數據接收 //初始化套接口 WSAStartup(MAKEWORD(2, 2), wsaData); //創建套接口用于接收數據報 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //指定地址與端口并與套接口綁定 RecvAddr.sin_family=AF_INET; RecvAddr.sin_port=htons(Port); RecvAddr.sin_addr.s_addr=htonl(INADDR_ANY); bind(RecvSocket, (SOCKADDR *) RecvAddr, sizeof (RecvAddr)); return 0; } (4)在OnTimer()函數周期接收從Simulink發送的數據報,并解析出高度數據,主要代碼如下: void CTestDlg::OnTimer(UINT nIDEvent) { …… FlightData RecvBuf; //聲明數據接收結構體變量 memset((char*)RecvBuf,0,sizeof(FlightData)); //重置接收緩沖區 recvfrom(RecvSocket,(char*)RecvBuf, sizeof(FlightData), 0, (SOCKADDR *) SenderAddr, SenderAddrSize); //接收數據包 m_Graph.PlotY(RecvBuf.altitude,0); //在控件中顯示高度數據 …… } (5)最后在OnDestroy()函數中關閉套接口,并釋放資源,主要代碼如下: void CTestDlg::OnDestroy() { …… //接收完數據包,關閉套接口 closesocket(RecvSocket); //釋放資源并退出 WSACleanup(); } 經過編譯、鏈接成功后,在Simulink環境下啟動HL20自動著陸演示模型,同時運行VC++生成的EXE可執行文件,NTGraph控件將實時顯示高度數據,展示整個著陸過程,可以看到它與Scope 模塊顯示的數據曲線完全一致,成功實現了Simulink與VC++之間的UPD網絡通信。VC++接收的高度數據如圖5所示。 <\\192.168.0.25\$d\8月\8-2\補\補!現代電子技術201313\Image\26t5.tif> 圖5 VC++接收的高度數據 5 結 語 利用Simulink與VC++各自的優勢進行聯合設計,特別是動態分布式仿真中具有實際意義,兩種開發環境之間的數據通信是必須要解決的問題。本文提出了利用C++語言的S?函數,在仿真模型中連接S?函數模塊,并利用基于Socket的網絡編程模型實現數據通信,簡單而且靈活,對利用Simulink和VC++進行聯合分布式仿真具有一定的借鑒意義。 參考文獻 [1] 劉杰.基于模型的設計及其嵌入式實現[M].北京:北京航空航天大學出版社,2010. [2] 任泰明.TCP/IP網絡編程[M].北京:人民郵電出版社,2009. [3] [美]李普曼,[美]拉茹瓦,[美]穆.C++ Primer中文版[M].李師賢,譯.4版.北京:人民郵電出版社,2006. [4] [美] PROSISE J. MFC Windows程序設計[M].北京博彥科技發展有限公司,譯. 2版. 北京:清華大學出版社,2007. [5] 侯浩亮,姚新宇,馮曉梅,等.C MEX S函數在Simulink中的應用[J].微計算機信息,2010(19):140?141. [6] 馬強,岳妍瑛,王歡,等.一種基于模式挖掘的網絡通信安全方法研究[J].現代電子技術,2012,35(17):78?82.