[AUTOSAR][诊断管理][ECU][$19] 读取ECU的DTC故障信息

一、简介

  1. 在车载诊断中常用的诊断协议有ISO 14229等,在协议中主要定义了诊断请求、诊断响应的报文格式及ECU该如何处理诊断请求的应用。
  2. 其中ISO 14229系列标准协议定义了用于行业内诊断通信的需求规范,也就是UDS。UDS主要应用于OSI七层模型的第七层------应用层,它支持的汽车总线包括:CAN、LIN、FlexRay、Ethernet及K-LINE。
  3. 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)子服务介绍

  1. 快照数据概念介绍

    前面讲19服务常用子服务的时候,提到了Subfunction为04的子服务,使用04子服务对服务端进行请求,可以获取DTC发生时记录的快照数据。那04子服务是如何获取快照数据的呢?首先我们需要理解什么是快照数据。从ISO 14229-1协议可知,快照数据为发生某一故障时记录的DTC的电压、发动机转速、时间戳等,从而使工程师在ECU出现故障时能及时了解车辆的历史和实时故障信息。

  2. 报文格式介绍

    接下来通过介绍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

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
相关推荐
赞哥哥s5 天前
初始化函数的用法-节约flash空间的“妙计”
autosar·flash·data·bss
正午游巳14 天前
第二十节:MCAL GPT理论
汽车·嵌入式·autosar·车载嵌入式
正午游巳14 天前
第二十一节:MCAL GPT实操
汽车·autosar·汽车电子·车载嵌入式
酷酷的boy15 天前
AUTOSAR下网络时间(CAN)与本地 RTC 同步。
autosar·汽车电子
AUTOSAR组织1 个月前
AUTOSAR CP NvM 模块解析
汽车·autosar·软件架构·软件·标准
赞哥哥s1 个月前
2025年终总结简版
autosar
汽车软件工程师0011 个月前
ChatGpt指导嵌入式软件开发能力——2、TriCore深度专项训练
人工智能·chatgpt·autosar
汽车软件工程师0011 个月前
ChatGpt指导嵌入式软件开发能力
人工智能·chatgpt·autosar
汽车软件工程师0011 个月前
vector autosar,CAN 总线上能看到报文RTE 收不到信号COM 层 IPDU Callout 不触发
autosar
汽车软件工程师0011 个月前
vector autosar配置一个CAN接收报文,RTE层发现并未接收到信号,怎样查这个问题
开发语言·autosar