CAPL (Communication Access Programming Language) 是一種專門用于嵌入式系統和汽車電子測試領域的編程語言,特別是在 CAN (Controller Area Network) 總線和汽車網絡通信系統中被廣泛使用。它由 Vector 公司開發,主要用于編寫與汽車控制單元 (ECU) 進行通信的測試腳本。
一、初識 CAPL 腳本語言
1.CAPL 的基本特點
- 簡易語法:CAPL 的語法較為簡潔,類似于 C 語言,因此對于有編程背景的人來說,學習起來相對容易。
- 事件驅動:CAPL 腳本通常是事件驅動的,即它會響應特定的事件(如接收到 CAN 消息、定時器超時等)來觸發相關的代碼執行,沒有 main 函數。
- 強大的功能庫:CAPL 提供了一些用于網絡通信的內建函數,能夠方便地進行消息的發送、接收、信號的監控、定時器管理等。
2.CAPL 腳本的基本結構
CAPL 腳本一般包括以下幾個主要部分:
- 事件 (Event):CAPL 腳本最常見的元素是事件,事件可以是定時器、信號接收、CAN 消息的接收等。事件的執行通常是由某個觸發條件來啟動。
- 函數 (Function):CAPL 允許你創建函數,以便在多個地方復用代碼。函數的參數可以是消息對象、信號值或任何你需要的變量。
- 變量 (Variable):CAPL 變量可以用于存儲數據,變量的類型可以是整型、浮動類型、字符串、結構體等。
- 定時器 (Timer):CAPL 提供了定時器功能,可以設置延時后執行某些操作。
3.CAPL 腳本的應用場景
- CAN 總線仿真:通過 CAPL 腳本模擬不同的 CAN 總線節點,并驗證通信協議是否正確。
- 自動化測試:對車輛電子控制單元 (ECU) 進行功能測試,模擬不同的車載設備和網絡環境。
- 故障模擬:模擬 CAN 總線中的異常或故障情景,檢測系統如何應對。
- 實時數據監控與分析:通過 CAPL 腳本實時分析 CAN 總線傳輸的數據包,并做出響應。
4.CAPL 腳本示例(瀏覽即可)
// 定義一個接收消息的事件
on message CanMessage
{// 如果接收到的消息ID是 0x100if (this.id == 0x100){// 打印接收到的消息內容output("Received message with ID 0x100");// 發送一個回復消息output("Sending response...");message CanResponse;CanResponse.id = 0x200;CanResponse.data[0] = 0x01;CanResponse.data[1] = 0x02;output("Response sent.");output(CanResponse);}
}// 設置一個定時器事件,每5秒觸發一次
variables
{msTimer t1;
}on start
{setTimer(t1, 5000); // 設置定時器,5秒后觸發
}on timer t1
{output("Timer expired!");setTimer(t1, 5000); // 重新設置定時器,每5秒觸發一次
}
通過示例可以大致看看 capl 腳本構成是什么樣子,可以去對應一下第二節中所說的基本結構,接下來開始正式學習 capl 腳本
二、CAPL 腳本語言基本數據類型
1.新建文件
在學習語言之前,先來看看怎么創建該文件。打開 CANoe 軟件新建一個項目,在Simulation Setup 組件中:
上圖 1 最右側的 CAN1 是 CAN 總線,按照圖 2 的方式,在 CAN 總線上右擊點擊 Insert Network Node,添加一個 ECU 節點,這個節點便是我們的仿真節點,用來做模擬真實 ECU 在 CAN 總線上的報文信息發送與接收。點擊新建的 ECU 節點上的??圖標,選擇一個文件地址,然后在文件名欄寫入新建的和該 ECU 綁定的 capl 腳本文件名稱,最后點擊確定,進入 CAPL 腳本文件編輯界面。
新建文件后原始界面會出現以下默認模板:
/*@!Encoding:936*/
//這一行是一個注釋,指定了文件的編碼方式。
//在這個案例中,文件的編碼是 936,這是簡體中文字符集的編碼方式(GBK)。includes
{//用于引入其他必要的頭文件或庫文件。//可以通過在 {} 中列出需要的文件路徑,來包含其他CAPL腳本或外部庫文件。//這些文件可以提供函數、變量和其他CAPL文件中的定義。
}variables
{//用于聲明和定義變量。可以在這個塊中定義腳本中需要使用的所有變量,//比如整數、字符串、數組等。
}
2.基本數據類型
1.整數類型:int,long,short,unsigned
2.浮點類型:float,double
3.字符類型:char
4.字符串類型:string,char[ ]( CAPL不直接定義 string
類型,通常通過 char[]
或 string
類型處理字符數組來實現。 )
5.布爾類型:bool
6.枚舉類型:enum
7.時間類型:time
8.指針類型:int*等
下面是一個簡單的示例,通過觸發鍵盤上的鍵然后執行一些操作(建議實操一下)
/*@!Encoding:936*/
includes
{}variables
{int a=20;int b=0366;float c=1.23;float d=23E10;char e ='e';char f[25] = "good job!";
}on key 'c'
{write("%d",a);write("%d",b);write("%f",c);write("%f",d);write("%c",e);write("%s",f);
}
3.結構體類型
在 CAPL 中,結構體通常與 message
關鍵字配合使用,用來定義消息格式。結構體字段可以是基本數據類型(如 int
, float
, char[]
)。可以通過 message
類型來表示結構體字段,然后直接用于發送和接收 CAN 消息。CAPL 中的結構體定義通常是在 message
中完成,便于與 CAN 總線通信相關的數據結構配合使用。
下面是一個簡單的示例,通過觸發鍵盤上的 a 鍵然后執行一些操作(建議實操)
/*@!Encoding:936*/
includes
{}variables
{}on key 'a'
{struct Person{int age;long hight;char name[50];};struct Person p1 = {18,180,"張三"};struct Person p2 = {20,182,"李四"};write("姓名%s,年齡:%d,身高:%d",p1.name,p1.age,p1.hight);write("姓名%s,年齡:%d,身高:%d",p2.name,p2.age,p2.hight);
}
4.message 類型(重點)
在 CAPL 中,message
類型是一個非常重要的概念,用于定義 CAN 總線上的消息格式。CAPL 是為汽車電子系統中的 CAN 總線通信設計的,它通過定義 message
類型來描述消息的結構、字段和信號。CAPL 中的 message
類型不僅定義了數據字段的類型,還通常用于接收和發送消息。
報文選擇器
關鍵字 | 描述 | 數據類型 | 訪問權限 |
CAN | CAN 報文傳輸的邏輯通道,有效值范圍:1-32 | word | —— |
MsgChannel | CAN 報文傳輸的邏輯通道,有效值范圍:1-32 | word | —— |
ID | CAN 報文標識符 | dword | —— |
Name | 報文在 dbc 文件中的名稱 | char[ ] | readonly |
DIR | 報文傳輸方向 | byte | —— |
RTR | 遠程幀標志位 | byte | —— |
DLC | 報文的數據長度,有效值范圍:0-15 | byte | —— |
Byte(x) | 數據字節位有效值范圍:0-63(8-63 字節僅適用于 CAN-FD) | byte | —— |
選中 output 然后按 F1,就會出現官方幫助文檔,會有不同協議的 output 函數解釋
5.定時器類型
在 CAPL 中,定時器是用于實現時間延遲或周期性任務的機制。
CAPL 中的定時器主要有以下幾種類型:
1??setTimer
定時器
setTimer
用于設置一個定時器,它會在指定的時間后觸發一次。定時器會一直運行,直到觸發或被取消。
setTimer(TimerName, TimeInterval);//TimerName: 定時器的名稱,可以是任何合法的標識符。
//TimeInterval: 觸發時間間隔,單位為毫秒(ms)。例如,1000 表示 1 秒。
2??cancelTimer
cancelTimer
用于取消已設置的定時器。它會停止定時器的計時,不再觸發定時器相關的事件。
cancelTimer(TimerName);//TimerName: 要取消的定時器名稱。
如何使用定時器實現超時處理示例:
variables
{message MyMessage 0x01; // 假設這是要發送的消息int counter = 0; // 用于計數
}on start
{setTimer(MyTimer, 1000); // 啟動定時器,每1秒觸發一次
}on timer MyTimer
{counter++; // 增加計數output("Timer triggered, count: %d", counter);if (counter >= 5){cancelTimer(MyTimer); // 如果計數達到5次,則取消定時器output("Timer stopped after 5 triggers.");}else{setTimer(MyTimer, 1000); // 每隔1秒重新啟動定時器}
}
6.CAPL 和 C語言的區別
CAPL 語言和 C語言在設計之處有很多相似的地方,但是為了更好的熟悉 CAPL,這里著重說明一下兩者比較明顯的區別
1??函數定義
CAPL:CAPL 中的函數可以在 functions 部分定義,并且通常沒有 return 類型,除非需要返回值,通常在函數中簡化了對事件和定時器的處理
C語言:C語言中的函數通常需要明確的返回類型,包含返回值/參數等
2??事件處理
CAPL: CAPL 腳本中處理事件的方式與 C 語言的傳統方式不同,CAPL 是事件驅動的,可以定義對消息、定時器等的響應,語法非常簡潔。
C語言: CAPL 腳本中處理事件的方式與 C 語言的傳統方式不同,CAPL 是事件驅動的,可以定義對消息、定時器等的響應,語法非常簡潔。
3??定時器
CAPL:CAPL 中的定時器非常簡便,可以直接通過變量定義并使用。
timers
{TimerName = 1000; // 1000ms
}
C語言: C 語言沒有直接的定時器類型,通常使用 sleep
、usleep
或操作系統提供的定時器功能來模擬定時器。
4??消息發送和接收
CAPL: CAPL 提供了內置的函數來簡化 CAN 總線消息的發送和接收。使用 output
來發送消息,on message
來接收消息。
on message CanMessage
{output("Message received!");
}// 發送消息
message CanMessage msg;
msg.Signal = 100;
output(msg);
C語言: C 語言沒有專門的函數用于處理 CAN 消息,通常需要使用外部庫來實現。發送和接收的實現會依賴于 CAN 總線的驅動和庫。
// 偽代碼:發送CAN消息
sendMessage(CanMessage);// 偽代碼:接收CAN消息
receiveMessage(&CanMessage);
5??類型安全
CAPL: CAPL 是一種較為簡化的語言,它的類型系統較為寬松,允許某些類型的隱式轉換。例如,CAPL中某些變量類型可以直接賦值,而不需要強制類型轉換。
int a = 5;
float b = 2.5;
a = b; // 這種隱式轉換是允許的
C語言: C 語言中對于類型轉換要求嚴格,需要顯式的類型轉換。
6??結構體與數組
CAPL: CAPL 中的結構體和數組語法較為簡化,特別是在事件和消息處理時可以直接使用消息的信號作為結構體成員。
message MyMessage
{int speed;float temperature;
}MyMessage msg;
msg.speed = 100;
msg.temperature = 36.6;
C語言: C 語言提供強大的結構體和數組支持,需要手動定義并管理。
struct MyMessage {int speed;float temperature;
};struct MyMessage msg;
msg.speed = 100;
msg.temperature = 36.6;
7??內存管理
CAPL: CAPL 是為特定應用(如CAN總線通信)設計的語言,并不需要開發者手動管理內存。內存管理通常由工具環境處理。
C語言: C 語言提供了對內存的直接控制,使用 malloc
、free
進行內存分配和釋放。開發者需要顯式地管理內存。
三、發送報文
這一節具體來解析怎么發送一幀報文
/*@!Encoding:936*/
includes
{}variables
{message 0x720 msg_tx; // 定義接收消息,ID 為 0x720
}on key 'b'
{msg_tx.dlc = 3; // 設置數據長度碼 (DLC) 為 3msg_tx.ID = 0x720; // 設置消息 ID 為 0x720msg_tx.byte(0) = 0x02; // 設置消息的第一個字節為 0x02msg_tx.byte(1) = 0x10; // 設置消息的第二個字節為 0x10msg_tx.byte(2) = 0x01; // 設置消息的第三個字節為 0x01// 發出報文write("Sending message with ID 0x666, Data: 0x11 0x22");output(msg_tx);
}
具體代碼如上,呈現的結果如下圖(Rx 需要在工程代碼中實現)
代碼量不多,過程也很簡單,首先先定義一個 message 類型的變量,名為 msg_tx,接著在按鍵 b 事件中進行結果內容實現:
msg_tx.dlc = 3;
:DLC
(數據長度碼)表示消息的數據部分的字節數。dlc = 3
表示該消息的數據部分有 3 個字節。
msg_tx.ID = 0x720;
:設置消息的 ID 為 0x720
,這個 ID 在 CAN 總線上用于標識消息。每個消息在 CAN 總線上都有一個唯一的 ID。
msg_tx.byte(0) = 0x02;
:設置消息的第一個字節為 0x02
。
msg_tx.byte(1) = 0x10;
:設置消息的第二個字節為 0x10
。
msg_tx.byte(2) = 0x01;
:設置消息的第三個字節為 0x01
。
output(msg_tx);
:output
是 CAPL 中用于發送消息的函數,將 msg_tx
消息發送到 CAN 總線,并且在 Trace 窗口中顯示發送的消息。
最后用 write 函數將結果打印在終端。
也可以周期性發送:
variables
{message 0x720 msg_tx; // 定義接收消息,ID 為 0x720msTimer myTimer; // 定義定時器,用于周期性觸發
}on start
{setTimer(myTimer, 10); // 啟動定時器,10 毫秒后第一次觸發
}on timer myTimer
{// 設置消息內容msg_tx.dlc = 3; // 數據長度為 3msg_tx.ID = 0x720; // 設置消息 ID 為 0x720msg_tx.byte(0) = 0x02; // 設置第一個字節為 0x02msg_tx.byte(1) = 0x10; // 設置第二個字節為 0x10msg_tx.byte(2) = 0x01; // 設置第三個字節為 0x01// 發送消息output(msg_tx); // 發送消息 msg_tx// 重新設置定時器,確保每 10 毫秒發送一次setTimer(myTimer, 10); // 10 毫秒后再次觸發定時器
}