之前斷斷續(xù)續(xù)分享過一些Vector工具的使用總結,如下所示,包括CANape、CANoe、CANalyzer。
然而里面還有一項最重要的,也是平時使用過程中,提效很明顯的CAPL腳本的使用,覆蓋整車各節(jié)點的模擬、軟件刷寫、診斷測試等等,今天就來理一理CAPL腳本。 首先理一理基本語法和常用語句。在打開CAPl編輯界面時,下默認組成部分有:Include、Variable、System、Value Objects,其中Include為需要包含的已存在的頭文件,一般不配置;Variable為申明與定義全局變量,需要定義的變量包括需要發(fā)送的信號以及其數據類型。CAPL中的數據類型有: 無符號整型:byte(1字節(jié)),word(2字節(jié)),dword(4字節(jié))
有符號整型:int(2字節(jié)),long(4字節(jié))
浮點數:float(8字節(jié)),double(16字節(jié))
CAN消息類型:Message;定時器類型:timer(單位為s),msTimer(單位為ms);
單個字符:char(1字節(jié))。
除了界面基礎的信息外,在CAPL腳本中,我們大量使用官方定的的一些接口,這些接口通常需要查看help文檔或者是CAPL的手冊,下面是梳理的一些常用接口。
1、定時器
CAPL中的定時器的使用相當頻繁,比如測試時需要向定時發(fā)送某條CAN報文時就需要用定時器;定時器的聲明:
msTimer myTimer1;//聲明了一個ms定時器,定時單位是毫秒 timermyTimer2;//聲明了一個以秒為單位的定時器;
設置定時器:
setTimer(myTimer1,400);//設置定時器myTimer1為一個400ms定時器; setTimerCyclic(myTimer2,2);//設置定時器myTimer2為一個2s為周期的循環(huán)定時器;
設置定時器定時事件,即當定時器計時時間到時將要執(zhí)行的操作:
on timer myTimer1 { ....... }
2、信息的操作和發(fā)送
//CAN消息發(fā)送: message0x7ffMsg;//聲明一個message,ID=0x7ff Msg.dlc=8;//設置其DLC=8; Msg.id=0x100;//更改其ID=0x100; Msg.byte(0)=55;//設置數據場的第一個字節(jié)為55 output(Msg);//發(fā)送Msg //CANFD消息發(fā)送: msg1.FDF=1; msg1.BRS=1; msg1.dlc=8; Msg.id=0x100;//更改其ID=0x100; msg1.byte(0)=0x44; msg1.byte(10)=0x10; msg1.byte(11)=0x11; output(Msg);//發(fā)送Msg
3、節(jié)點上下線操作
節(jié)點是在dbc中定義的,如VCU,BMS,MCU等,有時需要將它們離線,離線后不再向總線上發(fā)送報文,在線時可以向總線上發(fā)送報文。
節(jié)點上線:
voidtestSetEcuOnline(dbNodeaNode); void testSetEcuOnline(char aNodeName[]);
節(jié)點下線:
voidtestSetEcuOffline(dbNodeaNode); void testSetEcuOffline(char aNodeName[]);
4、檢查錯誤幀
進行CAN通訊的測試時,檢查錯誤幀是很常見的,要用CAPL腳本實現自動檢測錯誤幀也不困難,它的核心就是調用錯誤檢查函數ChkStart_ErrorFrameOccured(),該函數一旦被調用,CANoe就會從此函數被調用時開始持續(xù)檢測總線上有沒有出現錯誤幀。
下面是一個小的例子
dword chechId; dword numCheckEvents; checkId=ChkStart_ErrorFrameOccured();//開始檢測錯誤幀 TestAddCondition(checkId);//添加檢測條件,如果出現了錯誤幀,則輸出報告中會記錄下來 TestWaitForTimeout(5000);//持續(xù)檢測5s checkControl_Stop(checkId);//停止檢測錯誤幀 numCheckEvents=ChkQuery_NumEvents(checkId);//對5s內的檢測情況進行獲取,若函數返回0則沒有出現錯誤幀 if(numCheckEvents>0) TestStepFail("Error Frames Occured");
5、添加事件信號
這種事件信號相當于信號量機制,一般使用在需要等待某個或者是多個條件滿足時進行下一步操作。
具體做法是:在一個位置添加需要等待的事件,程序中的其他地方,如果某個事件發(fā)生了(如周期超界等),提供該事件的供應,則等待的程序段獲得了該事件,繼續(xù)執(zhí)行下面的操作。主要使用的函數有以下幾個:
//供應text事件 long TestSupplyTextEvent( char aText[] ); //添加text事件 long TestJoinTextEvent(char[]aText); //等待text事件,有一個出現則程序執(zhí)行下一步 long TestWaitForAnyJoinedEvent(dword aTimeout); //等待text事件,所有等待事件都出現則程序執(zhí)行下一步 long TestWaitForAllJoinedEvents(dword aTimeout);
以下是一個例子:
TestJoinTextEvent("Test finished"); TestJoinTextEvent("Error Frame Occured"); TestWaitForAnyJoinedEvents(20000); 或者: TestWaitForAllJoinedEvents(20000); 在系統事件on errorFrame中: on errorFrame { TestSupplyTextEvent("Error Frame occured"); } 在系統的on message 中: on message 0x400 { TestSupplyTextEvent("Test Finished") }
6、回調函數
CAPL中也有類似于C語言中的回調函數的機制,如檢測報文周期和錯誤幀的函數中就可以使用,當周期超界或者總線出現錯誤幀就會自動調用回調函數執(zhí)行一些操作;如:
ErrChkId=ChkStart_ErrorFramesOccured("Callback_ErrorFrameOccured");//檢查錯誤幀,如果發(fā)現錯誤幀就調用回調函數 回調函數設計如下: void Callback_errorFrameOccured(dword chk_id) { float t; t=timeNow()/100000.0;//記錄出現錯誤幀的時間 testStep("ErrorFrameTimeStamp","%.6f s",t);//打印該事件戳 TestSupplyTextEvent("ErrorFrameOccured");//供應Text事件 }
7、監(jiān)視總線的情況,這一般會用在查看一段時間內,總線上有沒有出現通訊異常的情況。需要使用函數ChkStart_NodeBabbling( ). 如,檢測一段時間內總線有沒有出現停止通訊的情況:
CheckId=ChkStart_NodeBabbling(CAN::PT_MCU,0);//立即開始檢查總線狀態(tài) testWaitForTimeout(2000);//延時2s ChkControl_Stop(CheckId);//停止檢測 QueryNumberEvents=ChkQuery_NumEvents(CheckId);//如果在2s內總線停止通訊,則QueryNumberEvents!=0
8、關于獲取關鍵時間點
(1)CANoe中獲取定時器當前計時值的函數為:timerToElapse();該函數原型如下:
long timerToElapse(timer); long timerToElpase(msTimer);
(2)獲取等待某個事件的時間,需要使用函數TestGetLastWaitElapsedTimeNS(),其原型如下:
float TestGetLastWaitElapsedTimeNS();
(3)獲取當前的仿真時間點:
float timeNowFloat();
(4)等待指定報文:
long TestWaitForMessage(dbMessage aMessage,dword aTimeout); long TestWaitForMessage(dword aMessageId,dword aTimeout);
若在aTimeout時間內等到了指定ID的報文,函數返回1,否則返回0;
(5)獲取報文的數據,等到了報文之后,如果想知道報文的具體內容可以使用函數:
message msg; long result; result=TestGetWaitEventMsgData(msg); .....處理msg.....
9、多總線測試
設置總線背景,一般都總線測試都會有兩路及以上的CAN,這時若要通過CAPL腳本獲取某個CAN通道上的報文時,就需要先設置好總線背景,即將總線設置為值監(jiān)聽某一路的CAN通道。下面是一個例子:
void BusContextConfiguration(char yBus[]) { yBusContext=GetBusNameContext(yBus);//這里的yBusContext為全局變量 SetBusContext(yBusContext); } //使用: BusContextConfiguration("CAN1");//將總線監(jiān)聽設為CAN1
此時等待某一路的CAN報文可是這樣實現:
res=testWaitForMessage(CAN1::NM_IPU,600);//等待CAN1上的名稱為NM_IPU的報文,等待事件為600ms
10、診斷報文的發(fā)送和接收
request_A.SendRequest();//診斷請求 TestWaitForDiagResponse(request_A,5000);//診斷接收
11、
將診斷請求 / 響應寫入報告
TestReportWriteDiagObject (diagRequest req); TestReportWriteDiagObject (diagResponse resp); TestReportWriteDiagResponse (diagRequest req);
12、獲取診斷請求 / 響應的原始數據
long diagGetPrimitiveByte( diagRequest request, DWORD bytePos); long diagGetPrimitiveByte( diagResponse response, DWORD bytePos);
13、獲取診斷請求 / 響應的參數
long diagGetParameter (diagResponse obj, char parameterName[], double output[1]) long diagGetParameter (diagRequest obj, char parameterName[], double output [1]) double diagGetParameter (diagResponse obj, char parameterName[]) double DiagGetParameter (diagRequest obj, char parameterName[]) long diagGetParameter (diagResponse obj, long mode, char parameterName[], double output[1]) long DiagGetParameter (diagRequest obj, long mode, char parameterName[], double output [1]) double diagGetParameter (diagResponse obj, long mode, char parameterName[]) double diagGetParameter (diagRequest obj, long mode, char parameterName[])
最后分享最近剛使用CAPL腳本的一些注意點以及一個示例
第一CAPL 的局部變量是靜態(tài)局部變量。經過使用發(fā)現,在 variables{ }之外,事件或者函數內部定義的局部變量是靜態(tài)局部變量,其值不會因為退出本事件或者函數,而變?yōu)槌跏贾?。所以如果真的需要一個局部變量,在每次退出之前,重新使用賦值語句賦為初始值。
第二建議使用系統變量或者環(huán)境變量,這樣可以跨不同的capl腳本操作,比如檢測某個環(huán)境變量或者系統變量的變化,來執(zhí)行一些動作。
on sysvar sysvar::EngineStateSwitch { $EngineState::OnOff = @this; if(@this) $EngineState::EngineSpeed = @sysvar::Engine::EngineSpeedEntry; else $EngineState::EngineSpeed = 0; }
第三個就是以太網轉CAN的capl腳本示例:
/*@!Encoding:1252*/ variables { // // Constants // const WORD kPort = 23; // UDP port number for instance const WORD kRxBufferSize = 1500; const WORD kTxBufferSize = 1500; // // Structure of UDP payload // _align(1) struct CANData { BYTE dlc; BYTE flags; // Bit 7 - Frame type (0 = standard, 1 = extended) // Bit 6 - RTR bit ('1' = RTR bit is set) DWORD canId; BYTE canData[8]; }; // // Global variables // UdpSocket gSocket; CHAR gRxBuffer[kRxBufferSize]; CHAR gTxBuffer[kTxBufferSize]; DWORD gOwnAddress; DWORD gModuleAddress= 0xFFFFFFFF; // default is the broadcast address 255.255.255.255 and the TCP/IP stack will build the Network broadcast address } // // Measurement start handler // on start { DWORD addresses[1]; // get own IP address of the Windows TCP/IP stack IpGetAdapterAddress( 1, addresses, elcount(addresses) ); gOwnAddress = addresses[0]; // open UDP socket gSocket = UdpSocket::Open( 0, kPort ); if (gSocket.GetLastSocketError() != 0) { write( "<%BASE_FILE_NAME%> Open UDP socket failed, result %d. Measurement stopped!", gSocket.GetLastSocketError() ); stop(); return; } if (gSocket.ReceiveFrom( gRxBuffer, elcount(gRxBuffer) ) != 0) { if (gSocket.GetLastSocketError() != 997) // ignore pending IO operation { write( "<%BASE_FILE_NAME%> UDPReceive failed, result %d. Measurement stopped!", gSocket.GetLastSocketError() ); stop(); return; } } } // // On receive UDP data handler using CAPL Callback // void OnUdpReceiveFrom( dword socket, long result, dword address, dword port, char buffer[], dword size) { DWORD dataOffset; struct CANData canData; message * canMsg; if(address==gOwnAddress)return;//ignoreownbroadcasts // // Store IP address of module to reach // if (gModuleAddress == 0) { gModuleAddress = address; } // // Handle received data // dataOffset = 0; while (dataOffset + __size_of(struct CANData) <= size) { memcpy( canData, buffer, dataOffset ); canMsg.id = (canData.canId & 0x1FFFFFFF) | ((canData.flags & 0x80) ? 0x80000000 : 0); canMsg.dlc = canData.dlc & 0x0f; canMsg.rtr = ((canData.flags & 0x40) ? 1 : 0); canMsg.byte(0) = canData.canData[0]; canMsg.byte(1) = canData.canData[1]; canMsg.byte(2) = canData.canData[2]; canMsg.byte(3) = canData.canData[3]; canMsg.byte(4) = canData.canData[4]; canMsg.byte(5) = canData.canData[5]; canMsg.byte(6) = canData.canData[6]; canMsg.byte(7) = canData.canData[7]; output( canMsg ); dataOffset += __size_of(struct CANData); } // // Receive more data // if (gSocket.ReceiveFrom( gRxBuffer, elcount(gRxBuffer) ) != 0) { if (gSocket.GetLastSocketError() != 997) // ignore pending IO operation { write( "<%BASE_FILE_NAME%> UDPReceive failed, result %d. Measurement stopped!", gSocket.GetLastSocketError() ); stop(); return; } } } // // Handler for CAN messages // on message * { int i; struct CANData canData; if ((this.dir == RX) && (gModuleAddress != 0)) { canData.canId = this.id & 0x1FFFFFFF; canData.flags = ((this.id & 0x80000000) ? 0x80 : 0x00) | ((this.rtr == 1) ? 0x40 : 0x00); canData.dlc = this.dlc; for( i = 0; i < 8; i++ ) { canData.canData[i] = (i < this.dlc) ? this.byte(i) : 0; } memcpy( gTxBuffer, canData ); gSocket.SendTo( gModuleAddress, kPort, gTxBuffer, __size_of(struct CANData) ); } else if (gModuleAddress == 0) { write( "<%BASE_FILE_NAME%> Tx not possible. Module to reach must send packets first." ); //Server simulation } } 審核編輯:黃飛
-
C語言
+關注
關注
180文章
7604瀏覽量
136695 -
函數
+關注
關注
3文章
4327瀏覽量
62573 -
腳本
+關注
關注
1文章
389瀏覽量
14858
原文標題:CAPL腳本使用介紹
文章出處:【微信號:eng2mot,微信公眾號:汽車ECU開發(fā)】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論