目录
六、自定义函数实战——编写一个用于发送ldf中未定义LIN报文发送的函数。
一、什么是函数
函数是编程中的基本构建块之一,它是一段用于完成特定任务的代码块。通过定义函数,我们可以将复杂的程序分解成更小、更易于管理的部分。函数可以接受输入(称为参数),执行一系列操作,并可能返回结果(称为返回值)。使用函数可以提高代码的重用性、可读性和可维护性。
二、函数的分类
函数在大体上可以分为库函数和自定义函数两种。
库函数是我们使用的编程语言中的一些与业务逻辑无关的可以多次复用的代码,为了方便我们的使用,由编译器或者编程语言官方,或者第三方开发者封装完成供我们拿到即可使用的代码,比如之前使用到的write()、output()等函数都属于CAPL的库函数。
自定义函数是由代码编写者在编写代码时根据自己的需求,自己实现的函数,这是由于库函数虽然功能及其强大,但库函数通常都是与业务逻辑无关的代码才会封装为接口函数,这就决定了库函数而我们平常编写业务逻辑时,对某些需要高度复用的逻辑,我们通常需要自己编写自定义函数来实现复用的目的。
三、函数的组成及函数的形式
函数的形式通常是这样的
/*
函数的一般形式
*/
返回值类型 函数名称(函数参数)
{
业务逻辑代码;
返回值;
}
函数返回值:函数执行完毕后返回给调用者的值。在编程中,函数可以执行一系列操作,并可能需要根据这些操作的结果向调用者提供反馈。这种反馈就是通过函数的返回值来实现的。
函数名称:函数的名称
函数参数:在编程中,函数的参数(Parameters)是函数定义时指定的,用于接收传递给函数的数据的变量。这些参数在函数被调用时接收实际的值(称为实际参数或实参),然后在函数体内部被使用。参数允许函数执行不同的任务或操作不同的数据,而无需为每种情况编写单独的函数。
返回值类型:反馈的函数执行的结果的数据类型
四、函数的使用
以CAPL的库函数output为例,讲解一下函数的基础用法,前面我们也已经多次使用过了这个函数。
我们先来看看官方提供的output函数的说明
函数语法为 void output(message msg);
前面的void就是返回值的类型。void是空的意思,即这个output函数的返回值类型为空,也就是说没有返回值。
output是函数的名称
(message msg)则是这个函数接受的参数,表示接受一个类型为message的参数。
表示我们在使用这个函数时,括号内需要填一个message类型的变量。
我们之前每次使用时,确实也是都是把我们定义的message作为参数写在了括号里面进行使用。
五、如何定义并实现一个函数
首先,我们需要遵循函数定义的格式。
在CAPL中,所有的函数在定义前无需声明,所以我们直接进行函数功能的定义即可。
编写一个简单的函数,用于实现两个数字相加作为例子,讲解一下函数的定义。
像这样
/*
返回值类型:word类型
函数名: AddFunction
参数:byte型数据1,byte型数据2
功能:计算两个参数相加的结果
返回值:返回两个参数相加的结果
*/
word AddFunction(byte num1,byte num2)
{
return num1+num2;
}
接着我们使用一下这个函数,看下如何使用这个函数
由于这个函数具有一个word类型的返回值,所以我们可以定义一个word类型的变量用于接受它的返回值。
并且它具有两个byte类型的参数,那么我们按照格式,调用时往括号里填入两个byte类型的数据即可。
像这样
on key'A'
{
word Sum;//定义一个word类型变量sum
Sum = AddFunction(15,30);//使用sum来接受函数AddFunction的返回值,即将这个函数的返回值赋值给sum
write("调用函数之后,sum的结果为%d",Sum);
}
启动工程,按下大写字母A,看看现象
可以发现,write窗口中打印了sum的结果为45。
而我们的代码中并没有45这个数字,只有15和30这两个数字,说明执行这个代码之后,代码为我们计算了15+30的值。
在哪里做的加法呢?
往上寻找发现我们在AddFunction中写了
return num1+num2;
诶,是不是有点像样了。
我们在调用这个函数时,在括号里填上了15,30 。
根据我们定义的格式,就相当于执行的时候,让num1=15,num2=30了。
(15,30)这就是这个函数的实参(实际参数)
而(byte num1,byte num2)则被称为这个函数的形参(形式上的参数)
每当我们调用时,填写的实参的值,就会被传递到函数的形参上去,随后执行函数内部的逻辑。
我们再试一下,修改一下调用时两个函数的实参值,再次看看。
/*
返回值类型:word类型
函数名: AddFunction
参数:byte型数据1,byte型数据2
功能:计算两个参数相加的结果
返回值:返回两个参数相加的结果
*/
word AddFunction(byte num1,byte num2)
{
return num1+num2;
}
on key'A'
{
word Sum;
Sum = AddFunction(96,45);
write("调用函数之后,sum的结果为%d",Sum);
}
这里我修改了调用时的两个实参为(96,45)我们再次运行看看
这次打印的结果为141了,正好是96+45的结果。
发现没有,函数一旦写好,就具有了可以多次使用的能力,函数执行的结果是可以由参数控制的,填入不同的参数,可以得到不同的结果。
那么一段逻辑类似的功能,我们就可以编写为函数,在每次需要使用这个类似功能的时候,只要调用并修改实参的值,就可以得到完全不同的结果了。
六、自定义函数实战——编写一个用于发送ldf中未定义LIN报文发送的函数。
首先我们需要定义一个ldf中没有的LIN报文,像之前一样。
随后定义一个定时器,用于定时自动发送我们自定义的这个LIN报文
再定义一个变量,用于存放LIN报文的8字节数据(实际使用中可以按需定义为7字节、6字节等实际上的字节,这里定义为8字节已经可以涵盖所有的情况。)
这里使用qword来存储这个8字节数据,qword的表示范围正好是64位无符号整数,刚好可以存放8字节数据,如果使用byte来定义,那么在赋值的时候也只能按照每个byte去赋值,需要写8次赋值语句,这里我们使用qword来定义,直接存放8字节,赋值时只需要赋值一次即可。
先将这个8字节数据都初始化为0。
随后再prestart中先将rtr置为0并且发送一次帧头。
而后我们在start中先将这个报文的初始值初始化为全0,再启动循环发送定时器,让这个LIN报文进行自动发送。
然后,开始实现我们的自定义函数。
这里我实现了两个自定义函数
一个用来根据各个信号的起始位和长度以及内容,将其转化为8字节的qword数据
一个用来修改rtr和output我们的自定义LIN报文帧。
随后,我们只需要在其他事件内需要修改LIN报文Data值时调用这两个函数即可。
这里我定义了两个系统变量,通过系统变量值改变的事件来修改Data值。
本系列文章还没有讲解到关于系统变量的部分,这里先简单介绍下如何定义系统变量。
在Environment栏目下,选择SystemVariables,打开系统变量面板。
在弹出的界面左边,红框区域内,右击鼠标,选择new,并在弹出的界面给new的系统变量取一个名字,随后点击ok就定义完成了。
然后我们打开panel,创建两个input/output box,并将这两个输入输出框分别与刚刚创建的两个系统变量绑定起来(第四篇文章有介绍),绑定完毕后保存即可。
最后,我们在CAPL中使用这两个系统变量事件来触发代码。
系统变量事件的语法是
on sysvar +系统变量名//此种形式表示必须要系统变量的值发生改变才触发
{
逻辑代码;
}
或者
on sysvar_updata +系统变量名//此种形式只要系统变量被更新(哪怕未改变,只要被更新,就会触发)
{
逻辑代码;
}
代码直接附上,此代码是较为成熟的代码,可以直接使用在后面需要用到的地方。
variables
{
linFrame 0x36 LinUndefineFrame;//定义一个ldf中未定义的LIN报文帧
msTimer TaskTimer1;//定时器,用于自动发送未定义的LIN报文
qword LIN_8ByteData = 0x0ll;//初始化八字节数据为0x0。末尾加两个小写的L字母,用于表示这是一个64bit的数据。
}
on preStart//预启动事件
{
LinUndefineFrame.rtr = 0;//将rtr置为0才能修改数据。
output(LinUndefineFrame);//发送帧头
}
on timer TaskTimer1//定时器任务,用于发送LIN报文帧
{
output(LinUndefineFrame);
}
on start
{
LinUndefineFrame.qword(0) = LIN_8ByteData;
output(LinUndefineFrame);//发送帧头
LinUndefineFrame.rtr = 1;//置为1才能将报文发送出去
output(LinUndefineFrame);//发送LIN报文帧
setTimerCyclic(TaskTimer1,500);//工程启动即循环开启定时器,发送未定义的LIN帧
}
/*
返回值:无
函数名:SendUndefineLinFrame
参数:void,无参
功能:向LIN总线上发送未定义的LIN帧,发送的LIN帧的数据是全局变量_8ByteDataInitValue。
*/
void SendUndefineLinFrame(void)
{
LinUndefineFrame.rtr = 0;//置为0才能修改报文值
LinUndefineFrame.qword(0) = LIN_8ByteData;//qword,直接修改8byte数据,将变量LIN_8ByteData的值赋值给LIN帧Data
output(LinUndefineFrame);//发送帧头
LinUndefineFrame.rtr = 1;//置为1才能将报文发送出去
output(LinUndefineFrame);//发送LIN报文帧
}
/*
返回值:无
函数名:LinFrame8ByteDataToQword
参数:
DataStartBit:信号的起始位
Data:信号的数据,即要设置的信号的原始值
DataLength:信号的所占的长度(bit)
功能:用于将每个信号按照所在的位置,将信号赋值到64Bit的相应的位置中去,即可以只修改指定位的值,不修改其他位。
*/
void LinFrame8ByteDataToQword(byte DataStartBit,qword Data,byte DataLength)
{
qword Mask;//定义掩码,用于保留从0到startbit-1和stopbit+1到63的位,其余位为0
qword NewData;//新的数据,用于存放Data按照偏移位置移位后的值。
Mask = ~(((1 << DataLength) -1) << DataStartBit);//计算掩码。
//使用位操作来获得每次修改指定位需要用到的掩码。
NewData = Data << DataStartBit;//把Data根据起始位进行偏移来得到按照位置放置的值
LIN_8ByteData = (LIN_8ByteData&Mask)|NewData;//先将八字节数据与掩码进行按位与,清空需要修改的位的值。
//随后再把要修改的新数据与清空后的数据进行按位或后重新赋值给八字节数据
SendUndefineLinFrame();//调用发送未定义LIN帧函数,将修改后的八字节数据发送出去。
}
on sysvar UndefineFrameSig0//系统变量事件
{
LinFrame8ByteDataToQword(8,@this,8);//将该系统变量的值,按照起始位为8和数据长度为8,转换成qword数据。
}
on sysvar UndefineFrameSig1
{
LinFrame8ByteDataToQword(16,@this,16);//将该系统变量的值,按照起始位为16和数据长度为8,转换成qword数据。
}
这里我创建两个系统变量,在panel上通过修改与这两个系统变量绑定的控件的值,来动态修改两个系统变量。
随后,在系统变量事件内,使用this指针获取当前系统变量的值,然后调用我自定义的将指定的数据转换成LIN报文的函数,将系统变量代表的信号按照位置赋值进去。
随后,启动工程,看看现象。LIN报文已经在自动发送了,接下来我们动态修改一下LIN报文各个字节的值。
可以看到,当我修改了信号0之后,LIN报文也随之改变。
修改信号1对应的面板后,LIN报文的第二个字节和第三个字节也对应修改为我输入的值
当我再次修改信号0的值之后,其余的值未被改变,实现了只修改指定位的功能。