与 ISO-TP 交互

与 CAN 交互 中所述,ECU 通常会通过 CAN 提供诊断功能,并遵循若干标准文档的要求:

诊断涉及客户端(诊断工具)与服务器(ECU)之间的大量数据传输,例如将新的固件文件发送到 ECU。 CAN 帧的有效载荷最多只能容纳 8 字节(对于 CAN FD 则为 64 字节),因此最好使用传输层来支持更大有效载荷的传输。 ISO-TP(ISO 15765-2)(有时也称为 CAN-TP)是一种传输层,旨在促进此类数据传输。

ISO-TP 基础

ISO-TP(ISO 15765-2)是 UDS 和 KWP2000 使用的传输层。它允许传输最大达 4095 字节的有效载荷。其工作原理是将每个 CAN 帧的第一个字节(或多个字节)用作报头,这一点与 IP 协议占用以太网帧前几个字节的方式类似。

ISO-TP 通常用于建立一对一的通信通道:一个客户端和一个服务器。客户端通过发送具有特定(静态)标识符的 CAN 帧来发出请求,而服务器则通过发送具有另一个特定(静态)标识符的 CAN 帧进行响应。例如,RAMN 的 ECU B 监听 ID 为 0x7e1 的 CAN 帧(由诊断工具发送),并将其解释为 ISO-TP 帧,然后通过发送 ID 为 0x7e9 的 CAN 帧作出应答。这称为正常寻址 ,但还有其他类型的寻址方式(参见寻址)。

ISO-TP 报头采用动态大小的报头(协议控制信息,PCI)。报头的类型由报头的第一个半字节( 前四位 )定义。共有四种类型的报头:

  • 单帧 [SF,首字节为 0X]:这是用于 ISO-TP 有效载荷的单字节报头,适用于 7 字节或更短的有效载荷。
  • 首帧 [FF,首字节为 1X]:这是一个两字节报头,用于启动 8 字节或更长 ISO-TP 有效载荷的传输。
  • 连续帧 [CF,首字节为 2X]:这是一个单字节报头,用于延续由“首帧”报头开始的传输。
  • 流控帧 [FC,首字节为 3X]:这是一个三字节报头,由接收方用来控制 ISO-TP 传输的速度。

尽管 ISO-TP 具备流控机制,但它并未包含用于确认帧已正确重组的“确认”机制。表示 ISO-TP 数据的 CAN 帧可能会用任意值进行填充(常见的填充值包括 0xCC、0xAA 和 0x00),以确保其始终为 8 字节长度。

单帧

单帧(报头格式为“0X”)是简单的 ISO-TP 帧:其报头表明,整个 ISO-TP 有效载荷包含在 CAN 帧中,位于报头之后。这意味着该 ISO-TP 帧的长度为 7 字节或更短,否则有效载荷及其一字节的报头将无法容纳在一个单独的 CAN 帧中。

第二个半字节的值表示 ISO-TP 有效载荷的长度。例如,“01”表示一个字节的 ISO-TP 有效载荷,“05”则表示五字节的 ISO-TP 有效载荷。

对于单帧而言,无需流量控制,因此单帧即代表了整个 ISO-TP 传输过程。例如,如果诊断工具希望向 ECU B 发送 ISO-TP 有效载荷“00 11 22 33 44”(5 字节),它将发送以下 CAN 帧:

ID: 0x7e1 DLC: 6 Payload: 05 00 11 22 33 44

请注意,ISO-TP 有效载荷的大小为 5 字节 ,但相关的 CAN 帧却是 6 字节 ,因为 CAN 帧中有一字节用作报头。

首帧与连续帧

首帧(报头格式为“1XXX”)与连续帧(报头格式为“2X”)配合使用,以传输大于 7 字节的有效载荷。首帧指定了 ISO-TP 有效载荷的长度,并且包含该有效载荷的前 6 个字节 。首字节的第二位半字节与报头的第二个字节拼接后,表示有效载荷的长度。例如,“10 09”的报头表示一个 9 字节的 ISO-TP 帧,“1F FF”则表示一个 0xFFF(4095)字节的 ISO-TP 帧。

连续帧包含该有效载荷的剩余部分。报头字节的第二位半字节用作索引,以确保有效载荷能够按顺序重新组装。

例如,如果诊断工具希望将 ISO-TP 有效载荷“00 11 22 33 44 55 66 77 88 99 AA BB CC DD”(共 14 字节)发送至 ECU B,则会发送以下 CAN 帧:

ID: 0x7e1 DLC: 8 Payload: 10 0E 00 11 22 33 44 55 ID: 0x7e1 DLC: 8 Payload: 21 66 77 88 99 AA BB CC ID: 0x7e1 DLC: 2 Payload: 22 DD

请注意,这代表了诊断工具所发送的全部内容,但对于大于7字节的帧,诊断工具实际上必须等待流控制帧,如下文所述。

流量控制帧

流量控制帧(报头格式为“3XYYZZ”)由 ISO-TP 接收方发送,用于控制传输速度。由于此类帧由接收方发送,因此其使用的 CAN ID 与其他帧不同。作为对“首帧”的响应,至少总会发现一个流量控制帧。

流量控制帧的格式为“3X YY ZZ”,包含以下参数:

  • 标志位[X,4 位]:此字段包含实际的流量控制数据。1 表示“继续”,2 表示“等待”,3 表示“中止”。
  • 块大小 [YY, 8 位]:此参数指定客户端在等待下一个流量控制帧之前应发送的帧数。0 表示“无需等待,直接发送所有内容”。
  • 最小分离时间 (STmin) [ZZ, 8 位]:此字段指定了客户端在帧传输之间应等待的时间。0 表示“尽可能快速发送”,取值范围为 1 到 127 时,表示以毫秒为单位的延迟。0xF1 至 0xF9 用于指定 100 毫秒到 900 毫秒之间的较大延迟。介于这些值之间的数值已被标准保留。

现代 ECU 通常会以“30 00 00”作为对“第一帧”的响应,这意味着“以最高速度发送所有数据”——在这种情况下,这将是整个通信过程中唯一的流控制帧。

如果 ECU 回复的是“30 00 F9”,客户端也会在不等待流控制帧的情况下发送所有帧,但每帧之间会间隔 900 毫秒。

如果 ECU 回复的是“30 01 00”,客户端将发送第一个“连续帧”,并在继续发送之前等待另一个流控制帧。

示例

以下是一个完整的 ISO-TP 传输示例:

ID: 0x7e1 DLC: 8 Payload: 10 0E 00 11 22 33 44 55 ("I want to send a 14-byte payload, here are the first bytes") ID: 0x7e9 DLC: 3 Payload: 30 00 00 ("Send the rest without waiting, at maximum speed") ID: 0x7e1 DLC: 8 Payload: 21 66 77 88 99 AA BB CC ("Here is the second part") ID: 0x7e1 DLC: 2 Payload: 22 DD ("Here is the third part")

请注意,最后没有确认应答。如果 ECU 使用的块大小不为零,则相同的数据交换将如下所示。

ID: 0x7e1 DLC: 8 Payload: 10 0E 00 11 22 33 44 55 ("I want to send a 14-byte payload, here are the first bytes") ID: 0x7e9 DLC: 3 Payload: 30 01 00 ("Send one CAN frame and wait") ID: 0x7e1 DLC: 8 Payload: 21 66 77 88 99 AA BB CC ("Here is the second part") ID: 0x7e9 DLC: 3 Payload: 30 01 00 ("Send one CAN frame and wait") ID: 0x7e1 DLC: 2 Payload: 22 DD ("Here is the third part")

Linux 上的 ISO-TP

can-utils 软件包提供了多种工具用于与 ISO-TP 交互。您可能会用到以下三个命令:isotpsend 用于发送 ISO-TP 帧,isotprecv 用于接收 ISO-TP 帧,以及 isotpdump 用于转储并解析 ISO-TP 流量。此外,您仍然可以使用前一指南中介绍的基本命令(参见与 CAN 交互)。

UDS 命令将在另一节中详细说明。本节主要使用基本的 UDS 命令 “3E 00”,该命令应由 ECU 回应 “7E 00”。我们将此命令发送至 ECU B,它使用 ID 0x7e1 接收命令,使用 ID 0x7e9 回应命令。任何其他随机命令,很可能是无效的,应以 7F <您发送的第一个字节> <错误码字节> 来回应,以指示您的命令存在错误。

candump

在使用 ISO-TP 进行实验时,你可能希望显示原始的 CAN 流量。这可以通过使用 candump 并配合正确的过滤器来实现。例如,如果你只想显示 ECU B 的 ISO-TP 流量对应的 CAN 帧,则应仅显示 ID 为 0x7e1 和 0x7e9 的 CAN 帧:

candump can0,7e1:7ff,7e9:7ff

如果您想显示现代车辆中通常出现的所有 ISO-TP 帧,可以使用 ID 介于 0x7e0 和 0x7f 之间的过滤器来筛选帧(请参阅 CAN 过滤器 ):

candump can0,7e0:7f0

在试验 ISO-TP 时,建议您始终保留一个终端执行此命令,以便可以直接观察 CAN 帧。

cansend

尽管不推荐这样做,但并没有任何限制阻止您手动创建 ISO-TP 报头并发送 CAN 帧。要将 ISO-TP 有效载荷“3E 00”发送到 ECU B,您可以输入以下命令:

cansend can0 7e1#023E00

在您的 candump 终端上,您应该会看到两条消息:

  • 您的请求(“3E 00”,由您通过 ID 0x7e1 发送)。
  • ECU 的响应(“7E 00”,由 ECU B 通过 ID 0x7e9 发送)。

请注意,CAN 帧的 DLC(有效载荷大小)为 3,但 ISO-TP 有效载荷仅为 2 字节——这是因为 CAN 有效载荷的第一个字节用于指示 ISO-TP 有效载荷的大小。此外,请注意,您的请求 CAN 报文中并未指定响应 CAN ID,该 ID 是静态设定的,不同于 TCP 等其他协议中的源端口和目的端口。

如果您忽略流控制帧,并仅寄希望于时间 timing 正确,也可以直接发送分段帧。例如,要发送有效载荷 00 11 22 33 44 55 66 77(8 字节),您可以输入:

cansend can0 7e1#1008001122334455 && cansend can0 7e1#216677

并在 candump 中观察响应结果。

尽管在 ISO-TP 上层可能会出现错误信息(例如上述截图中的“7F 00 31”UDS 错误消息),但 在 ISO-TP 层却没有任何错误信息 。如果在格式化 ISO-TP 帧时出现错误,ECU 将直接不予响应。例如,对于以下格式错误的请求,ECU 不会作出任何回应,也不会报告相关错误。

ECU 通常对 ISO-TP 请求设置了非常快速的超时机制,如果帧发送不够迅速,传输将被关闭。建议您不要自行格式化 ISO-TP 帧,而是使用诸如 isotpsend 之类的工具。

isotpsend

命令 isotpsend 允许您向 ISO-TP 服务器发送任意有效载荷。作为参数,您需要提供 CAN 接口、源 CAN ID(客户端用于传输数据的 ID)以及目标 CAN ID(服务器用于传输流控帧的 ID)。有效载荷必须通过标准输入逐字节提供。在 Linux 中,这可以使用 echo 命令来完成。例如,使用以下命令将两字节的 ISO-TP 有效载荷“3E 00”发送到 ECU B:

echo "3E 00" | isotpsend -s 7e1 -d 7e9 can0

这与之前使用 candump 捕获的 CAN 流量完全相同,只不过这次您无需自己添加报头。不过,您仍需提供目标 ID,因为 isotpsend 会等待流控帧的到来。

注意

请注意,“源”对应于目标 ECU 所监听的 ID——这一点对某些人来说可能有些反直觉。与 TCP 协议中的“源”和“目的”的类比存在局限性:对于 ISO-TP 而言,“源”和“目的”ID 是一组静态配对。如果你想与另一个 ECU 通信,你需要同时更改目的

你可以使用 isotpsend 发送最大 4095 字节的有效载荷,而无需担心 ISO-TP 的格式问题。例如:

echo "00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF 00 11 22 33" | isotpsend -s 7e1 -d 7e9 can0

应显示如下截图所示的流量。

如果你只想测试 ISO-TP 链路,而对有效载荷内容不关心,可以使用-D 选项让 isotpsend 为你生成特定大小的有效载荷。此外,你还可以使用-f 功能来强制设置客户端分离时间的值(单位为纳秒)。例如,使用以下命令生成一个 100 字节的有效载荷,并在每次传输之间设置 1 秒的延迟:

isotpsend -s 7e1 -d 7e9 -D 100 -f 1000000000 can0

你应该能够在 candump 终端中观察到缓慢的传输过程。

isotprecv

命令 isotpsend 允许您发送 ISO-TP 帧,但它不会监听来自服务器(ECU)的响应。为此,您需要在另一个终端中使用 isotprecv,并指定相同的参数:

isotprecv -s 7e1 -d 7e9 can0

这将接收一个有效载荷后关闭,但您可以使用 -l 选项来监听多个 ISO-TP 有效载荷:

isotprecv -s 7e1 -d 7e9 -l can0

如果您在一个终端中打开其中一条命令,并在另一个终端中使用 isotpsend,那么您应该能够在 isotprecv 终端中看到 ECU 对您的 ISO-TP 有效载荷的响应。例如,在另一个终端中输入以下命令:

echo "3E 00" | isotpsend -s 7e1 -d 7e9 can0

然后观察 isotprecv 终端,它应该会显示来自 ECU 的响应。

警告

使用 isotprecv 和 isotpsend 时,请勿混淆源和目标——它们应使用相同的参数。如果您改为使用 isotprecv -s 7e9 -d 7e1 can0,您将不会监听 ECU B 的响应,而是会伪装成 ECU B,并监听与之相同的命令。通常情况下,真实 ECU 中的源 ID 低于目标 ID,因此:

  • 如果第一个参数小于第二个参数,则您正在监听 ECU 的响应(典型用法)。
  • 如果第二个参数小于第一个参数,则您正在伪装成一个 ECU(例如,如果您想开发一个 ECU 模拟器)。

请注意,请求不会显示在 isotprecv 中,仅显示响应。您可以使用 isotpdump 在同一终端中同时显示请求和响应。

isotpdump

命令 isotpdump 将在同一窗口中同时显示请求和响应。与 candump 类似,建议您保持一个终端窗口始终打开该命令。

使用以下命令转储您与 ECU B 之间的流量:

isotpdump -s 7e1 -d 7e9 -c can0

-c 选项为消息添加颜色,以便轻松区分请求(红色)和响应(蓝色)。

如果您在另一个终端中输入以下 isotpsend 命令,应该能够观察到 ISO-TP 流量。

echo "3E 00" | isotpsend -s 7e1 -d 7e9 can0

您可以使用-a 选项以显示 ASCII 格式,并使用-u 选项将有效载荷解释为 UDS 流量:

isotpdump -s 7e1 -d 7e9 -c can0 -a -u

与 candump 不同,isotpdump 会自动去除报头,方便您直接识别实际的 ISO-TP 有效载荷。不过,它仍然会逐个显示 CAN 帧。当有效载荷较大时,这种显示方式可能会显得有些繁杂,例如:

isotpsend -s 7e1 -d 7e9 can0 -D 200

isotpsniffer

如果您只想查看重构的有效载荷,可以改用 isotpsniffer。您可以使用以下语法:

isotpsniffer -s 7e1 -d 7e9 can0 -c

这应该只会显示重构后的有效载荷,例如在使用 isotpsend 传输大容量有效载荷时:

isotpsend -s 7e1 -d 7e9 can0 -D 100

isotpperf

您可以使用 isotpperf 来测量 ISO-TP 连接的性能,例如针对 ECU B:

isotpperf -s 7e1 -d 7e9 can0

这将显示正在进行的传输,并测量其耗时。

您可以通过本地强制设置-STmin 值(使用-f 选项)来尝试各种传输。打开另一个窗口,输入以下命令以发送最大尺寸且具有不同时间间隔的有效载荷:

isotpsend -s 7e1 -d 7e9 can0 -f 100000 -D 4095 isotpsend -s 7e1 -d 7e9 can0 -f 1000000 -D 4095 isotpsend -s 7e1 -d 7e9 can0 -f 10000000 -D 4095

isotpserver

如果您希望通过 IP 网络访问 ECU 的 ISO-TP 服务器,可以使用 isotpserver。例如,如果您希望将来自 ECU B(ISO-TP 对 0x7e1/0x7e9)的 ISO-TP 流量转发到 IP 端口 7777,可以使用以下命令:

isotpserver -s 7e1 -d 7e9 -l 7777 can0
警告

此命令允许任何能够访问您 IP 服务器的人员与 ECU B 进行通信。请仅在您清楚自己操作的情况下使用此功能。

您可以使用例如 netcat 连接到此端口:

nc localhost 7777

您可以发送以 ASCII 格式嵌入在尖括号 <> 中的 ISO-TP 帧,例如,如果您输入 <3E00> 并按下回车键,您应在同一终端中收到 <7E00>

此命令可用于远程与多人共享一个 ECU。但请注意,只有 ISO-TP 响应(且仅限响应)会被广播给所有客户端。您还可以使用 isotptun 命令创建基于 ISO-TP 的 IP 隧道。

(略微)高级 ISO-TP 概念

物理寻址与功能寻址

寻址方式有两种:

  • 物理寻址
  • 功能寻址

在正常寻址模式下,诊断工具一次仅与一个 ECU 进行通信。

在功能寻址中,诊断工具可以同时与一组 ECU 进行通信。它仅支持单帧,因为单帧无需流量控制,因此多个接收端可轻松同时接收。ECU 常用的函数地址为 0x7DF,该地址由 OBD-II PID 定义。

寻址

ISO-TP 中定义了多种寻址类型:

  • 普通寻址 :地址 ID 完全包含在 CAN 标识符中(例如,ECU B 的 0x7e1 和 0x7e9)。它适用于标准(11 位)和扩展(29 位)CAN 标识符。
  • 普通固定寻址 :这是普通寻址的一种子格式,仅适用于 29 位扩展标识符,其中地址必须遵循 J1939 格式
  • 扩展寻址 :CAN 有效载荷的第一个字节用作额外的地址字节。这意味着单帧和连续帧的报头大小变为两个字节,首帧为三个字节,流控帧则为四个字节。
  • 混合寻址 :正常固定寻址与扩展寻址的混合使用。

CAN FD

较新版本的 ISO-TP 利用了 CAN FD 提供的额外带宽(参见 CAN FD)。具体而言:

  • 单帧可容纳最多 62 字节的有效载荷。为此,CAN 帧的第一个字节应为“00”,而紧随其后的字节则用于指示 ISO-TP 有效载荷的大小(最多 62 字节,因为前两个字节已被报头占用)。
  • 分段帧(首帧+连续帧)可容纳最大 4GB 大小的有效载荷。为此,前两个字节应为“1000”,而随后的四个字节则用于指示 ISO-TP 有效载荷的大小。

故障排除

如果您遇到超时问题(例如出现“read socket t: Connection timed out”之类的错误信息),这表明连接不可靠,即以下情况之一:

  • ECU A(您的 USB 转 CAN 适配器)将数据从 USB 传输到 CAN 的速度不够快。
  • 您的 PC 通过 USB 发送的数据不正确。

如果问题出在 ECU A 上,其屏幕上应会显示错误信息。

如果您使用的是虚拟机,其 USB 设置可能不够可靠。在 VirtualBox 中,前往“设置”->“USB”,并勾选“USB 3.0 (xHCI) 控制器”(请确保您的虚拟机已关闭)。

如果仍然失败,请尝试使用 -f 选项与 isotpsend 配合,以强制其降低速度,例如使用“-f 100000”。