一、简介
- 在车载诊断中常用的诊断协议有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; // 服务号,回复上位机需要 |0x40out[2] = data[1];out[3] = AUTOSAR_DIAG_DTC_STATE_BIT; // 获取DTC掩码out[4] = 0; // SAE_J2012-DA_DTCFormat_00uint16_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; // 服务号,回复上位机需要 |0x40out[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 { // == 6uint8_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; // 服务号,回复上位机需要 |0x40out[2] = data[1]; // SUB_ID 子功能IDout[3] = data[2]; // DTC_ID 低16bitout[4] = data[3]; // DTC_ID 高8bitout[5] = data[4]; // DTC_ID 低8bitout[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_LENout[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; // 服务号,回复上位机需要 |0x40out[2] = data[1]; // SUB_ID 子功能IDout[3] = data[2]; // DTC_ID 低16bitout[4] = data[3]; // DTC_ID 高8bitout[5] = data[4]; // DTC_ID 低8bitout[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_LENout[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, maskout[1] = UDS_ID | 0x40; // 服务号,回复上位机需要 |0x40out[2] = data[1]; // SUB_ID 子功能IDout[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_LENout[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;}// 通过状态掩码报告DTCcase 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;}// 报告支持的全部DTCcase 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