3. ISO 15765-2 (DoCAN) 传输层原理:单帧、多帧与流控制

好,咱们今天来聊聊 DoCAN 传输层。说实话,这是整个诊断协议栈里最核心、也最容易出 bug 的地方。很多刚入行的朋友觉得它就是个分包拆包,其实没那么简单。

ISO 15765-2 定义了 CAN 总线上的诊断消息怎么传。CAN 报文一次最多只能带 8 个字节,但诊断请求动不动就几十上百字节。怎么办?就得靠传输层来搞定。

3.1 单帧传输:小数据的直通车

先说说最简单的场景。如果你的诊断数据(包括服务ID、子功能、数据)总长度不超过 7 个字节,那就用单帧。

为什么是 7 个?因为 CAN 报文有 1 个字节的协议控制信息(PCI),剩下 7 个才是数据。PCI 里包含了帧类型和长度信息。

单帧 PCI 格式:

Byte 0: [ 帧类型(4bit) | 数据长度(4bit) ]

帧类型 = 0x0 表示单帧

数据长度 = 实际数据字节数 (0-7)

举个例子,一个读取 VIN 码的请求:

// 请求: 22 F1 90 (读取VIN)
// 单帧报文: 03 22 F1 90
// 03 表示单帧,长度3字节
// 22 F1 90 是实际数据

嗯,这里要注意。单帧的 PCI 只有 4 位表示长度,所以最大只能表示 7。超过 7 就得用多帧了。

3.2 多帧传输:大块头的搬运工

当数据超过 7 个字节,就得拆成多个 CAN 报文来发。这就是多帧传输。

多帧分三个阶段:

  1. 首帧(FF):告诉接收方我要发大包了,总长度多少
  2. 连续帧(CF):一段一段地发数据
  3. 流控制帧(FC):接收方告诉发送方怎么发、发多快

首帧 PCI 格式:

Byte 0: [ 帧类型(4bit) | 高4位长度 ]

Byte 1: 低8位长度

总长度 = (高4位 << 8) | 低8位,最大 4095 字节

我记得第一次做多帧接收时,被这个长度计算坑过。高4位在第一个字节的低半字节,低8位在第二个字节。很多人会搞反。

// 首帧示例:总长度 100 字节
// PCI: 10 64
// 10 = 首帧(1) | 长度高4位(0)
// 64 = 长度低8位
// 总长度 = 0x0064 = 100 字节

3.3 流控制机制:别把接收方撑死

流控制是多帧传输的灵魂。发送方不能一股脑把数据全扔出去,得听接收方的指挥。

流控制帧包含三个参数:

参数 含义 典型值
FS (Flow Status) 流控制状态 0: 继续发送 (CTS)
1: 等待 (WAIT)
2: 溢出 (OVFLW)
BS (Block Size) 块大小 0: 无限制
1-255: 每发N帧等一次FC
STmin (Separation Time) 帧间最小间隔 0x00: 无延迟
0x01-0x7F: 1-127ms
0xF1-0xF9: 100us-900us

说白了,BS 控制「发多少帧停一下」,STmin 控制「每帧之间隔多久」。这两个参数配合使用,就能精确控制传输速率。

我的经验:

STmin 的编码有点绕。0x00 表示无延迟,0x01-0x7F 表示毫秒级。但 0xF1-0xF9 表示微秒级,比如 0xF1 = 100us。我曾经在移植时忘了处理微秒级的情况,结果高速传输时丢帧严重。

3.4 多帧传输的完整流程

咱们走一遍完整的多帧发送流程,你想想看:

  1. 发送方发首帧(FF),告诉接收方总长度
  2. 接收方收到后,回复流控制帧(FC),带上 BS 和 STmin
  3. 发送方根据 BS 发连续帧,每帧带 7 字节数据
  4. 如果 BS > 0,发完 BS 帧后等待下一个 FC
  5. 如果 BS = 0,发送方可以一直发,直到发完
  6. 接收方收到所有数据后,重组完整报文

避坑指南:

我曾经遇到过一个 ECU,它的 BS 设为 0 但 STmin 设得特别小。结果我的发送方按 BS=0 的逻辑一口气发了所有帧,但接收方处理不过来,导致溢出。后来我加了个保护:即使 BS=0,也强制加一个最小帧间隔。

3.5 时间参数:超时是万恶之源

多帧传输里有很多时间参数,任何一个超时都会导致传输失败。

参数 定义 典型值 说明
N_As 发送帧到总线的时间 0-1000ms 发送方把帧放到总线上
N_Bs 接收方回复FC的时间 0-1000ms 从收到FF到发出FC
N_Cs 发送方发CF的时间 0-1000ms 从收到FC到发出CF
N_Ar 接收方等待帧的时间 0-1000ms 接收方等待下一帧
N_Br 发送方等待FC的时间 0-1000ms 发送方等待流控制
N_Cr 接收方等待CF的时间 0-1000ms 接收方等待连续帧

这些时间参数在 ISO 15765-2 里都有推荐值,但实际项目中经常需要调整。我建议你在移植时把这些参数做成可配置的,方便后期调试。

3.6 实际项目中的常见问题

做 DoCAN 传输层移植,有几个坑我几乎每次都会遇到:

  • BS=0 的处理:有些 ECU 的 BS=0 表示无限制,但有些表示不支持多帧。得看具体实现。
  • STmin 的精度:微秒级的 STmin 在非实时操作系统上很难保证。我一般会加一个定时器补偿。
  • 连续帧的 SN:连续帧的序号从 1 开始,到 15 后回绕到 0。接收方要正确处理序号回绕。
  • 溢出处理:接收方缓冲区不够时,发 OVFLW 状态。发送方收到后要停止发送并上报错误。

我的建议:

移植时先实现单帧,再实现多帧。多帧里先做接收,再做发送。这样每一步都能独立验证,出了问题也好定位。

好了,DoCAN 传输层的核心原理就这些。说白了就是:小数据用单帧,大数据拆多帧,流控制管节奏,超时机制保可靠。下一章咱们聊聊怎么把这些原理落地到代码里。