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

汽车诊断协议精讲:ISO 14229(UDS)与 ISO 15765 的协作机制及代码示例

0
分享至


1. 引言

现代汽车电子系统由数十个甚至上百个电子控制单元(ECU)构成,它们通过 CAN、LIN、FlexRay 等总线协同工作。当车辆出现故障时,诊断工具(如诊断仪)需要与 ECU 通信,读取故障码、实时数据,执行动作测试等。这一套通信规则就是 汽车诊断协议

在基于 CAN 总线的诊断应用中,最核心的两个标准是:

  • ISO 14229 :定义了 统一诊断服务(UDS,Unified Diagnostic Services) ,是 应用层 协议,规定了诊断请求和响应的格式、服务含义。

  • ISO 15765 :定义了 基于 CAN 总线的诊断传输(DoCAN,Diagnostics over CAN) ,是 传输层/网络层 协议,负责将应用层 UDS 消息拆分成适合 CAN 总线传输的小帧,并在接收端重组。

很多初学者容易混淆这两者。一句话总结:UDS 告诉你“问什么问题”,ISO 15765 教你“怎样把问题装进 CAN 帧里送过去”。而 ISO 14229 正是 UDS 的标准编号。

本文将从概念到代码,深入浅出地比较这两个协议,并给出 C++ 实现示例。

2. 协议栈概览(OSI 模型视角)

OSI 层

汽车诊断协议栈

标准

作用

应用层

UDS

ISO 14229

诊断服务定义:读数据、写数据、例程控制等

传输层

DoCAN (ISO 15765-2)

ISO 15765-2

分段、重组、流控制

数据链路层

CAN 2.0B

ISO 11898

8 字节 CAN 帧、仲裁、错误检测

物理层

CAN 物理层

ISO 11898

电平、拓扑


  • UDS (ISO 14229)ISO 15765 不是对立关系,而是上下层协作关系。

  • ISO 15765 同时也定义了与 UDS 无关的 时间参数 (如 P2、P2* 等)和 通信控制

3. UDS (ISO 14229) 详解 —— “说什么”

UDS 是一套面向汽车场景设计的诊断服务集合,每个服务用 服务标识符(SID, Service Identifier) 表示(1 字节)。典型服务包括:

SID (hex)

服务名称

功能简述

0x10

Diagnostic Session Control

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

0x11

ECU Reset

重置 ECU

0x22

Read Data By Identifier

通过 DID 读取数据(如 VIN、软件版本)

0x2E

Write Data By Identifier

通过 DID 写入数据

0x19

Read DTC Information

读取故障码

0x14

Clear Diagnostic Information

清除故障码

0x31

Routine Control

启动/停止例程(如动作测试)


3.1 UDS 请求/响应格式

请求格式(诊断仪 → ECU):

[SID] [子功能(可选)] [参数...]

例如:读取 DID 0xF190 的数据(假设 DID 占 2 字节)

22 F1 90
  • 0x22 = ReadDataByIdentifier SID

  • 0xF190 = 数据标识符(如 VIN)

肯定响应格式(ECU 成功处理):

[SID+0x40] [子功能(若请求有)] [数据...]

例如对上述请求的肯定响应:

62 F1 90 41 32 ... (实际数据)
  • 0x62 = 0x22+0x40

否定响应格式(ECU 无法处理):

0x7F [SID] [NRC]

例如:请求的 DID 不支持,返回 7F 22 31(0x31 = RequestOutOfRange)

3.2 子功能与抑制肯定响应位

许多服务(如 SessionControl、ECUReset)的第一个字节称为子功能,其最高位(bit7)为 抑制肯定响应位(SPR, Suppress Pos Resp)

  • SPR = 1:ECU 不发送肯定响应,只发送否定响应(用于减少总线负载)。

  • SPR = 0:ECU 发送肯定响应。

例如:切换到扩展会话(子功能 0x03),且不需要肯定响应:

10 83   // 0x80 | 0x03 = 0x83
3.3 UDS 示例(无分段)

一个完整诊断请求通过 CAN 发送(假设不超 8 字节):

  • 请求: 10 03 (切换到扩展会话,需要肯定响应)

  • 响应: 50 03 00 32 01 F4 (肯定响应,附带一些参数)

4. ISO 15765 详解 —— “怎么送”

CAN 数据链路层一帧最多承载 8 字节数据,而一个 UDS 消息可能长达几十甚至几百字节(如读取 VIN 的 17 个字符,加上 DID 等)。ISO 15765-2(常称为 ISO-TP)定义了分段传输协议,将长消息切分成多个 CAN 帧,并管理发送/接收状态。

4.1 四种帧类型(N_PDU 类型)

帧类型

简称

用途

协议控制信息(PCI)字节

单帧

SF

消息总长 ≤ 7 字节(经典 CAN)

第一个字节高 4 位 = 0x0,低 4 位表示数据长度

首帧

FF

长消息的第一帧,告知总长度

第一个字节高 4 位 = 0x1,低 4 位 + 第二字节组成 12 位总长

连续帧

CF

首帧之后的后续数据

第一个字节高 4 位 = 0x2,低 4 位表示序列号(SN,0~15 循环)

流控制帧

FC

接收端控制发送端速率

第一个字节高 4 位 = 0x3,低 4 位表示流控制状态;后续字节包含块大小(BS)和最小间隔时间(STmin)


4.2 帧格式详表(经典 CAN,11 位 ID,单字节 PCI)

单帧 (SF)

| Byte0        | Byte1 ~ Byte7 (最多7字节数据) |
| 0x0 | Len | UDS 数据 (Len 字节) |
  • Len = 4 位,范围 0~7(实际长度 0~7,若消息总长 >7 必须用多帧)

  • 注意:有些实现使用 首个字节为 PCI,剩余 7 字节 ,Len 不包含 PCI 自身。

首帧 (FF)

| Byte0        | Byte1        | Byte2~Byte7 |
| 0x1 | Len_H | Len_L | 前 6 字节 UDS 数据 |
  • 总长度 = (Len_H << 8) | Len_L,范围 8~4095 字节

连续帧 (CF)

| Byte0        | Byte1~Byte7 |
| 0x2 | SN | 后续 7 字节 UDS 数据 |
  • SN 从 1 开始,每发一个 CF 加 1,到 15 后回绕到 0。

流控制帧 (FC)

| Byte0        | Byte1        | Byte2        | Byte3~Byte7(保留)|
| 0x3 | FS | BS (块大小) | STmin (最小间隔) | 0x00... |
  • FS (Flow Status):0=继续发送(CTS),1=等待(WAIT),2=溢出(OVFLW)

  • BS (Block Size):发送端每发 BS 个 CF 后,需等待下一个 FC 才可继续(0 表示无限制)

  • STmin:两个连续帧之间的最小间隔时间(0~127 或 0xF1~0xF9 编码)

4.3 多帧传输流程示例

发送方(如诊断仪)想发送一个 20 字节的 UDS 消息:

  1. 发送 首帧 :PCI=0x10,长度=20,并携带前 6 字节数据。

  2. 接收方收到 FF 后,计算所需 CF 数量,准备接收缓冲区,并发送 流控制帧 (BS, STmin)。

  3. 发送方根据 FC 发送 连续帧 ,每帧 7 字节数据,直到全部发完。

5. UDS 与 ISO 15765 的比较

比较维度

UDS (ISO 14229)

ISO 15765-2 (DoCAN)

OSI 层

应用层

传输层 / 网络层

关注点

诊断服务语义:做什么操作、数据含义、错误码

消息分段、流控制、CAN 帧打包与重组

数据单元

UDS 请求/响应(无长度上限)

N_PDU(单帧/首帧/连续帧/流控制帧)

关键参数

SID、子功能、DID、DTC、NRC

块大小(BS)、最小间隔(STmin)、序列号(SN)

独立性

可运行于其他传输层(如 DoIP、UART)

专为 CAN 设计,可传输任何上层数据(不仅是 UDS,也可以是 OBD、KWP2000)

标准文档

ISO 14229-1

ISO 15765-2

核心关系:UDS 消息被封装成 ISO 15765 的 N_PDU 载荷。没有 ISO 15765,UDS 无法穿越 CAN 总线的 8 字节限制;没有 UDS,ISO 15765 只是在传输无意义的字节流。

6. C++ 代码举例:实现 ISO 15765 打包器与 UDS 消息组装

以下代码演示:

  • UDSRequest 类:构造一个简单的 UDS 请求(如 ReadDataByIdentifier)。

  • ISO15765Packer 类:将 UDS 请求字节流转换为多个 CAN 帧( vector )。

  • ISO15765Unpacker 类:将收到的 CAN 帧还原成 UDS 消息。

为了简洁,假设 CAN 帧结构为 {id, data[8], dlc},且使用标准 CAN ID(0x7DF 诊断请求,0x7E8 响应)。仅实现发送端打包逻辑。

6.1 基础数据结构

#include  
          
#include
#include
#include

struct CanFrame {
uint32_t id; // 标准 11 位 ID
uint8_t dlc; // 数据长度 (0~8)
uint8_t data[8];
};

// 辅助打印
void printCanFrame(const CanFrame& frame) {
std::cout << "CAN ID: 0x" << std::hex << frame.id << " DLC:" << std::dec << (int)frame.dlc << " Data:";
for (int i = 0; i < frame.dlc; ++i) {
std::cout << " " << std::hex << (int)frame.data[i];
}
std::cout << std::dec << std::endl;
}
6.2 UDS 请求构造(应用层)

class UDSRequest {
public:
UDSRequest(uint8_t sid, conststd::vector& data = {})
: m_sid(sid), m_data(data) {}

std::vector serialize() const {
std::vector msg;
msg.push_back(m_sid);
msg.insert(msg.end(), m_data.begin(), m_data.end());
return msg;
}

// 示例:构造读取 DID 的请求 (0x22)
static UDSRequest makeReadDataByIdentifier(uint16_t did) {
return UDSRequest(0x22, {static_cast(did >> 8), static_cast(did & 0xFF)});
}

private:
uint8_t m_sid;
std::vector m_data;
};
6.3 ISO 15765 打包器(将 UDS 消息转成 CAN 帧序列)

class ISO15765Packer {
public:
// 输入:UDS 原始字节流(无 PCI)
// 输出:CAN 帧列表(请求 ID 固定为 0x7DF 示例)
static std::vector pack(const std::vector& udsMsg, uint32_t canId = 0x7DF) {
std::vector frames;
size_t len = udsMsg.size();

if (len <= 7) {
// 单帧模式
CanFrame sf;
sf.id = canId;
sf.dlc = static_cast(1 + len); // PCI + 数据长度
sf.data[0] = static_cast(0x00 | len); // 单帧 PCI
std::copy(udsMsg.begin(), udsMsg.end(), &sf.data[1]);
frames.push_back(sf);
} else {
// 多帧模式:首帧 + 连续帧
// 首帧
CanFrame ff;
ff.id = canId;
ff.dlc = 8; // 首帧总是 8 字节
uint16_t totalLen = static_cast(len);
ff.data[0] = 0x10 | ((totalLen >> 8) & 0x0F); // 高 4 位为 1,低 4 位为长度高 4 位
ff.data[1] = totalLen & 0xFF; // 长度低 8 位
size_t firstPayload = std::min((size_t)6, len); // 首帧携带 6 字节数据
std::copy(udsMsg.begin(), udsMsg.begin() + firstPayload, &ff.data[2]);
frames.push_back(ff);

// 后续数据按 7 字节分块,生成连续帧
size_t offset = firstPayload;
uint8_t sn = 1; // 连续帧序列号从 1 开始
while (offset < len) {
CanFrame cf;
cf.id = canId;
cf.dlc = 8;
cf.data[0] = 0x20 | (sn & 0x0F); // 连续帧 PCI
size_t chunkSize = std::min((size_t)7, len - offset);
std::copy(udsMsg.begin() + offset, udsMsg.begin() + offset + chunkSize, &cf.data[1]);
// 如果不足 7 字节,剩余字节填 0x00(可选,但通常 DLC=8 会包含无用数据)
if (chunkSize < 7) {
std::fill(&cf.data[1 + chunkSize], &cf.data[8], 0x00);
}
frames.push_back(cf);
offset += chunkSize;
sn = (sn + 1) & 0x0F;
}
}
return frames;
}
};
6.4 使用示例

int main() {
// 1. 构造 UDS 请求:读取 DID = 0xF190
auto udsReq = UDSRequest::makeReadDataByIdentifier(0xF190);
std::vector udsMsg = udsReq.serialize(); // 输出: 0x22 0xF1 0x90 (3 字节)

// 2. 打包成 CAN 帧(单帧)
auto frames = ISO15765Packer::pack(udsMsg, 0x7DF);
for (constauto& frame : frames) {
printCanFrame(frame);
}

// 3. 模拟一个较长的 UDS 消息(例如 20 字节的写数据请求)
std::vector longMsg(20, 0xAA); // 模拟 20 字节数据
longMsg[0] = 0x2E; // WriteDataByIdentifier SID
longMsg[1] = 0xF1;
longMsg[2] = 0x90;
// 剩余 17 字节为写入值

auto framesLong = ISO15765Packer::pack(longMsg, 0x7DF);
std::cout << "\nLong message (20 bytes) packed into " << framesLong.size() << " frames:\n";
for (constauto& frame : framesLong) {
printCanFrame(frame);
}

return0;
}

预期输出

CAN ID: 0x7df DLC:4 Data: 0 22 f1 90
Long message (20 bytes) packed into 4 frames:
CAN ID: 0x7df DLC:8 Data: 10 14 2e f1 90 aa aa aa // 首帧,总长度20
CAN ID: 0x7df DLC:8 Data: 21 aa aa aa aa aa aa aa // CF SN=1
CAN ID: 0x7df DLC:8 Data: 22 aa aa aa aa aa aa aa // CF SN=2
CAN ID: 0x7df DLC:8 Data: 23 aa aa aa aa aa aa aa // CF SN=3 (最后7字节,但剩余6字节,尾部补0)
6.5 ISO 15765 解包器(片段)

实际 ECU 需要接收 CAN 帧并重组。以下展示核心逻辑:

class ISO15765Unpacker {
public:
// 输入:一个 CAN 帧,输出:是否完成一个完整的 UDS 消息
bool feed(const CanFrame& frame, std::vector& outUdsMsg) {
if (frame.dlc == 0) returnfalse;
uint8_t pciType = (frame.data[0] >> 4) & 0x0F;
switch (pciType) {
case0x0: { // 单帧
uint8_t len = frame.data[0] & 0x0F;
outUdsMsg.assign(&frame.data[1], &frame.data[1] + len);
returntrue;
}
case0x1: { // 首帧
uint16_t totalLen = ((frame.data[0] & 0x0F) << 8) | frame.data[1];
m_buffer.clear();
m_buffer.reserve(totalLen);
// 首帧携带 6 字节数据
size_t payloadLen = std::min((size_t)6, frame.dlc - 2);
m_buffer.insert(m_buffer.end(), &frame.data[2], &frame.data[2] + payloadLen);
m_expectedLen = totalLen;
m_nextSn = 1;
m_waitingForFC = false; // 本示例简化流控制,实际需要等待 FC
returnfalse;
}
case0x2: { // 连续帧
uint8_t sn = frame.data[0] & 0x0F;
if (sn != m_nextSn) {
// 序列号错误,可做错误处理
returnfalse;
}
size_t payloadLen = std::min((size_t)7, frame.dlc - 1);
m_buffer.insert(m_buffer.end(), &frame.data[1], &frame.data[1] + payloadLen);
m_nextSn = (m_nextSn + 1) & 0x0F;
if (m_buffer.size() >= m_expectedLen) {
outUdsMsg.swap(m_buffer);
returntrue;
}
returnfalse;
}
case0x3: // 流控制帧(发送端处理,接收端不处理)
default:
returnfalse;
}
}
private:
std::vector m_buffer;
uint16_t m_expectedLen = 0;
uint8_t m_nextSn = 0;
bool m_waitingForFC = false;
};
7. 实际应用中的重要参数与注意事项
  1. CAN ID 分配:诊断通信通常使用 11 位 ID,如物理寻址(特定 ECU)和功能寻址(广播给所有 ECU)。UDS 不规定 ID,ISO 15765 也不规定,通常由整车厂定义(例如 0x7DF 用于功能请求,0x7E8 为第一个 ECU 的响应)。

  2. 流控制参数

    • **BS (Block Size)**:接收端可以限制连续帧个数,防止发送端淹没接收端缓冲区。

    • **STmin (Separation Time)**:两个连续帧之间的最小时间间隔,用于低速 ECU。

  3. 时间参数 P2 / P2*:UDS 应用层定义了 ECU 处理请求的最大响应时间(P2 = 50ms 典型值),ISO 15765 不涉及这个,但诊断仪必须实现超时管理。

  4. 多协议并存:UDS 也可以运行在 DoIP(以太网)、LIN、FlexRay 上,而 ISO 15765 专用于 CAN。因此 ISO 15765 可视为 UDS over CAN 的“运输层”。

8. 结论
  • UDS (ISO 14229) 是汽车诊断的“语言”,定义了丰富而统一的服务,让诊断工具与不同 ECU 能够互通。

  • ISO 15765 是这套语言在 CAN 总线上的“信使”,解决了 CAN 帧过小的限制,提供了可靠的分段、重组与流控制机制。

  • 二者相辅相成:没有 UDS,ISO 15765 传输的只是无意义的字节;没有 ISO 15765,UDS 将无法穿越 CAN 总线的长度壁垒。

理解这两者的分工是开发任何基于 CAN 的诊断工具或 ECU 固件的基础。通过本文的 C++ 代码示例,读者可以亲手打包一个 UDS 请求,观察它如何被拆分成 CAN 帧,从而更直观地掌握汽车诊断协议栈的层次协作。在实际工程中,建议使用成熟的 ISO-TP 库(如 isotp-c)和 UDS 库,但掌握原理始终是调试复杂问题的关键。

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

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.

相关推荐
热点推荐
广州增城一小区发生电梯坠梯, 从15层坠至负2层,一业主受伤!不少业主曾多次反映困梯、坠梯问题,但一直没有得到有效解决

广州增城一小区发生电梯坠梯, 从15层坠至负2层,一业主受伤!不少业主曾多次反映困梯、坠梯问题,但一直没有得到有效解决

大风新闻
2026-04-15 22:16:05
再也别信“红灯能右转”!现在规则已变,这三种情况绝对不能转

再也别信“红灯能右转”!现在规则已变,这三种情况绝对不能转

复转这些年
2026-04-15 11:20:06
两条新闻,看完让人后背发凉!

两条新闻,看完让人后背发凉!

胖胖说他不胖
2026-04-15 10:00:20
国台办:统一后台湾财政税收全用于民生,无需在防务上花巨资

国台办:统一后台湾财政税收全用于民生,无需在防务上花巨资

南方都市报
2026-04-15 16:48:15
日币跌得我都不敢相信:3.8元人民币可以换100日元!

日币跌得我都不敢相信:3.8元人民币可以换100日元!

新浪财经
2026-04-15 13:58:02
东北某县级电视台餐厅午餐,着实没想到啊

东北某县级电视台餐厅午餐,着实没想到啊

微微热评
2026-04-15 22:25:29
炸穿台湾政坛!蒋友松强行迁走两蒋悬棺,半世纪漂泊终要归乡

炸穿台湾政坛!蒋友松强行迁走两蒋悬棺,半世纪漂泊终要归乡

陈漎侃故事
2026-04-14 17:28:18
谁能想到,苏林上任首访中国,竟是自家人都摆不平的大麻烦

谁能想到,苏林上任首访中国,竟是自家人都摆不平的大麻烦

动物奇奇怪怪
2026-04-15 13:19:42
伊朗总统赞扬中国等6国反战立场

伊朗总统赞扬中国等6国反战立场

国际在线
2026-04-15 06:55:12
爆冷!国强五连鞭横扫利索夫斯基,中国小将闯进世锦赛32强!

爆冷!国强五连鞭横扫利索夫斯基,中国小将闯进世锦赛32强!

世界体坛观察家
2026-04-16 02:05:57
警惕“理论”幌子下的现实粉饰

警惕“理论”幌子下的现实粉饰

烽火瞭望者
2026-04-15 06:14:31
女子利用天气预报频繁购买飞机延误险,5年买中900多次,获赔近300万,被抓时:我符合保险理赔要求

女子利用天气预报频繁购买飞机延误险,5年买中900多次,获赔近300万,被抓时:我符合保险理赔要求

谭老师地理大课堂
2026-04-15 20:11:42
现在二手车为啥越来越难卖?不是没人买车,是贩子们还活在十年前

现在二手车为啥越来越难卖?不是没人买车,是贩子们还活在十年前

复转这些年
2026-04-15 11:25:47
CBA最新排名!前5基本确定,辽宁紧咬第8名,北控锁12,6队被淘汰

CBA最新排名!前5基本确定,辽宁紧咬第8名,北控锁12,6队被淘汰

篮球资讯达人
2026-04-16 03:03:49
臭气熏天,记者现场“yue”个不停!网友:很久没看到这么有“活人感”的报道,隔着屏幕都能感受到

臭气熏天,记者现场“yue”个不停!网友:很久没看到这么有“活人感”的报道,隔着屏幕都能感受到

深圳晚报
2026-04-15 14:53:52
美媒:美国与伊朗考虑将停火延长两周

美媒:美国与伊朗考虑将停火延长两周

界面新闻
2026-04-15 23:37:28
关于文章被处理的通知

关于文章被处理的通知

言立方
2026-04-15 18:22:17
变卦了?郑丽文访美日期确定,声称两岸要和平,美国同样至关重要

变卦了?郑丽文访美日期确定,声称两岸要和平,美国同样至关重要

阿龙聊军事
2026-04-15 06:17:36
15球追平兰帕德,凯恩成欧冠淘汰赛进球并列最多的英格兰球员

15球追平兰帕德,凯恩成欧冠淘汰赛进球并列最多的英格兰球员

懂球帝
2026-04-16 04:02:26
被打到求救才想起祖国?真主党日暮途穷连喊三件事!

被打到求救才想起祖国?真主党日暮途穷连喊三件事!

环球格局观
2026-04-15 19:15:59
2026-04-16 04:35:00
新能源自动驾驶 incentive-icons
新能源自动驾驶
专注于半导体行业资讯
957文章数 346关注度
往期回顾 全部

汽车要闻

空间丝毫不用妥协 小鹏GX首发评测

头条要闻

法国全票通过 “将不义之财归还中国”

头条要闻

法国全票通过 “将不义之财归还中国”

体育要闻

三球准绝杀戴大金链:轰30+10自我救赎

娱乐要闻

谢娜现身环球影城,牵手女儿温馨有爱

财经要闻

业绩失速的Lululemon:"健康"人设崩塌?

科技要闻

ChatGPT十亿用户又怎样?Anthropic直接贴脸

态度原创

本地
家居
教育
数码
房产

本地新闻

12吨巧克力有难,全网化身超级侦探添乱

家居要闻

简而不减 暖居之道

教育要闻

黄冈小升初招生题,求面积,方法思路太绝了

数码要闻

明基新款显示器首发12499元:4K专业级色彩校准 根据环境光自动调整

房产要闻

重磅调规!341亩商改住+中小学用地!宝龙城这把稳了?

无障碍浏览 进入关怀版