读取DTC并识别故障类型(汽车车门)
DTC的全称是,即诊断故障码,它是由车载诊断系统识别的故障状态的数字通用标识符。怎么去理解呢?可以去想象一个场景,如果我们的汽车在运行时出现的故障,我们很大可能会送去维修。那么维修人员如何快速的定位到汽车出现故障的地方呢?这时候他们就会使用专业的诊断仪器直接读取出当前车辆的故障码。
这是来到车企公司接到的第一个小型任务,我在几位同事师傅的帮助下花了4天完成了该任务。一路上,坎坷荆棘不断但统统被我攻克下来了。在这里也是希望本人的这篇文章能够帮助到各位博友,如果你做的项目和本人的类似那你可以参考本人的设计思路。
在做这次项目之前我只会简单使用canoe与ECU信息的收发,写这个目的呢,是为了激励各位博友遇到困难别放弃,有不会的就多问问前辈,如果你做的东西和博主像似,也可以私信博主一起探讨,分享自己的学习成果是我最开心的事情,谢谢大家!!!(如果你不会canoe与ECU通信呢!也别着急,去B站搜:老贾教车载测试 中的CANOE从入门到精通)
关于什么是DTC?
DTC的全称是Diagnostic Trouble Code,即诊断故障码,它是由车载诊断系统识别的故障状态的数字通用标识符。
怎么去理解呢?可以去想象一个场景,如果我们的汽车在运行时出现的故障,我们很大可能会送去维修。那么维修人员如何快速的定位到汽车出现故障的地方呢?这时候他们就会使用专业的诊断仪器直接读取出当前车辆的故障码。他们之所以能读出故障码是因为汽车的车载控制器会时刻监控汽车的运行情况,在发现汽车故障的时候会将相关信息进行存储,维修人员通过诊断仪向汽车发送19服务(请求读取故障信息)的请求,车载控制器就会反馈对应的故障信息,而故障码DTC就是这些故障信息的身份ID。
该怎么实现读取DTC并识别故障类型呢?

该如何去发送DTC请求呢?
因为这里我使用的是编写CAPL语言去实现发送DTC请求,我就直接举实例去说明如果有不懂的地方可以去B站搜 会开车的鸟哥 中发送DTC请求看全篇讲解(这里我就长话短说了);
车辆左前门PDM:物理接受ID为:771;
物理发送ID为:779;
车辆右前门PDM:物理接受ID为:770;
物理发送ID为:778;
车辆左后门PDM:物理接受ID为:773;
物理发送ID为:77B;
车辆右前门PDM:物理接受ID为:772;
物理发送ID为:77A;
声明:本博主因为做的项目是读取汽车车门DTC,及识别故障类型。其接受信息的ID为771,发送ID为779。并不是所有的车门都是这个物理ID ,这里举例子是为了方便各位看明白。
首先呢我们要了解DTC的结构组成
这里我就以实例说明,因为叙述DTC的发送牵扯到一系列类内容讲起来晦涩难懂,为了各位博友轻松看完本文我就不叙述那些繁琐的知识点了,但你遇到你不懂的地方要及时去了解清楚,或者和博主讨论;
![]()
可以从上图看出是向车门PDM发送了一帧ID为771的8位字节的数据(DTC请求)。
其中起始帧03:代表数据长度为3 ::: [19]、[02]、[09]
第一帧19:代表19服务 :::DTC的发送请求服务ID标识符为19(一个诊断服务中有多个服务请求,这里DTC的为19)(这里涉及到UDS协议)
第二帧02:代表19服务中子功能02:::其功能为通过状态掩码报告DTC
第三帧09:代表其状态掩码为09::::::0 0 0 0 1 0 0 1 :::末置位1代表识别到当前存在故障标志位,倒数第4个1代表故障经过一段时间依旧存在(大体理解一下,方便看后续内容)
上面就是发送DTC的请求大体内容,主要是先让各位博友有个概念方便深入了解;
该如何读取DTC呢?
当我们发送完DTC请求后,车门PDM会反馈出以下内容:(这里我赘诉一下:这里是我弄出一些故障之后报出来的报文)

可以从上图看出是从车门PDM接收到了两帧ID为779的16位字节的数据。
内容解释

图片中黄色荧光笔标记的1表示该帧数据为连续帧,后续红色标记的00B部分表示数据长度为11;
图中:
第一帧数据59:代表返回的服务ID:::0x19+0x40;
第二帧数据 02:代表59服务中子功能02:::其功能为通过状态掩码报告DTC
第三帧数据09:代表代表其状态掩码为09
后续第4帧,第5帧,第6帧,第7帧为一组合帧:[91][17][16]组合起来代表一个故障:电源电压过低,而第7帧[09] :代表此故障为当前故障且在发生;
再后续第8帧,第9帧,第10帧;第11帧为1组合帧:[D1][65][87]组合起来也代表一个故障: 与雷达通讯失效,而第11帧[08]:代表此故障为历史故障当前未发生
那又有博友问这个中间771 30开头的这个是什么呢?
问的好:这是一个流控帧。用来指示发送网络实体是否可以继续进行消息传输。。因为返回来的信息不止8位,所以当发完连续帧中的一帧后可以加入流控帧用来指示后续还能传输多少数据,且隔多久传输数据;
上图中:
起始帧30:流控帧的开头;
第一帧0A:后续可以发送的数据长度为10;
第二帧0A : 间隔时间位10ms;
根据上诉的理解相信各位博友对DTC发送请求和ECU返回来的信息有了一定了解;
那该如何识别故障类型呢?
哈哈这就太简单了,写一个就写以下这么一个小小的程序就行了
for(i=0;i<rxLength;i++)
{
if(DataRX[i]== 0x09&&i>5)
{
dtcCode=DataRX[i-3]<<16|DataRX[i-2]<<8|DataRX[i-1];
write("Received DTC Code: %X", dtcCode);
}
}
该程序的意思是:当接受的数据为0x09时,其该数据在大于5位置,就将该帧的前3个数据移位并使用一个数组(dtcCode)存起来。然后一个swtich判断dtcCode的内容就行了。
下面是我使用capl语言写的发送DTC请求以及识别故障的代码,代码中都有详细的备注
下面程序中调用了OSEKE的库:比如流动帧的初始化
/*@!Encoding:936*/
includes
{
}
variables
{
//左前门反馈的报文ID
dword PLG_FL_Diag_ResId = 0x779;
//左前门发送DTC服务ID
dword PLG_FL_Diag_PhyId = 0x771;
dword PLG_FR_Diag_ResId = 0x778;
dword PLG_FR_Diag_PhyId = 0x770;
dword PLG_RL_Diag_ResId = 0x77B;
dword PLG_RL_Diag_PhyId = 0x773;
dword PLG_RR_Diag_ResId = 0x77A;
dword PLG_RR_Diag_PhyId = 0x772;
//状态判断用于响应
enum bl_uds_status
{
REQUEST_OK = 0,
REQUEST_Send_Err,
RESPONSE_OK,
RESPONSE_PENDING,
RESPONSE_NOT_OK,
RESPONSE_Unknow,
RESPONSE_Crc_Err
};
enum b2_uds_status
{
FR_REQUEST_OK = 0,
FR_REQUEST_Send_Err,
FR_RESPONSE_OK,
FR_RESPONSE_PENDING,
FR_RESPONSE_NOT_OK,
FR_RESPONSE_Unknow,
FR_RESPONSE_Crc_Err
};
enum b3_uds_status
{
RL_REQUEST_OK = 0,
RL_REQUEST_Send_Err,
RL_RESPONSE_OK,
RL_RESPONSE_PENDING,
RL_RESPONSE_NOT_OK,
RL_RESPONSE_Unknow,
RL_RESPONSE_Crc_Err
};
enum b4_uds_status
{
RR_REQUEST_OK = 0,
RR_REQUEST_Send_Err,
RR_RESPONSE_OK,
RR_RESPONSE_PENDING,
RR_RESPONSE_NOT_OK,
RR_RESPONSE_Unknow,
RR_RESPONSE_Crc_Err
};
byte gVar_Bl_DataBuffer_tx[0x80000];//定义发送数据的数组
word gVar_Bl_Txlength;//定义发送数据数组的长度
byte DataRX[100];//接收数据的长度
dword dtcCode;//当前故障的故障ID
dword dtcCode1;//历史故障的故障ID
byte btnlaststate;//用于判断按键状态,实现按一次按钮只执行一次发送
}
/*
左前门服务ID:771
定义发送19服务,子功能02,掩码为09
*/
enum bl_uds_status Bl_DiagReq_ReadDTC()
{
enum bl_uds_status ret;
ret = REQUEST_OK;
Bl_Write_Diag_DataBuffer_tx(0 ,0x19);
Bl_Write_Diag_DataBuffer_tx(1 ,0x02);
Bl_Write_Diag_DataBuffer_tx(2 ,0x09);
Bl_TransimitDataRequest(PLG_FL_Diag_PhyId, 3);
return ret;
}
/*
右前门服务ID:770
定义发送19服务,子功能02,掩码为09
*/
enum b2_uds_status FR_Bl_DiagReq_ReadDTC()
{
enum b2_uds_status ret;
ret = FR_REQUEST_OK;
Bl_Write_Diag_DataBuffer_tx(0 ,0x19);
Bl_Write_Diag_DataBuffer_tx(1 ,0x02);
Bl_Write_Diag_DataBuffer_tx(2 ,0x09);
Bl_TransimitDataRequest(PLG_FR_Diag_PhyId, 3);
return ret;
}
/*
左后门服务ID:773
定义发送19服务,子功能02,掩码为09
*/
enum b3_uds_status RL_Bl_DiagReq_ReadDTC()
{
enum b3_uds_status ret;
ret = RL_REQUEST_OK;
Bl_Write_Diag_DataBuffer_tx(0 ,0x19);
Bl_Write_Diag_DataBuffer_tx(1 ,0x02);
Bl_Write_Diag_DataBuffer_tx(2 ,0x09);
Bl_TransimitDataRequest(PLG_RL_Diag_PhyId, 3);
return ret;
}
/*
右后门服务ID:772
定义发送19服务,子功能02,掩码为09
*/
enum b4_uds_status RR_Bl_DiagReq_ReadDTC()
{
enum b4_uds_status ret;
ret = RR_REQUEST_OK;
Bl_Write_Diag_DataBuffer_tx(0 ,0x19);
Bl_Write_Diag_DataBuffer_tx(1 ,0x02);
Bl_Write_Diag_DataBuffer_tx(2 ,0x09);
Bl_TransimitDataRequest(PLG_RR_Diag_PhyId, 3);
return ret;
}
//流控帧的初始化
void Bl_OSEKTL_Init()
{
//切换到正常地址
OSEKTL_SetNrmlMode();
OSEKTL_SetBS(0x0A);
OSEKTL_SetSTMIN(0x0A);
OSEKTL_UseBSOfFC();
OSEKTL_UseSTminOfFC();
OSEKTL_SetDLC8();
OSEKTL_SetEvalOneFC();
OSEKTL_SetUseFC(1);
OSEKTL_Set0Padding();
OSEKTL_Set0Padding();
OSEKTL_Set0Pattern(0x00);
OSEKTL_SetRxId(PLG_FL_Diag_ResId);
//OSEKTL_SetRxId(PLG_FR_Diag_ResId);
//OSEKTL_SetRxId(PLG_RL_Diag_ResId);
//OSEKTL_SetRxId(PLG_RR_Diag_ResId);
OSEKTL_Set2003Ext(1);
}
void FR_Bl_OSEKTL_Init()
{
//切换到正常地址
OSEKTL_SetNrmlMode();
OSEKTL_SetBS(0x0A);
OSEKTL_SetSTMIN(0x0A);
OSEKTL_UseBSOfFC();
OSEKTL_UseSTminOfFC();
OSEKTL_SetDLC8();
OSEKTL_SetEvalOneFC();
OSEKTL_SetUseFC(1);
OSEKTL_Set0Padding();
OSEKTL_Set0Padding();
OSEKTL_Set0Pattern(0x00);
//OSEKTL_SetRxId(PLG_FL_Diag_ResId);
OSEKTL_SetRxId(PLG_FR_Diag_ResId);
//OSEKTL_SetRxId(PLG_RL_Diag_ResId);
//OSEKTL_SetRxId(PLG_RR_Diag_ResId);
OSEKTL_Set2003Ext(1);
}
void RL_Bl_OSEKTL_Init()
{
//切换到正常地址
OSEKTL_SetNrmlMode();
OSEKTL_SetBS(0x0A);
OSEKTL_SetSTMIN(0x0A);
OSEKTL_UseBSOfFC();
OSEKTL_UseSTminOfFC();
OSEKTL_SetDLC8();
OSEKTL_SetEvalOneFC();
OSEKTL_SetUseFC(1);
OSEKTL_Set0Padding();
OSEKTL_Set0Padding();
OSEKTL_Set0Pattern(0x00);
//OSEKTL_SetRxId(PLG_FL_Diag_ResId);
//OSEKTL_SetRxId(PLG_FR_Diag_ResId);
OSEKTL_SetRxId(PLG_RL_Diag_ResId);
//OSEKTL_SetRxId(PLG_RR_Diag_ResId);
OSEKTL_Set2003Ext(1);
}
void RR_Bl_OSEKTL_Init()
{
//切换到正常地址
OSEKTL_SetNrmlMode();
OSEKTL_SetBS(0x0A);
OSEKTL_SetSTMIN(0x0A);
OSEKTL_UseBSOfFC();
OSEKTL_UseSTminOfFC();
OSEKTL_SetDLC8();
OSEKTL_SetEvalOneFC();
OSEKTL_SetUseFC(1);
OSEKTL_Set0Padding();
OSEKTL_Set0Padding();
OSEKTL_Set0Pattern(0x00);
//OSEKTL_SetRxId(PLG_FL_Diag_ResId);
//OSEKTL_SetRxId(PLG_FR_Diag_ResId);
//OSEKTL_SetRxId(PLG_RL_Diag_ResId);
OSEKTL_SetRxId(PLG_RR_Diag_ResId);
OSEKTL_Set2003Ext(1);
}
OSEKTL_ErrorInd(int error)
{
}
//按下按键Button_FL(左前门故障按钮),执行左前门的19服务发送
/*如果不是用btnlaststate会出按一次按钮触发两次19服务发送,(按下触发一次,松开触发一次)*/
on sysvar ButtonVar::Button_FL
{
Bl_OSEKTL_Init();//调用流控帧初始化,以及配置收回信息的物理ID
if(@ButtonVar::Button_FL == 0 && btnlaststate == 1)
{
Bl_DiagReq_ReadDTC();
}
btnlaststate = @ButtonVar::Button_FL;
}
//按下按键Button_FR(右前门故障按钮),执行右前门的19服务发送
on sysvar ButtonVar::Button_FR
{
FR_Bl_OSEKTL_Init();//调用流控帧初始化,以及配置收回信息的物理ID
if(@ButtonVar::Button_FR == 0 && btnlaststate == 1)
{
FR_Bl_DiagReq_ReadDTC();
}
btnlaststate = @ButtonVar::Button_FR;
}
//按下按键Button_RL(左后门门故障按钮,)执行左后门的19服务发送
on sysvar ButtonVar::Button_RL
{
RL_Bl_OSEKTL_Init();//调用流控帧初始化,以及配置收回信息的物理ID
if(@ButtonVar::Button_RL == 0 && btnlaststate == 1)
{
RL_Bl_DiagReq_ReadDTC();
}
btnlaststate = @ButtonVar::Button_RL;
}
//按下按键Button_RR(右后门门故障按钮),执行右后门的19服务发送
on sysvar ButtonVar::Button_RR
{
RR_Bl_OSEKTL_Init();//调用流控帧初始化,以及配置收回信息的物理ID
if(@ButtonVar::Button_RR == 0 && btnlaststate == 1)
{
RR_Bl_DiagReq_ReadDTC();
}
btnlaststate = @ButtonVar::Button_RR;
}
/**
* @brief Bl_Write_Diag_DataBuffer_tx
* @par[in]
* @endcode
* 写入诊断数据发送数组
*/
void Bl_Write_Diag_DataBuffer_tx(dword i , byte value)
{
if(i >= elCount(gVar_Bl_DataBuffer_tx))
{
return ;
}
gVar_Bl_DataBuffer_tx[i] = value;
}
/**
* @brief Bl_TransimitDataRequest
* @par[in]
* @endcode
*发送诊断请求
*/
void Bl_TransimitDataRequest(dword NodeId,word length)
{
Bl_Write_gVar_Bl_Txlength(length);
//waitDataTxConf = 1;
OSEKTL_SetTxId(NodeId);
OSEKTL_DataReq(gVar_Bl_DataBuffer_tx,length);
}
void Bl_Write_gVar_Bl_Txlength(dword len)
{
gVar_Bl_Txlength = len;
}
OSEKTL_DataInd( long rxLength)
{
int i=0,n=0;
enum bl_uds_status ret;
OSEKTL_GetRxData(DataRX,rxLength);//数据接收
for(i=0;i<rxLength;i++)
{
if(DataRX[i]== 0x09&&i>5)
{
dtcCode=DataRX[i-3]<<16|DataRX[i-2]<<8|DataRX[i-1];
write("Received DTC Code: %X", dtcCode);
//putValueToControl("故障检测","DTCnumber",dtcCode);
switch (dtcCode)
{
case 0x911717:
write("Type: 电源电压高");
putValueToControl("DTC CHECK","failreason","电源电压高\n");
break;
case 0x911716:
write("Type: 电源电压低");
putValueToControl("DTC CHECK","failreason","电源电压低\n");
break;
case 0xA7C000:
write("Type: 电动侧门电机电气故障");
putValueToControl("DTC CHECK","failreason","电动侧门电机电气故障\n");
break;
case 0xA7C100:
write("Type: 电动侧门关闭电机编码器故障");
putValueToControl("DTC CHECK","failreason","电动侧门关闭电机编码器故障\n");
break;
case 0xA7C200:
write("Type: 侧门电机过流");
putValueToControl("DTC CHECK","failreason","侧门电机过流\n");
break;
case 0xA7C300:
write("Type: 侧门开启时间过长");
putValueToControl("DTC CHECK","failreason","侧门开启时间过长\n");
break;
case 0xA7C400:
write("Type: 侧门关闭时间过长");
putValueToControl("DTC CHECK","failreason","侧门关闭时间过长\n");
break;
case 0xA7C500:
write("Type: 编码器供电异常");
putValueToControl("DTC CHECK","failreason","编码器供电异常\n");
break;
case 0xA7C600:
write("Type: 侧门控制器电机驱动电路故障");
putValueToControl("DTC CHECK","failreason","侧门控制器电机驱动电路故障\n");
break;
case 0xA7C700:
write("Type: 异常上电复位");
break;
putValueToControl("DTC CHECK","failreason","异常上电复位\n");
case 0xC07988:
write("Type: Body CAN 总线故障");
putValueToControl("DTC CHECK","failreason","Body CAN 总线故障\n");
break;
case 0xC31287:
write("Type: 与VIU1丢失通讯");
putValueToControl("DTC CHECK","failreason","与VIU1丢失通讯\n");
break;
case 0xD16587:
write("Type: 与DRD_FL丢失通讯");
putValueToControl("DTC CHECK","failreason","与DRD_FL丢失通讯\n");
break;
default:
write("Type: Unknown");
putValueToControl("DTC CHECK","failreason","Unknown\n");
break;
}
}
// 当出现的数据是[08](当前故障的判断指针)
else if(DataRX[i]== 0x08&&i>5)
{
dtcCode1=DataRX[i-3]<<16|DataRX[i-2]<<8|DataRX[i-1];
write("Received DTC Code: %X", dtcCode1);
//putValueToControl("故障检测","DTCnumber",dtcCode);
switch (dtcCode1)
{
case 0x911717:
write("历史故障Type: 电源电压高");
putValueToControl("DTC CHECK","failreason_1","电源电压高\n");
break;
case 0x911716:
write("历史故障Type: 电源电压低");
putValueToControl("DTC CHECK","failreason_1","电源电压低\n");
break;
case 0xA7C000:
write("历史故障Type: 电动侧门电机电气故障");
putValueToControl("DTC CHECK","failreason_1","电动侧门电机电气故障\n");
break;
case 0xA7C100:
write("历史故障Type: 电动侧门关闭电机编码器故障");
putValueToControl("DTC CHECK","failreason_1","电动侧门关闭电机编码器故障\n");
break;
case 0xA7C200:
write("历史故障Type: 侧门电机过流");
putValueToControl("DTC CHECK","failreason_1","侧门电机过流\n");
break;
case 0xA7C300:
write("历史故障Type: 侧门开启时间过长");
putValueToControl("DTC CHECK","failreason_1","侧门开启时间过长\n");
break;
case 0xA7C400:
write("历史故障Type: 侧门关闭时间过长");
putValueToControl("DTC CHECK","failreason_1","侧门关闭时间过长\n");
break;
case 0xA7C500:
write("历史故障Type: 编码器供电异常");
putValueToControl("DTC CHECK","failreason_1","编码器供电异常\n");
break;
case 0xA7C600:
write("历史故障Type: 侧门控制器电机驱动电路故障");
putValueToControl("DTC CHECK","failreason_1","侧门控制器电机驱动电路故障\n");
break;
case 0xA7C700:
write("历史故障Type: 异常上电复位");
break;
putValueToControl("DTC CHECK","failreason_1","异常上电复位\n");
case 0xC07988:
write("历史故障Type: Body CAN 总线故障");
putValueToControl("DTC CHECK","failreason_1","Body CAN 总线故障\n");
break;
case 0xC31287:
write("历史故障Type: 与VIU1丢失通讯");
putValueToControl("DTC CHECK","failreason_1","与VIU1丢失通讯\n");
break;
case 0xD16587:
write("历史故障Type: 与DRD_FL丢失通讯");
putValueToControl("DTC CHECK","failreason_1","与DRD_FL丢失通讯\n");
break;
default:
write("历史故障Type: Unknown");
putValueToControl("DTC CHECK","failreason_1","Unknown\n");
break;
}
}
}
}
最后我已将自己的上位机置顶了,谢谢各位博友的观看,上位机中不明白的地方可以和我讨论,有些东西太细,拉出来说容易看懵。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)