ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

8字节CAN帧如何传4KB诊断数据?ISO 15765-2从原理到实战

8字节CAN帧如何传4KB诊断数据?ISO 15765-2从原理到实战

搞车载诊断的朋友,十有八九都撞上过这个场景:读内存、刷固件、采集数据,一条 UDS 诊断消息轻轻松松就超过 8 个字节,可 CAN 一帧的 payload 天生就只有 8 字节。你第一反应可能是“拆开多帧发不就行了”,但拆包这件事远没有看起来简单——帧与帧之间的顺序怎么定、收方怎么知道总长度、丢了帧要不要重发、收发两端节奏不同步怎么办,这些规矩如果各做各的,整车诊断就全乱套了。

这套规矩就是 ISO 15765-2,也就是业内常说的 TP 层(Transport Protocol Layer),也叫网络层。UDS 跑在它上面,CAN 跑在它下面,它夹在中间专门干“拆包、编号、流控、重组”的脏活累活。这篇文章我就从原理到实战,把“8 字节 CAN 帧怎么传 4KB 诊断数据”这件事彻底拆开讲透。无论你是刚入门的嵌入式工程师,还是被 PT 刷写问题折磨过的老手,这篇都值得花十分钟读完,后面直接能用到项目里。

1. 为什么 CAN 只能带 8 字节,UDS 却要面对 4KB 数据

1.1 CAN 帧载荷的天花板

先看客观约束。这里说的 8 字节,指的是 Classical CAN(CAN 2.0)单帧数据场的上限,DLC 最大就是 8。CAN FD 把上限提到了 64 字节,但那是另一个故事,本文默认讨论的是最主流的 Classical CAN 诊断场景,也就是绝大多数量产车现在还在跑的方式。

一个标准 CAN 数据帧,不管数据场是 0 字节还是 8 字节,总线上的物理开销是固定的:帧起始、仲裁段、控制段、CRC、ACK、EOF、帧间隔。以 11 位标准帧为例,8 字节数据时整帧在总线上占 111 个位时间,算下来在 500 kbps 下大约 222 微秒一帧;29 位扩展帧再多 18 个位,大约 258 微秒一帧。这个数字后面算耗时要用,先记住。

问题在于,诊断一层的需求完全不受这 8 字节限制。UDS 里随便一个服务都可能要求大块数据:

  • 0x22按标识符读取数据,读一段 VIN、配置信息可能就几百字节;
  • 0x23按地址读内存,读取量动辄一两 KB;
  • 0x34请求下载 +0x36传输数据,刷写 Bootloader 或应用程序时,单条0x36请求可以携带几千字节的固件块。

一刀切的矛盾就出现了:CAN 底层一次只能拿 8 字节,UDS 上层却想一次性塞 4KB。

1.2 协议栈里的“三明治”:UDS、TP、CAN 各干哪层活

要理解 ISO 15765-2,先看它在协议栈里的位置。按 OSI 模型粗略对应:

  • 应用层:ISO 14229,也就是 UDS,定义0x22、0x23、0x34这些服务和它们的正负响应格式。UDS 只管“我要读内存地址 X 的长度 Y”,完全不管数据怎么上车。
  • 网络层/传输层:ISO 15765-2,就是 TP 层。它负责把上层一大坨数据切成多个 CAN 帧,再在接收端拼回去,同时负责流控和时序。
  • 数据链路层:ISO 11898,CAN 控制器干的活,处理位填充、CRC、仲裁这些物理细节。

UDS 发一条消息时,并不感知 CAN 帧边界;TP 层提供的服务就是“你给我一大块数据,我保证完整送到对端”。所以从 UDS 视角看,这条“虚拟通道”能传的最大报文长度是 4095 字节——约等于 4KB。

这里有个经常被问的点:为什么偏偏是 4095,不是 4096,也不是更大?因为 TP 层首帧里用 12 个 bit 来声明总长度,0xFFF就是 4095,这是协议硬编码的天花板。你后面会看到,所有“4KB”的说法都来源于这个 12 位长度字段。

1.3 为什么简单拆包不行

有人会问:既然 CAN 一帧 8 字节,那我把 4095 字节按 7 字节一份,分成 585 份,直接连续发出去不就完了?理论上能发,但接收端根本没法用:

  1. 不知道总长度。接收方收到一堆帧,不知道这条消息什么时候结束,也不知道该准备多大的缓冲区。要么缓冲区开小了装不下,要么开大了浪费内存。
  2. 不知道帧的顺序。CAN 总线是有仲裁和优先级的,高优先级帧随时可能插进来;发送端自己的队列也可能把帧重排。接收端必须能识别“这是第几片”,才能按序拼装。
  3. 没有流控。发送方连发几百帧,接收方 CPU 可能正在忙别的,CAN 控制器 FIFO 一满,后面的帧直接被硬件丢弃,没有任何机制让发送方“等一下”。
  4. 丢了帧没法发现。少了一帧,拼出来的数据就是错的;如果没有序号和长度校验,应用层拿到错数据还浑然不觉。

ISO 15765-2 解决这四件事的手段,就是定义四种帧类型。这也是 TP 层全部奥义的所在。

2. ISO 15765-2 四种帧:看懂 PCI 字节就懂了 TP 层

TP 层所有帧都长在同一个 CAN 帧里,区别只在于数据场开头的 PCI(Protocol Control Information,协议控制信息)字节怎么解释。数据场最多 8 字节,PCI 占掉一部分,剩下的才是真正承载数据的 payload。四种帧分别是:单帧(SF)、首帧(FF)、连续帧(CF)、流控帧(FC)。

2.1 Single Frame:小消息一把梭

单帧是最简单的:整条消息就 7 个字节以内,一个 CAN 帧直接带走。

PCI 格式是0x0N,高 4 位固定为0000表示这是单帧,低 4 位N表示这条消息携带的实际数据字节数(0~7)。比如发一条0x10 0x01的诊断会话控制请求,数据就 2 字节,PCI 写0x02,整帧就是:

CAN ID 0x7E0 02 10 01 00 00 00 00 00

PCI(02)后面的10 01是 UDS 数据,后面 5 个字节是 padding。单帧不需要接收方回任何东西,发完即完,所以它是效率最高的一种传输方式。平时刷写协议里的大量短请求、短响应,全是单帧。

2.2 First Frame:先报总长度,再带第一批数据

当 UDS 消息超过 7 字节,就得走多帧流程,第一步由发送方发一个首帧 FF。

首帧的 PCI 占 2 个字节。第一个字节高 4 位固定0001表示首帧,低 4 位是 12 位总长度字段的高 4 位;第二个字节是总长度字段的低 8 位。合起来0x1M 0xLL,MLL就是整条消息的字节数,范围 0~4095。

为什么首帧要占 2 字节 PCI?因为必须用那多出来的 8 个 bit 把总长度声明出来,接收端才能决定缓冲区大小、才知道要等多少数据。首帧剩下的 6 个字节也不能浪费,会先带上消息开头的 6 字节数据,比如响应报文最前面的服务 ID 和参数。

举个例子,一条总长度为0xFFF(4095 字节)的响应,首帧数据场长这样:

CAN ID 0x7E8 10 FF 63 22 D1 D2 D3 D4

10 FF表示总长度 4095,后面63 22 D1 D2 D3 D4是前 6 字节数据(63是0x23服务的正响应,22是地址/长度格式标识,D1~D4是头 4 个数据字节)。

到这一步,发送方把话撂下了:“我要发 4095 字节,这是开头 6 字节,你准备好了吗?”

2.3 Consecutive Frame:按 4 位序号背货

接收方收到首帧后,如果决定接收,会回一个流控帧(下一节讲)。发送方收到允许发送的流控帧后,就开始连续发连续帧 CF。

连续帧的 PCI 只占 1 字节:高 4 位固定0010,低 4 位是序号 SN,范围 0~15。第一个连续帧的 SN 从 1 开始,之后每个连续帧加 1,到 15 之后回绕到 0,再继续 1、2、3……这是协议里很容易被忽略的细节:SN 是 4 位计数器,不是一直累加的。校验接收端必须按1,2,...,15,0,1,2...的循环来判断丢帧。

每个连续帧最多带 7 字节数据。对于 4095 字节的消息,首帧已经带走 6 字节,剩下 4089 字节全部由 CF 扛:

4089 = 584 × 7 + 1

也就是 584 个满载的连续帧,再加最后一个只带 1 字节的连续帧,总共 585 个连续帧。加上首帧,整条 4KB 消息一共 586 个 CAN 帧。

连续帧的 PCI 示例:

0x21 D5 D6 D7 D8 D9 D10 D11 // 第 1 个 CF,7 字节 0x22 D12 D13 D14 D15 D16 D17 D18 // 第 2 个 CF ... 0x2F ... // 第 15 个 CF,SN=15 0x20 ... // 第 16 个 CF,SN 回绕到 0 0x21 ... // 第 17 个 CF,SN 回到 1,继续

最后一个连续帧可能不满 7 字节,剩下的字节填充成 0x00 或按 OEM 规范填充,接收端靠首帧声明的总长度自行截断。

2.4 Flow Control:接收方手里的闸门

流控帧 FC 是这四种帧里唯一由接收方发出的帧,用来回答首帧:“你发吧,但按我的节奏来”。FC 的 PCI 占 3 个字节:

  • 第一个字节高 4 位固定0011,低 4 位是流控状态 FS;
  • 第二个字节是块大小 BS;
  • 第三个字节是最小间隔时间 STmin。

FS 有三种取值:

FS 值含义说明
0ContinueToSend允许继续发送
1Wait接收方忙,让发送方等待
2Overflow接收方缓冲区溢出,中止传输

BS 表示“允许连续发送多少个 CF”:0表示不限制,连续帧随便发;1~255表示发完这么多个连续帧后,必须停下来等下一个 FC。STmin 表示“相邻两个连续帧之间的最小发送间隔”,单位通常是 0.1ms,比如0x01表示 100 微秒,0x0A表示 1ms,0xFF表示 127ms;另外0xF1~0xF9是 100~900 微秒的微调档位,这是 2016 版之后的用法,老项目里少见。

FC 帧数据场只有前 3 字节是 PCI,剩下 5 个字节是填充。一个典型的“放行”流控帧长这样:

CAN ID 0x7E0 30 00 00 00 00 00 00 00

30表示 FS=0(放行),00表示 BS=0(不限制块数),00表示 STmin=0(不要求间隔)。这是刷写场景里最常见的配置,因为快。

2.5 四种帧速查表

把四种帧整理成一张表,项目里查起来最方便:

帧类型PCI 格式数据场字节Payload 能力谁发
单帧 SF0x0N(N=数据长度)17 字节发送方
首帧 FF0x1M LL(12 位总长度)26 字节发送方
连续帧 CF0x2S(S=序号)17 字节发送方
流控帧 FC0x3F BS STmin35 字节(一般填充)接收方

需要特别强调 FC 的方向:FC 的源地址是接收方,不是发送方。比如 ECU 发一条 4KB 响应给诊断仪,首帧和连续帧的 CAN ID 是0x7E8,而回给 ECU 的流控帧 CAN ID 是0x7E0。不少人在抓包的时候只盯响应 ID,找不到 FC 就以为对方没回,其实是方向看反了。

3. 实操拆解:一条 4KB 读内存响应的完整旅程

讲完帧格式,我们把一个典型的读内存场景从头到尾走一遍。场景:诊断仪向 ECU 发送0x23(按地址读内存)请求,ECU 返回 4095 字节数据,正好顶到 TP 层报文长度上限。

3.1 请求侧:一条 8 字节的单帧就搞定

0x23请求本身很短,可以塞进单帧。假设地址格式标识0x22表示“2 字节地址 + 2 字节长度”,地址是0x0001,长度是0x0FFD(4093 字节),请求数据就是:

23 22 00 01 0F FD

6 字节,不超过 7 字节上限,所以走单帧。完整 CAN 帧:

CAN ID 0x7E0: 06 23 22 00 01 0F FD 00

06是 PCI,表示后面有 6 字节数据;最后一个00是填充。发送这一个帧,请求侧完成。

注意响应总长度怎么来的:0x23的响应格式是63+ 地址格式标识 + 数据,也就是63 22占 2 字节,加上 4093 字节数据,正好 4095 字节。想读 4094 字节都不行,因为响应会变成 4096 字节,超过 12 位长度字段上限,TP 层根本无法表示。

3.2 响应侧:4095 字节怎么切成 586 帧

ECU 侧拿到 4095 字节的响应后,TP 层做切割。前面算过了:

  • 首帧 FF 带 6 字节;
  • 剩余 4089 字节,每个连续帧带 7 字节;
  • 4089 ÷ 7 = 584 余 1,所以是 584 个满载连续帧加 1 个含 1 字节的末帧,共 585 个连续帧;
  • 总帧数 = 1 首帧 + 585 连续帧 = 586 帧。

发送顺序严格固定:FF 必须先发,然后等待接收方的 FC,收到 FC 之后才能发 CF。CF 之间按 BS 和 STmin 的约束发送。

3.3 字节级时序:从 FF 到最后一个 CF 长什么样

我们假设 BS=0、STmin=0,接收方一上来就全量放行。抓包看到的帧序列大致如下:

序号CAN ID方向数据场说明
10x7E0请求06 23 22 00 01 0F FD 00单帧请求
20x7E8响应10 FF 63 22 D1 D2 D3 D4首帧,总长 4095,带 6 字节
30x7E0流控30 00 00 00 00 00 00 00FC:放行,BS=0,STmin=0
40x7E8响应21 D5 D6 D7 D8 D9 D10 D11CF1,SN=1
50x7E8响应22 D12 D13 ... D18CF2,SN=2
...............
5870x7E8响应29 D4089最后一个 CF,SN=9,只带 1 字节

最后那个 CF 的 SN 为什么是9?585 个连续帧按 16 个序号循环,585 = 36 × 16 + 9,余 9,所以最后一个循环里的第 9 个序号就是 SN=9。这是纯算出来的,实际抓包时你可以数帧校验。

末尾那个只带 1 字节数据的 CF,CAN 数据场按协议帧格式仍有 8 字节物理空间,剩下 6 字节是填充。有的实现会直接把 DLC 设成 2 减少总线占用,有的控制器不支持非 8 DLC 就只能填满,两种做法都合规。

3.4 500 kbps 下耗时到底多少

算一笔账。11 位标准帧、8 字节数据时,一帧上总线约 222 微秒。586 帧全部发完:

586 × 222 µs ≈ 130 ms

这是纯总线传输时间,没算任何 STmin 间隔。也就是说,在 500 kbps、BS=0、STmin=0 的理想情况下,一条 4KB 响应从首帧到末帧大约需要 130 毫秒。加上请求帧、ECU 内部处理、CAN 控制器 FIFO 排队,整条事务跑到 150~200 毫秒都算正常。

如果把 STmin 设成 1ms,那 585 个连续帧之间要多等 1ms,总耗时立刻变成130 + 585 ≈ 715 ms,直接慢了五倍多。这就是为什么刷写项目里对 STmin 极其敏感——它直接决定一个 Bootloader 刷几十分钟还是几个小时。

如果总线是 250 kbps,每帧约 444 微秒,586 帧就是约 260 毫秒。低速总线 + 大数据量 + 严格 STmin,这个组合在实车上能把超时逼出来。

4. 流控与定时参数:快慢与成败都在这几个数

TP 层能不能稳、能不能快,全看 BS、STmin 和几个 N_* 超时参数。这一节把它们的选型和坑点讲清楚。

4.1 BS 与 STmin 怎么选

BS(块大小)和 STmin(最小间隔)是接收方在 FC 里发号施令的两个旋钮,它们决定了发送方的“油门”。

BS=0 的场景:接收方一次放行全部连续帧,发送方只要满足 STmin 就能一直发到结束。这是刷写、大容量读取的首选,因为帧间没有额外握手,吞吐率最高。代价是接收方要有足够大的缓冲区或足够快的处理速度,否则 FIFO 溢出丢帧。

BS=n 的场景:接收方要求发送方每发 n 个连续帧就停下来等下一个 FC,相当于把一条长消息切成若干个小块,每块都握手一次。这样接收方可以一块一块地消化数据,缓冲区不用开太大。代价是每个块之间多一个 FC 往返,整条消息耗时增加。

举一个具体对比:4095 字节、585 个连续帧,500 kbps 总线,单帧 0.222ms。

  • BS=0、STmin=0:1 次 FC + 585 CF,约 130ms;
  • BS=16、STmin=1ms:需要585/16 ≈ 37个块,每个块之间多一个 FC 往返(约 0.222ms)加 16 个 CF 之间的 1ms 间隔,总耗时大约130 + 585×1 + 37×0.5 ≈ 734ms。

同样的数据,一个 130ms 搞定,一个 734ms。实际车载项目中,诊断仪侧一般倾向于 BS=0、STmin 尽量小;ECU 作为接收方(比如刷写时接收固件)如果内存紧张,才会用 BS 分块。但很多 OEM 规范会对 STmin 设下限,比如要求不小于 1ms,目的是防止某些能力弱的 ECU 被连续帧冲垮。

4.2 N_* 超时参数矩阵

ISO 15765-2 定义了一组超时参数,每个都对应一个“等不到下一帧就报错”的窗口。这些参数在诊断仪和 ECU 两端都配了默认值,但实际项目里 OEM 经常会收紧。常用的几个:

参数含义ISO 默认值常见 OEM 值
N_Bs接收方收到 FF 后发出 FC 的最大时间1000 ms200~500 ms
N_Br发送方收到 FC 后发出第一个 CF 的最大时间1000 ms1000 ms
N_Cs发送方两个相邻 CF 之间的最大间隔1000 ms500~1000 ms
N_Cr接收方两个相邻 CF 之间的最大间隔1000 ms500~1000 ms
N_Ar收到一帧后到发出下一帧的最大时间1000 ms1000 ms

诊断仪做服务超时处理时,一般以 N_Bs 或 N_Cr 为准;ECU 侧则以 N_Br、N_Cs 为准。调试时遇到“响应超时”,第一件事就是看抓包里 FC 到底回没回、FC 到第一个 CF 之间隔了多久。很多所谓“TP 层超时”,本质是某一端的 N_* 参数配得太紧,或者另一端响应太慢。

4.3 扩展寻址与 CAN FD 的变体

前面所有容量计算都假设“正常寻址”:请求和响应使用各自的物理 CAN ID(如0x7E0/0x7E8),数据场里不携带地址字节。

但某些项目里,同一个 CAN ID 下要挂多个逻辑节点,就得用“扩展寻址”:在 PCI 之后塞一个目标地址字节(N_TA),也叫做“扩展地址”。这一个字节直接吃掉数据空间,各帧 payload 变成:

帧类型正常寻址扩展寻址
SF7 字节6 字节
FF 首批数据6 字节5 字节
CF7 字节6 字节
FC3 字节 PCI + 5 填充3 字节 PCI + 1 地址 + 4 填充

所以同样的 4KB 数据,走扩展寻址的连续帧变多,总耗时上升。诊断开发时,地址模式先跟客户确认清楚,不然抓包看着 ID 都正常,就是数据对不上。

CAN FD 则是把数据场从 8 字节放大到 64 字节。ISO 15765-2 在 2016 版里支持了 CAN FD 上的 TP:首帧可以带 62 字节数据,连续帧可以带 64 字节。同样 4095 字节的消息,在 CAN FD 下只需要1 个 FF + 63 个 CF ≈ 64 帧,比 Classical CAN 的 586 帧少一个数量级。但帧类型、PCI 语义、流控逻辑跟 Classical CAN 完全一致,先把本文这套搞懂,CAN FD 只是把容量数字换一换。

5. 实战踩坑与排查清单

TP 层的坑我在项目里踩过不少,也帮别人排查过不少。以下几种现象出现频率最高,直接按“现象 → 根因 → 解法”整理成表,能省一大半调试时间。

5.1 典型现象与根因对照表

现象可能根因解决办法
诊断仪发完 FF 后一直等不到响应接收方缓冲区太小,直接回了 Overflow;或 N_Bs 超时查抓包看有没有 FC;确认接收方缓冲配置是否大于首帧声明长度
发了几个 CF 后发送方停了BS 被设成非 0,发送方在等下一个 FC,而接收方忘了发对抓包里的 FC 块序号;确认 BS 值双方理解一致
接收端报“丢帧”SN 回绕处理错误:把 0 当成了非法序号按1~15,0循环校验,不要把 SN=0 当错误
最后数据多出几个字节接收端没用首帧总长度截断,连同 padding 一起交给应用层重组完成后按 FF 声明的 4095 截断
刷写速度异常慢STmin 被设得过大,或每块都要等 FC在 FC 里把 STmin 设小、BS 设 0
高负载时 TP 频繁超时总线上高优先级报文抢占,CF 间隔超过 N_Cr调大 N_Cr/N_Cs,或降低诊断报文 CAN ID 优先级

5.2 抓包怎么看

看 TP 层诊断,在 CANoe、PCAN、周立功这些工具里过滤出诊断 CAN ID,然后按下面步骤读:

  1. 找到以0x1x开头的帧,这就是 FF,先把0x1M LL拼成总长度。比如10 FF就是 4095。
  2. 确认紧跟着 FF 的、来自对端的0x3x帧就是 FC,读它的 FS、BS、STmin。
  3. 数一数0x2x的连续帧个数,对照公式ceil((总长 - 6) / 7)检查有没有缺帧。
  4. 顺着0x21→0x2F→0x20→0x21检查 SN 循环,任何跳跃都代表丢帧或重组错误。
  5. 最后一个 CF 之后,比对已收字节数是否等于 FF 声明的总长度。

实际操作中最容易看走眼的是 FC 方向。FF/CF 从 ECU 来,FC 从诊断仪来,两个 CAN ID 不一样,别只盯着一个 ID 找。

5.3 工程落地建议

最后说几个工程落地的经验,都是从教训里换来的:

缓冲区按天花板开。接收路径的缓冲区直接按 4095 字节规划,别按“当前业务只要 1KB”去省。业务需求一变,缓冲区没跟着变,Overflow 就来了。多帧重组缓冲区最好带长度校验,收到的字节数一旦超过 FF 声明长度,当场报错。

填充字节要统一。各帧的 padding 用什么值,OEM 规范经常有明确要求(有的要0x00,有的要0xAA)。发送方和接收方对这种字节的态度必须一致:发送方按规范填,接收方重组时一律忽略。最怕的是接收方对 padding 做严格比对,双方规范写得不一样,白白丢一帧。

SN 回绕写进代码评审清单。4 位 SN 从 15 回绕到 0,这个逻辑太容易写错了。写if (sn == 0) sn = 0x20这种代码时,一定要让同事评审——我见过不止一次因为这里写错,导致大数据量传输必现丢帧。

功能寻址别传大消息。0x7DF 功能寻址适合发短命令,因为多个 ECU 会同时回响应,TP 层无法处理多路响应交错。大块数据传输必须走物理寻址,这是规范隐含的要求,也是很多“刷写时总线乱成一锅粥”问题的根源。

超时参数先按 OEM 规范来,再留余量。自己搭测试台时,把 N_* 都设成规范值的 1.5 到 2 倍去测;如果这个余量下还频繁超时,问题基本不在超时配置,而在总线负载或对端处理速度。

我在实际项目里排查过一个问题:某控制器刷写时偶发“传输中止”,抓了很久的包,最后发现是诊断仪在收到 FF 后,因为应用层忙于处理上一帧,回 FC 超过了 N_Bs,ECU 直接放弃传输。问题根本不在 TP 层协议本身,而在于诊断仪实现里把 N_Bs 配得太紧,又没给应用层留够处理时间。这类问题用抓包软件一眼就能定位:FF 和 FC 之间的时间戳差摆在那里,对照超时配置就知道是谁的锅。

ISO 15765-2 作为 UDS 的基础传输通道,原理并不复杂,就是“一报长度、二回闸门、三段编号发货、四端拼装验收”。真正让它在量产项目里显得难缠的,永远是那些边边角角的参数配置和实现细节。把四种帧的 PCI 读熟,把 BS、STmin、N_* 这几个数装进脑子里,再复杂的刷写问题,抓包一看基本都能有个七成判断。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进