一、导入CDD文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、新建ECU节点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、CAPL中与诊断相关函数及事件

函数:diagSetTarget

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

函数:诊断请求

//通过diagSendRequest函数来发送物理寻址的诊断请求
long diagSendRequest (diagRequest obj)
//通过diagSendFunctional函数来发送功能寻址的诊断请求
long diagSendFunctional( diagRequest request)

变量:诊断请求

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

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

在这里插入图片描述

事件:诊断响应事件

写法 作用
on diagResponse * 匹配所有诊断响应
on diagResponse DomeECU.Programming_Start 只匹配 CDD 中名为 Programming_Start 的服务响应
on diagResponse DomeECU.Programming_Start //Programming_Start 表示仅切换到编程会话的诊断响应会触发该事件
{
  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除了事件回调之外,还提供了一个函数用于直接获取对应的诊断响应

long diagGetLastResponse (diagRequest req, diagResponse respOut);

long diagGetLastResponse (diagResponse respOut);

on key'B'
{
  diagRequest DomeECU.Extended_Start diagReq;
  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]);//打印诊断响应
  }
}

四、示例

示例1:通用诊断请求

/*@!Encoding:936*/
includes
{
}
variables
{
}
on start
{
  // 定义诊断ECU名称,此名称需要与CDD文件中 "ECU qualifier" 名称一致
  char ECU_Name[32] = "DomeECU";
  diagSetTarget(ECU_Name);  
}
on key'A'
{
  diagRequest* diagReq;//定义一个通用的诊断对象
  byte diagReqData[2]={0x10, 0x02};//定义请求内容
  diagReq.Resize(2);//重新设定诊断请求的长度
  diagReq.SetPrimitiveData(diagReqData,2);//设置诊断请求的原始数据,设置为数组中的内容,并设置数据长度
  diagsendRequest(diagReq);//发送诊断请求
  write("使用cAPL发送诊断请求");
}

注:进入非默认会话后,ECU 会有一个会话超时时间(通常几秒到几十秒),如果超时未收到新的诊断请求,ECU 会自动切回默认会话,导致后续诊断失败。Vector 的诊断栈(基于 CDD/ODX 文件配置)为了维持会话,会在会话即将超时前,自动发送 3E 00(TesterPresent 服务) 来 “保活”,防止会话超时。

在这里插入图片描述
当报文数据超过DLC时,会自动拆分为多帧。

示例2:发送诊断数据库中的诊断请求

on start
{
  // 定义诊断ECU名称,此名称需要与CDD文件中 "ECU qualifier" 名称一致
  char ECU_Name[32] = "DomeECU";
  diagSetTarget(ECU_Name);  
}

on key'A'
{
  // 定义2E写DID诊断对象
  diagRequest DomeECU.SMKVersion_Write diagReq;

  // 2E报文格式:SID(0x2E) + DID(2字节) + 自定义写入数据
  // 示例:DID=0xF1E4,写入数据0x04 0x05 0x06 0x07
  byte sendBuf[7] = {0x2E, 0xF1, 0xE4, 0x04, 0x05, 0x06, 0x07};
  
  // 设置请求原始数据
  diagReq.Resize(elcount(sendBuf));
  diagReq.SetPrimitiveData(sendBuf, elcount(sendBuf));

  // 发送请求
  diagSendRequest(diagReq);
  write("已发送 2E 写DID诊断请求");
}

示例3:27服务解锁

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

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

seedArray:种子数组
seedArraySize:种子的长度
securityLevel:安全访问等级,2701则等级为0x1,2703则等级为0x32709则等级为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,第二个字节为种子请求+1
    memcpy_off(keyReqData,2,gKeyArray,0,4);//将秘钥拷贝到秘钥请求数组中去
    diagReqKey.Resize(keyReqLen);//调节诊断请求大小
    diagReqKey.SetPrimitiveData(keyReqData,keyReqLen);//设置发送秘钥的诊断请求的数据
    diagSendRequest(diagReqKey);//发送秘钥诊断请求
  }
}

参考

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐