一、简介
- 在车载诊断中常用的诊断协议有ISO 14229等,在协议中主要定义了诊断请求、诊断响应的报文格式及ECU该如何处理诊断请求的应用。
- 其中ISO 14229系列标准协议定义了用于行业内诊断通信的需求规范,也就是UDS。UDS主要应用于OSI七层模型的第七层——应用层,它支持的汽车总线包括:CAN、LIN、FlexRay、Ethernet及K-LINE。
- UDS中的服务根据其功能分为6大类,共26种。其中包含的0x19服务(ReadDTCInformation)则是UDS中的重中之重。那么我们今天就一起进入到19服务中,感受其中的奥秘。
二、 服务介绍
19服务(ReadDTCInformation)用于读取ECU的DTC故障信息,此服务允许客户端从服务器读取诊断故障代码(DTC)的相关信息。此服务包含28个子服务(Subfunction),常用的5种子服务如下:
- 0x01 reportNumberOfDTCByStatusMask(读取客户端定义状态掩码匹配的DTC数量)
- 0x02 reportDTCByStatusMask(读取客户端定义状态掩码匹配的DTC)
- 0x04 reportDTCSnapshotRecordByDTCNumber(检索客户端定义DTC掩码的快照数据)
- 0x06 reportDTCExtDataRecordByDTCNumber(读取某个DTC及其相关的扩展数据,扩展数据包括DTC状态,优先级,发生次数,时间戳,里程等。)
- 0x0A reportSupportedDTC(读取ECU支持的所有DTC的状态,包含支持的各个DTC编号以及相关状态)
今天主要解析19服务中的04子服务,也就是检索客户端定义DTC的快照号对应的快照记录数据,在AUTOSAR中也叫冻结帧。
(1)子服务介绍
-
快照数据概念介绍
前面讲19服务常用子服务的时候,提到了Subfunction为04的子服务,使用04子服务对服务端进行请求,可以获取DTC发生时记录的快照数据。那04子服务是如何获取快照数据的呢?首先我们需要理解什么是快照数据。从ISO 14229-1协议可知,快照数据为发生某一故障时记录的DTC的电压、发动机转速、时间戳等,从而使工程师在ECU出现故障时能及时了解车辆的历史和实时故障信息。 -
报文格式介绍
接下来通过介绍19 04子服务请求和响应的报文格式,分析报文中各个字节的相关定义。
请求格式
从图1中可知,19 04的请求报文包括四个部分,其中服务ID和Subfunction就不用过多解释了。DTCMaskRecord表示某个故障的DTC,当系统检测到一个故障发生时,则会存储其对应的故障数值,这个故障数值就是DTC。通过读取DTC可知一个故障发生时的具体位置以及原因和类型。通常UDS中DTC占3个字节,OBD Ⅱ占2个字节,在ISO 15031-6中定义的DTC由两个字节根基和一个字节的故障类型组成。我们通常用到的DTC格式都是由ISO 15031-6中定义的。图2是ISO 15031-6中定义的DTC的两个字节根基,图中很详细地解释了每一个Bit的含义。
SnapshotRecordNumber需要提前定义,可以有多个。如SnapshotRecordNumber设置为FF,则表示读取所有的快照数据组。
响应格式
图3为响应报文格式,当使用19 04对ECU进行请求时,ECU给出的肯定响应的报文格式由七部分组成。此时的DTCAndStatusRecord由三个字节的DTC和一个字节的StatusOfDTC组成,StatusOfDTC表示DTC的状态。假设现在的DTC状态为0x09,则Bit0和Bit3置1。如某个DTC一直存在并且确认,则在ECU响应的报文中的StatusOfDTC为0x09。如图4
SnapshotRecordNumber这个字节表示DTC快照记录的组号;DTCSnapshotRecordNumberOfldentifiers表示快照DID的个数,占一个字节;Dataldentifier这部分由两个字节组成,表示快照数据对应的DID,DTCSnapshotRecord表示快照DID对应的具体数据。
三、示例代码
(1)19_read_dtc_info.c
/********************************************************************************
* @file 19_read_dtc_info.c
* @author jianqiang.xue
* @version V1.0.0
* @date 2023-05-30
* @brief 读取DTC信息
********************************************************************************/
/* Includes ------------------------------------------------------------------*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "modules.h"
#include "os_api.h"
#include "edebug.h"
#include "kv_sys.h"
#include "ecu_ble_uart.h"
/* Private includes ----------------------------------------------------------*/
#include "std_math.h"
#include "app_can.h"
#include "can_nm.h"
#include "app_nm.h"
#include "diag_main.h"
#if AUTOSAR_DIAG_DTC_SWITCH
#include "dtc_main.h"
#endif
/* Private define ------------------------------------------------------------*/
#define UDS_ID 0x19
#define MAX_BUFF_SIZE 150
/* Private typedef -----------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/***************软定时器创建***************/
/* Private func --------------------------------------------------------------*/
/**
* @brief 通过状态掩码报告DTC数目
* @param *data: 数据指针
* @param len: 数据长度
* @retval 0--成功 >0--错误
*/
static int8_t uds19_01(uint8_t *data, uint16_t len, uint8_t *out) {
if (len != 3) {
LOGE("len err != 3, sub%02x", data[1]);
send_nrc_data(UDS_ID, NRC_INCORRECT_MESSAGE_LENTH);
return 1;
}
if (data[1] & 0x80) { // 无需应答
LOGI("No answer required, sub%02x", data[1]);
} else {
// 回复正响应码 单帧格式: len, 服务ID|0x40, 子功能ID,
out[0] = 6; // 数据总长度= 服务号 + sub_id + mask + data + num(2byte)
out[1] = UDS_ID | 0x40; // 服务号,回复上位机需要 |0x40
out[2] = data[1];
out[3] = AUTOSAR_DIAG_DTC_STATE_BIT; // 获取DTC掩码
out[4] = 0; // SAE_J2012-DA_DTCFormat_00
uint16_t dtc_count = get_dtc_num_by_mask(data[2]);
out[5] = (uint8_t)(dtc_count >> 8);
out[6] = (uint8_t)(dtc_count & 0xFF);
out[7] = 0xAA;
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
}
return 0;
}
/**
* @brief 通过状态掩码报告DTC
* @param *data: 数据指针
* @param len: 数据长度
* @retval 0--成功 >0--错误
*/
static int8_t uds19_02(uint8_t *data, uint16_t len, uint8_t *out) {
if (len != 3) {
LOGE("len err != 3, sub%02x", data[1]);
send_nrc_data(UDS_ID, NRC_INCORRECT_MESSAGE_LENTH);
return 1;
}
if (data[1] & 0x80) { // 无需应答
LOGI("No answer required, sub%02x", data[1]);
} else {
// 回复正响应码 单帧格式: len, 服务ID|0x40, 子功能ID,
out[1] = UDS_ID | 0x40; // 服务号,回复上位机需要 |0x40
out[2] = data[1];
out[3] = AUTOSAR_DIAG_DTC_STATE_BIT; // 获取DTC掩码
uint8_t d_len = 0;
d_len = get_dtc_status_by_mask(data[2], &out[4], MAX_BUFF_SIZE - 4 - 1); // 4--数据头 1--连续帧头预留
out[0] = 3 + d_len; // 数据总长度 = 服务号 + sub_id + mask + ndata
// 判断数据长度,单帧还是连续帧发送
if (out[0] > 7) {
memmove(out + 1, out, out[0] + 1); // 单帧改连续帧格式,数据总长度 + 长度位,数据总长度 + 长度位
out[0] = NWL_FIRST_FRAME << 4; // 数据帧格式(nwl_frame_st_t)
if (g_tx_msg.data != 0) {
free(g_tx_msg.data);
g_tx_msg.data = NULL;
}
// 发送首帧(只含6byte data)后,剩余长度:去除 (服务号|sub_id|mask + D0D1D2)
d_len = out[1] - 3 - 3;
g_tx_msg.data = malloc(d_len);
if (g_tx_msg.data == NULL) {
send_nrc_data(UDS_ID, NRC_ACCESS_DENIED);
return 2;
}
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
memcpy(g_tx_msg.data, out + 8, d_len);
g_tx_msg.len = d_len;
} else {
memset(&out[out[0] + 1], 0xAA, 8 - out[0] - 1); // 空白区填充指定值
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
}
}
return 0;
}
/**
* @brief 通过DTC码报告DTC 快照记录
* @param *data: 数据指针
* @param len: 数据长度
* @retval 0--成功 >0--错误
*/
static int8_t uds19_04(uint8_t *data, uint16_t len, uint8_t *out) {
uint32_t dtc_id;
if (len != 6) {
LOGE("len err != 6, sub%02x", data[1]);
send_nrc_data(UDS_ID, NRC_INCORRECT_MESSAGE_LENTH);
return 1;
} else { // == 6
uint8_t record_numb = data[5]; // 记录号
if ((record_numb > SS_NB_MAX) && record_numb != 0xFF && record_numb != 0x00) {
LOGE("record numb nonsupport:%02x, sub%02x", record_numb, data[1]);
send_nrc_data(UDS_ID, NRC_REQUEST_OUT_OF_RANGE);
return 2;
}
dtc_id = data[2];
dtc_id <<= 8;
dtc_id += data[3];
dtc_id <<= 8;
dtc_id += data[4];
uint8_t SN = is_dtc_id(dtc_id); // 得到dtc id,对应的数组索引
if (SN == 0xFF) {
LOGE("dtc id id err:%02x, sub%02x", dtc_id, data[1]);
send_nrc_data(UDS_ID, NRC_SUBFUNCTION_NOT_SUPPORTED);
return 2;
}
if (data[1] & 0x80) { // 无需应答
LOGI("No answer required, sub%02x", data[1]);
} else {
// 回复正响应码 单帧格式: len, 服务ID|0x40, 子功能ID,
out[1] = UDS_ID | 0x40; // 服务号,回复上位机需要 |0x40
out[2] = data[1]; // SUB_ID 子功能ID
out[3] = data[2]; // DTC_ID 低16bit
out[4] = data[3]; // DTC_ID 高8bit
out[5] = data[4]; // DTC_ID 低8bit
out[6] = get_dtc_snap_shot_status(SN, record_numb); // DTC Status 最近一次错误状态
out[7] = record_numb;
out[8] = SS_TYPE_MAX_NUMBER;
uint16_t d_len = 0;
d_len = get_dtc_snap_shot_by_id(SN, record_numb, &out[9], MAX_BUFF_SIZE - 8 - 1); // 7--数据头 1--连续帧头预留
// 数据总长度 = 服务号 + 子功能ID + DTC_ID(3byte) + DTC Status + record_numb + MAX_NUMBER + DATA_LEN
out[0] = 8 + d_len;
memmove(out + 1, out, out[0] + 1); // 单帧改连续帧格式,数据总长度 + 长度位
out[0] = NWL_FIRST_FRAME << 4; // 数据帧格式(nwl_frame_st_t)
if (g_tx_msg.data != 0) {
free(g_tx_msg.data);
g_tx_msg.data = NULL;
}
// 发送首帧(只含6byte data)后,剩余长度:去除 (服务号 + 子功能ID + DTC_ID(3byte) + DTC Status)
d_len = out[1] - 2 - 3 - 1;
g_tx_msg.data = malloc(d_len);
if (g_tx_msg.data == NULL) {
send_nrc_data(UDS_ID, NRC_ACCESS_DENIED);
return 3;
}
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
memcpy(g_tx_msg.data, out + 8, d_len);
g_tx_msg.len = d_len;
}
}
return 0;
}
/**
* @brief 通过DTC码报告DTC扩展数据记录
* @param *data: 数据指针
* @param len: 数据长度
* @retval 0--成功 >0--错误
*/
static int8_t uds19_06(uint8_t *data, uint16_t len, uint8_t *out) {
uint32_t dtc_id;
if (len != 6) {
LOGE("len err != 6, sub%02x", data[1]);
send_nrc_data(UDS_ID, NRC_INCORRECT_MESSAGE_LENTH);
return 1;
}
uint8_t record_numb = data[5]; // 记录号
if ((record_numb > SS_NB_MAX) && record_numb != 0xFF && record_numb != 0x00) {
LOGE("record numb nonsupport:%02x, sub%02x", record_numb, data[1]);
send_nrc_data(UDS_ID, NRC_REQUEST_OUT_OF_RANGE);
return 2;
}
dtc_id = data[2];
dtc_id <<= 8;
dtc_id += data[3];
dtc_id <<= 8;
dtc_id += data[4];
uint8_t SN = is_dtc_id(dtc_id); // 得到dtc id,对应的数组索引
if (SN == 0xFF) {
LOGE("dtc id id err:%02x, sub%02x", dtc_id, data[1]);
send_nrc_data(UDS_ID, NRC_SUBFUNCTION_NOT_SUPPORTED);
return 2;
}
if (data[1] & 0x80) { // 无需应答
LOGI("No answer required, sub%02x", data[1]);
} else {
// 回复正响应码 单帧格式: len, 服务ID|0x40, 子功能ID,
out[1] = UDS_ID | 0x40; // 服务号,回复上位机需要 |0x40
out[2] = data[1]; // SUB_ID 子功能ID
out[3] = data[2]; // DTC_ID 低16bit
out[4] = data[3]; // DTC_ID 高8bit
out[5] = data[4]; // DTC_ID 低8bit
out[6] = get_dtc_snap_shot_status(SN, record_numb); // DTC Status 最近一次错误状态
out[7] = record_numb;
//os_delay(1);
uint16_t d_len = 0;
d_len = get_dtc_snap_shot_ex_data(SN, record_numb, &out[8], MAX_BUFF_SIZE - 8 - 1); // 7--数据头 1--连续帧头预留
// 数据总长度 = 服务号 + 子功能ID + DTC_ID(3byte) + DTC Status + record_numb + DATA_LEN
out[0] = 7 + d_len;
if (out[0] > 7) {
memmove(out + 1, out, out[0] + 1); // 单帧改连续帧格式,数据总长度 + 长度位
out[0] = NWL_FIRST_FRAME << 4; // 数据帧格式(nwl_frame_st_t)
if (g_tx_msg.data != 0) {
free(g_tx_msg.data);
g_tx_msg.data = NULL;
}
// 发送首帧(只含6byte data)后, 去除 (服务号 + 子功能ID + DTC_ID(3byte) + DTC Status)
d_len = out[1] - 2 - 3 - 1;
g_tx_msg.data = malloc(d_len);
if (g_tx_msg.data == NULL) {
send_nrc_data(UDS_ID, NRC_ACCESS_DENIED);
return 3;
}
memcpy(g_tx_msg.data, out + 8, d_len);
g_tx_msg.len = d_len;
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
} else {
memset(&out[out[0] + 1], 0xAA, 8 - out[0] - 1); // 空白区填充指定值
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
}
}
return 0;
}
/**
* @brief 报告支持的全部DTC
* @param *data: 数据指针
* @param len: 数据长度
* @retval 0--成功 >0--错误
*/
static uint8_t uds19_0A(uint8_t *data, uint16_t len, uint8_t *out) {
if (len != 2) {
LOGE("len err != 2, sub%02x", data[1]);
send_nrc_data(UDS_ID, NRC_INCORRECT_MESSAGE_LENTH);
return 1;
}
if (data[1] & 0x80) { // 无需应答
LOGI("No answer required, sub%02x", data[1]);
} else {
// 回复正响应码 单帧格式: len, 服务ID|0x40, 子功能ID, mask
out[1] = UDS_ID | 0x40; // 服务号,回复上位机需要 |0x40
out[2] = data[1]; // SUB_ID 子功能ID
out[3] = AUTOSAR_DIAG_DTC_STATE_BIT; // 掩码
uint16_t d_len = 0;
d_len = get_all_dtc_status(&out[4], MAX_BUFF_SIZE - 4 - 1); // 4--数据头 1--连续帧头预留
// 数据总长度 = 服务号 + 子功能ID + 掩码 + DATA_LEN
out[0] = 3 + d_len;
// LOGD("%u,%u\r\n", out[0], d_len);
if (out[0] > 7) {
memmove(out + 1, out, out[0] + 1); // 单帧改连续帧格式,数据总长度 + 长度位
out[0] = NWL_FIRST_FRAME << 4; // 数据帧格式(nwl_frame_st_t)
if (g_tx_msg.data != 0) {
free(g_tx_msg.data);
g_tx_msg.data = NULL;
}
// 发送首帧(只含6byte data)后, 去除 (服务号 + 子功能ID + DTC_ID(3byte) + DTC Status)
d_len = out[1] - 2 - 3 - 1;
g_tx_msg.data = malloc(d_len);
if (g_tx_msg.data == NULL) {
send_nrc_data(UDS_ID, NRC_ACCESS_DENIED);
return 3;
}
memcpy(g_tx_msg.data, out + 8, d_len);
LOGD("%x,%u,%x,%u\r\n", g_tx_msg.data[d_len-1], d_len, out[out[1]+1], out[1]+1);
g_tx_msg.len = d_len;
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
} else {
memset(&out[out[0] + 1], 0xAA, 8 - out[0] - 1); // 空白区填充指定值
app_can_enqueue_msg(CAN_MSG_EVENT_SEND, NWL_RES_ADDR, out, 8);
}
}
return 0;
}
void uds19_main(nwl_msg_t* p) {
uint8_t data[MAX_BUFF_SIZE];
uint8_t d_len = 0;
if (p->len < 2) {
LOGE("len err < 2");
send_nrc_data(UDS_ID, NRC_INCORRECT_MESSAGE_LENTH);
goto end;
}
switch (p->data[1] & 0x7F) { // 子功能,bit7为应答位。 =1则不允许应答
// 通过状态掩码报告DTC数目
case 0x01: {
if (uds19_01(p->data, p->len, data) != 0) {
}
break;
}
// 通过状态掩码报告DTC
case 0x02:
if (uds19_02(p->data, p->len, data) != 0) {
}
break;
// 通过DTC码报告DTC 快照记录
case 0x04: {
if (uds19_04(p->data, p->len, data) != 0) {
}
break;
}
// 通过DTC码报告DTC扩展数据记录
case 0x06: {
if (uds19_06(p->data, p->len, data) != 0) {
}
break;
}
// 报告支持的全部DTC
case 0x0A: {
if (uds19_0A(p->data, p->len, data) != 0) {
}
break;
}
default:
send_nrc_data(UDS_ID, NRC_SUBFUNCTION_NOT_SUPPORTED);
break;
}
end:
return;
}
#if AUTOSAR_DIAG_SWITCH && USE_UDS_19 && AUTOSAR_DIAG_DTC_SWITCH
DIAG_SERVICE_REG(UDS_ID, DIAG_NO_SECURITY_LEVEL, (DEFAULT_SESSION|EXTENDED_SESSION),
(DIAG_PHYS_REQ|DIAG_FUNC_REQ), NULL, NULL, uds19_main);
#endif