[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
相关推荐
Nice__J3 天前
AutosarMCAL开发——基于EB Gpt驱动
autosar
车载诊断技术4 天前
电子电气架构 --- 车身电子的未来发展
网络·数据库·架构·汽车·autosar
MasterTomato10 天前
AUTOSAR开源OS——Trampoline的编译与使用(一)
开源·操作系统·autosar·trampoline
CyberSecurity_zhang13 天前
汽车功能安全--TC3xx LBIST触发时机讨论
汽车·ecu·功能安全·lbist
美好生活丶14 天前
HexView 刷写文件脚本处理工具-命令行介绍(八)-文件合并(/MO /MT)
单片机·autosar·汽车电子·hexview·刷写文件
大叔带刺16 天前
打渔的寓言--汽车软件开发技术进化史
信息安全·以太网·autosar
车载诊断技术23 天前
电子电气架构 --- 软件定义汽车需要怎么样的EE架构
网络·架构·汽车·autosar·电子电器架构
车载诊断技术23 天前
电子电气架构---EEA的发展趋势
网络·架构·汽车·autosar·电子电器架构
车载诊断技术1 个月前
电子电气架构 --- 智能驾驶域控制器供应商简介
网络·架构·网络拓扑·autosar·电子电器架构
天马行空工作坊1 个月前
汽车电子中间件概述
中间件·汽车·autosar·dds