深入浅出学习CAN系列-了解CAPL编程语言

前言

        上一篇文章介绍了如何创建CANoe工程,需要先阅读这篇文章,才能对整个工程有所了解,包括节点、报文、信号等关键信息。

        本文的重点是分析该工程中各个节点CAPL语言是怎么编写的,主要目的在于理解整个工程的实现,具体关于CAPL语言的理论知识总结等以后再分享。

深入浅出学习CAN系列-了解CAPL编程语言

        CAPL就是Communication Application Programming Laguage的缩写,CAPL类似于C语言的语法。CAPL语法是C语言的,又有一些C++的特性,this指针,事件等,我觉得和我之前学的JavaScript语言有一些互通的地方。        

        如图,我们会分别介绍三个节点的CAPL程序。

Engine.can

        首先来看一下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{ }是工程中用到的各类事件。

全局变量   

  1. message 报文变量

        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程序由事件驱动,工程运行过程中发生指定的事件时才会运行相应的事件处理函数。

深入浅出学习CAN系列-了解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中报文的两个信号有变化的话,会实时更新。

Doors.can

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报文发送到总线。

        其余两个消息事件的内容同理,这边就不赘述了。

console.can

/*@!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 :只要对系统变量操作了,值不变化也会触发

上一篇:C# 实现WinForm窗口最小化到系统托盘代码


下一篇:WinForm设置任务栏托盘程序