打开网易新闻 查看精彩图片

UDS(统一诊断服务)详解及 C++ 代码示例 一、UDS 概述

UDS(Unified Diagnostic Services,统一诊断服务)是 ISO 14229 标准定义的汽车诊断协议,广泛应用于 ECU(电子控制单元)的诊断、刷写、标定等场景。它运行在 CAN、CAN-FD、Ethernet 等底层传输协议之上(如 ISO 15765-2 即 DoCAN)。

UDS 的核心思想是:客户端(诊断仪)发送服务请求,服务端(ECU)执行并返回响应。所有交互都通过SID(Service Identifier,服务标识符)来区分功能。

1.1 请求/响应模型

+-----------+     Request (SID + Data)      +-----------+
| Tester | ----------------------------> | ECU |
| (Client) | <---------------------------- | (Server) |
+-----------+ Response (SID+0x40 + Data)+-----------+

关键规则:

  • 肯定响应:响应 SID = 请求 SID + 0x40

    • 例如请求0x22(ReadDataByIdentifier),肯定响应为0x62。

  • 否定响应:固定为0x7F,格式为7F

    • NRC(Negative Response Code)表示失败原因,如0x13(报文长度错误)、0x31(请求超出范围)、0x22(条件不满足)等。

1.2 常用 UDS 服务

SID

服务名

功能

肯定响应

0x10

DiagnosticSessionControl

切换诊断会话(默认/编程/扩展)

0x50

0x11

ECUReset

ECU 复位

0x51

0x14

ClearDiagnosticInformation

清除故障码

0x54

0x19

ReadDTCInformation

读取故障码

0x59

0x22

ReadDataByIdentifier

按 DID 读数据

0x62

0x27

SecurityAccess

安全访问(种子/密钥)

0x67

0x2E

WriteDataByIdentifier

按 DID 写数据

0x6E

0x31

RoutineControl

例程控制

0x71

0x3E

TesterPresent

保持会话

0x7E

0x34/0x36/0x37

刷写相关

下载/传输/退出

0x74/0x76/0x77

1.3 常见 NRC

NRC

含义

0x10

General reject

0x11

Service not supported

0x12

Sub-function not supported

0x13

Incorrect message length

0x22

Conditions not correct

0x31

Request out of range

0x33

Security access denied

0x78

Response pending(用于长耗时操作)

二、C++ 代码示例

下面实现一个简化但结构完整的 UDS 服务端,模拟 ECU 处理请求与生成响应。代码演示了:

  • 请求/响应帧结构

  • 服务分发(dispatch)

  • 肯定响应 SID + 0x40

  • 否定响应 0x7F + NRC

  • DID 读写、DTC 读取、清除故障码

#include  

#include
#include
#include
#include
#include

using Byte = uint8_t;
using Frame = std::vector ;

// ---------------- UDS 常量定义 ----------------
namespace UDS {
// 服务 SID
constexpr Byte SID_DiagnosticSessionControl = 0x10;
constexpr Byte SID_ECUReset = 0x11;
constexpr Byte SID_ClearDiagnosticInformation = 0x14;
constexpr Byte SID_ReadDTCInformation = 0x19;
constexpr Byte SID_ReadDataByIdentifier = 0x22;
constexpr Byte SID_WriteDataByIdentifier = 0x2E;
constexpr Byte SID_TesterPresent = 0x3E;

// 肯定响应偏移
constexpr Byte POS_RESP_OFFSET = 0x40;

// 否定响应
constexpr Byte NEG_RESP_SID = 0x7F;

// NRC
constexpr Byte NRC_GeneralReject = 0x10;
constexpr Byte NRC_ServiceNotSupported = 0x11;
constexpr Byte NRC_SubFunctionNotSupport = 0x12;
constexpr Byte NRC_IncorrectLength = 0x13;
constexpr Byte NRC_ConditionsNotCorrect = 0x22;
constexpr Byte NRC_RequestOutOfRange = 0x31;
}

// ---------------- 打印工具 ----------------
void printFrame(const std::string& tag, const Frame& f) {
std::cout << tag << ": ";
for (Byte b : f)
std::cout << std::hex << std::uppercase
<< std::setw(2) << std::setfill('0') << (int)b << " ";
std::cout << std::dec << "\n";
}

// ---------------- 模拟 ECU ----------------
class UdsServer {
public:
UdsServer() {
// 初始化一些 DID 数据
didTable_[0xF190] = {'V','I','N','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
didTable_[0xF187] = {'P','A','R','T','-','0','0','1'};
// 初始化 DTC
dtcList_ = {0x00A001, 0x00B002};
}

// 核心入口:接收请求,返回响应
Frame handleRequest(const Frame& request) {
if (request.empty()) return negativeResponse(0x00, UDS::NRC_IncorrectLength);

Byte sid = request[0];
std::cout << "\n[ECU] 收到请求 SID=0x" << std::hex << (int)sid << std::dec << "\n";

switch (sid) {
case UDS::SID_DiagnosticSessionControl: return handleSessionControl(request);
case UDS::SID_ECUReset: return handleEcuReset(request);
case UDS::SID_ClearDiagnosticInformation: return handleClearDtc(request);
case UDS::SID_ReadDTCInformation: return handleReadDtc(request);
case UDS::SID_ReadDataByIdentifier: return handleReadDid(request);
case UDS::SID_WriteDataByIdentifier: return handleWriteDid(request);
case UDS::SID_TesterPresent: return handleTesterPresent(request);
default: return negativeResponse(sid, UDS::NRC_ServiceNotSupported);
}
}

private:
std::map didTable_; // DID -> 数据
std::vector dtcList_; // 故障码列表
Byte currentSession_ = 0x01; // 默认会话

// ---------- 构造响应 ----------
Frame positiveResponse(Byte sid, const Frame& data = {}) {
Frame resp;
resp.push_back(sid + UDS::POS_RESP_OFFSET);
resp.insert(resp.end(), data.begin(), data.end());
return resp;
}

Frame negativeResponse(Byte sid, Byte nrc) {
return { UDS::NEG_RESP_SID, sid, nrc };
}

// ---------- 各服务处理 ----------

// 0x10 会话控制:请求 [10, subFunc]
Frame handleSessionControl(const Frame& req) {
if (req.size() != 2)
return negativeResponse(req[0], UDS::NRC_IncorrectLength);

Byte sub = req[1] & 0x7F;
if (sub != 0x01 && sub != 0x02 && sub != 0x03)
return negativeResponse(req[0], UDS::NRC_SubFunctionNotSupport);

currentSession_ = sub;
// 响应:50
Frame data = { sub, 0x00, 0x32 };
return positiveResponse(req[0], data);
}

// 0x11 ECU 复位:请求 [11, subFunc]
Frame handleEcuReset(const Frame& req) {
if (req.size() != 2)
return negativeResponse(req[0], UDS::NRC_IncorrectLength);
Byte sub = req[1] & 0x7F;
// 响应:51
return positiveResponse(req[0], { sub });
}

// 0x14 清除 DTC:请求 [14, groupHigh, groupMid, groupLow]
Frame handleClearDtc(const Frame& req) {
if (req.size() != 4)
return negativeResponse(req[0], UDS::NRC_IncorrectLength);

dtcList_.clear();
std::cout << "[ECU] 故障码已清除\n";
// 肯定响应只有 SID
return positiveResponse(req[0]);
}

// 0x19 读取 DTC:请求 [19, subFunc=0x02, statusMask]
Frame handleReadDtc(const Frame& req) {
if (req.size() != 3)
return negativeResponse(req[0], UDS::NRC_IncorrectLength);

Byte sub = req[1];
if (sub != 0x02) // 只支持 reportDTCByStatusMask
return negativeResponse(req[0], UDS::NRC_SubFunctionNotSupport);

// 响应:59 02 [DTC(3B) status(1B)]...
Frame data = { sub, 0xFF };
for (uint32_t dtc : dtcList_) {
data.push_back((dtc >> 16) & 0xFF);
data.push_back((dtc >> 8) & 0xFF);
data.push_back(dtc & 0xFF);
data.push_back(0x09); // 状态:confirmedDTC
}
return positiveResponse(req[0], data);
}

// 0x22 读数据:请求 [22, DID_H, DID_L, ...]
Frame handleReadDid(const Frame& req) {
if (req.size() < 3 || (req.size() % 2) == 0)
return negativeResponse(req[0], UDS::NRC_IncorrectLength);

Frame data;
for (size_t i = 1; i + 1 < req.size(); i += 2) {
uint16_t did = (req[i] << 8) | req[i + 1];
auto it = didTable_.find(did);
if (it == didTable_.end())
return negativeResponse(req[0], UDS::NRC_RequestOutOfRange);
data.push_back(req[i]);
data.push_back(req[i + 1]);
data.insert(data.end(), it->second.begin(), it->second.end());
}
return positiveResponse(req[0], data);
}

// 0x2E 写数据:请求 [2E, DID_H, DID_L, data...]
Frame handleWriteDid(const Frame& req) {
if (req.size() < 4)
return negativeResponse(req[0], UDS::NRC_IncorrectLength);

uint16_t did = (req[1] << 8) | req[2];
Frame value(req.begin() + 3, req.end());
didTable_[did] = value;

// 响应:6E DID_H DID_L
return positiveResponse(req[0], { req[1], req[2] });
}

// 0x3E TesterPresent:请求 [3E, 00]
Frame handleTesterPresent(const Frame& req) {
if (req.size() != 2)
return negativeResponse(req[0], UDS::NRC_IncorrectLength);
Byte sub = req[1] & 0x7F;
if (sub != 0x00)
return negativeResponse(req[0], UDS::NRC_SubFunctionNotSupport);
return positiveResponse(req[0], { 0x00 });
}
};

// ---------------- 主函数:模拟诊断仪 ----------------
int main() {
UdsServer ecu;

// 构造若干诊断请求
std::vector requests = {
{ 0x10, 0x03 }, // 进入扩展会话
{ 0x22, 0xF1, 0x90 }, // 读 VIN
{ 0x22, 0xF1, 0x87 }, // 读零件号
{ 0x19, 0x02, 0xFF }, // 读取 DTC
{ 0x14, 0xFF, 0xFF, 0xFF }, // 清除 DTC
{ 0x19, 0x02, 0xFF }, // 再次读取 DTC
{ 0x2E, 0xF1, 0x87, 'N','E','W' }, // 写零件号
{ 0x22, 0xF1, 0x87 }, // 验证写入
{ 0x22, 0x12, 0x34 }, // 读不存在的 DID -> NRC 0x31
{ 0x99, 0x00 }, // 不支持的服务 -> NRC 0x11
};

for (const auto& req : requests) {
printFrame("[Tester] 请求", req);
Frame resp = ecu.handleRequest(req);
printFrame("[ECU] 响应", resp);
}
return 0;
}
2.1 编译与运行

g++ -std=c++17 -Wall -O2 uds_demo.cpp -o uds_demo
./uds_demo
2.2 预期输出(节选)

[Tester] 请求: 10 03 
[ECU] 收到请求 SID=0x10
[ECU] 响应: 50 03 00 32

[Tester] 请求: 22 F1 90
[ECU] 收到请求 SID=0x22
[ECU] 响应: 62 F1 90 56 49 4E 30 30 30 30 30 30 30 30 30 30 30 30 30

[Tester] 请求: 19 02 FF
[ECU] 收到请求 SID=0x19
[ECU] 响应: 59 02 FF 00 A0 01 09 00 B0 02 09

[Tester] 请求: 14 FF FF FF
[ECU] 收到请求 SID=0x14
[ECU] 故障码已清除
[ECU] 响应: 54

[Tester] 请求: 22 12 34
[ECU] 响应: 7F 22 31 ← NRC 0x31 RequestOutOfRange

[Tester] 请求: 99 00
[ECU] 响应: 7F 99 11 ← NRC 0x11 ServiceNotSupported
三、代码要点解析
  1. 服务分发机制handleRequest()用switch按 SID 分发,这正是真实 ECU 中 UDS 栈的典型结构。

  2. 肯定响应 SID 计算resp[0] = sid + 0x40,如0x22 → 0x62、0x10 → 0x50。

  3. 否定响应格式统一统一封装negativeResponse(sid, nrc)返回{0x7F, sid, nrc},便于所有服务复用。

  4. 参数长度校验每个服务都先校验报文长度,不符合则回NRC 0x13(IncorrectMessageLength)。

  5. DID 数据表用std::map 模拟 ECU 内部的数据存储;写数据后立即读可验证。

  6. 可扩展性增加新服务只需:

  • 定义 SID 常量

  • 在switch中加case

  • 实现对应的handleXxx()返回positiveResponse或negativeResponse

四、进一步扩展方向

方向

多帧传输

ISO 15765-2 的 SF/FF/CF/FC 机制,处理 >7 字节(CAN)的数据

会话与安全联动

某些服务(如 0x2E、0x31)需要在扩展会话 + 安全解锁后才能执行

定时器 S3

超时未收到TesterPresent自动回默认会话

P2/P2* 管理

长耗时服务先回NRC 0x78,再发最终响应

DTC 状态位

按 ISO 14229-1 的 8 位状态位(testFailed、confirmedDTC 等)解析

这份代码是一个可运行的骨架,把握住了 UDS 的核心:SID 分发 → 参数校验 → 肯定/否定响应。在此基础上加入多帧、会话管理、安全访问,就能构造出接近量产的 ECU 诊断栈。