欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > CANoe入门——CANoe的诊断模块,调用CAPL进行uds诊断

CANoe入门——CANoe的诊断模块,调用CAPL进行uds诊断

2025/4/2 3:39:24 来源:https://blog.csdn.net/weixin_45255231/article/details/146324922  浏览:    关键词:CANoe入门——CANoe的诊断模块,调用CAPL进行uds诊断

目录

一、诊断窗口介绍

二、诊断数据库文件管理

三、添加基础诊断描述文件(若没有CDD/ODX/PDX文件)并使用对应的诊断功能进行UDS诊断

3.1、添加基础诊断描述文件

3.2、基于基础诊断,使用诊断控制台进行UDS诊断

3.2.1、生成基础诊断

3.2.2、添加诊断服务

3.2.3、发送诊断服务

3.2.4、使用基础诊断进行27服务安全解锁

3.2.5、添加安全解锁dll算法文件

3.2.6、其他uds服务

四、有诊断数据库文件(CDD、PDX、ODX)进行uds诊断

五、通过CAPL的仿真节点调用诊断库函数进行uds诊断

5.1、CAPL中使用进行uds诊断的基本步骤

5.2、CAPL中定义诊断请求

5.3、CAPL中发送诊断请求

5.3.1、通用诊断请求

5.3.2、发送诊断数据库中的诊断请求

5.4、CAPL中获取诊断响应

5.4.1、诊断响应回调函数(诊断响应事件)方式获取诊断响应

5.4.2、使用函数直接获取诊断响应(不借助事件回调)

5.5、CAPL中借助诊断描述文件中导入的seedKey.dll文件通过安全访问

5.6、CAPL发送其他诊断请求


CANoe的诊断功能支持解析多个总线的诊断信息,CANoe支持通过诊断控制台或者CAPL对诊断功能进行调用,通过模拟Tester(上位机)与ECU进行诊断测试,模拟ECU和其他ECU节点进行诊断等。

一、诊断窗口介绍

CANoe的诊断功能菜单如图所示,在这里采取从左往右的形式依次对这几个功能进行简要的介绍

1、Diagnostic/ISO TP:用于往CANoe工程中添加诊断描述文件,配置一些诊断参数,例如传输层,诊断层参数。

2、Basic Diagnostic:基础诊断,可在1中通过添加一个基础诊断文件后激活,可以自定义一些简单的诊断服务

3、Diagnostic Parameters:用于配置需要读取的诊断响应中的参数,可配置某一诊断请求为单次手动发送或者类似IG一样的自动发送功能

4、Diagnostic Console:数据库诊断窗口,用于发送诊断描述文件中定义的诊断服务,并在窗口内显示对应诊断请求的响应,需要导入诊断数据库(CDD、ODX、PDX)文件或添加基础诊断文件后激活使用

5、Fault Memory:访问ECU的故障码模块

6、Session Control:用于控制ECU的诊断会话和安全访问

7、OBD——II:车载OBD诊断功能

8、CANdelaStudio:CANoe自带的一个CDD查看、编辑器

9、ODXStudio:CANoe自带的一个PDX、ODX查看、编辑器

10~12:涉及BMS J1939传输协议的内容,由于本人没怎么接触过J1939,也不怎么使用,不做介绍。

二、诊断数据库文件管理

在CANoe中,要想能够使用CANoe的诊断功能,必须先为该CANoe工程配置一个诊断描述文件(基础诊断、CDD文件、ODX/PDX文件),诊断描述文件便是常说的诊断数据库,这个文件中往往包含着ECU支持的各种诊断服务和诊断参数等信息。

添加诊断描述文件:

点击如图所示图标,进入诊断描述文件配置界面。

在CAN(由于本工程是创建的CAN模板,故这里为CAN,LIN工程的话这里显示为LIN)处右击鼠标,选择添加一个诊断描述文件。

如图所示,可以选择添加四种类型的诊断描述文件。

如果有CDD或者PDX/ODX文件,可以选择第一个选项,添加对应的诊断数据库。

如果没有,可以选择第二个选项,添加一个基础诊断文件。(第三个和第四个我没用过)

三、添加基础诊断描述文件(若没有CDD/ODX/PDX文件)并使用对应的诊断功能进行UDS诊断

3.1、添加基础诊断描述文件

在上一步中选择第二个选项,Add Basic Diagnostic Description(UDS),

添加完成之后如图所示,在这里可以对传输层和诊断层以及附加的诊断描述文件进行配置。

如图是传输层参数配置界面:

Addressing:用于配置目标ECU的物理寻址、功能寻址、诊断响应的ID

Addition ISO TP:配置传输层参数

STmin:流控帧参数,用于告知发送方发送连续帧时的最短间隔时间。

Block Size:用于告知发送方在接收到下一条流控帧之前,本次可以发送的连续帧的数量,为0表示没有限制。

FC Delay:流控帧与FF CF之间的间隔时间

Max length:传输层支持的最大字节数,当接收到的长度大于该长度时,CANoe将会报错并结束传输。

Mixing of CAN 2.0 and CANFD Frames:

若配置为ignore,则忽略与配置不符的CAN报文,如:本工程配置为CAN工程,则会忽略CANFD的诊断报文;

若配置为accept,则可以接收与本工程配置不符的CAN报文,但仅接收;
若配置为adapt,则不仅可以接收与本工程配置不符的CAN报文,且在接收到之后会使用对应的报文类型进行发送。

到此,传输层配置就结束了,接着是诊断层的配置

如图是诊断层配置界面:

Tester Present Request:若勾选此选项,则CANoe会在发送完成一次诊断服务后会每隔S3 Client ms之后自动发送诊断仪在线请求(0x3E服务)

若勾选上方的From Description,则会默认发送诊断文件中的诊断仪在线命令,若勾选Manually,则可以自定义发送的诊断仪在线命令。

S3 Server Time:ECU离开非默认会话的超时时间,此时间必须大于S3 Client。

Timing:

P2 Client:CANoe发送请求后,需要在此时间内收到ECU给出的响应,否则抛出超时。

P2 Server:ECU收到诊断请求之后,需要在此时间以后给出对应的诊断响应

P2  extended client:当接收到NRC78之后,CANoe需要继续等待响应的时间

P2 extended server:当接收到NRC78之后,需要在此时间内给出下一次响应,否则判定为超时

Response code 0x21 supported:若勾选,则CANoe会在总线上收到0x21的NRC之后在Complete

within 时间内间隔repeat request after时间重复发送一次诊断请求

Seed and key DLL:安全访问算法动态链接库文件,导入此文件,CANoe会在27服务接收到种子之后自动调用此算法计算对应的秘钥。

Additional Descriptions:附加的诊断描述文件,这里可以添加一些当前诊断数据库文件中未定义的附加服务(由于这里使用的是基础诊断,就不再新增了)。

至此,基础诊断添加完毕,其实以上所提到的参数,如无特殊要求,使用默认值即可。

3.2、基于基础诊断,使用诊断控制台进行UDS诊断

3.2.1、生成基础诊断

我的诊断id配置如图。

配置基础诊断服务

在诊断菜单的左上角,打开Basic Diagnostic 基础诊断,可以看到,在基础诊断中已经有了CANoe预定的一些uds诊断服务,例如10服务  11服务等。

为了方便起见,我们使用CANoe自带的功能,先一键生成基础的诊断服务

点击上图中的edit处,选择Add All Services,添加所有诊断服务

添加完成之后,基础诊断界面的左侧就为每个诊断服务都生成了一个基本的诊断服务。

3.2.2、添加诊断服务

在对应的诊断服务处右击鼠标,即可新增一个对应的诊断服务

最上方的Service Name可以修改此服务的名称,红框中的value处可以修改对应的诊断子服务id

现在,我们就有了一个1001 默认会话服务,和一个1003拓展会话的诊断服务了。

3.2.3、发送诊断服务

运行工程,我们试着发送一下这两个诊断服务(此处的响应ECU是我使用同星智能的TsMaster软件模拟的ECU,大家如果有真实ECU可以接入真实ECU使用,如果没有真实的ECU,评论区我会提供一个使用使用CAPL模拟的ECU节点来进行使用

双击对应的服务来发送此服务

可以看到,当我发送1001诊断之后,ECU回复了肯定响应5001,已经实现了通过基础诊断来完成uds诊断的功能。

但是,细心的朋友应该发现诊断控制台中抛出了红色的错误信息,这是因为我们没有给这个诊断请求设置对应的响应属性导致的,接下来,我们一起设置一下这个诊断请求的响应

在基础诊断配置界面,找到需要配置的服务,点击红框中的Response,即可配置其对应的响应,可以看到,我这个服务默认的响应是24个bit的(2个字节的50 01 加上一个字节的参数),而我的ECU实际回复了两个字节(50 01),没有参数,导致响应字节匹配错误而报出异常,这里我将响应参数删掉,即没有子参数。

在参数处点击对应的参数,右键鼠标选择delete,删除掉参数即可(删除就是0bit),此时对应的响应就是2字节了

修改完成之后,点击最上方的commit重新生成一下对应的诊断控制台,随后,我们重新点击这两个服务进行一下诊断服务的发送

可以看到,此时右下角已经不会抛出异常错误了(长度已经匹配正确)

看到这里,应该就已经掌握了添加一个基础诊断进行发送uds诊断的能力了。

3.2.4、使用基础诊断进行27服务安全解锁

此处我们再添加一个服务,用于对ECU进行安全访问控制

打开基础诊断配置界面,选择27服务,这里将安全会话等级设置为自己需要的等级,我就设置为level1了,请求参数根据自己的需要进行设置,我这边没有参数,就对参数进行了删除,然后响应参数的话,我这边会回复4字节的种子,于是我将响应参数长度设置为4 * 8 = 32bit

然后,再添加一个27 02服务,用于发送秘钥,并将参数长度设置为4字节(32bit),通常情况下种子与秘钥的长度是一样的,种子为4字节,秘钥也就是4字节

重新commit,就可以试一下我们刚刚添加的27服务了

3.2.5、添加安全解锁dll算法文件

由于安全访问需要用到安全访问算法,我们需要在诊断描述文件那里添加一个seedKey的dll算法。

大家如果有,可以使用自己的dll算法,没有的话,我会在评论区提供一个我自己随便封装的一l算法文件dll,但使用我的dll就必须使用我的仿真代码来充当ECU了

双击此处导入dll文件,导入完成后点击ok即可

随后来到诊断控制台,即可双击2701服务进行安全解锁了。

依次进入拓展会话(10 03),2701请求种子,2702发送秘钥,即可对ECU实现解锁~。

3.2.6、其他uds服务

对于其他的uds服务,添加方法和流程是一样的,这里不再一一进行配置了,大家掌握了配置方法之后自己进行配置尝试即可。

四、有诊断数据库文件(CDD、PDX、ODX)进行uds诊断

这种情况就比较简单了,当大家手里有现成的CDD文件或者pdx文件之后,直接进行导入即可使用。

同样的,打开诊断描述文件配置界面

在对应的总线处右击鼠标,选择第一个选项即可

随后在弹出的界面中选择自己的cdd文件就可以了。

添加完成之后,只需要配置一下对应的27服务seedkey.dll文件即可使用

配置完成后,就会出现刚刚导入的cdd诊断了

其余使用项基本与基础诊断一致,不再赘述。

五、通过CAPL的仿真节点调用诊断库函数进行uds诊断

大家可以根据自己的需求,将CAPL代码文件创建在对应的节点内。

在我的工程中,我就新建了一个空白节点,将代码写在此节点内

5.1、CAPL中使用进行uds诊断的基本步骤

在CAPL中要通过诊断库函数进行uds诊断,需要先设置对应的诊断目标ECU(通过匹配目标ECU,CAPL才能知道我们的诊断请求应该通过哪个ID来发送,哪个ID是诊断响应)

这个过程通过函数:diagSetTarget来实现

long diagSetTarget (char ecuName[])

在CAPL中,诊断请求和响应也是一种变量类型,我们要发送诊断请求,可以通过定义一个诊断请求变量,然后调用CAPL的诊断发送函数来进行发送

这个过程通过关键字:diagRequest来定义诊断请求

通过diagSendRequest函数来发送物理寻址的诊断请求

通过diagSendFunctional函数来发送功能寻址的诊断请求

long diagSendRequest (diagRequest obj)

long diagSendFunctional( diagRequest request)

发送诊断请求就这么两个步骤

5.2、CAPL中定义诊断请求

诊断请求的定义有好几种方式

diagRequest * diagReq;//方式1,定义一个通用的诊断请求,不限制诊断服务类型,不限制ECU
diagRequest BasicDiagnosticsEcu.* diagReq1;//方式2,定义一个名为"BasicDiagnosticsEcu"的ECU的通用诊断服务,不限制诊断服务类型,限制ECU
diagRequest BasicDiagnosticsEcu.SecurityAccess390 diagReq2;//方式3,定义一个名为"BasicDiagnosticsEcu"的ECU的通用诊断服务,限制诊断服务类型,限制ECU

其中,诊断ECU名称就是诊断描述文件的名称,即下图红框处的名称

诊断响应的定义与请求完全一致,仅关键字不同。

定义诊断响应使用关键字 diagResponse

5.3、CAPL中发送诊断请求

5.3.1、通用诊断请求

代码

on start
{char ECU_Name[32] = "BasicDiagnosticsEcu";//定义诊断ECU名称,此名称需要与诊断描述文件完全相同diagSetTarget(ECU_Name);
}on key'A'
{diagRequest * diagReq;//定义一个通用的诊断对象byte diagReqData[2] = {0x10,0x02};//定义请求内容diagReq.Resize(2);//重新设定诊断请求的长度diagReq.SetPrimitiveData(diagReqData,2);//设置诊断请求的原始数据,设置为数组中的内容,并设置数据长度diagSendRequest(diagReq);//发送诊断请求write("使用CAPL发送诊断请求");
}

在这段代码中,我使用按键A来发送一个1002请求,由于我定义的是一个通用的诊断请求,CAPL并不知道这个请求的内容和数据长度,所以需要人为调整这个请求的数据和长度

运行代码,按下按键看看

通过运行结果可以看到,当按下A之后,对应的事件代码被触发,发送了我们定义的10 02请求

5.3.2、发送诊断数据库中的诊断请求

发送诊断数据库中的诊断请求,并通用的请求简单一些,只需要定义完成之后调用发送函数即可,像这样

代码

on start
{char ECU_Name[32] = "BasicDiagnosticsEcu";//定义诊断ECU名称,此名称需要与诊断描述文件完全相同diagSetTarget(ECU_Name);
}on key'B'
{diagRequest BasicDiagnosticsEcu.ExtendedSession diagReq;//定义"BasicDiagnosticsEcu"内的拓展会话请求diagSendRequest(diagReq);
}

运行代码,按下B按键看看

可以看到,按下按键B之后,对应的诊断请求:BasicDiagnosticsEcu.ExtendedSession被发送了出去,这个请求就是我们在基础诊断数据库中定义的.ExtendedSession 1003服务

5.4、CAPL中获取诊断响应

CAPL提供了获取诊断响应的两种方式

5.4.1、诊断响应回调函数(诊断响应事件)方式获取诊断响应

CAPL提供了类似于键盘事件,on start事件的诊断响应事件,当用户设置了诊断对象的ECU之后,收到诊断响应ID的数据,会触发这个事件

代码

on start
{char ECU_Name[32] = "BasicDiagnosticsEcu";//定义诊断ECU名称,此名称需要与诊断描述文件完全相同diagSetTarget(ECU_Name);
}on key'B'
{diagRequest BasicDiagnosticsEcu.ExtendedSession diagReq;//定义"BasicDiagnosticsEcu"内的拓展会话请求diagSendRequest(diagReq);
}on diagResponse *//*号表示所有的,即所有的诊断响应都会触发该事件
{byte diagRespData[4096];//定义存储诊断响应的buff数组long diagRespDataLen;//诊断响应长度int i;this.GetPrimitiveData(diagRespData,4096);//将诊断响应的原始数据存储在此数组中diagRespDataLen=this.GetPrimitiveSize(); // 获取诊断响应的大小write("诊断响应长度为%d",diagRespDataLen);for(i=0;i<diagRespDataLen;i++){write("收到的诊断响应数据为%.2LX",diagRespData[i]);//打印诊断响应}
}

运行代码看看

如图所示,CAPL识别到了对应的诊断响应,并获取到了诊断响应的值。

5.4.2、使用函数直接获取诊断响应(不借助事件回调)

CAPL除了事件回调之外,还提供了一个函数用于直接获取对应的诊断响应

long diagGetLastResponse (diagRequest req, diagResponse respOut);

long diagGetLastResponse (diagResponse respOut);

在需要的时刻调用此函数即可获取到对应的响应

代码

on start
{char ECU_Name[32] = "BasicDiagnosticsEcu";//定义诊断ECU名称,此名称需要与诊断描述文件完全相同diagSetTarget(ECU_Name);
}on key'B'
{diagRequest BasicDiagnosticsEcu.ExtendedSession diagReq;//定义"BasicDiagnosticsEcu"内的拓展会话请求diagSendRequest(diagReq);
}on key'C'
{diagResponse * resp;//定义一个诊断响应byte diagRespData[4096];//定义存储诊断响应的buff数组long diagRespDataLen;//诊断响应长度int i;diagGetLastResponse(resp);//将上一次的诊断响应赋值给刚刚定义的变量resp.GetPrimitiveData(diagRespData,4096);//将诊断响应的原始数据存储在此数组中diagRespDataLen=resp.GetPrimitiveSize(); // 获取诊断响应的大小write("诊断响应长度为%d",diagRespDataLen);for(i=0;i<diagRespDataLen;i++){write("收到的诊断响应数据为%.2LX",diagRespData[i]);//打印诊断响应}
}

在未按下C按键,即未调用获取响应的函数之前,write窗口没有诊断响应的信息

按下C按键之后,diagGetLastResponse函数被调用,成功获取到了上一次的响应。

以上,便是CAPL中获取诊断响应的两种方式,大家可以根据需要自行选择合适的方式。

5.5、CAPL中借助诊断描述文件中导入的seedKey.dll文件通过安全访问

在上面的文章中,我们已经知道了如何使用CAPL发送一个诊断请求,也知道了如何获取对应的诊断响应。

大家在使用CAPL进行诊断的过程中,免不了要经常进行27访问,那么如何在CAPL中通过安全访问呢?

这里可以直接调用诊断描述文件中导入的dll文件获取27秘钥

在CAPL中调用诊断描述文件中导入的dll文件获取27秘钥依靠函数diagGenerateKeyFromSeed

long diagGenerateKeyFromSeed ( byte seedArray[], dword seedArraySize, dword securityLevel, char variant[], char ipOption[], byte keyArray[], dword maxKeyArraySize, dword& keyActualSizeOut); // form 1

long DiagGenerateKeyFromSeed(char ecuQualifier[], byte seedArray[] , dword seedArraySize, dword securityLevel, char variant[], char option[] , byte keyArray[], dword maxKeyArraySize, dword& keyActualSizeOut); // form 2

通常情况下使用重载形式1即可

总共8个参数

seedArray:种子数组
seedArraySize:种子的长度
securityLevel:安全访问等级,2701则等级为0x1,2703则等级为0x3,2709则等级为0x9...
variant:诊断描述字符串,通常写一个空白字符串或者"Variant1"
ipOption:诊断描述字符串,通常写一个空白字符串或者"option"
keyArray:存放秘钥的数组
maxKeyArraySize:秘钥数组最大可能/最大允许的长度
keyActualSizeOut:秘钥数组中实际使用的字节/秘钥实际的长度。此参数在c++中是一个变量的引用,在CAPL中直接传变量即可

代码

variables
{diagRequest * diagReqSeed;//定义一个通用的诊断请求,用于请求种子diagRequest * diagReqKey;//定义通用的诊断请求,用于发送秘钥byte gSeedArray[4];//存储秘钥的数组,这里我由于我的种子长度为4,所以给4个字节dword gSeedArraySize = 4;//种子长度dword gSecurityLevel = 0x01;//安全等级char gVariant[200] = "Variant1";//诊断对象描述char gOption[200] = "option";//诊断对象描述byte gKeyArray[255];//存储种子的数组dword  gMaxKeyArraySize = 255;//种子可能的最大长度dword gActualSizeOut;//种子的实际长度
}on start
{char ECU_Name[32] = "BasicDiagnosticsEcu";//定义诊断ECU名称,此名称需要与诊断描述文件完全相同diagSetTarget(ECU_Name);//设置诊断对象名称
}On key'B'
{byte reqData[2] = {0x27,0x01};//定义一个种子请求数组内容word reqDataLen;//种子请求的长度word i;reqDataLen = elcount(reqData);//计算种子请求的长度diagReqSeed.Resize(reqDataLen);//重置用于请求种子的诊断请求的长度diagReqSeed.SetPrimitiveData(reqData,reqDataLen);//将请求数组的数据设置给用于请求种子的诊断请求diagSendRequest(diagReqSeed);//发送诊断请求
}on diagResponse *
{byte respData[4096];//定义存放诊断响应的数组word respDataLen; //存放诊断响应的数组长度respDataLen = elcount(respData);//计算长度this.GetPrimitiveData(respData,respDataLen);//获取诊断响应到响应数组中if(respData[0] == 0x67 && respData[1] == 0x01)//如果回复的前面两个字节是67 01,即回复的是种子{byte keyReqData[6];//秘钥请求数组word keyReqLen;//秘钥请求数组长度keyReqLen = elcount(keyReqData);//计算长度memcpy_off(gSeedArray,0,respData,2,4);//将respdata的第2~6个字节拷贝放在gSeedArray中,即种子diagGenerateKeyFromSeed(gSeedArray,gSeedArraySize,gSecurityLevel,gVariant,gOption,gKeyArray,gMaxKeyArraySize,gActualSizeOut);//调用诊断描述文件中dll计算秘钥的函数//此时已经获得了秘钥,存在在keyArray中keyReqData[0] = 0x27;keyReqData[1] = gSecurityLevel + 1;//设置对应的秘钥请求前两个字节的内容,第一个字节为0x27,第二个字节为种子请求+1memcpy_off(keyReqData,2,gKeyArray,0,4);//将秘钥拷贝到秘钥请求数组中去diagReqKey.Resize(keyReqLen);//调节诊断请求大小diagReqKey.SetPrimitiveData(keyReqData,keyReqLen);//设置发送秘钥的诊断请求的数据diagSendRequest(diagReqKey);//发送秘钥诊断请求}
}

运行一下,看看

由于在诊断响应处理中,我使用的是回调方式,并且在获取完秘钥之后就发送出去了,所以就自动的通过了安全访问~

5.6、CAPL发送其他诊断请求

发送其他的诊断请求的方法与上述完全一致,如果是定义的通用型的,则需要调用resize函数先重新设置诊断请求的大小,再调用SetPrimitiveData函数设置诊断请求的数据,随后调用diagSendRequest或者diagSendFunctional函数进行发送诊断请求即可。

如果是定义的数据库中的诊断请求,就更简单了,定义完成之后,直接调用发送函数即可。

CAPL的诊断函数异常强大,由于是入门篇,本篇就介绍到这里。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词