干过几年车载总线测试的人都知道,只要跟UDS诊断打交道,CANoe基本是绕不开的家伙。ISO15765这几个字听起来挺唬人,但它本质上解决一个问题:CAN总线一帧只能塞8个字节,诊断数据动不动就是几十个字节,怎么传?ISO15765规定了一套“拆包-打包”的规则,业内常叫它ISO-TP。很多人第一次在Trace里看到一堆以 10、21、30 开头的帧,直接懵掉——明明我发的是个读VIN的请求,怎么总线上回了一大串?
这篇文章就专门把这层窗户纸捅破。我会用CANoe作为分析工具,从ISO15765多帧传输的帧结构讲起,再到如何配置CANoe的传输层,最后用一次读取VIN(17字节数据)的真实报文,逐字节拆给你看。无论你是刚接触ECU测试的应届生,还是被诊断报文折磨过的老同志,照着走一遍,下次再看到带多帧传输的Trace,心里会踏实很多。
1. 多帧传输到底在解决什么问题
1.1 为什么会有ISO15765
CAN总线最早是给控制信号设计的,像转速、油门、刹车这类量,一帧8字节绰绰有余。但后来车厂开始做诊断和刷写,要把“读取故障码”“写入配置”“更新固件”这种动辄几十上百字节的数据塞进CAN,8字节显然不够用。
总不能为了一次诊断就前前后后发十几帧原始数据吧?接收方怎么知道哪些帧属于同一个消息?哪一帧在前、哪一帧在后?中间断了怎么办?于是ISO 15765-2成了事实标准,它定义了一种传输协议,也就是我们常说的ISO-TP。它把上层的数据拆成多帧,加上标识信息,按顺序发出去,接收方再把这些帧拼成完整数据。CANoe里很多诊断报文显示成一条“Diagnostic”消息,底层其实是ISO-TP在背后自动拆装。
1.2 四种帧类型的“角色分工”
ISO-TP用四种帧类型完成整个多帧流程:
- 单帧(SF):数据长度不超过7字节(经典CAN),直接用一帧传完。
- 首帧(FF):数据长度超过7字节时,第一帧先发出去,里面带着总长度和首段数据。
- 流控帧(FC):接收方收到首帧后,返回一个“允许发送”的指令,里面携带BS、STmin等参数。
- 连续帧(CF):发送方按流控帧的指示,一帧一帧把剩余数据发完。
这四种帧靠第一个字节的PCI(协议控制信息)区分。PCI高四位是帧类型标识,例如0x0代表单帧,0x1代表首帧,0x2代表连续帧,0x3代表流控帧。在CANoe的Trace窗口里,你往往会看到“SF/FF/FC/CF”的缩写,要能一眼认出来。
1.3 物理寻址与功能寻址
ISO15765的寻址通常分两种:
- 物理寻址:点对点,比如测试仪发给某个ECU,请求ID一般是0x7E0,响应ID是0x7E8。这种最常用。
- 功能寻址:广播,一个请求发给多个ECU,常用ID是0x7DF。功能寻址一般只用于请求,不用于响应。
CANoe里配置传输层的时候,要分清这两种寻址对应的CAN ID。很多人配置错误导致收不到响应,多半是物理寻址ID没配对。
2. 实战前必须搞定的CANoe环境配置
2.1 我推荐的CANoe配置方式
先说一下环境。我用的是CANoe 16/17系列,接口有VN1640和虚拟CAN通道。如果你只是想学多帧报文分析,不一定非要真实硬件,CANoe自带的虚拟CAN总线也能跑通整个流程。安装的时候务必确认装了“Diagnostics”相关组件,部分功能需要授权,没授权的话诊断窗口会用不了。
新建工程时选择CAN总线,配置两条虚拟通道:Channel1和Channel2。用CANoe的“Simulation Setup”把通道连接起来,再加上一个DBC文件定义好节点和报文。没有DBC也可以先裸发CAN帧,但解析不出ISO-TP消息,所以建议老老实实建一个基础DBC。
DBC我用CANdb++(Vector自带的数据库编辑器)创建。典型配置是定义两个节点:Tester(测试仪)和ECU,然后定义物理请求报文ID=0x7E0,物理响应报文ID=0x7E8。ISO-TP本身不强制这些ID具体是什么值,但UDS诊断领域约定俗成,大部分OEM都沿用这套逻辑。
2.2 配置传输层的关键步骤
DBC建好后,在CANoe的“Diagnostics/ISO TP”区域,右键添加一个ISO TP传输对象。这里要设置:
- 发送ID和接收ID:请求ID 0x7E0,响应ID 0x7E8。
- 寻址类型:物理寻址。
- 协议类型:ISO 15765-2(CAN)。
- 处理模式:可以把“对完整帧进行重组”打开,这样在Diagnostic窗口里看到的是一条完整诊断响应,而不是一堆拆分后的裸CAN帧。
这一步最容易踩的坑是“诊断描述文件(CDD)”。CANoe诊断控制台加载CDD后,可以直接用服务名发诊断请求,非常方便。但如果手头没有CDD,也可以手动发送CAN帧,靠CANoe的TP层去拆包。两种方式我都会在实战里覆盖到。
2.3 Trace窗口和过滤技巧
ISO-TP报文在Trace里非常刷屏,尤其是发送几十个字节的多帧响应,一秒钟可能几十条CF帧。我的习惯是先把Trace窗口的默认列配置好:查看CAN ID、数据场、帧类型、绝对时间、相对时间这几列,再打开设置里的“右键过滤”功能。
点击Trace窗口工具栏上的“Display Filter”图标,可以只显示请求ID和响应ID,或者只显示ISO-TP相关报文。不要在一堆其他网络管理、应用报文里找诊断帧,效率太低。还可以在“Write Window”里用CAPL脚本打印关键信息,比如打印收到的完整多帧数据,分析速度更快。
3. 手把手实战:用VIN读取把多帧报文拆明白
3.1 一个真实的读取VIN全过程
我以一个ECU返回VIN码的场景为例。UDS服务是22(读数据),DID是F190(VIN)。请求数据是22 F1 90,只有3个字节,CAN单帧完全能装下,所以请求帧只发一个单帧:
| 方向 | 帧类型 | CAN ID | Data |
|---|---|---|---|
| 请求 | SF | 0x7E0 | 02 22 F1 90 |
02是单帧PCI,代表单帧且数据长度为2字节,后边跟22 F1 90。这个请求没有任何悬念,一帧就完事了。
重点在响应。假设ECU返回的VIN是ABC123XYZ45678901,ASCII码一共17个字节。UDS响应数据为62 F1 90(2字节DID + 1字节服务响应)再加上17字节VIN,总共20字节。20字节超过了7字节,所以必须走ISO-TP多帧。
3.2 首帧FF怎么解析
ECU发送的第一帧如下:
| 方向 | 帧类型 | CAN ID | Data |
|---|---|---|---|
| 响应 | FF | 0x7E8 | 10 14 62 F1 90 41 42 43 |
首帧PCI的格式是:第一个字节高四位为1,表示首帧;低四位是完整数据长度的bit11~bit8;第二个字节是完整数据长度的bit7~bit0。所以10 14拼接得到长度0x014,也就是20字节,正好是将来重组后的完整响应长度。
首帧数据区只有6字节,放的是完整响应最前面的6个字节:
- 62:响应服务ID,与请求的22服务对应。
- F1 90:DID字段。
- 41、42、43:就是VIN里最前面的三个字符
A B C。
所以这条首帧的完整含义就是:我要传20字节数据,前6字节我已经发给你了,下面是后面14字节,等我收到流控帧再安排节奏。
3.3 流控帧FC怎么解析
ECU发完首帧后,正常不会立刻继续发连续帧。它必须先等测试仪(Tester)回一个流控帧,告诉它“你按这个节奏来发”。
测试仪收到首帧后,回一个流控帧:
| 方向 | 帧类型 | CAN ID | Data |
|---|---|---|---|
| 请求 | FC | 0x7E0 | 30 00 00 |
流控帧PCI的第一个字节高四位是3,代表流控帧。低四位叫FS(Flow Status):
- 0:Continue,继续发。
- 1:Wait,先等着。
- 2:Overflow,接收缓冲区溢出,丢弃本次传输。
示例里的FS=0,表示可以继续。第二个字节是BS(Block Size),表示允许发送方连续发送的连续帧数量。BS=0是个特殊值,表示“不限帧数,一口气发完”。第三个字节是STmin(Separation Time min),表示两个连续帧之间的最小时间间隔。STmin=0x00代表不强制额外延时,但很多工具还是会留一点间隔,避免CAN发送缓冲溢出。
3.4 连续帧CF的序号和数据拼接
收到FC后,ECU开始连发剩余的连续帧。完整响应的20字节中,首帧已经带了6个,还剩14个字节,刚好分成两个连续帧每个7字节:
第一帧CF:
| 方向 | 帧类型 | CAN ID | Data |
|---|---|---|---|
| 响应 | CF | 0x7E8 | 21 31 32 33 58 59 5A 34 |
PCI第一个字节高四位是2,表示连续帧;低四位是序列号SN。第一个连续帧的SN通常是1(部分协议栈也有从0开始的,后续会聊到)。数据区放的是后续7个字节:31 32 33 58 59 5A 34,也就是ASCII字符1 2 3 X Y Z 4。
第二帧CF:
| 方向 | 帧类型 | CAN ID | Data |
|---|---|---|---|
| 响应 | CF | 0x7E8 | 22 35 36 37 38 39 30 31 |
SN=2,数据区是剩余7个字节:35 36 37 38 39 30 31,也就是5 6 7 8 9 0 1。
接收方拿到FF+CF1+CF2后,按顺序把数据区拼接起来:
- FF数据6字节:62 F1 90 41 42 43
- CF1数据7字节:31 32 33 58 59 5A 34
- CF2数据7字节:35 36 37 38 39 30 31
拼起来就是62 F1 90 41 42 43 31 32 33 58 59 5A 34 35 36 37 38 39 30 31,转成ASCII就是ABC123XYZ45678901。到这里,一次完整的多帧响应就解析完了。
3.5 传输参数的计算逻辑
有人会问,BS和STmin怎么选?简单说:
- BS决定“发几帧停一下”。BS=N,表示发送方每发N个连续帧后,必须等接收方重新发一个FC才继续。
- STmin决定“帧与帧之间的最小时间”。如果ECU接收处理速度慢,STmin就要给大一点;如果太快,又会造成总线负载升高。
STmin的具体含义还要注意单位。0x00到0x7F表示0到127ms;0x80到0xF0时,单位变成100us,比如0xFA其实不是标准值。常见的10是1ms,32是5ms,64是10ms。实际项目中,STmin会根据ECU底层驱动能力来确定。CANoe的诊断传输层配置里可以直接选,配置完它会自动生成对应的FC参数。
4. 多帧传输常见的坑:现象与排查
做过几轮诊断测试后,你会发现多帧传输的问题翻来覆去就那么几个。我整理了一份在CANoe环境下的避坑经验,按出现频率排序。
4.1 看不见首帧或连续帧,Trace里全是裸CAN帧
很多时候明明发了20字节的响应,Trace窗口里却看不到带有10、21、30的报文,反而看到一串完整的原始数据被拆成8字节一条的CAN帧。这是CANoe把ISO-TP层“帮助”了,它默认重组了完整消息,并且在Diagnostics窗口和Trace里只展示重组后的结果。
解决方法是到Trace窗口的“Display”里打开ISO-TP的详细模式,或者直接在报文列表里看“CAN TP”列。如果你希望看到传输层的裸帧,可以右键“ISO TP”选项,选择“show transport protocol frames”。不要误以为ECU没发多帧,其实数据早就到了。
4.2 发完FF后一直没有FC响应
典型的故障是ECU发完首帧后,测试仪不回复流控帧。排查顺序如下:
- 检查故障注入:是不是CAPL脚本或者DBC里把流控帧屏蔽了。
- 检查地址ID:确认请求ID和响应ID配对是否正确。
- 检查诊断协议配置:CANoe的ISO TP传输层如果发送ID和接收ID设置反了,收到的FF会被当成无效帧丢弃。
- 检查超时时间:CANoe默认的N_As/N_Bs超时是1秒,如果测试仪没来得及解析,也会出现超时。
这个坑最隐蔽的地方在于,很多新手用CDD和诊断窗口发送请求时,CANoe会自动帮你回FC,但如果你直接用“CAN IG”面板手动发送帧,CANoe不会自动回流控帧,此时ECU发完FF就一直等,最终超时报N_Bs超时。所以我建议,纯学习阶段就用诊断窗口或CAPL来走完整流程,别用手动CAN发送去模拟测试仪。
4.3 CF序号为什么不是从0开始
我在拿到首帧之后,见过很多人在Trace里盯着第一个连续帧的SN看。有人收到21开头的帧,就会问:为什么不是20?
ISO15765标准里,SN是一个4位循环计数器,从0到15循环。但在标准实现中,首帧之后第一个连续帧的SN通常从1开始,后续依次递增。部分工具或协议栈会从0开始,这个在判断时会有歧义。我的经验是不要纠结起始值,重点检查连续帧的SN是否连续。如果中间出现跳号,比如前面是SN=3,后面直接SN=5,那基本可以判定丢帧,需要做重传处理。
CANoe在重组时如果发现SN不连续,会在Trace里显示错误或者直接超时。你可以在诊断窗口的“Trace Filter”里勾选“Protocol errors”,快速定位这类问题。
4.4 BlockSize与STmin搭配出问题
有时候首帧发完,流控帧也回得很快,但连续帧发了几帧就停了。这时候排查BS和STmin的取值。BS=3代表每发3个CF,就要等接收方再发一次FC。如果接收方迟迟不发第二个FC,传输就卡住。BS=0虽然不限次数,但工程上不建议在复杂的CAN网络中无脑设0,因为一旦总线拥堵,要么出错误帧,要么接收方缓冲区溢出。
STmin设置太小时,比如0x00,连续帧之间几乎没有间隔。如果ECU底层写Flash或者做校验,可能来不及处理,导致后续CF被丢弃。一般项目里STmin至少设到0x0A(10ms)。在CANoe里可以通过“CAN Statistics”窗口看到总线负载和错误帧情况,如果错误帧多,先怀疑STmin。
4.5 常见问题速查表
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| Trace只显示重组后的诊断消息 | CANoe默认合并TP层 | 开启“show transport protocol frames” |
| 发完FF后无FC | 手动发送帧导致无流控响应 | 改用诊断窗口或CAPL |
| 报文接收超时N_Bs | FC未返回或超时参数过短 | 检查ID、协议配置和超时时间 |
| 连续帧SN跳变 | 丢帧或总线错误 | 检查STmin和BS,查看错误帧 |
| 响应长度与实际不符 | FF中的长度计数错 | 对照FF的12位长度字段重新计算 |
| 功能寻址收不到响应 | 功能寻址不能用于响应 | 请求用0x7DF,响应必须物理寻址0x7E8 |
5. 进阶玩法与个人心得
5.1 用CAPL脚本自动发送并校验多帧
手动发一次UDS请求没有问题,但做压力测试和自动化回归时,就要用CAPL来跑。我常用的套路是:
- 用
diagSetP2Parameter设置超时。 - 用
diagSendRequest发送CDD里定义好的诊断请求。 - 通过
on diagResponse回调接收完整响应。 - 在回调里用
diagGetParameter解析响应中的VIN字节并比对预期值。
用CAPL的好处是,你不需要关心底层拆包拼包逻辑,CANoe的协议栈已经把FF/CF/FC全部处理好了。它会给你一个完整响应的字节数组,直接用MemCmp做结果校验。批量刷写、反复读写DID的自动化脚本,基本都是这个思路。
5.2 与Python联合测试的扩展
有些团队习惯用Python控制CANoe做集成测试。可以用CANoe的COM接口启动工程、发送诊断请求、读取响应。比如调用CANoe.Application对象,再通过Diagnostic对象触发请求,这样就能在Python测试框架里跑诊断自动化。
不过这种方案对CANoe版本依赖较强,COM接口偶尔会因为版本不匹配出幺蛾子。如果是学习阶段,先用CAPL把逻辑调通,再考虑Python封装。
5.3 一个调试小技巧
最后分享一个我压箱底的小技巧。排查多帧传输问题时,我习惯在Trace窗口添加两列:Ack和Error。当某个CF帧出现CRC或ACK错误时,这两列会标红,立刻能定位到物理层干扰。很多时候你以为ISO-TP配置不对,其实是总线上丢帧。
另外,CANoe的“Graphics Window”配合ISO-TP层,可以把BS和STmin的时序可视化。调了几次参数后,你会对“STmin=1ms和5ms的实际总线效果”有直观感觉。这些参数的变化用肉眼很难看出来,但图形曲线骗不了人。
根据我个人经验,ISO15765多帧传输真正难的不是协议本身,而是你第一次面对一堆看似杂乱帧时的心态。记住每类帧的角色:FF是报幕员,FC是交通指挥,CF是跑腿的,SF是一句话能说完的事。用CANoe多看几次真实报文,多拆几轮字节,这个坎很快就能迈过去。