网易首页 > 网易号 > 正文 申请入驻

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

0
分享至


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 诊断栈。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
咸鱼涉黄:“夜总会”不会消失,但会转移

咸鱼涉黄:“夜总会”不会消失,但会转移

梁欢欢的理智与情感
2026-09-22 11:04:57
日方声称“绝不能接受‘中国代表团访问日俄争议岛屿’”,外交部回应

日方声称“绝不能接受‘中国代表团访问日俄争议岛屿’”,外交部回应

环球网资讯
2026-10-14 15:33:53
500万人抢2.2万张票,有消息称存款千万可得肖战演唱会门票?银行客服回应

500万人抢2.2万张票,有消息称存款千万可得肖战演唱会门票?银行客服回应

极目新闻
2026-10-15 16:30:17
台湾和上海都是 2000 多万人口,上半年台湾 GDP3.48 万亿,上海多少

台湾和上海都是 2000 多万人口,上半年台湾 GDP3.48 万亿,上海多少

抽象派大师
2026-09-20 16:57:21
9月汽车销量榜单出炉,小米汽车把友商带到了沟里

9月汽车销量榜单出炉,小米汽车把友商带到了沟里

阿芒娱乐说
2026-10-14 12:29:02
笑死!李诞代言汉堡王遭全网群嘲,网友神评:麦当劳可是权志龙啊

笑死!李诞代言汉堡王遭全网群嘲,网友神评:麦当劳可是权志龙啊

喜欢历史的阿繁
2026-10-14 09:05:48
6 个降压药,3个影响性功能,3个提高性功能,您一定要知道

6 个降压药,3个影响性功能,3个提高性功能,您一定要知道

医学科普汇
2026-10-13 18:26:25
公安部多次提醒:卖掉旧手机,不只是丢自己隐私,孩子也会受牵连

公安部多次提醒:卖掉旧手机,不只是丢自己隐私,孩子也会受牵连

你在偷看谁
2026-10-08 19:57:01
从高原明珠到臭水湖,耗资500亿也没能挽救滇池,现在成效如何?

从高原明珠到臭水湖,耗资500亿也没能挽救滇池,现在成效如何?

小嵩
2026-10-15 01:37:26
罗马8分钟被1战术连捅3次!皇马造欧冠史上第1逆转 7万人目瞪口呆

罗马8分钟被1战术连捅3次!皇马造欧冠史上第1逆转 7万人目瞪口呆

风过乡
2026-10-15 06:32:19
即将搭载歼35、空警600?中国山东舰航母改造8个多月,究竟在改什么?

即将搭载歼35、空警600?中国山东舰航母改造8个多月,究竟在改什么?

军武次位面
2026-10-15 18:38:31
养老金大局已定? 不出意外的话,从10月开始,养老金或迎来3个变

养老金大局已定? 不出意外的话,从10月开始,养老金或迎来3个变

寻迹追心
2026-10-15 12:31:52
比尔盖茨表示:他这辈子犯过最大的错误,就是把原本属于微软的移动生态,拱手让给了安卓

比尔盖茨表示:他这辈子犯过最大的错误,就是把原本属于微软的移动生态,拱手让给了安卓

元气科技馆
2026-10-10 21:56:30
美国太空军事基地开放,电磁战武器曝光,美军罕见承认在委内瑞拉、伊朗“实战”用过

美国太空军事基地开放,电磁战武器曝光,美军罕见承认在委内瑞拉、伊朗“实战”用过

红星新闻
2026-10-15 16:33:57
直面争议不回避!上海男篮出征表态,正式回应怀特塞德兴奋剂违规事件

直面争议不回避!上海男篮出征表态,正式回应怀特塞德兴奋剂违规事件

体育见习官
2026-10-14 15:26:09
2场轰33分+8记三分!火箭2阵主攻手出炉?伊森+斯玛特变副手,22岁新人快速进化

2场轰33分+8记三分!火箭2阵主攻手出炉?伊森+斯玛特变副手,22岁新人快速进化

熊哥爱篮球
2026-10-15 18:59:10
太狗血了!石家庄女子身怀二胎临近生产,发文曝光丈夫出轨在读大专女生,女子还曾邀请该女生一同外出聚会

太狗血了!石家庄女子身怀二胎临近生产,发文曝光丈夫出轨在读大专女生,女子还曾邀请该女生一同外出聚会

火山詩话
2026-10-15 09:57:59
女子晒11999元iPhone 18Pro订单,不法分子先后找5名“跑腿”骑手,冒领手机后接力转运寄往外地,警方连夜拦截

女子晒11999元iPhone 18Pro订单,不法分子先后找5名“跑腿”骑手,冒领手机后接力转运寄往外地,警方连夜拦截

都市快报橙柿互动
2026-10-15 01:08:37
血压无论高到多少,没超过这两个数,都不要自己吓自己

血压无论高到多少,没超过这两个数,都不要自己吓自己

牛锅巴小钒
2026-10-14 16:57:47
原来她就是白应苍的枕边人,网传两人已离婚,携带孩子跑路国外

原来她就是白应苍的枕边人,网传两人已离婚,携带孩子跑路国外

娱乐圈见解说
2026-10-13 08:09:26
2026-10-15 21:48:49
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1024文章数 348关注度
往期回顾 全部

科技要闻

核弹院士彭先觉:可控核聚变 不必再等50年

头条要闻

美多名官员透露:美国军方或动用"炸弹之母"打击伊朗

头条要闻

美多名官员透露:美国军方或动用"炸弹之母"打击伊朗

体育要闻

五大联赛第二射手,开着11年前的二手车

娱乐要闻

蒋劲夫晒体脂率引关注,家暴史再被扒

财经要闻

洋餐饮的中国生意:红利消退后的集体交权

汽车要闻

年轻人专属的大五座 小鹏MONA L05预售16.98万起

态度原创

旅游
本地
手机
公开课
军事航空

旅游要闻

踏访达州红色地标:长征精神在这里代代相传|长征精神

本地新闻

这个十一,林都伊春彻底出圈!

手机要闻

华为智慧屏迎秋季版本6大升级,新增iOS快捷投屏、支持一遥两控等功能

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

朝鲜潜射两种导弹

无障碍浏览 进入关怀版