上一篇文章介绍了如何创建CANoe工程,需要先阅读这篇文章,才能对整个工程有所了解,包括节点、报文、信号等关键信息。
本文的重点是分析该工程中各个节点CAPL语言是怎么编写的,主要目的在于理解整个工程的实现,具体关于CAPL语言的理论知识总结等以后再分享。
CAPL就是Communication Application Programming Laguage的缩写,CAPL类似于C语言的语法。CAPL语法是C语言的,又有一些C++的特性,this指针,事件等,我觉得和我之前学的JavaScript语言有一些互通的地方。
如图,我们会分别介绍三个节点的CAPL程序。
首先来看一下Engine节点engine.can的代码。
/*@!Encoding:936*/ includes { } variables { //The following three messages are defined for transmission message EngineStatus EngineStat; //定义一个在DBC中message name为EngineStatus的报文变量EngineStat message LockingRq LkCtrlRq; message WindowRq WindowCtrl; //The following timer is for simulating the cyclic message transmission msTimer msTimer_EngineStatus; //定义一个ms定时器msTimer_EngineStatus } on start { setTimerCyclic(msTimer_EngineStatus,100); } on timer msTimer_EngineStatus{ //响应定时器事件msTimer_EngineStatus,将报文发送到总线 EngineStat.Velocity = @sysvar::testNS::Velocity; //获取面板中变量输入的值 EngineStat.IgnitionStatus = @sysvar::testNS::IgnitionStart; output(EngineStat); //将报文发送到总线中 }
先从宏观的角度来看,这个CAPL程序的结构包含了头文件、全局变量、事件函数,没有自定义函数。
includes{ }头文件是对其他文件及dll文件的包含说明,variables{ }是对全局变量的声明定义,on start{ },on timer{ }是工程中用到的各类事件。
CAPL提供了各种网络对应的报文类。本文以CAN message为例,报文变量定义格式:message + message ID/message name + variable
使用message关键字来声明一个报文变量,message后是message ID或CANoe工程导入DBC后的message name,然后是在CAPL程序中要使用的报文变量名。
message EngineStatus EngineStat;
如上,定义一个在DBC中message name为EngineStatus的报文变量EngineStat,此外还定义了报文LockingRq和报文WindowRq。
2. msTimer 定时器变量
CAPL提供两种定时器变量: timer:基于秒(s)的定时器 、msTimer:基于毫秒(ms)的定时器。
msTimer msTimer_EngineStatus;
如上,定义了一个ms定时器msTimer_EngineStatus。
CAPL是一种面向过程、由事件驱动的类C语言。
事件驱动针对于顺序执行,其区别如下:
顺序执行:顺序执行流程中,子例程或过程函数按照代码编写顺序逐句执行。
事件驱动:CAPL程序由事件驱动,工程运行过程中发生指定的事件时才会运行相应的事件处理函数。
事件起始关键字 on
on后加某种事件,工程运行时条件触发,则执行函数体内的语句。
关键字this
系统变量、环境变量或CAN报文事件中,可以用this关键字访问其指代的数据内容。
on start { setTimerCyclic(msTimer_EngineStatus,100); }
如上,on start{ }函数在工程开始时调用,setTimerCyclic(msTimer_EngineStatus,100) 设置定时器msTimer_EngineStatus为一个100ms为周期的循环定时器;
on timer msTimer_EngineStatus{ //响应定时器事件msTimer_EngineStatus,将报文发送到总线 EngineStat.Velocity = @sysvar::testNS::Velocity; //获取面板中变量输入的值 EngineStat.IgnitionStatus = @sysvar::testNS::IgnitionStart; output(EngineStat); //将报文发送到总线中 }
如上,定义了一个响应定时器事件,函数中EngineStat.Velocity = @sysvar::testNS::Velocity;表示面板panel中的系统变量Velocity变化后,将其赋值给EngineStat报文的Velocity 信号。最后的output()函数将报文发送到总线中。
整段engine.can的代码,实现了周期(100ms)循环执行msTimer_EngineStatus事件,将EngineStatus报文每100ms发送到总线中。如果面板panel中报文的两个信号有变化的话,会实时更新。
includes { } variables { byte WindowState = 0; byte IgnitionState; message LockingState LockingStat; message WindowState WindowStat; } on message EngineStatus { //Storing Ignition State if(this.IgnitionStatus) IgnitionState = 1; //Ignition is on else IgnitionState = 0; // Ignition is off //Locking based on velocity if(IgnitionState == 1 && this.Velocity > 15 && LockingStat.LockState == 0){ LockingStat.LockState = 1; output(LockingStat); } } on message WindowRq{ if(IgnitionState){ if(this.WindowRequest == 1) //Roll up window if(WindowState > 0) WindowState--; if(this.WindowRequest == 2) //Roll down window if(WindowState<15) WindowState++; } WindowStat.WindowPosition = WindowState; output(WindowStat); } on message LockingRq{ if(this.LockRequest) LockingStat.LockState = 0; else LockingStat.LockState = 1; output(LockingStat); }
variables { byte WindowState = 0; byte IgnitionState; message LockingState LockingStat; message WindowState WindowStat; }
如上,定义了两个报文变量LockingStat和WindowStat,以及用byte声明了一个字节的变量WindowState和IgnitionState。
CAN消息事件
通过”on message”定义消息事件,该事件会在指定的报文消息被接收时被调用。
on message EngineStatus { //Storing Ignition State if(this.IgnitionStatus) IgnitionState = 1; //Ignition is on else IgnitionState = 0; // Ignition is off //Locking based on velocity if(IgnitionState == 1 && this.Velocity > 15 && LockingStat.LockState == 0){ LockingStat.LockState = 1; output(LockingStat); } }
如上,当收到报文EngineStatus后,执行函数括号中的内容。当报文中的信号IgnitionStatus为1时,IgnitionState变量赋值为1,表示汽车已经启动,反之变量赋值为0。
第二个if语句表示,当汽车已经启动IgnitionState == 1,车速大于15this.Velocity > 15,车是解锁状态LockingStat.LockState,同时符合这三个条件的时候,将LockingStat.LockState置为1,即将车上锁。随后将LockingState报文发送到总线。
其余两个消息事件的内容同理,这边就不赘述了。
/*@!Encoding:936*/ includes { } variables { message LockingRq LkCtrlRq; message WindowRq WinRq; } on sysvar_update testNS::LockRq{ if(@sysvar::testNS::LockRq) LkCtrlRq.LockRequest = 1; //request unlock doors else LkCtrlRq.LockRequest = 0; //request Lock Doors output(LkCtrlRq); //Outputs message onto the bus } on sysvar_update testNS::WindowRequest{ WinRq.WindowRequest = @sysvar::testNS::WindowRequest; output(WinRq); //Outputs message onto the bus } on message WindowState{ @sysvar::testNS::WindowState = this.WindowPosition; }
on message WindowState{ @sysvar::testNS::WindowState = this.WindowPosition; }
如上,当收到报文WindowState后,将信号WindowPosition的值赋值给系统变量WindowState,即在面板上可以看到该值的变化。
读取或者设置系统变量的值,用 ‘@’ 或者 sysGetVariable,系统变量要通过 域操作符"::" 在前面加上他的域空间才行。
on sysvar_update testNS::WindowRequest{ WinRq.WindowRequest = @sysvar::testNS::WindowRequest; output(WinRq); //Outputs message onto the bus }
如上,当在面板panel对系统变量WindowRequest进行操作后,会执行函数括号里的内容,将系统变量的值赋值给报文的WindowRequest信号,然后将报文发送到总线。
on sysvar 和 on sysvar_update的区别
on sysvar :值变化才会触发
on sysvar_update :只要对系统变量操作了,值不变化也会触发