前言与导读:
基于前几篇文章我们已经梳理了HEX文件、S19文件的读取和UDS关键的0x34/0x36/0x37等服务的结构。
基于此,我们差不多就完成了前期的知识储备了,那么完成最终的目的——使用capl实现我们还需要解决以下几个问题。
1、文件如何读取?
2、文件读取后,常见的hex和S19都是以ASCIL码格式,而我们使用0x36服务发送出去时,是需要发送的是byte类型的十六进制数字,如何将ASCIL码格式的字符转换为byte类型的十六进制数字?
3、类型装换好之后,还需要去判断文件的类型,读取文件中关键的地址信息,数据段开始-结束标志
4、实际项目中,uds刷写是存在一个固定化的流程,如先要进10 03会话,然后使用0x28/0x85等服务关闭通讯和DTC检测,有些主机厂还存在0x31服务等?如何构建发送体系?
5、实际发送过程中,还有可能出现否定响应,当uds刷写的重要步骤,出现否定响应时,就应该退出刷写?这个功能该如何实现?
6、0x36服务,发送大量数据,不可能使用单帧发送完全部数据。还需要使用多帧数据来发送数据。多帧情况下,还需要接收流控帧,根据流控帧的信息决定,接下来是否继续发送。此外多帧中的续帧,序号从0x21-0x2F循环。
7、此外还需要考虑时间参数是否合理,如As、Ar、Ac、Bs。。。P2、P2*,S参数不能出现超时。
小结:故总体看上去,任务是非常多的,而且很多问题都是一环套一环,一个环节出现差错,会导致整体的刷写失败。
但是千里之行,始于足下,逻辑理清了,我们一个一个来攻克
1、文件如何读取?
1.1)openFileRead 函数
先看官方文档
openFileRead小结:
1*)存在两种Form;
2*)两种Form的格式的返回值类型都是dword;
3*)mode=0,代表打开的是text类型的文件。 mode=1,代表打开的是binary(二进制)类型的文件
1.1.1 形参Filename的解释
重点关注图中标注出来的Filename,我们先看官方文档是怎么说的:
标注出来的一段话,意思是Filename(文件名)是通过绝对路径来进行传参,所谓绝对路径,就是指完整的路径。
OpenFileRead ("Data.Txt",0); 如上面一段语句,filename="Data.Txt"只给出了文件名,那么就引出一个问题,就是函数如何如何准确的找到这个函数
接下来这段描述了函数找文件的方法
1:首先在batabase文件所在的文件夹下,寻找需要打开的文件,如果没有找就执行步骤2
2:继续到cfg所在的文件夹下寻找
故:当我们使用openfileread()来打开文件时,是需要手动将需要打开的文件放置到以上两个文件夹中的任意一个。
1.1.2 形参FileEncoding的解释
这个暂时不是很懂,也没有查到相关比较详细的资料,大多时候是可以不使用的
小结:
故我们写代码时:
*1)先使用OpenFileRead函数打开我们需要读取的Hex文件或S19文件
*2)根据OpenFileRead的返回值还要定义一个dword 类型的变量,通常我们将这个变量称为:“文件句柄”
dword fileHandle;
fileHandle = OpenFileRead("D:\\word\\text_hex",0); //获取文件句柄,以文本形式打开路径中的文件
拓展延伸:对文档路径的设置方法
大家看我标红的部分,绝对路径,除了上述方法,为了增加代码移植性和可维护性,我们还有两种办法,如下
*1)使用CANoe中的系统变量,和输入框(panel),先在panel中设置一个输入框,再设定一个系统变量 ,将输入框输入的绝对地址和系统变量关联后,在capl代码中将地址赋值给字符串。
代码如下:
char file_path[50]={0};
sysGetVariableString(sysvar::files_space::driverPath,file_path, elCount(file_path)); //获取文件路径
2*) 使用setFilePath函数设置路径
|
2.1*)setFilePath函数的介绍
setFilePath函数Return Values
无任何返回值
setFilePath函数Example
//set directory for reading |
1.2 文件的读取
我们可以使用CAPL自带的函数, Filegetstringsz 来执行读取任务。
1.2.1 首先来看官方文档
图中从上往下看,我们使用这个函数,可以直接读取一行数据到字符数组中去,但是在调用这个函数前,我们还要定义一个字符数组用来存放我们读取到的这一行字符串。
还有以下几点需要注意:
(1)字符数组长度的定义,如:char buff_rowdata[Length],Length的长度不能太短,否则是读不到一行里全部的内容的,这里建议大家设置Length=256。这样就能很好的兼容S19和hex文件,而且也不会出现数组截断的情况发生。至于为什么?大家思考一下?
本段小结:代码如下
const word Length=256;
dword FileHandle_1;
char buff_rowdata[Length];
FileHandle_1=OpenFileRead("D:\\word\\text_hex",0);
Filegetstringsz(buff_rowdata,elcount(buff_rowdata),FileHandle_1);
注意和提示:
这里注意,elcount()函数和strlen()函数的区别:
**1)elcount是可以对所有数组(整形数组,实数数组,字符数组,结构体数组)等所有数组的元素,且这些数组是可以没有初始化的。
**2)strlen只能计算有效的字符长度。
char array_1[100];
write("elcount计算的长度是%d,strlen计算的是%d",elcount(array_1),strlen(array_1));输出的结果是:
这样大家就明白了吧!!!
2、文件读取后 ,格式装换的问题
此段可以分为两个部分:
(1)ASCIL码装换位unsigned char类型(等价于CAPL中的Byte)。
(2)需要根据被烧写文档的结构,将ascil码两两为一组,组合转换为unsigned char (等价于CAPL中的Byte)类型,同理这种装换也有两种方法来实现。下文细说。
2.1 ASCIL码装换位unsigned char类型
2.1.1 利用CAPL自带函数进行装换
on key 'H'
{
byte z1;
byte z2;
byte z3;
byte z4;char Arry_1[1]=“F”
z1 = (byte) atol("200"); //atol函数本身装换的是long,z1是Byte,故前面需添加强制转换
z2 = (byte) atol("0xFF");
z3 = (byte) atol("050");
z4 = (byte) atol("200.0");z5 = (byte) atol(Arry_1);
write("z1=%d,z2=%d,z3=%d,z4=%d,z5=%d",z1,z2,z3,z4,z5);
}write界面查看结果:
Program / Model z1=200,z2=255,z3=50,z4=
可以看出以下特征被转换字符
**1)参数只能是字符串,或代表字符串的地址;
**2) 默认被转换的字符,在书写形式上,默认为10进制;
**3)表现形式为16进制的字符串,也能被直接转换,但是必须在字符前添加0x;为2进制的也能直接装换。
小结:strtod()、strtol()、strtoll等函数也能实现同样的功能,但是这种自带函数,存在一个很大的弊端,就是我们必须要先将字符串,剪切好,然后在使用转换函数进行装换。
2.1.2 手写代码进行装换
基于上段说CAPL自带函数转换的缺点,我们还能自己手写代码?来将ASCIL字符装换为16进制unsigned char。我也推介大家使用这种方法。思路如下
1:先将单个ASCIL字符转换为16进制数
2:两个装换为16进制的数,通过移位后相加,就得到我们需要的结果
// 将char类型转换为byte类型,char类型是指S19文件中读取到的变量类型,转换为Byte类
// 型,适用于发送!!
byte Type_conversion_char_byte(char buffer_one) //这个buffer_one文件是干嘛用的?,从//返回值类型可以看出
{
byte buffer_return; //此值用来作为返回值!!!
if(buffer_one<71 && buffer_one>64) //表示buffer_one是A-G大写
{
buffer_return=buffer_one-55; //-55后是将装换后的字符,代表的“字符显示的10进制数”
}
else if(buffer_one<58 && buffer_one>47)
{
buffer_return=buffer_one-48;
}
else if(buffer_one=='S') //为S的时候返回FF,区分s3文件
{
buffer_return=0xFF;
}
return buffer_return;
}
举实例说明:
有下面一段S19文件的截图
图中S19数据段的3A该如何转换?
Void OtherFunction(viod)
{
Type_conversion_char_byte(buff_rowdata[10]); //将字符'3'转换为ETX (End Of Text),
//而EXT,则是对应的数字就是3。
Type_conversion_char_byte(buff_rowdata[11]);
}
这里 我们只完成了第一步,我们现在只是转换成功,现在我们还需要将读取的数据组合成0x36服务能发送的值
//先定义两个变量
byte Data_1=Type_conversion_char_byte(buff_rowdata[10]);
byte Data_2=Type_conversion_char_byte(buff_rowdata[11]);
byte Data_For_0x36Trans = Data_1<<4 + Data_2;
如此Data_For_0x36Trans的值就转变为了 0x3A=十进制58,大家自己可以试试
3 文件的识别
3.1 文件类型的识别
这里建议大家,回头S19文件的格式,我们以S类型的文件为例。思路如下
**1)需要判断读取的每一行数据的前两个字符,先确定行的类型 用来判断需要读取那些数据
举例说明:例1:
如上图,如果读取到S0开头的行,就说明了,此行 是标题行,16bit的地址栏是没有用的,不需要读取,数据段都是一些版本信息,供应商标识码等,可以根据特殊要求读取,一般情况下,不需要读取。
举例说明:例2:以下是整个S系列文件所有的类型
以S系列文件为例:
** 行可以划分为标题行、数据行、统计行、结束行。
** 数据行:目前存在3种,S1、S2、S3。
** S1、S2、S3 类型的地址字段的长度,分别为2Byte、3Byte、4Byte。
** S1、S2、S3 对应的结束行,分别为 S9、S8、S7。这也是我们通常所说的S19格式文件、S28文件的来源。
hex文件就不再演示了,主要是要搞懂文件的结构即可。