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

UDS 节点识别(Node Identification)详解与 C++ 实现

0
分享至


UDS 节点识别(Node Identification)详解与 C++ 实现 一、什么是 UDS 节点识别

UDS(Unified Diagnostic Services,统一诊断服务)是 ISO 14229 标准定义的汽车诊断协议。在整车网络中,通常存在多个 ECU(电子控制单元),诊断仪(Tester)需要知道"我到底在跟哪个 ECU 说话",以及"网络上还有哪些节点"。

节点识别(Node Identification)就是让诊断设备能够发现、区分、确认网络上各个 ECU 身份的一组服务。它在多 ECU 环境中的价值体现在:

  • 拓扑发现:识别总线上存在哪些 ECU

  • 寻址确认:确认当前通信的目标节点

  • 版本管理:读取各节点的软硬件版本、序列号

  • 诊断路由:在多节点环境下正确分发诊断请求

二、节点识别相关的核心 UDS 服务

SID

服务名

用途

0x22

ReadDataByIdentifier

通过 DID 读取 VIN、序列号、软件版本等

0x2E

WriteDataByIdentifier

写入 VIN 等识别信息

0x3E

TesterPresent

保持会话,配合节点在线检测

0x10

DiagnosticSessionControl

切换会话(识别有时需要扩展会话)

0x31

RoutineControl

触发某些节点自检例程

0x27

SecurityAccess

受保护的识别信息需先解锁


常用识别 DID(数据标识符)

DID

含义

0xF190

VIN 车辆识别码

17 位 ASCII

0xF18C

ECU 序列号

唯一标识该 ECU

0xF187

备件号

硬件零件号

0xF189

软件版本号

应用软件版本

0xF191

硬件版本号

硬件版本

0xF195

系统供应商标识

供应商代码

0xF186

当前诊断会话

用于确认在线状态


报文格式(请求/响应)

请求:

22 F1 90
│ └──── DID (2 bytes)
└─────── SID

肯定响应:

62 F1 90 

│ │ └── 数据(如 17 字节 VIN)
│ └──────── 回显的 DID
└─────────── SID + 0x40

否定响应:

7F 22 

│ │ └── 否定响应码(如 0x31 请求超范围)
│ └───── 原始 SID
└──────── 否定响应标识
三、C++ 实现示例

下面用一个简洁但完整的示例,模拟诊断仪对多个 ECU 做节点识别:发送0x22读 VIN/序列号,解析响应,并汇总节点信息。

说明:为聚焦 UDS 逻辑,这里用接口 ITransport 抽象底层(CAN/DoIP),可替换为真实 SocketCAN / ISO-TP 实现。

#include  

#include
#include
#include
#include
#include
#include

// ============================================================
// 1. 基础类型与常量
// ============================================================
using Bytes = std::vector;

namespace Uds {
constexpr uint8_t SID_ReadDataByIdentifier = 0x22;
constexpr uint8_t SID_PositiveOffset = 0x40;
constexpr uint8_t SID_NegativeResponse = 0x7F;

// 常用识别 DID
constexpr uint16_t DID_VIN = 0xF190;
constexpr uint16_t DID_SerialNumber = 0xF18C;
constexpr uint16_t DID_SoftwareVer = 0xF189;
constexpr uint16_t DID_HardwareVer = 0xF191;
}

// 否定响应码
enum class Nrc : uint8_t {
ServiceNotSupported = 0x11,
SubFunctionNotSupported = 0x12,
RequestOutOfRange = 0x31,
SecurityAccessDenied = 0x33,
ConditionsNotCorrect = 0x22,
Unknown = 0xFF
};

// ============================================================
// 2. 传输层抽象(真实项目替换为 CAN / DoIP)
// ============================================================
class ITransport {
public:
virtual ~ITransport() = default;
// 向 target 节点发送请求,返回响应原始字节(不含 ISO-TP 分帧)
virtual std::optional request(uint16_t targetAddr,
const Bytes& payload) = 0;
};

// ============================================================
// 3. UDS 客户端:封装节点识别相关服务
// ============================================================
class UdsClient {
public:
explicit UdsClient(ITransport& transport) : tp_(transport) {}

// 读取指定 DID 的原始数据
struct ReadResult {
bool success = false;
Bytes data; // 成功时的数据段
Nrc nrc = Nrc::Unknown; // 失败时的否定码
};

ReadResult readDataByIdentifier(uint16_t addr, uint16_t did) {
Bytes req = {
Uds::SID_ReadDataByIdentifier,
static_cast(did >> 8),
static_cast(did & 0xFF)
};

ReadResult result;
auto resp = tp_.request(addr, req);
if (!resp || resp->size() < 1) return result;

// 否定响应
if ((*resp)[0] == Uds::SID_NegativeResponse) {
if (resp->size() >= 3) result.nrc = static_cast ((*resp)[ 2]);
return result;
}

// 肯定响应: 0x62 DID_H DID_L
if ((*resp)[0] == (Uds::SID_ReadDataByIdentifier + Uds::SID_PositiveOffset)
&& resp->size() >= 3) {
uint16_t echoDid = (static_cast((*resp)[1]) << 8) | (*resp)[2];
if (echoDid == did) {
result.success = true;
result.data.assign(resp->begin() + 3, resp->end());
}
}
return result;
}

// 读取 VIN(17 字节 ASCII)
std::optional readVin(uint16_t addr) {
auto r = readDataByIdentifier(addr, Uds::DID_VIN);
if (!r.success || r.data.size() != 17) return std::nullopt;
return std::string(r.data.begin(), r.data.end());
}

// 读取序列号(按 ASCII 处理,实际可能是二进制)
std::optional readSerialNumber(uint16_t addr) {
auto r = readDataByIdentifier(addr, Uds::DID_SerialNumber);
if (!r.success) return std::nullopt;
return std::string(r.data.begin(), r.data.end());
}

private:
ITransport& tp_;
};

// ============================================================
// 4. 节点信息 & 节点发现器
// ============================================================
struct NodeInfo {
uint16_t address = 0;
std::string vin;
std::string serialNumber;
std::string softwareVersion;
bool online = false;
};

class NodeIdentifier {
public:
explicit NodeIdentifier(UdsClient& client) : client_(client) {}

// 遍历候选地址,识别在线节点
std::vector discover(const std::vector& candidateAddrs) {
std::vector found;
for (auto addr : candidateAddrs) {
NodeInfo info;
info.address = addr;

if (auto vin = client_.readVin(addr)) {
info.vin = *vin;
info.online = true;
}
if (auto sn = client_.readSerialNumber(addr)) {
info.serialNumber = *sn;
}
// 只有至少读到 VIN 或序列号才认为节点存在
if (info.online || !info.serialNumber.empty()) {
found.push_back(std::move(info));
}
}
return found;
}

private:
UdsClient& client_;
};

// ============================================================
// 5. 模拟传输层:用于演示节点识别流程
// ============================================================
class MockTransport : public ITransport {
public:
// 注册某地址的 DID -> 数据
void registerDid(uint16_t addr, uint16_t did, const Bytes& data) {
db_[addr][did] = data;
}

std::optional request(uint16_t targetAddr,
const Bytes& payload) override {
if (payload.size() < 3 || payload[0] != Uds::SID_ReadDataByIdentifier)
return std::nullopt;

uint16_t did = (static_cast(payload[1]) << 8) | payload[2];

auto nodeIt = db_.find(targetAddr);
if (nodeIt == db_.end()) return std::nullopt; // 无此节点(超时)

auto didIt = nodeIt->second.find(did);
if (didIt == nodeIt->second.end()) {
// 节点存在但不支持该 DID -> 否定响应
return Bytes{Uds::SID_NegativeResponse,
Uds::SID_ReadDataByIdentifier,
static_cast(Nrc::RequestOutOfRange)};
}

// 组装肯定响应: 62 DID_H DID_L
Bytes resp = {static_cast(Uds::SID_ReadDataByIdentifier + 0x40),
static_cast(did >> 8),
static_cast(did & 0xFF)};
resp.insert(resp.end(), didIt->second.begin(), didIt->second.end());
return resp;
}

private:
std::map> db_;
};

// ============================================================
// 6. 使用示例
// ============================================================
int main() {
MockTransport transport;

// ---- 模拟网络上的三个 ECU ----
// ECU A @ 0x7A0: 有完整信息
transport.registerDid(0x7A0, Uds::DID_VIN,
Bytes{'L','S','V','N','M','4','2','B','1','2','3','4','5','6','7','8','9'});
transport.registerDid(0x7A0, Uds::DID_SerialNumber,
Bytes{'S','N','-','E','C','U','A','-','0','0','1'});

// ECU B @ 0x7A2: 只有序列号(没写 VIN)
transport.registerDid(0x7A2, Uds::DID_SerialNumber,
Bytes{'S','N','-','E','C','U','B','-','0','0','2'});

// 0x7A4 不存在(不在 db 中,模拟离线节点)

// ---- 执行节点识别 ----
UdsClient client(transport);
NodeIdentifier identifier(client);

std::vector candidates = {0x7A0, 0x7A2, 0x7A4};
auto nodes = identifier.discover(candidates);

std::cout << "===== Node Identification Result =====\n";
for (const auto& n : nodes) {
std::cout << "Addr 0x" << std::hex << n.address << std::dec << "\n"
<< " VIN : " << (n.vin.empty() ? " " : n.vin) << "\n"
<< " Serial : " << (n.serialNumber.empty() ? " " : n.serialNumber) << "\n";
}
std::cout << "Total online nodes: " << nodes.size() << "\n";
return 0;
}
运行输出

===== Node Identification Result =====
Addr 0x7a0
VIN : LSVNM42B123456789
Serial : SN-ECU-A-001
Addr 0x7a2
VIN :
Serial : SN-ECU-B-002
Total online nodes: 2

0x7A4因为在模拟传输层里没有任何 DID 注册,readVin返回nullopt,被正确识别为离线节点排除。

四、真实项目中的注意事项

  1. 寻址方式:物理寻址(点对点,如 0x7A0)用于识别具体 ECU;功能寻址(0x7DF)可广播,但响应会冲突,通常不用于读识别数据。

  2. 超时与重试:request需实现 P2/P2* 超时(默认 50ms / 5000ms),离线节点靠超时判定,注意别阻塞太久——多节点扫描可用异步并发。

  3. 会话与安全:某些 OEM 把序列号放在扩展会话或需0x27解锁后才能读,识别前可能要先0x10 0x03。

  4. 响应长度:VIN 固定 17 字节,但序列号长度因厂商而异,解析时不要硬编码。

  5. 多帧传输:VIN 等较长数据超过 7 字节,需要 ISO-TP(ISO 15765-2)分帧,示例里由传输层屏蔽了。

  6. DID 支持表:不同 ECU 支持的 DID 不同,遇到 NRC0x31属正常,应继续尝试其它 DID。

五、小结

节点识别是 UDS 诊断的基础能力:以0x22 ReadDataByIdentifier为核心,结合0x10、0x27、0x3E等服务,诊断仪就能在多 ECU 网络中发现节点、确认身份、读取 VIN/序列号/版本信息。上面的 C++ 示例通过传输层抽象 + 客户端封装 + 发现器三层结构,把这个流程清晰地实现了出来,把MockTransport换成真实 SocketCAN / DoIP 即可用于实际工程。

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

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.

相关推荐
热点推荐
姚明229公分已经够高?NO!后面的巨兽一个比一个高!

姚明229公分已经够高?NO!后面的巨兽一个比一个高!

岁月有情1314
2026-10-10 15:13:23
98岁李嘉诚去世传闻真相大白!新加坡总理发声,近照曝光有情况

98岁李嘉诚去世传闻真相大白!新加坡总理发声,近照曝光有情况

探索新高度
2026-10-16 16:11:56
江西一男子刷短视频时误点保险广告,被自动扣取7.2万余元保费,客服称线上投保无需签字,退款仅退7000多元,律师:违规销售应全额退款

江西一男子刷短视频时误点保险广告,被自动扣取7.2万余元保费,客服称线上投保无需签字,退款仅退7000多元,律师:违规销售应全额退款

台州交通广播
2026-10-15 23:55:05
汪小菲带女儿游怀柔,12岁小玥儿身高将近1米5,侧脸都是大S影子

汪小菲带女儿游怀柔,12岁小玥儿身高将近1米5,侧脸都是大S影子

观鱼听雨
2026-09-27 23:50:39
“让企退人员闭嘴”翻车!千万人的民生事,凭什么不让人表达观点

“让企退人员闭嘴”翻车!千万人的民生事,凭什么不让人表达观点

笑熬浆糊111
2026-09-21 08:59:01
蒋介石晚年反复痛悔:一生中最错误的决定,就是抗战结束后没有及时把主力部队撤出东北

蒋介石晚年反复痛悔:一生中最错误的决定,就是抗战结束后没有及时把主力部队撤出东北

磊子讲史
2026-07-20 15:48:31
改籍叛国、卖队泄密?定居泰国执教的冯坤,因一句话实现口碑逆转

改籍叛国、卖队泄密?定居泰国执教的冯坤,因一句话实现口碑逆转

主宰未来
2026-09-24 15:32:39
和日本队闹翻,加盟中国羽超,志田千阳1年薪水有多少?曾代言中国品牌被骂

和日本队闹翻,加盟中国羽超,志田千阳1年薪水有多少?曾代言中国品牌被骂

喜欢体育的猫
2026-10-14 10:32:22
泰国王后独自驾驶王室专机抵达日本出席活动,全程约4600公里飞行近7小时,落地后自己开车携狗离开

泰国王后独自驾驶王室专机抵达日本出席活动,全程约4600公里飞行近7小时,落地后自己开车携狗离开

界面新闻
2026-10-15 16:02:36
倒计时!哈登即将成为第九位30000分先生,助攻榜同时冲进前十

倒计时!哈登即将成为第九位30000分先生,助攻榜同时冲进前十

独坐山巅前
2026-10-16 09:50:45
普京:将对乌攻击俄能源和物流设施作出回应;知情人士:乌克兰“爱国者”拦截弹库存耗尽,英国将追加提供“风暴之影”巡航导弹

普京:将对乌攻击俄能源和物流设施作出回应;知情人士:乌克兰“爱国者”拦截弹库存耗尽,英国将追加提供“风暴之影”巡航导弹

上观新闻
2026-10-16 06:33:57
腿都哆嗦了!乌克兰机器人真上战场了,拿枪就开火,全世界第1次!

腿都哆嗦了!乌克兰机器人真上战场了,拿枪就开火,全世界第1次!

喀秋莎大世界
2026-10-08 23:32:03
1958年江青前夫临终时不断喊着:江青到底是什么人?

1958年江青前夫临终时不断喊着:江青到底是什么人?

史看人生
2026-10-16 06:00:03
宏远早报!弗格表忠心,新大外今日体检,卢卡继承奎因财产!

宏远早报!弗格表忠心,新大外今日体检,卢卡继承奎因财产!

体坛卡卡说
2026-10-16 12:32:11
1956年,毛主席在上海会见知名女演员上官云珠,刚一见面,主席便当面关切地问她:“听说此前有人欺负过你?”

1956年,毛主席在上海会见知名女演员上官云珠,刚一见面,主席便当面关切地问她:“听说此前有人欺负过你?”

人生录
2026-10-04 19:00:50
俄外长:美国为什么唯独忌惮中国?因为中国是唯一一个在美国搭建的全球化赛场里,按照美国制定的规则,击败了美国的存在?

俄外长:美国为什么唯独忌惮中国?因为中国是唯一一个在美国搭建的全球化赛场里,按照美国制定的规则,击败了美国的存在?

扶苏聊历史
2026-10-15 16:01:49
仅存53天、无一国承认!近代最为悲壮的短命政权——中华共和国!

仅存53天、无一国承认!近代最为悲壮的短命政权——中华共和国!

小豫讲故事
2026-10-13 06:00:10
北京最难改危楼!光华里5、6号楼破解70年死结,居民原地住新房

北京最难改危楼!光华里5、6号楼破解70年死结,居民原地住新房

林子说事
2026-10-16 09:58:34
北京交通规划给小客车重新定了位,摇号留不留不是重点,用车端才是下一仗

北京交通规划给小客车重新定了位,摇号留不留不是重点,用车端才是下一仗

辉哥说动漫
2026-10-16 07:32:30
余承东最新激烈表态:正义可能会迟到,但永远不会缺席!

余承东最新激烈表态:正义可能会迟到,但永远不会缺席!

鞭牛士
2026-10-16 00:01:34
2026-10-16 17:55:01
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
1024文章数 348关注度
往期回顾 全部

科技要闻

杨天若院士:AI是中国弯道超车的机会

头条要闻

涉案5.6亿"云养牛"骗局崩盘:12万人养了8年"空气牛"

头条要闻

涉案5.6亿"云养牛"骗局崩盘:12万人养了8年"空气牛"

体育要闻

工资一分不要,曼联名将想把母队带回西甲

娱乐要闻

郭富城现身香港大学,袁咏仪夫妇全程冷漠不搭话

财经要闻

一半AI算力,去拍爽剧了

汽车要闻

特斯拉会飞?全新Roadster亮相

态度原创

数码
本地
手机
房产
公开课

数码要闻

小米推出“米家洗衣机巨洗净波轮6kg”:可一次性清洗26件T恤,预售价587元

本地新闻

因新荣记出圈,这座浙东小城才是吃货天堂

手机要闻

荣耀开启MagicOS 11第三批机型内测招募,覆盖Robot Phone等近30款机型

房产要闻

方案曝光!海口西海岸,又有新盘要出!

公开课

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

无障碍浏览 进入关怀版