BUG召唤师头像
CANoe 中 CAPL 诊断测试实战:从协议基础到安全解锁封面图

CANoe 中 CAPL 诊断测试实战:从协议基础到安全解锁

前言

在汽车电子开发与测试中,诊断功能是保障 ECU 正常工作、快速定位故障的关键环节。CANoe 作为 Vector 公司推出的总线开发与测试工具,提供了强大的诊断仿真和测试能力,而 CAPL 语言则让工程师能够灵活地编写自动化诊断测试脚本。

本文围绕 CANoe 中的 CAPL 诊断测试展开,系统介绍从诊断协议基础到安全解锁的完整知识体系,涵盖诊断描述文件、传输层与诊断层参数、常用诊断函数、通用函数封装以及安全解锁等核心内容。


一、诊断协议介绍

ISO 14229-1 是一项国际标准,用于规范汽车诊断通信,也被称为 UDS。标准定义了诊断通信的数据通信方式,命令结构,数据格式和错误码,也定义了诊断仪器和车辆之间的通信方式,包括物理层和传输层协议;

ISO 14229-1 处于 OSI 模型中的顶层,该标准适用于所有的网络协议;

CAN 总线的物理层和传输层的协议为 ISO 11898-2 和 ISO 15765-2;

LIN 总线的物理层和传输层的协议为 ISO 17987-4 和 ISO 17987-2;

以太网的物理层和传输层的协议为 ISO 13400-3 和 ISO 13400-2;

1. 诊断描述文件

CANoe 的诊断功能非常强大,常见的诊断描述文件的格式有 CDD,ODX,PDX;

CDD:CANdela Diagnostic Description,是 Vector 公司开发的一种诊断描述文件,可通过 CANdelaStudio 软件创建和编辑;

ODX:Open Diagnostic Data Exchange,是全球通用的诊断描述格式,可通过 ODXStudio 软件创建和编辑;

PDX:Open Diagnostic Data Exchange/Packed ODX,可以将多个 ODX 文件打包成一个 PDX 文件;

2. 诊断描述信息

ECU qualifier:诊断目标 ECU 的名称,该参数在 CANoe 环境中必须是唯一的。在 CAPL 代码中使用 diagSetTarget(char ecuName[]) 函数设置诊断对象时,ecuName 参数传递的值就是 ECU qualifier 参数的值;

Diagnostic tester:在 CANoe 中使用诊断控制台发送诊断请求时,用户可以选择使用物理地址发送诊断或者使用功能地址发送诊断;

Simulation by:如果对仿真的 ECU 发送诊断,这里需要选择 ECU,如果是真实在线的 ECU,无需选择;

3. 传输层参数

Override manually:如果诊断描述文件的信息和实际总线信息不匹配,可以勾选该选项,手动更改参数;

Addressing:定义了诊断请求的物理寻址,功能寻址,以及诊断响应的报文 ID;

STmin:多包之间最小的响应间隔时间;

Block size:流控帧中接收方最多一次性接收的包数;

FC delay:流控帧的延迟时间;

MaxLength:诊断能支持的最大的包长;

4. 诊断层参数

Override manually:如果加载的诊断文件信息和实际总线信息不匹配,可以勾选该选项,手动更改参数;

Tester Present's request:如果勾选这个选项,在诊断仪和 ECU 建立了诊断会话之后,CANoe 将周期行的发送 0x3E 服务来维持诊断会话在线。在 CAPL 中可以使用 diagStopTesterPresent() 和 diagStartTesterPresent() 禁用和启用该功能;

S3 client time:CANoe 自动发送会话保持诊断请求的周期;

S3 server time:服务器离开非默认会话的超时时间,这个时间必须要大于 S3 client time;

P2 client:客户端成功发送请求消息,到接收到服务端的应答消息的超时时间;

P2 extended client:客户端成功发送请求消息,在接收到服务端响应的 NRC 0x78 之后,客户端使用此时间再次等待 ECU 响应;

P2 Server:服务端在接收到客户端的请求消息后,开始答复消息的运行时间,典型值为 50ms;

P2 extended server:如果服务端在 P2 Server 时间内无法给出答复,则可以响应 NRC 0x78,之后服务器必须在 P2 Server Extended 时间内给出响应,也可以继续响应 NRC 0x78,再重置该定时器;

Seed & Key DLL:如果客户端请求的服务器需要使用 0x27 服务安全解锁之后才能请求,则需要在此加载 DLL 文件;

二、传输层协议

一帧 CAN/LIN 报文最多可以携带 8 个字节的有效数据,一帧 CANFD 报文最多可以携带 64 字节的有效数据,一帧 Flexray 报文最多可以携带 254 字节的有效数据,一帧标准以太网报文最多可以携带 1500 字节有效数据。

在应用层,ISO 14229-1 协议规定诊断数据的长度是可以大于单帧报文的负载长度的。传输层负责的内容就是将数据进行有效得分组传输。

不同的总线类型对应得传输协议标准不同,CAN 总线是基于 ISO 15765-2 进行传输的。

协议控制信息(N_PCI)将 CAN 总线的诊断数据分为单帧(Single Frame,SF),首帧(First Frame,FF),连续帧(Consecutive Frame,CF)和流控帧(Flow Control Frame,FC)。

N_PDU nameN_PCI Bytes
Byte1 Bit7-4Byte1 Bit7-4Byte2Byte3Byte4Byte5Byte6

SingleFrame(SF)

(CAN_DL <= 8)

0000SF_DL-----

SingleFrame(SF)

(CAN_DL > 8)

00000000SF_DL----

FirstFrame(FF)

(FF_DL <= 4095)

0001FF_DL----

FirstFrame(FF)

(FF_DL > 4095)

000100000000 0000FF_DL
ConsecutiveFrame(CF)0010SN-----
FlowControl(FC)0011FSBSSTmin---

三、CAPL 诊断函数

1. 发送诊断请求

下列函数可以以诊断物理寻址或功能寻址的方式发送诊断请求;

long diagSendRequest(diagRequest request):以物理寻址方式发送诊断请求;

long diagSendFunctional(diagRequest request):以功能寻址方式发送诊断请求;

2. 诊断目标设置

如果 CANoe 工程中加载了多个 ECU 的诊断描述文件,则不建议直接发送诊断请求,而是要先设置诊断目标 ECU;

指定了诊断的 ECU,代码就只能用于测试当前 ECU;

long diagSetTarget(char ecuName[]):设置诊断目标;

3. 等待诊断请求发送

long testWaitForDiagRequestSent(diagRequest request, dword timeout):返回地响应值为 1,表示诊断请求发送到总线上,在工程应用中,需要再次使用这个函数确认;

4. 等待接收诊断响应

long testWaitForDiagResponse(diagRequest request, dword timeout):等待接收诊断响应;

long testWaitForDiagResponse(dword timeout):不指定诊断请求,等待接收诊断响应;

返回值为 1 表示接收到诊断响应;

返回值为 0 表示超时,指定时间内没接收到诊断响应;

返回值为 < 0 表示内部错误;

函数收到肯定响应或者否定响应都会立即退出,如果收到 NRC 0x78,该函数会使用 P2*Server 时间继续等待,知道 timeout 参数超时;

5. 判断响应类型

testWaitForDiagResponse() 函数只能判断 ECU 是否返回响应,但无法判断是肯定响应还是否定响应;

long diagGetLastResponseCode(diagRequest request):获取最后一次响应结果;

long diagGetLastResponseCode():获取最后一次响应结果;

返回值 -1:肯定响应;

返回值 0:超时,无响应;

返回值 > 0:否定响应,返回 NRC 响应码;

6. 打印诊断数据到测试报告

下列函数可以把诊断请求数据和诊断响应数据输出到测试报告中;

long testReportWriteDiagObject(diagRequest request):打印诊断请求数据;

long testReportWriteDiagResponse(diagRequest request):打印诊断响应数据;

7. 代码示例

variables
{
  diagRequest * request;
  char ecuName[20] = "Door";
}

testcase TC_001_diagFunction_Test()
{
  byte data[2] = {0x10, 0x01};
  long ret;
  
  // 1. 指定诊断节点
  diagSetTarget(ecuName); 
  // 2. 设置诊断请求
  diagResize(request, 2);
  diagSetPrimitiveData(request, data, elCount(data));
  // 3. 发送诊断请求
  ret = diagSendRequest(request);
  testStep("", "pysical address, ret = %d", ret);
  if(ret != 0)
    return;
  // 4. 确认诊断请求发送到总线上
  ret = testWaitForDiagRequestSent(request, 1000);
  if(ret == 1)
  {
    testStepPass("", "Request was sent successfully");
    testReportWriteDiagObject(request); // 打印诊断请求
  }
  else
  {
    testStepFail("", "Request could not be sent!");
    return;
  }
  // 5. 确认接收到诊断响应
  ret = testWaitForDiagResponse(request, 5000);
  if(ret != 1)
  {
    testStepFail("No response received or internal error.");
    return;
  }
  testStepPass("Received ECU response successfully.");
  testReportWriteDiagResponse(request); // 打印诊断响应
  // 6. 判断响应类型
  ret = diagGetLastResponseCode(request);
  if(ret >= 0)
  {
    testStepFail("", "Negative response, NRC code is 0x%X", ret);
    return;
  }
  testStepPass("Received ECU positive response successfully");
}

测试结果:

四、诊断通用函数(一)

基于上述诊断函数,可以将上述代码封装成通用的函数,提高代码的复用度;

void diag_sendRequest(diagRequest request):发送诊断请求并期望 ECU 肯定应答;

void diag_SendRequest(diagRequest* request)
{
  long ret;
  
  // 1. 发送诊断请求
  ret = diagSendRequest(request);
  testStep("", "pysical address, ret = %d", ret);
  if(ret != 0)
  {
    testStepFail("Request was sent failed");
    return;
  }
  // 2. 确认诊断请求发送到总线上
  ret = testWaitForDiagRequestSent(request, 1000);
  if(ret != 1)
  {
    testStepFail("", "Request could not be sent!");
    return;
  }
  testStepPass("", "Request was sent successfully");
  testReportWriteDiagObject(request); // 打印诊断请求
  // 5. 确认接收到诊断响应
  ret = testWaitForDiagResponse(request, 5000);
  if(ret != 1)
  {
    testStepFail("No response received or internal error.");
    return;
  }
  testStepPass("Received ECU response successfully.");
  testReportWriteDiagResponse(request); // 打印诊断响应
  // 6. 判断响应类型
  ret = diagGetLastResponseCode(request);
  if(ret >= 0)
  {
    testStepFail("", "Negative response, NRC code is 0x%X", ret);
    return;
  }
  testStepPass("Received ECU positive response successfully");
}

测试代码:

variables
{
  diagRequest * request;
  char ecuName[20] = "Door";
}

testcase TC_002_sendRequestFunction_Test()
{
  byte data[2] = {0x10, 0x01};
  
  diagSetTarget(ecuName);
  diagResize(request, 2);
  diagSetPrimitiveData(request, data, elCount(data));
  
  diag_SendRequest(request);
}

测试结果:

五、诊断服务数据设置

1. 诊断参数设置

long diagSetParameter(diagRequest request, char parameterName[], double newValue):更改请求的参数值,发送不同的诊断数据;

long diagSetParameter(diagRequest request, char parameterName[], char newValue[]):更改请求的参数值,发送不同的诊断书数据;

这个函数能够修改诊断参数,需要提前知道诊断参数的名称,实际测试中并不好用;如果使用 testWaitForDiagResponse() 接收诊断响应,容易遇到接收不到诊断响应的问题;

testcase TC_003_setRequestParameter_Test()
{
  diagRequest Door.DefaultSession_Start request;
  long ret;
  
  diagSetTarget(ecuName);
  
  diagSetParameter(request, "DiagSessionType", 0x03);
  diag_SendRequest(request);
}

实际上查看 log 确实可以接收到响应,但是使用 testWaitForDiagResponse() 接收不到;

2. 单字节设置诊断服务

long diagSetPrimitiveByte(diagRequest request, dword bytePos, dword newValue):设置诊断服务指定位置的字节进行重新赋值;

testcase TC_004_setPrimitiveByte_Test()
{
  diagRequest Door.DefaultSession_Start request;
  long ret;
  
  diagSetTarget(ecuName);
  
  DiagSetPrimitiveByte(request, 1, 0x03);
  diag_SendRequest(request);
}

3. 设置全部的诊断服务

long diagSetPrimitiveData(diagRequest request, byte* buffer, dword buffersize):重置诊断服务的全部数据;

variables
{
  diagRequest * request;
  char ecuName[20] = "Door";
}

testcase TC_005_setPrimitiveData_Test()
{
  long ret;
  byte data[4095];
  
  diagSetTarget(ecuName);
  diagResize(request, 2);
  
  data[0] = 0x10;
  data[1] = 0x02;
  diagSetPrimitiveData(request, data, 2);
  diag_SendRequest(request);
}

通常来说,设置全部的诊断服务更加常用,它可以搭配通用的诊断请求(使用通配符定义的诊断请求),在测试中更加灵活;

搭配通用的诊断请求,需要注意,一定要设置诊断请求的字节数,diagResize(diagRequest request, dword buteNum),否则无法确定诊断请求有多少个字节。

六、诊断通用函数(二)

1. 通用诊断函数(一)

我们可以基于上面的诊断服务数据设置的方法,封装一个通用性更强的函数,这个函数不必依赖于 CDD 文件。

void diag_SendRequest_PrimitiveData(byte primitiveData[], long dataSize)
{
  diagRequest * request;
  long ret;
  
  // 1. 设置诊断请求的字节数
  diagResize(request, dataSize);
  // 2. 设置诊断请求的全部字节
  diagSetPrimitiveData(request, primitiveData, dataSize);
  // 3. 发送诊断请求
  ret = diagSendRequest(request);
  testStep("", "pysical address, ret = %d", ret);
  if(ret != 0)
  {
    testStepFail("Request was sent failed");
    return;
  }
  // 4. 确认诊断请求发送到总线上
  ret = testWaitForDiagRequestSent(request, 1000);
  if(ret != 1)
  {
    testStepFail("", "Request could not be sent!");
    return;
  }
  testStepPass("", "Request was sent successfully");
  testReportWriteDiagObject(request); // 打印诊断请求
  // 5. 确认接收到诊断响应
  ret = testWaitForDiagResponse(request, 5000);
  if(ret != 1)
  {
    testStepFail("No response received or internal error.");
    return;
  }
  testStepPass("Received ECU response successfully.");
  testReportWriteDiagResponse(request); // 打印诊断响应
  // 6. 判断响应类型
  ret = diagGetLastResponseCode(request);
  if(ret >= 0)
  {
    testStepFail("", "Negative response, NRC code is 0x%X", ret);
    return;
  }
  testStepPass("Received ECU positive response successfully");
}

测试代码:

testcase TC_006_sendRequestFunction_Test()
{
  byte primitiveData[4095];
  
  primitiveData[0] = 0x10;
  primitiveData[1] = 0x03;
  
  diagSetTarget(ecuName);
  
  diag_SendRequest_PrimitiveData(primitiveData, 2);
}

2. 通用诊断函数(二)

上述的函数都期望诊断响应为正响应,并且都是以物理寻址的方式发送诊断请求。在诊断测试中,往往也需要注入错误的诊断数据,期望 ECU 不响应或者否定响应,并且还可以选择物理寻址或者功能寻址,基于这个需求,封装通用的诊断函数;

long diag_SendRequest_Generic(byte primitiveData[], long dataSize, long expResCode, byte sendType)
{
  diagRequest * request;
  long ret;
  
  // 1. 设置诊断请求字节数
  diagResize(request, dataSize);
  // 2. 设置诊断请求数据
  diagSetPrimitiveData(request, primitiveData, dataSize);
  // 3. 发送诊断请求
  if(sendType == 0) // 物理寻址
  {
    ret = diagSendRequest(request);
    testStep("", "Physical Address, ret = %d", ret);
  }
  else // 功能寻址
  {
    ret = diagSendFunctional(request);
    testStep("", "Functional Address, ret = %d", ret);
  }
  if(ret != 0)
  {
    testStepFail("Request could not be sent!");
    return 0;
  }
  // 4. 确认诊断请求是否发送成功
  ret = testWaitForDiagRequestSent(request, 1000);
  if(ret != 1)
  {
    testStepFail("Request could not be sent!");
    return 0;
  }
  testStepPass("Request was sent successfully!");
  testReportWriteDiagObject(request);
  // 5. 确认诊断响应
  ret = testWaitForDiagResponse(request, 5000);
  switch(ret)
  {
    case 0: // 超时
      if(expResCode == 0)
      {
        testStepPass("No response was received, as expected.");
      }
      else
      {
        testStepFail("No response was received, but expected receive response.");
      }
      break;
    case 1: // 收到响应
      testReportWriteDiagResponse(request);
      ret = diagGetLastResponseCode(request);
      if(ret == -1) // 肯定响应
      {
        if(expResCode == -1)
        {
          testStepPass("Positive response was received, as expected.");
        }
        else
        {
          testStepFail("", "Positive response was received, but NRC 0x%X was expected", expResCode);
        }
        return 1;
      }
      else
      {
        if(ret == expResCode)
        {
          testStepPass("", "NRC 0x%X was received, as expected.", ret);
        }
        else
        {
          testStepFail("", "NRC 0x%X was received when 0x%X was expected.", ret, expResCode);
        }
      }
      break;
    default: // 内部错误
        testStepFail("", "There a Error Code received = %d", ret);
      
  }
  return 0;
}

测试代码:

variables
{
  char ecuName[20] = "Door";
  
  const long No_Response = 0;
  const long Positive_Response = -1;
  const long NRC_Response_12 = 0x12;
  const long NRC_Response_13 = 0x13;
  const byte gPhys = 0;
  const byte gFunc = 1;
}

testcase TC_007_sendRequestFunction_Test()
{
  byte data[4095];
  
  diagSetTarget(ecuName);
  
  data[0] = 0x10;
  data[1] = 0x04;
  
  diag_SendRequest_Generic(data, 2, NRC_Response_12, gPhys);
}

七、获取诊断响应数据

1. 获取诊断响应参数值

double diagGetRespParameter(diagRequest request, char parameterName[]):返回诊断响应的参数值;

long diagGetRespParameter(diagRequest request, char parameterName[], char buffer[], dword bufferLen):以字符串的方式返回诊断响应的参数值;

long diagGetRespParameterRaw(diagRequest request, char parameterName[], byte buffer[], dword bufferLen):以 byte 数组的方式返回诊断响应的参数值;

上述函数都需要知道参数名,才能获取对应的参数值,实际应用中用的不多;

2. 获取诊响应的所有数据

long diagGetRespPrimitiveSize(diagRequest request):返回诊断响应的数据大小;

long diagGetRespPrimitiveByte(diagRequest request, dword bytePos):根据字节索引读取诊断数据,每次获取一个字节,通常放在循环里面使用;

上述函数都不需要知道参数名,就可以直接获取所有的响应数据,通常搭配循环使用;

variables
{
  diagRequest * request;
  char ecuName[20] = "Door";
}

testcase TC_008_diagResponseData_Test()
{
  long ret;
  byte primitiveData[4095] = {0x10, 0x01};
  long size;
  long i;
  byte resp[4095];
  
  diagSetTarget(ecuName);
  diagResize(request, 2);
  diagSetPrimitiveData(request, primitiveData, 2);
  
  diag_SendRequest(request);
  
  size = DiagGetRespPrimitiveSize(request);
  for(i = 0; i < size; i++)
  {
    resp[i] = DiagGetRespPrimitiveByte(request, i);
    testStep("", "resp[%d] = 0x%X", i, resp[i]);
  }
}

八、通用诊断函数(三)

前面的通用函数实现了诊断请求的发送和诊断响应结果的判断,下面实现一个包含诊断请求发送和获取诊断响应数据的函数;

long diag_SendRequestAndGetResponse(byte requestData[], long requestSize, byte sendType, byte responseData[])
{
  diagRequest * request;
  long ret;
  long size;
  long i;
  
  // 1. 调整诊断请求的大小和数据
  diagResize(request, requestSize);
  diagSetPrimitiveData(request, requestData, requestSize);
  // 2. 发送诊断请求
  if(sendType == 0)
  {
    ret = diagSendRequest(request);
    testStep("", "Physical Address, ret = %d", ret);
  }
  else
  {
    ret = diagSendFunctional(request);
    testStep("", "Functional Address, ret = %d", ret);
  }
  if(ret != 0)
  {
    testStepFail("Request could not be sent!");
    return 0;
  }
  // 3. 确认诊断请求发送成功
  ret = testWaitForDiagRequestSent(request, 2000);
  if(ret != 1)
  {
    testStepFail("Request could not be sent!");
    return 0;
  }
  testStepPass("Request was sent successfully!");
  testReportWriteDiagObject(request);
  // 4. 确认接收到诊断响应
  ret = testWaitForDiagResponse(request, 5000);
  if(ret != 1)
  {
    testStepFail("Not Received ECU Response!");
    return 0;
  }
  testStepPass("Receive ECU Response successfully!");
  // 5. 解析诊断响应
  size = DiagGetRespPrimitiveSize(request);
  testStep("", "Response size = %d", size);
  for(i = 0; i < size; i++)
  {
    responseData[i] = DiagGetRespPrimitiveByte(request, i);
  }
  return size;
}

测试代码:

testcase TC_009_diagRequestAndResponseFunction_Test()
{
  byte requestData[4095] = {0x10, 0x03};
  byte responseData[4095];
  long size;
  long i;
  
  diagSetTarget(ecuName);
  
  size = diag_SendRequestAndGetResponse(requestData, 2, 0, responseData);
  for(i = 0; i <size; i++)
  {
    testStep("", "responseData[%d] = 0x%X", i, responseData[i]);
  }
}

九、安全解锁

有些诊断服务,需要安全解锁后,才能获取响应的读写权限,CAPL 也内置了一些相关的函数来实现解锁功能。

1. testWaitForUnlockEcu 函数

long testWaitForUnlockEcu(long securityLevel);

long testWaitForUnlockEcu(char ecuQualifier, dword securityLevel);

上述函数,传入安全等级进行解锁,返回值为 0 表示安全解锁成功;使用上述函数,需要在诊断配置窗口加载 Seed & Key DLL.dll 文件。

上述函数的优点是能够直接按照 DLL 的算法解锁,优点是快,但无法观察到解锁的细节;

testcase TC_010_unlockEcu_Test()
{
  byte reqData[4095] = {0x10, 0x03};
  long ret;
  
  diagSetTarget(ecuName);
  diagResize(request, 2);
  diagSetPrimitiveData(request, reqData, 2);
  
  diag_SendRequest(request);
  
  ret = testWaitForUnlockEcu(ecuName, 0x01);
  testStep("", "ret = %d(0 表示成功, 否则失败)", ret);
}

2. diagGenerateKeyFromSeed 函数

在某些场景下,需要注入错误的密钥验证 ECU 的防攻击性和安全性是否满足规范要求。此时 testWaitForUnlockEcu 函数就不适用了。

long diagFenerateKeyFromSeed(

byte seedArray[],

dword seedArraySize,

dword securityLevel,

char variant[],

char ipOption[],

byte keyArray[],

dword maxKeyArraySize,

dword& keyActualSizeOut

);

  • seedArray:输入的 seed 数组;
  • seedArraySize:输入的 seed 数组的长度;
  • securityLevel:安全解锁等级;
  • variant:诊断描述文件中的 variant 参数值,一般传入空字符串;
  • ipOption:可选参数,一般传入空字符串;
  • keyArray:该函数计算出来的 key 数组;
  • maxKeyArraySize:输入的 keyArray 数组允许的最大字节数;
  • keyActualSizeOut:输出的 keyArray 数组的实际字节数;

封装通用的安全解锁函数:

void securityAccess(long securityLevel)
{
  byte reqData[4095];
  diagRequest * request;
  long ret;
  long size;
  byte seedArray[255];
  byte keyArray[255];
  long i;
  dword actualKeySize;
  dword seedSize;
  
  reqData[0] = 0x27;
  reqData[1] = securityLevel;
  
  // 1. 设置诊断请求
  diagResize(request, 2);
  diagSetPrimitiveData(request, reqData, 2);
  // 2. 发送诊断请求并判断肯定响应
  ret = diag_SendRequest(request);
  if(ret != 1)
  {
    testStepFail("can not generate key.");
    return;
  }
  // 3. 接收诊断响应, 获取 seedArray
  size = DiagGetRespPrimitiveSize(request);
  seedSize = size - 2;
  for(i = 2; i < size; i++)
  {
    seedArray[i - 2] = DiagGetRespPrimitiveByte(request, i);
  }
  // 5. 计算密钥
  ret = diagGenerateKeyFromSeed(seedArray, seedSize, securityLevel, "", "", keyArray, elCount(keyArray), actualKeySize);
  if(ret != 0)
  {
    testStepFail("can't generate key!");
    return;
  }
  // 6. 发送密钥
  reqData[0] = 0x27;
  reqData[1] = securityLevel + 1;
  for(i = 0; i < actualKeySize; i++)
  {
    reqData[i + 2] = keyArray[i];
  }
  diagResize(request, actualKeySize + 2);
  diagSetPrimitiveData(request, reqData, actualKeySize + 2);
  diag_SendRequest(request);
}

测试代码:

testcase TC_011_securityAccess_Test()
{
  byte reqData[4095] = {0x10, 0x03};
  
  diagSetTarget(ecuName);
  diagResize(request, 2);
  diagSetPrimitiveData(request, reqData, 2);
  diag_SendRequest(request);
  
  securityAccess(0x01);
}

通过上面的方式,可以将诊断过程中的请求数据和响应数据,都打印到测试报告中。在故障注入的测试中,能够将故障注入可视化,看到种子和密钥的信息,实现了整个解锁过程;


总结

本文系统介绍了 CANoe 中 CAPL 诊断测试的完整流程,从 UDS 诊断协议、诊断描述文件到传输层与诊断层参数,再到诊断请求的发送、响应判断和通用函数封装,最后讲解了安全解锁的两种实现方式。

通过封装通用诊断函数,可以显著提高测试代码的复用度和开发效率。读者掌握这些方法后,即可根据实际测试需求灵活编写诊断测试脚本,提升诊断测试的效率和可维护性。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/weixin_41965831/article/details/167784682

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--