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 报文来发。这就是多帧传输。
多帧分三个阶段:
- 首帧(FF):告诉接收方我要发大包了,总长度多少
- 连续帧(CF):一段一段地发数据
- 流控制帧(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 多帧传输的完整流程
咱们走一遍完整的多帧发送流程,你想想看:
- 发送方发首帧(FF),告诉接收方总长度
- 接收方收到后,回复流控制帧(FC),带上 BS 和 STmin
- 发送方根据 BS 发连续帧,每帧带 7 字节数据
- 如果 BS > 0,发完 BS 帧后等待下一个 FC
- 如果 BS = 0,发送方可以一直发,直到发完
- 接收方收到所有数据后,重组完整报文
避坑指南:
我曾经遇到过一个 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 传输层的核心原理就这些。说白了就是:小数据用单帧,大数据拆多帧,流控制管节奏,超时机制保可靠。下一章咱们聊聊怎么把这些原理落地到代码里。