3. UDS网络层与传输机制:ISO 15765-2 (DoCAN) 协议
好,咱们进入第三章。这一章聊的是UDS的“运输队”——ISO 15765-2,也就是常说的DoCAN协议。说白了,它就是负责把UDS的“大件行李”(诊断消息)拆成小包,通过CAN总线运过去,到了目的地再拼回来。
我刚开始做诊断开发时,总觉得网络层就是个“打包拆包”的活儿,没啥技术含量。直到有一次,一个多帧传输的流控制参数没配好,导致ECU直接“罢工”,我才意识到——嗯,这里面的门道可不少。
3.1 为什么需要网络层?
CAN总线的数据帧,一帧最多只能带8个字节的数据。但UDS的诊断请求或响应,动不动就几十上百个字节。比如你要读ECU的全部故障码,或者刷写一段Bootloader程序,数据量远超8字节。
所以,ISO 15765-2就出来“救场”了。它定义了:
- 单帧(SF):数据≤7字节时,一帧搞定。
- 多帧(FF + CF):数据>7字节时,拆成多帧发送。
- 流控制(FC):接收方告诉发送方“你慢点发”或“可以发了”。
核心要点:网络层对上层(应用层)是透明的。你写诊断代码时,只需要关心发送和接收的数据内容,网络层自动帮你完成拆包、组包、流控。但作为开发者,你必须理解它的机制,否则出了问题你连从哪查起都不知道。
3.2 单帧与多帧传输
3.2.1 单帧(Single Frame, SF)
当诊断消息的总长度 ≤ 7字节时,直接用单帧发送。CAN数据帧的8个字节中,第一个字节是协议控制信息(PCI),剩下7个字节放数据。
单帧PCI格式:
| 位 | 7-4 | 3-0 |
|---|---|---|
| 含义 | 帧类型(0x0) | 数据长度(1-7) |
举个例子,发送一个“诊断会话控制(0x10 0x03)”请求,只有2个字节,用单帧发送:
CAN ID: 0x7DF
数据: 0x02 0x10 0x03 0x00 0x00 0x00 0x00 0x00
↑ ↑ ↑
PCI 服务ID 子功能
0x02 = 单帧 + 2字节数据
3.2.2 多帧(Multi-Frame)
当数据长度 > 7字节时,就需要多帧传输了。多帧分为三个阶段:
- 首帧(First Frame, FF):发送方先发一个首帧,告诉接收方“我要发这么多数据”。
- 流控制帧(Flow Control, FC):接收方回复一个流控制帧,告诉发送方“你可以发了”或“等一下”。
- 连续帧(Consecutive Frame, CF):发送方按流控制帧的要求,一帧一帧地把剩余数据发完。
首帧PCI格式:
| 位 | 7-4 | 3-0 | 字节2 | 字节3-7 |
|---|---|---|---|---|
| 含义 | 帧类型(0x1) | 数据长度高4位 | 数据长度低8位 | 数据(最多6字节) |
连续帧PCI格式:
| 位 | 7-4 | 3-0 |
|---|---|---|
| 含义 | 帧类型(0x2) | 序列号(1-15,循环) |
个人经验:连续帧的序列号是从1开始的,不是0。而且序列号到15后,下一个又从1开始。我曾经在测试时,序列号从0开始发,结果ECU直接不理我了——嗯,这个坑踩过一次就记住了。
3.3 流控制机制
流控制是多帧传输的“交通警察”。接收方通过流控制帧,告诉发送方:
- 继续发送(CTS):你可以发了。
- 等待(WAIT):我还没准备好,你等一下。
- 溢出(OVFLW):我缓冲区满了,你发太多了。
流控制帧PCI格式:
| 位 | 7-4 | 3-0 | 字节2 | 字节3 |
|---|---|---|---|---|
| 含义 | 帧类型(0x3) | 流状态(0=CTS, 1=WAIT, 2=OVFLW) | 块大小(BS) | 最小间隔时间(STmin) |
三个关键参数:
- 块大小(Block Size, BS):接收方允许发送方连续发送的CF帧数量。BS=0表示可以无限发,直到数据发完。
- 最小间隔时间(Separation Time, STmin):发送方每发完一帧CF后,必须等待的最小时间(单位:ms或μs)。
- 流状态(FS):CTS、WAIT、OVFLW三种状态。
避坑指南:我曾经在一个项目中,接收方的BS设成了0,但STmin设成了0x00(表示0ms)。结果发送方以总线极限速度狂发CF帧,接收方的缓冲区瞬间溢出,整个诊断通信直接挂死。后来我学乖了——BS=0时,STmin至少设成0x01(1ms),给接收方一点喘息的时间。
3.4 网络层定时参数
网络层定义了四个关键定时参数,用来保证通信的可靠性。这些参数在ISO 15765-2中有明确规定,但不同OEM可能会有自己的调整。
| 参数 | 缩写 | 默认值 | 说明 |
|---|---|---|---|
| N_As | 发送超时 | 0-1ms(CAN 2.0) | 发送方从请求发送到CAN帧实际发送的最大时间 |
| N_Ar | 接收超时 | 0-1ms(CAN 2.0) | 接收方从CAN帧到达到应用层读取的最大时间 |
| N_Bs | 首帧到流控制超时 | 1000ms | 发送方发送首帧后,等待接收方回复流控制帧的最大时间 |
| N_Br | 流控制到首帧超时 | 1000ms | 接收方发送流控制帧后,等待发送方发送首帧的最大时间 |
| N_Cs | 连续帧发送超时 | 1000ms | 发送方发送连续帧后,等待接收方回复流控制帧的最大时间 |
| N_Cr | 连续帧接收超时 | 1000ms | 接收方等待下一帧连续帧的最大时间 |
我的习惯:在开发测试阶段,我会把N_Bs和N_Cs设成500ms,而不是标准的1000ms。这样一旦通信卡住,我能更快地发现问题。量产时再改回1000ms,给ECU留足处理时间。
3.5 网络层状态机
网络层的发送和接收,各自有一个状态机。我画了一张图,帮你理清整个流程:
3.6 实际开发中的注意事项
最后,我总结几个实际开发中容易踩的坑:
- CAN ID的配置:物理寻址和功能寻址的CAN ID不能搞混。物理寻址是一对一,功能寻址是一对多。我见过有人把功能寻址的ID配成了物理寻址,结果ECU只响应一个,其他全不理。
- STmin的取值:STmin的单位是ms,但0x00表示0ms,0x01-0x7F表示1-127ms,0xF1-0xF9表示100-900μs。别搞混了。
- 序列号溢出:连续帧的序列号只有4位,到15后必须回到1。有些ECU在序列号溢出时处理不当,会导致丢帧。
- 超时处理:网络层超时后,必须终止当前传输,并回到空闲状态。不能“死等”,否则整个诊断通信会卡死。
我的建议:在开发初期,就用CAN工具(比如CANoe、PCAN)抓一下网络层的交互日志。看看FF、FC、CF的时序对不对,BS和STmin是否合理。这一步做好了,后面应用层的调试会顺利很多。