4、UDS网络层:ISO 15765-2协议、单帧与多帧传输、流控制机制、网络层定时参数

好,咱们进入第四讲。前面聊了诊断的“语言”和“语法”,这一讲咱们聊聊诊断报文在总线上到底是怎么“跑”起来的。说白了,就是ISO 15765-2这个网络层协议。

我个人习惯把这一层叫做“快递员”。应用层(UDS)写好了一封信(诊断请求/响应),网络层负责把这封信打包、装箱、贴上地址,然后通过CAN总线这个“物流通道”发出去。如果信很短,一个包裹就搞定;如果信很长,就得拆成好几个包裹,分批发送,到了目的地再拼起来。

嗯,这里要注意,ISO 15765-2只定义了CAN总线上的网络层。如果你用的是CAN FD或者以太网,那规则又不一样了。咱们今天只聊经典CAN。

4.1 单帧传输:小数据包怎么发?

先看最简单的场景。你发一条诊断请求,比如“读取ECU的VIN码”,请求本身可能只有3个字节(SID + DID)。响应呢?VIN码是17位ASCII字符,加上响应SID,总共18个字节。

CAN一帧数据最多能带8个字节(经典CAN)。那这18个字节怎么塞进去?

答案就是:单帧(Single Frame, SF)

单帧的格式很简单。第一个字节的高4位是帧类型标识,单帧是0x0。低4位表示数据长度(Data Length, DLC),范围是0到7。注意,这里的数据长度是指后续有效诊断数据的字节数,不包括协议头本身。

举个例子,一个3字节的诊断请求,CAN报文数据域是这样的:

字节0: 0x03  (高4位0x0表示单帧,低4位0x3表示后续有3个有效字节)
字节1: 0x22  (诊断请求SID,比如读取DID)
字节2: 0xF1  (DID高字节)
字节3: 0x90  (DID低字节)
字节4-7: 填充字节(通常填0x00或0xAA,取决于ECU实现)

我在项目中遇到过一个问题:某个ECU对填充字节特别敏感,填0x00它正常响应,填0x55它就报错。后来查手册才发现,它把填充字节也纳入了校验范围。所以,填充字节不要乱填,最好和ECU供应商确认一下

小技巧: 单帧的最大有效数据长度是7个字节(因为DLC占4位,最大值为7)。如果你的诊断数据正好是7个字节,那恭喜你,一帧搞定。如果超过7个字节,就得用多帧了。

4.2 多帧传输:大数据包怎么拆?

当诊断数据超过7个字节时,单帧就装不下了。这时候需要把数据拆成多个CAN帧,分批发送。这就是多帧传输(Multi-Frame Transmission)

多帧传输涉及三种帧类型:

  • 首帧(First Frame, FF):发送方告诉接收方:“我要发一个大数据包,总长度是XX字节,这是第一块。”
  • 流控帧(Flow Control, FC):接收方回应:“我知道了,你按这个节奏发,一次发几帧,间隔多久。”
  • 连续帧(Consecutive Frame, CF):发送方按流控帧的要求,一帧一帧地把剩余数据发完。

咱们一步步拆解。

4.2.1 首帧(FF)

首帧的第一个字节高4位是0x1,表示这是首帧。低4位和第二个字节组合成一个12位的值,表示整个诊断数据的总长度(不包括协议头)。

举个例子,你要发送一个20字节的诊断响应:

首帧CAN数据域:
字节0: 0x10  (高4位0x1表示首帧,低4位0x0是总长度的高4位)
字节1: 0x14  (总长度的低8位,0x14 = 20)
字节2-7: 诊断数据的前6个字节

总长度 = (0x10 & 0x0F) << 8 | 0x14 = 0x00 << 8 | 0x14 = 20。没错,就是20个字节。

首帧本身已经携带了6个字节的有效数据(字节2到字节7)。剩下的14个字节,需要通过连续帧发送。

注意: 总长度字段是12位,最大值是4095。也就是说,一次多帧传输最多能发4095个字节的诊断数据。对于UDS诊断来说,这通常足够了。但如果你要刷写一个几百KB的固件,那得分成多个多帧会话。

4.2.2 流控帧(FC)

发送方发出首帧后,不能立即发连续帧。它得等接收方回应一个流控帧。流控帧的作用是告诉发送方:“我准备好了,你按这个节奏来。”

流控帧的格式如下:

字节0: 0x30  (高4位0x3表示流控帧,低4位是流控状态)
字节1: 块大小(Block Size, BS)
字节2: 最小间隔时间(Separation Time, STmin)

流控状态有三种:

  • 0x0:继续发送(CTS, Continue To Send)。这是最常见的状态,表示“我准备好了,你发吧”。
  • 0x1:等待(WAIT)。接收方暂时处理不过来,让发送方等一等。发送方收到WAIT后,应该继续等待,直到收到CTS或超时。
  • 0x2:溢出(OVFLW, Overflow)。接收方的缓冲区满了,装不下了。发送方收到OVFLW后,应该中止本次传输。嗯,这种情况我遇到过,ECU的接收缓冲区设计得太小,刷写时经常溢出,后来让硬件工程师改了改。

块大小(BS):接收方允许发送方连续发送的连续帧数量。比如BS=5,表示发送方可以连续发5个连续帧,然后必须停下来等待下一个流控帧。BS=0表示没有限制,可以一直发到结束。

最小间隔时间(STmin):发送方在发送连续帧时,相邻两帧之间的最小时间间隔。单位是毫秒(0x00-0x7F)或微秒(0xF1-0xF9)。比如STmin=0x10,表示间隔16毫秒。

我个人习惯把STmin设得稍微大一点,比如20ms。为什么呢?因为有些老旧的ECU处理速度慢,间隔太小它会丢帧。我曾经在调试一个2008年的ECU时,STmin设成5ms,结果连续帧发到第3帧就丢了,排查了半天才发现是ECU的CAN控制器太老了。

4.2.3 连续帧(CF)

发送方收到流控帧(状态为CTS)后,开始发送连续帧。连续帧的第一个字节高4位是0x2,低4位是一个4位的序列号(SN, Sequence Number)。序列号从1开始,每发一帧加1,到15后回绕到0。

举个例子,接上面的20字节响应,首帧已经发了6个字节,还剩14个字节。假设流控帧设置BS=5,STmin=10ms:

连续帧1 (SN=1): 字节0: 0x21, 字节1-7: 第7-13个字节 (7个字节)
连续帧2 (SN=2): 字节0: 0x22, 字节1-7: 第14-20个字节 (7个字节)

注意,连续帧的数据域最多也是7个字节。最后一帧如果数据不够7个字节,剩余字节可以填充(通常填0x00)。

发送完2个连续帧后,因为BS=5,还没达到块大小限制,所以不需要等待流控帧,直接继续发。如果BS=2,那发完2帧后就得停下来,等下一个流控帧。

核心要点: 多帧传输的本质是“拆包-流控-组包”。发送方拆包,接收方通过流控帧控制节奏,发送方按节奏发连续帧,接收方按序列号组包。任何一个环节出错(比如丢帧、序列号错乱),整个传输就失败了。

4.3 流控制机制:谁说了算?

你可能会问:“为什么要有流控制?直接一股脑发完不就行了?”

原因很简单:接收方的处理能力有限

ECU的CAN控制器通常有一个接收缓冲区,大小可能是几十个字节。如果发送方以1Mbps的速率狂发,接收方来不及处理,缓冲区就会溢出,导致丢帧。流控制机制就是让接收方告诉发送方:“你慢点,我消化不了。”

流控制的核心参数有三个:

参数 含义 典型值 我的建议
流控状态 CTS/WAIT/OVFLW 0x00 (CTS) 除非特殊情况,否则用CTS
块大小 (BS) 连续发送的CF帧数 0 (无限制) 或 1-255 刷写时建议设成16或32,平衡效率与可靠性
最小间隔 (STmin) 相邻CF帧的最小间隔 10ms-100ms 保守点设20ms,高速场景可设5ms

我在实际项目中见过一个坑:某个ECU的BS设成了0(无限制),但它的接收缓冲区只有64字节。发送方连续发了10个连续帧(70字节数据),结果缓冲区溢出,ECU直接挂了。后来我把BS改成8,问题解决。所以,BS=0虽然效率高,但风险也高,除非你确认接收方缓冲区足够大

4.4 网络层定时参数:别让ECU等太久

网络层协议定义了几个关键的定时参数,用来确保通信的可靠性。如果超时了,发送方或接收方就要采取相应的措施(比如重发或中止)。

咱们一个一个看:

4.4.1 N_As:发送超时

发送方从开始发送一个CAN帧(包括首帧、连续帧、流控帧)到该帧成功发送到总线上的最大时间。如果超过这个时间还没发出去,发送方应该中止传输。典型值:1000ms。

4.4.2 N_Bs:接收超时(首帧后等待流控帧)

发送方发出首帧后,等待接收方回应流控帧的最大时间。如果超时没收到流控帧,发送方认为接收方出了故障,中止传输。典型值:1000ms。

4.4.3 N_Cr:接收超时(连续帧后等待流控帧)

发送方在发送完一个块(BS指定的帧数)后,等待下一个流控帧的最大时间。如果超时,中止传输。典型值:1000ms。

4.4.4 N_Br:发送超时(接收方等待首帧)

接收方在空闲状态下,等待发送方发送首帧的最大时间。如果超时没收到首帧,接收方认为发送方放弃了。典型值:1000ms。

4.4.5 N_Cs:发送超时(接收方等待连续帧)

接收方在发送完流控帧后,等待发送方发送连续帧的最大时间。如果超时,接收方认为发送方出了问题。典型值:1000ms。

经验之谈: 这些定时参数在ISO 15765-2中都有推荐值,但ECU厂商可以自定义。我建议你在开发测试阶段,把这些超时时间设得宽松一点(比如2000ms),等系统稳定了再收紧。我曾经因为超时设得太短,导致刷写时频繁超时中断,排查了三天才发现是ECU处理速度慢,超时时间不够。

4.5 实战中的常见问题与避坑指南

好了,理论讲完了,咱们聊聊实战中容易踩的坑。

问题1:连续帧序列号错乱

发送方发的连续帧序列号必须是连续的(1,2,3,...),如果跳号或重复,接收方会认为传输错误,直接丢弃整个多帧报文。我曾经遇到一个情况:发送方的CAN驱动在发送连续帧时,因为中断优先级问题,导致两帧的顺序颠倒了。排查了很久才发现是驱动bug。

问题2:流控帧丢失

如果流控帧在总线上丢失了,发送方会一直等待,直到N_Bs或N_Cr超时。这会导致整个诊断会话挂起。解决办法是:发送方在超时后重发首帧或连续帧,或者干脆中止传输,让应用层重新发起请求。

问题3:STmin设置不合理

STmin设得太小,接收方处理不过来;设得太大,传输效率低。我建议你根据ECU的实测性能来调整。怎么测?用示波器抓CAN总线波形,看接收方处理一帧数据需要多长时间,然后STmin设成这个时间的1.5倍到2倍。

避坑指南: 我曾经在一个项目中,ECU的STmin要求是10ms,但实际测试发现它需要15ms才能处理完一帧。我一开始没注意,结果刷写时总是丢帧。后来把STmin改成20ms,问题解决。所以,不要完全相信ECU手册上的参数,一定要实测

好了,这一讲的内容就到这里。网络层是UDS诊断的“骨架”,理解了单帧、多帧、流控和定时参数,你就能看懂诊断报文在总线上是怎么流动的了。下一讲,咱们聊聊诊断会话控制,看看ECU怎么管理不同的诊断模式。