ページナビゲーション
- ページの先頭へ
CAPLを使う際のヒントとコツ

CAPLはVector Informatik(ベクター本社)によって開発された、Cライクな手続き型プログラミング言語です。プログラムブロックの実行はイベントによって制御されます。CAPLプログラムは専用のブラウザーで開発およびコンパイルされます。これにより、システム変数をはじめ、データベースに含まれているあらゆるオブジェクト (メッセージ、シグナル、環境変数)へのアクセスが可能になります。また、CAPLはユーザーをサポートするために、多くの定義済み関数を備えています。
CAPLは、CANoeやCANalyzerによってサポートされています。
まず、CAPLの基礎について解説します。主にCAPLを初めて学ぶ方を対象にしていますが、十分な知識があるユーザーの方もCAPLを構成する際に生かせるヒントを見つけていただけると思います。次に、CAPLの高度な機能について説明します。最後に、パフォーマンスとメモリのニーズや、データベースと連想配列を使用する際のヒントとコツを解説します。
CAPLは下記動画でも紹介しています。
- 短縮版動画「CAPL Basics」 (3分30秒)(言語:英語)
- 完全版オンラインセミナー(録画)「Introduction to CAPL」 (60分)(言語:英語)
はじめに
CAPLは初めてDOS版CANalyzerに導入されて以来、20年以上にわたり、単純な刺激入力から複雑なバスノードのシミュレーションまで幅広いタスクの実装に役立ってきました。(以下、「CANoe」をCANoeとCANalyzerの2製品の総称として使用します。)CAPLは常に、特定のタスクをできるだけシンプルに解決することを目標としてきました。代表的なタスクは、受信メッセージへの反応、シグナル値のチェックと設定、メッセージの送信です。プログラムはこれらの用途に特化したもので、かつ、付加的なオーバーヘッドはありません。
CANoeユーザーが通常行うプログラミング作業の多くは、短くて単純なものかもしれません。しかし、それほど単純には済まないタスクも数多く存在します。だからこそ、CAPLは長年にわたって拡張が加えられ、「可能な限りシンプルに」の原則に従って、複雑なタスクも解決できるプログラミング言語に進化してきています。
「CAPL」は、Communication Access Programming Languageの略です。当初はCANだけでしたが、次第に拡張され、LIN、FlexRay、MOST、J1587、ARINC、CANopenなどのあらゆる車両バスシステムに対応するようになりました。
他の多くのプログラミング言語と同様、CAPLの構文規則はC言語に基づいています。CやC#言語、あるいは最近の多様なスクリプト言語の知識があれば、短期間でCAPLを使いこなせるようになります。ただし、CAPLプログラムにはCプログラムと一線を画す、独自の特徴がいくつかあります。
- CAPLプログラムは、イベント駆動型です。つまり、CAPLプログラムは個々の関数の集まりであって、その関数の1つ1つが、メッセージの受信、シグナルの変化、タイマーの期限切れ、さらには「環境」の変化といった、解析対象のシステム内のイベントに反応します。たとえば、「EngineState」というメッセージに反応するには、「On message EngineState」を使用します (図1)。
- CAPLプログラムは、解析対象のシステムのコンセプトに応じた固有のデータベースを使用します。このデータベース内でメッセージやシグナルの名前を定義し、その名前をプログラムコード内で直接使用できます。図1のメッセージの「EngineState」や、このメッセージ内のシグナルの「EngineSpeed」がこの名前に当たります。
- CAPLプログラムには、ポインタ型は使用しません。そのため、Cプログラミングでしばしば発生しがちな潜在的なプログラミングエラーやプログラムクラッシュの原因を根本から排除することができます。しかし、エラーを招きやすいとはいえ、ポインタの概念が非常に優れていることは確かです。そのためCAPLにはポインタに代わる機能がいくつか用意されています。動的メモリの代用となる連想配列もその1つです。
CAPLとC言語に共通する重要な性質として忘れてはならないのが、CAPLは常にコンパイルされるという点です。つまり、CAPLは効率的に実行できる、柔軟性の高いマシンコードに変換されます。

YouTube掲載動画
CAPL Basics
3 Examples of CAPL programming in CANoe and CANalyzer (3分30秒)(言語:英語)
![[Translate to Japanese:] Playback](https://cdn.vector.com/cms/content/products/canoe/capl/CAPL_webinar_recording_graphic.jpg)
オンラインセミナー(録画)
Introduction to CAPL
... for CANoe and CANalyzer (60分)(言語:英語)
タスクの説明
- ここでのタスクはCANネットワークを監視することです。このバスのノード、メッセージ、転送されるシグナルといった要素は、データベースに記述されています。
- 「EngineState」メッセージを受信すると、それに含まれる「EngineSpeed」シグナルを取得し、パネルに表示させます。
- 「LightState」メッセージを受信すると、それに含まれる「HeadLight」と「Flashlight」のシグナルを取得し、グラフィック表示用パネルに表示させます。
プログラムの説明
図1にある行番号はCAPLプログラムの一部ではなく、個々の行やセクションを参照しやすいよう挿入されたものです。また、図1では見た目をコンパクトにまとめるため、角かっこは改行せずに配置しています。
CAPLプログラムではグローバル変数とグローバル定数の定義が可能です。これは「variables」セクション (1行目から5行目)で行われます。このプログラムでは、ここに記載されている定数と変数がグローバルに定義されています。これらはプログラム内の任意の場所で使用できますが、同じCANoeアプリケーション内の別のプログラムでは使用できません。他のセクションではイベントに対する反応 (7行目から17行目)と補助(ユーザー定義)関数 (19行目から28行目)が定義されています。
7行目から9行目はメッセージイベントプロシージャの最小限の形を示しています。この関数はこのメッセージがバス上で送信されるたびに呼び出されます。CANの場合、これが呼び出される正確な時点はCANコントローラーでのTXまたはRXの割込みの時点、すなわちメッセージが正しく送信された直後になります。特定の関数を呼び出すトリガーとなるメッセージは、this構文で指定できます。
8行目では、受信されたメッセージ (this)から「EngineSpeed」シグナルの値が読み出され、変換 (/1000.0)後にシステム変数に割り当てられています。
11行目から17行目は「LightState」メッセージのためのメッセージイベントプロシージャを示していて、方向指示器に関する情報を伝送しています。その処理はEngineStateメッセージと似ていますが、次のような特徴があります。12行目では、先ほど転送されたメッセージ(this)の方向フラグ(.dir)がチェックされています。このプログラムでは受信したメッセージ (RX値)のみ考慮する必要があります。なぜなら、ノードから送信されたメッセージも、イベントプロシージャ (TX値)をもトリガーするためです。この場合、エラーメッセージが15行目に出力されます。
シグナルをインターフェイス (各種の状態がそれぞれのビットマップで表示されるパネル)上で表示させる処理はやや複雑であるため、その実装は独立した関数に任されています。13行目では、パラメーターとして求められる2つのメッセージシグナルを用いて、「SetLightDsp」が呼び出されています。
最後に、19行目から28行目では独立した関数を定義しており、転送されたシグナル値に応じて、Lightsのネームスペースにあるシステム変数「LightDisplay」にそれぞれの値を書き込んでいます。このサンプルコンフィギュレーションでは、この変数によってディスプレイパネルに適切なビットマップが表示されます。
実行モデル
CAPLとCあるいはC++との主な違いは、プログラム要素が呼び出されるタイミングとその方法にあります。たとえば、Cでは、処理シーケンスはすべて、最初のmain()関数から始まります。一方CAPLプログラムでは、それぞれが以下のようなイベントに反応する一連のプロシージャが含まれています。
- システムによるトリガー:これらのイベントには、on preStart、on start、on preStop、on stopMeasurementや時間制御およびキーボードイベントのon timerとon keyなどの、実行する測定の初期化や後処理に役立つものが含まれています。
- バス通信によるトリガー:通信やエラー処理に関連したバスイベントに反応するイベントプロシージャにはさまざまなタイプのものがあり、それらはバスの種類に大きく依存します。これらのイベントには、CANのon messageやon busOffや、FlexRayのon frFrameやon frStartCycleなどがあります。
- Value Objectによるトリガー: これらのオブジェクトには、CANoe/CANalyzerでグローバルに使用できるシステム変数と環境変数のほか、バス通信をデータ解釈したシグナル値が含まれます。この解釈は専用のデータベースによって行われます。このコンセプトについては、以降のセクションで説明します。
イベントプロシージャはアトミック:CANoeのシミュレーションモデルはイベント指向です。イベントプロシージャでは、CANoeがすべてのアクションをモデルの観点から同時に、具体的にはトリガーイベントが発生したタイミングで実行します。PCで生じる実際の計算時間は無視されます。
シミュレーション時間とタイムスタンプ:ただし、PCによって生成された実際のイベント、たとえばoutput()によるバス出力などには、リアルタイムクロックのタイムスタンプが与えられます。これらのイベントのシーケンスと時間ポイントは、バスのプロトコル、ドライバー、ハードウェアの特性から影響を受けます。仮想バスでは、そのような影響を及ぼすパラメータの一部を排除できます。その場合、バスイベントは同時に開始され、たとえばCANであれば、output()から複数のメッセージを出力する際、確実にアービトレーションを実施して送信できます。
システム変数の更新:ユーザーはCAPLを使ってプログラムの外にある環境変数やシステム変数を変更することも可能です。CAPLはイベント処理と同じタイミングで値を変更しますが、その変更はそのイベント処理が完了するまでは変数に反映されません。イベント処理中に読取りアクセスを行うと、その変数に新しい値が設定されているように見えても、常に古い値が返されます。これにより、値の変化が、ある特定の時点での1回しか発生しえないというメリットがあります。
実行モデルは状況に依存:CANoeやCANalyzerでは、さまざまな方法でCAPLが使用されます。そのため、その実行モデルも必ずしも一様ではありません。CANoeシミュレーションの仮想ノードはバス上に並行して存在し、それらは互いに完全に独立しています。トリガーイベントは常にすべてのプログラムにて有効です。対照的に、測定設定のノードやCANalyzerのノードは順次処理、すなわち各ノードが次のノードへと出力を渡していく形で処理されます。各ノードはその出力を次のノードに渡します。処理を進めるためには、渡されたイベントを明確に次のノードに渡さなければなりません。on *およびon [*]のプロシージャは、その目的で用意されています。
その他、テストプロシージャが外部イベントを待機できるタイプのテスト用プログラムがあります。CAPLの場合、そのようなイベントにはシミュレーション時間を用いて処理を再開します。対照的に、通常のイベントプロシージャでは、ループ処理など待機状態が発生するとシミュレーションシステム全体が停止します。これはCAPLを使用する場合によくある、エラー原因の1つです。そのため、外部DLLのビジーウェイトやウェイトコマンドは使用しないようにしてください。
CAPLでの効率的なプログラミング
C言語のプリプロセッサは強力なツールである一方、使用を誤ってエラーを招く場合もあります。したがってCAPLでは、C言語でよく知られているプリプロセッサディレクティブの一部のみをC言語と同等のセマンティクスで提供します。
#include: include文、変数、プロシージャなど、CAPLプログラムのセクションがすべて揃った、任意のファイルを読み込みます。Cとは対照的に、includeファイルのテキストは、単にCAPLファイルに挿入されるのではなく、セクションとして挿入されます。includeファイルのすべてのセクションが、親となるCAPLファイルにあたかも最初から含まれていたかのように、全体に適用されます。CAPLではセクションの順序にはまったく意味はありません。そのため、コンパイラは重複しているすべてのシンボルをエラーとして報告します。また、includeファイルと親ファイルのコードとデータは相互に使用することが可能です。先ほど重複シンボルは避けると述べましたが、これには1つ例外があります。on start、on preStart、on preStop、on stopMeasurementは、includeファイルと親ファイルの両方に存在することもあるということです。これらの関数では、最初にincludeファイルのコード、次に親ファイルのコード、というように順次実行されます。つまり、includeファイルは「データタイプの宣言」、「変数の定義」、「(インライン) 関数ライブラリの提供」という3つのタスクの実行に使われることになります。
#pragmaライブラリ:CAPLプログラムは、適切なCAPL DLLインターフェイスさえ実装すれば、他言語で作成されたWindows DLLを使用することができます。これらのDLLは直接、#pragma library("capldll.dll")というディレクティブでリンクできます。
マクロ:CAPLには多数のマクロが事前に定義されており、ユーザーはそれらをコードや条件付きコンパイルで使用できます。コードで使用されるマクロは、コード内の任意の場所で制限なく使用できます。C言語とは対照的に、マクロは文字列定数、変数の識別子、関数名の中で自由に使用できます。マクロは常に%文字で始まり、%文字で終わります。また、基本的に汎用的なプログラムを記述するのに使用されます。
利用可能なコードマクロには、使用中のノード名、現在のチャネルインデックス、現在のネットワーク名、バスのタイプが含まれます。このコードは%FILE_NAME%を含むファイル、または、%BASE_FILE_NAME%を使用して現在コンパイルされているプログラムファイル名にアクセスできます。includeファイルの場合、後者コードは親ファイルを示します。
以下に簡単な例を2つ示します。
write("The node name is %NODE_NAME%"); putValue(envChannel%CHANNEL%Var1, %CHANNEL%); |
コードセクションにおける条件付きコンパイルのため、事前定義されたマクロのセットがあります。それらは#if、#else、#elifまたは#endifです。これらを使えばプログラム内で、プログラムタイプの仮想ノード、測定ノード、テストプログラムをはじめ、使用されているCANoeのバージョンも識別できます。
次は#pragma messageを使用する例です。
#if (TOOL_MAJOR_VERSION < 8) #pragma message(“This program needs at least CANoe V 8 “) #endif |
#pragma message: #pragma messageディレクティブを使用すれば、ユーザーはコンパイル処理中に独自のメッセージ、つまり、現在コンパイルされているCAPLプログラムのバージョン番号などのメッセージを出力することができます。このメッセージはコンパイラからのその他のメッセージ、警告、エラー、通常のメッセージなどと一緒に表示されます。
連想配列
CAPLはC言語とは異なり、参照データタイプとしてポインタオブジェクトをサポートしていないため、動的なメモリ管理は行っていません。このため、CAPLは非常に堅牢であり、メモリ不足でデバッグが困難なランタイム環境によく適しています。特に、CANoeの「CAPL-onBoard」機能はその恩恵を受けており、リアルタイム性を高めるために、特定のハードウェアバスインターフェイス上で直接プログラムを実行するようになっています。とはいえ、Windowsのランタイム環境ではメモリが不足することはめったにありません。そのため、このランタイム環境でのCAPLでは、プログラム起動時に格納するデータ量が不明でも、データを格納できる連想配列を用意しています。
連想配列は、他のプログラミング言語におけるマップや動的配列に相当するコンテナです。CAPLは内部的に、これらの配列に効率的なハッシュテーブルを使用します。そのため、どのようなメッセージや測定値が発生するか事前にわからない場合でも、これらの特殊な配列によってバスメッセージや測定値を保存することができます。
CAPLでは、連想配列は単純な配列として宣言されますが、通常のサイズエントリーの代わりにキータイプを持っています。
連想配列の例を2つ示します。
long lastTime [long]; char[30] translate[ char[] ]; |
変数lastTimeは、longキーとlong値をマッピングする配列です。一方、translateは文字列キー(長さ制限なし)を30文字までの文字列値にマッピングします。次の例では、lastTimeを使用して、CANネットワーク上で発生した各メッセージIDのタイムスタンプを保存しています。
on message CAN1.*{ = this.time; |
CAPLは、ユーザーの利便性を高めるために、ドット表記を用いた連想配列変数用に以下のメソッドを提供しています。
- containsKeyは、特定のキーがすでに含まれているかを調べる
- sizeは、含まれるキーの数を返す
- clearは連想配列を完全に空にし、removeは連想配列からキーを1つ削除する。実際に、removeとclearはメモリを解放する
最後に、for命令には連想配列用に特別な形式があります。この形式は、lastTimeに実際に含まれるすべてのキーに対して反復処理を行います。
for (long akey: lastTime) {[…]} … |
データベースへのアクセス
本ページのはじめにセクションでは、CAPLにおけるバス固有データベースの主な使用方法を説明し、これによりメッセージとシグナルの名前を使用することができます。プログラミングの観点から言えば、シグナルへのアクセスが複雑な理由として、効率を上げるために通常はメッセージのデータペイロードの中にしっかりと詰め込まれているためです。したがって、一般にシグナルは任意のビット長を持ち、メッセージのデータペイロードの中に配置されます。また、インテルまたはモトローラのいずれかのフォーマットで保存することもできます。
CAPLユーザーは、シグナル名を利用したシンボルベースのアクセスによってこれらの細かな作業負荷を軽減できます。シグナル値の読み取りや設定の際、CAPLコンパイラは自動的に、ビットのマスキング、スワップ、ビットシフトを含むシグナルの精確なビットパターンを考慮します。
ユーザーの利便性向上のため、データベース内に、他のオブジェクトを定義する事によってCAPLプログラミング言語の特性が改善されています。たとえば、シグナル値のステータスにプレーンテキスト名を使用するために、シンボル値テーブルをシグナルに関連づけることができます。さらに、データベース作成者は、他の属性オブジェクトを定義し、プログラムコードで使用することもできます。
CAPLは、シンボルを使用してデータベースオブジェクトを直接利用することが可能です。プログラム実装の時点では、対象となるオブジェクト候補がわからない場合がありますが、CAPLユーザーは、ネットワークノードから伝送されるメッセージ名や識別子などのシンボリック名やプロパティに動的にアクセスすることができます。
以下は簡単な例です。
message * m; int i, mx; mx=elcount(aNet::aNode.Tx); for (i = 0; i < mx; ++i) { m.id=aNet::aNode.TX[i]; write(DBLookup(m).Name); } |
これらのシンボリックアクセス手法を先に紹介した連想配列と併用することで、汎用的なプログラムを実装することができます。
パフォーマンス
CAPLプログラムの多くは、重要なリアルタイム性を満たす必要があります。CAPLでシミュレートされたノードの実行モデルは、CAPLプログラムが任意の速度で実行されるというモデルコンセプトにも準拠しています。この理想に近づけるために、CAPLプログラムはコンパイルされます。すなわち、特定の実行対象マイクロプロセッサの機械語にコンパイルされます。さらに、最適化されたコードを使用することで、シグナルへの複雑なアクセスを実現します。ユーザーのためのヒントをいくつか紹介します。
writeEx():write関数は、CANoeおよびCANalyzerの出力Windowに特定のテキストを出力するために使用されます。writeEx関数を使用すると、より大量のデータを出力することができます。
たとえば、トレースWindowやログファイルに直接書き込むことにも使用できます。writeExで生成されたテキスト出力は、あらゆる点でバスイベントと同様に扱われるため、優先度の高い処理や実バスイベントとのタイムスタンプ同期などが実現されています。
イベントプロシージャ:CAPLプログラムは、イベントに反応するプロシージャの組み合わせで構成されています。これらのイベントの中には非常に頻繁に発生するものがあるため、必要なイベントだけを処理するようにすることで、プログラムの性能は大幅に良くなります。たとえば、ユーザーが特定のシグナルを含むFlexRayスロットにのみ興味がある場合は、on frSlot signalnameで定義する方が、on frSlot *で定義するよりも効率的です。
シグナル変化:シグナルとシステム変数には2つのイベントプロシージャがあります。on signal_updateとon sysvar_updateは、オブジェクトの値が全く変化していなくても、特定のデータオブジェクトへ書き込みアクセスが発生するたびに呼び出されます。これに対し、on signal_change (on signalと略す) とon sysvar_change (on sysvarと略す) は、シグナル変更が処理される場合に性能上の利点となります。これらのイベントプロシージャは、値の変化のみでトリガーがかかるよう最適化されています。
メモリに対する要求
C言語のような多くのブロック指向言語とは異なり、CAPLではローカルに定義された変数はデフォルトですべて静的です。つまり、それらはすべてプログラム開始時に作成され、これらの変数を格納するために使用されたメモリはプログラムの終了まで解放されません。結果として、多くのイベントプロシージャが同じ種類の大きな変数を定義し、それを実際に共有すると、CAPLは驚くほど大量のメモリを必要とすることがあります。
例:
testcase test789() { char outBuffer[1024]; [..] |
CAPLプログラムには、このようなテストプロシージャが何千とあり、そのうちの1つだけが一度に実行できます。各イベントプロシージャで同じタイプの大きなローカル変数を複数定義するよりも、Variableセクションでグローバルに大きな変数を1つ定義した方がメモリ使用量はかなり少なくなります。
たとえば、イベントデータを各メッセージIDの下に格納するなど、非常に大きな配列を作成することも好ましくはありません。CANの拡張IDは29ビットであるため、5億個以上の値を想定することができます。大きな配列を定義することはメモリの無駄づかいになるため、このような場合は前述のように連想配列を使用する方がよくなります。連想配列では、実際に使用されるキーの分だけメモリが必要になりますが、使用されないキーの分のメモリは不要です。
あまり知られていない便利な機能
最後に、あまり知られていないCAPLの機能を簡単にご紹介します。
- 構造体は、C言語のアプローチと同様に構造体を定義するために使用できます。構造体内でインテルとモトローラのフォーマットを変換することもできるコピー操作と共に、データ変換の柔軟な手法となっています。
- CAPL関数を呼び出す際、値パラメータに加えて参照パラメータを渡すことができます。参照パラメータを使用すると、1つの関数から複数の結果を返せるようになります。参照パラメータは、CAPL-DLL内で使用することも可能です。
- また、CAPLプログラムは誤った使い方をした場合にクラッシュしないように設計されています。一般的なポインタが存在しないことから、この堅牢性が言語構造により達成されます。
- 一方、配列の制限、スタックの制限、必要な演算時間の自動的なランタイムチェックにより、安定性は向上します。コンパイラは、コマンドラインバージョンも利用可能です。このバージョンはスクリプト言語を使用したシーケンス自動化に非常に有用です。
その他のCAPLノウハウ
![[Translate to Japanese:] Acadamy](https://cdn.vector.com/_processed_/a/b/csm_csm_Kursformate_942f447bc4_b78d4b0eac.jpg)
お客様に最適なCANoeまたはCANaylzer用CAPLトレーニングをお探しの際は、弊社Webサイトをご覧ください。
vTESTstudioと組み合わせたCAPLトレーニングもご提供しています。弊社Webサイトではコース一覧をご案内しています。
ご不明な点がございましたら、弊社トレーニング部までお気軽にお問い合わせください。
![[Translate to Japanese:] KnowledgeBase](https://cdn.vector.com/_processed_/f/9/csm_Vector_Icon_knowledge_base-20739-2021-06-22_6e5d872b38.png)
KnowledgeBase(言語:英語)
CAPLのオンライン情報はこちらをご覧ください。
> How to Stimulate Signals, System Variables from CSV via CAPL(言語:英語)
> How to Work with Different Encodings in CAPL(言語:英語)
> ...