与 UDS 交互

在“与 ISO-TP 交互”一节中介绍的 ISO-TP 层允许发送和接收最大长度为 4095 字节的任意有效载荷。现代车辆中的 ECU 很可能会将 ISO-TP 数据解释为本节所述的 UDS 命令。

UDS 基础知识

统一诊断服务 (UDS) 是一种用于 ECU 诊断和编程的协议。网上有许多相关的教程,你也可以找到总结其工作原理的海报。

UDS 遵循简单的请求-响应机制:你向 ECU 发送一个有效载荷,然后会收到一个作为响应的有效载荷。

请求的第一个字节表示服务 ID;有效载荷的其余部分则表示该服务的参数。第二个字节通常(但并非总是)表示服务的子功能。

应答的第一个字节表示对请求的响应:要么是肯定响应(服务 ID + 0x40),要么是否定响应(0x7f);有效载荷的其余部分则代表参数。

RAMN 标准 UDS 服务

RAMN ECU 支持多种 UDS 服务。它们为此使用以下 ISO-TP CAN ID:

  • ECU A 使用 0x7e0 接收命令,使用 0x7e8 发送应答。
  • ECU B 使用 0x7e1 接收命令,使用 0x7e9 发送应答。
  • ECU C 使用 0x7e2 接收命令,使用 0x7ea 发送应答。
  • ECU D 使用 0x7e3 接收命令,使用 0x7eb 发送应答。
  • 所有 ECU 均使用 0x7df 通过功能寻址接收命令(命令广播)。

如果您将 ECU A 用作 USB 转 CAN 适配器,则它不会通过 CAN 回应您的 UDS 请求;您应改用 USB 串行接口。

您可以通过查看 ramn_uds.c 源代码文件来确认 ECU 支持哪些标准服务:

  • 服务 0x10(诊断会话控制)
  • 服务 0x11(ECU 复位)
  • 服务 0x14(清除诊断信息)
  • 服务 0x19(读取 DTC 信息)
  • 服务 0x22(按标识符读取数据)
  • 服务 0x23(按地址读取内存)
  • 服务 0x27(安全访问)
  • 服务 0x2E(按标识符写入数据)
  • 服务 0x31(例程控制)
  • 服务 0x34(请求下载)
  • 服务 0x35(请求上传)
  • 服务 0x36(传输数据)
  • 服务 0x37(请求传输退出)
  • 服务 0x3D(按地址写入内存)
  • 服务 0x3E(测试人员存在)
  • 服务 0x85(控制 DTC 设置)
  • 服务 0x87(链路控制)

除了标准服务之外,ECU A 还增加了两项自定义服务:

  • 服务 0x41(在屏幕上显示指定像素)
  • 服务 0x42(加载并启动提供的 Chip-8 游戏)

这些服务将在本指南的后续部分进行说明。

服务 ID 低于 0x10 的诊断服务由 J1979 标准定义,并在 J1979 服务(OBD-II PIDs)中进行了说明。

警告

RAMN ECU 的 UDS/J1979 实现经过了轻微简化(包含大量虚拟参数),以更好地容错并更适合初学者使用。您尤其需要注意以下与实际 ECU 可能存在的差异:

  • RAMN ECU 采用普通寻址方式,但实际应用中的 ECU 可能会使用不同的寻址方法(参见寻址方式)。
  • 默认情况下,RAMN ECU 接受填充和未填充的 CAN 消息。而在实际应用中,ECU 可能仅接受填充的 CAN 消息(CAN 消息长度为 8)。您可以将 -p 0:0 选项与 isotpsend 结合使用,以零字节对 CAN 消息进行填充。

当您使用功能寻址时,只能使用单帧命令(有效载荷小于 7 字节)。如果 ECU 无法处理以功能寻址方式接收到的命令,通常将完全不会作出响应。

正面响应

如果 ECU 接受了您的请求,它将以一个有效载荷作为响应,该有效载荷以您所请求的服务 ID 加上 0x40 开头。例如,如果您请求的服务 ID 是 0x3E,并且 ECU 接受了该请求,则其响应将以 0x7E(0x3E + 0x40)开头。如果您请求的服务 ID 是 0x10,并且 ECU 接受了该请求,则其响应将以 0x50(0x10 + 0x40)开头。

服务通常允许您通过将服务参数的第一个比特设置为“1”(抑制肯定响应)来禁用肯定响应。

负面响应

如果 ECU 拒绝了您的请求,它将以一个以 0x7F 开头的三字节有效载荷进行回复。其中,第二个字节表示被拒绝的服务,第三个字节表示错误代码。您也可以在 ramn_uds.c 中找到可能的错误代码列表。您最有可能遇到的错误代码包括:

  • 0x11 - “服务不支持”。
  • 0x12 - “子功能不支持”:这意味着该服务很可能受支持,只是您请求的特定子功能不受支持。
  • 0x13 - “消息长度错误或格式无效”:这意味着该服务(以及适用时的子功能)是受支持的,但您的请求大小无效(参数字节数过多或不足)。
  • 0x31 - “请求超出范围”:这意味着您的请求格式有效且受支持,但参数超出了有效范围。例如,您尝试读取一个不存在的内存地址(但您提供的地址格式是有效的)。
  • 0x33 - “安全访问被拒绝”:这意味着在使用该服务之前,您需要先解锁(参见安全访问 (0x27))。
  • 0x7E - “活动会话中不支持该服务”:这意味着您必须首先请求一个不同的诊断会话(参见诊断会话控制 (0x10))。

您还可能遇到与时间相关的“错误”:

  • 0x21 - “忙,重复请求”:您的命令可能是正确的,但您必须稍后重试(您需要发送另一个请求)。
  • 0x78 - “请求已正确接收,响应待处理”:ECU 将稍后作出响应(您无需再次发送请求)。

这些错误代码是极其宝贵的信息来源,有助于您学习如何正确使用 UDS 服务。

使用 Linux 和测试仪存在 (0x3E) 的示例

您可以分别使用 isotpsend 和 isotprecv 来发送 UDS 命令并接收其响应。此外,您还可以使用 isotpdump 以 UDS 助记符显示 UDS 流量,从而便于解析。

如果您尚未完成,请确保将您的 RAMN 配置为 CAN Linux 接口(参见将 slcan 转换为 socketCAN)。

例如,要与接口 can0 上的 ECU B(使用 CAN ID 0x7e1/0x7e9)进行交互,请打开终端并输入:

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

此终端将显示您 UDS 请求的响应。再打开一个终端,并输入:

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

该终端将显示 UDS 流量(包括请求和响应)。

最后,打开第三个终端,你将使用它来输入你的 UDS 请求。例如,你可以通过以下命令发送一个 UDS 请求:

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

其中“3E 00”表示你要使用“Tester Present”服务,并携带参数“0x00”。Tester Present 是一种简单的服务,用于告知 ECU 当前正在进行操作,以防止其超时。

你应该在 isotprecv 终端上观察到收到了回复“7E 00”:

这里的 7E 等于 0x3E+0x40,表示 ECU 已接受你的请求(并以参数“0x00”进行响应)。你应该在 isotpdump 终端上看到,它已自动为你解析了该请求:

如果你发送了一个无效的命令,例如将“3E 00”误发为“3E 00 00”,你应该会观察到不同的 UDS 通信流量:

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

ECU 的响应为“7F 3E 13”。“7F”表示服务 ID 为“3E”的请求因错误代码“13”而被拒绝,正如您在 isotpdump 终端中所看到的,“13”的含义是“消息长度错误或格式无效”。

注意

如果您想与除 ECU B 之外的其他 ECU 进行通信,则需要为所有命令更改 -s 和 -d 选项。您可以使用 candump can0,7e0:7f0 代替 isotpdump 来显示任何 UDS 流量,但它不会为您解析 UDS 命令。

对于许多服务而言,第一个参数仅为 7 位长,且第一位用于指示是否抑制正面响应。如果你使用参数“0x80”,实际上发送的是参数“0x00”,并要求 ECU 在无错误时无需应答。

例如,如果你输入以下命令:

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

除非发生错误,否则你将不会收到来自 ECU 的任何响应。

你可能希望使用别名来缩短上述命令。你可以通过以下脚本创建有用的别名:

1CAN_INTERFACE_NAME=can0
2
3ECUB_ISOTP_SEND_CANID=7e1
4ECUB_ISOTP_RECV_CANID=7e9
5ECUC_ISOTP_SEND_CANID=7e2
6ECUC_ISOTP_RECV_CANID=7ea
7ECUD_ISOTP_SEND_CANID=7e3
8ECUD_ISOTP_RECV_CANID=7eb
9
10alias sendECUB="isotpsend -s $ECUB_ISOTP_SEND_CANID -d $ECUB_ISOTP_RECV_CANID $CAN_INTERFACE_NAME"
11alias sendECUC="isotpsend -s $ECUC_ISOTP_SEND_CANID -d $ECUC_ISOTP_RECV_CANID $CAN_INTERFACE_NAME"
12alias sendECUD="isotpsend -s $ECUD_ISOTP_SEND_CANID -d $ECUD_ISOTP_RECV_CANID $CAN_INTERFACE_NAME"
13
14alias recvECUB="isotprecv -s $ECUB_ISOTP_SEND_CANID -d $ECUB_ISOTP_RECV_CANID $CAN_INTERFACE_NAME -l"
15alias recvECUC="isotprecv -s $ECUC_ISOTP_SEND_CANID -d $ECUC_ISOTP_RECV_CANID $CAN_INTERFACE_NAME -l"
16alias recvECUD="isotprecv -s $ECUD_ISOTP_SEND_CANID -d $ECUD_ISOTP_RECV_CANID $CAN_INTERFACE_NAME -l"
17
18alias dumpECUB="isotpdump -s $ECUB_ISOTP_SEND_CANID -d $ECUB_ISOTP_RECV_CANID -c $CAN_INTERFACE_NAME -a -u"
19alias dumpECUC="isotpdump -s $ECUC_ISOTP_SEND_CANID -d $ECUC_ISOTP_RECV_CANID -c $CAN_INTERFACE_NAME -a -u"
20alias dumpECUD="isotpdump -s $ECUD_ISOTP_SEND_CANID -d $ECUD_ISOTP_RECV_CANID -c $CAN_INTERFACE_NAME -a -u"

你需要为每个打开的终端执行此脚本(可以简单地将内容复制粘贴到终端中并执行)。执行之后,你将能够通过以下方式从 ECU B 接收数据:

recvECUB

并简单地通过以下方式将数据发送至 ECU B:

echo "3E 00" | sendECUB

您也可以类似地使用命令别名 dumpECUB 来转储上述流量。

RAMN UDS 服务

诊断会话控制 (0x10)

许多 UDS 服务默认情况下不可用,您首先需要使用诊断会话控制服务请求一个“诊断会话”。默认情况下,ECU 处于“默认会话”(代码 0x01)。其他可能的标准会话包括“编程会话”(代码 0x02)、“扩展诊断会话”(代码 0x03)以及“安全系统诊断会话”(代码 0x04)。

真实的 ECU 通常会验证车辆状态是否允许会话切换。例如,在驾驶车辆时,您不应能够启动编程会话。如果将 RAMN 连接到驾驶模拟器,ECU 将检查车辆当前是否处于停止状态。如果未满足此条件,ECU 将以错误代码“0x22”进行响应,该代码表示“条件不正确”。

要使用此服务,只需将会话代码作为唯一参数提供,例如启动一个编程会话:

echo "10 02" | isotpsend -s 7e1 -d 7e9 can0

ECU 重置 (0x11)

此服务可用于重置 ECU。它仅有一个参数,即重置类型。RAMN ECU 仅支持重置类型 0x01(硬重置),使用方法如下:

echo "11 01" | isotpsend -s 7e1 -d 7e9 can0

或者,如果您不希望 ECU 在接收到请求时作出响应:

echo "11 81" | isotpsend -s 7e1 -d 7e9 can0
警告

如果 ECU 处于默认会话状态,则不会接受重置请求;您必须先使用诊断会话控制 (0x10) 才能使用此服务。

此命令支持功能寻址。如果您希望同时重置所有 ECU,可以将这些命令发送至 ID 0x7df:

echo "10 02" | isotpsend -s 7df -d 7e9 can0 echo "11 01" | isotpsend -s 7df -d 7e9 can0

请注意,这里的 -d 7e9 并不重要;该命令会被所有 ECU 接收并处理。

按标识符读取数据 (0x22)

这是从 ECU 读取数据的一种常用服务。它接受两个字节作为参数,这两个字节代表您希望读取的 16 位数据标识符 (DID)。尽管某些 ECU 允许一次性读取多个 DID,但 RAMN ECU 每次请求仅允许读取一个 DID。

部分 DID 具有标准含义(请参阅本教程以获取列表)。例如,您可以使用 DID 0xF184 查询 ECU 固件的编译时间:

echo "22 F1 84" | isotpsend -s 7e1 -d 7e9 can0

您也可以使用 DID 0xF18C 查询 ECU 的序列号硬件(该硬件在每个 ECU 中应是唯一的):

echo "22 F1 8C" | isotpsend -s 7e1 -d 7e9 can0

警告

通过 ID 读取数据可能会返回较长的数据负载,这意味着它们将通过 ISO-TP 进行分段传输。您必须主动设置一个 ISO-TP 接收器(例如,使用 isotprecv -s 7e1 -d 7e9 -l can0 )。如果没有激活的接收器,ECU 将无法接收到继续传输所需的“流控制帧”,您只会观察到其响应中的“首帧”。

通过标识符写入数据(0x2E)

您也可以使用 UDS 写入任意 DID。只需提供您想要写入的 DID 以及要写入的数据即可。例如,DID 0xF190 指的是 ECU 的车辆识别码(VIN)。您可以使用通过标识符写入数据服务,将一个 17 位字符的字符串写入 DID 0xF190。请务必先启动一个编程会话:

echo "10 02" | isotpsend -s 7e1 -d 7e9 can0

然后,使用通过标识符写入数据服务:

echo "2E F1 90 56 49 4E 30 31 32 33 34 35 36 37 38 39 41 42 43 44" | isotpsend can0 -s 7e1 -d 7e9

现在,您应该能够使用通过标识符读取数据来读取之前写入内存的任意 VIN:

echo "22 F1 90" | isotpsend -s 7e1 -d 7e9 can0

该值被写入闪存,因此即使在复位后仍会保留。如果您重新刷写 ECU 并重置其内存,通过标识符读取数据服务可能会提示您的请求超出范围。

您可以安装 xxd 工具,以便在可读的 ASCII 文本与 isotpsend 和 isotprecv 所使用的十六进制字符串之间轻松进行转换:

sudo apt-get install xxd

使用以下命令可将 ASCII 字符串(例如 VIN0123456789ABCD)转换为 isotpsend 可识别的十六进制字符串(例如 2E F1 90 56 49 4E 30 31 32 33 34 35 36 37 38 39 41 42 43 44):

echo -n "VIN0123456789ABCD" | xxd -p | sed 's/../& /g' #converts from ASCII to hexadecimal

反之亦然:

echo "56 49 4e 30 31 32 33 34 35 36 37 38 39 41 42 43 44" | xxd -r -p #hexadecimal to ASCII

读取 DTC 信息(0x19)

汽车爱好者通常希望获取的一类信息是诊断故障代码(DTC)。DTC 是车辆中出现的问题报告,并由 ISO 15031-6 标准定义。

DTC 由一个字母(U、C、P 或 B)和四个数字组成。

字母表示问题所属的领域:“U”代表网络(ECU A),“C”代表底盘(ECU B),“P”代表动力总成(ECU C),“B”代表车身(ECU D)。第一位数字表明该 DTC 是标准定义(“0”)还是制造商特定定义(“1”)。

例如,DTC“P0650”表示动力总成领域出现了问题。其中的“0”表示该 DTC 为标准定义,在此上下文中,“6”代表“计算机输出电路”,而“50”则表示“故障指示灯(MIL)控制电路故障”。

网上有大量关于 DTC 解读的信息可供参考。如果第一位数字是零,则 DTC 具有唯一定义;但如果第一位是“1”,则其定义因制造商而异,并且在不同车辆中可能有不同的含义。

在较早的 KWP2000 协议中,DTC 曾以两个字节的形式存储,该协议早于 UDS。UDS 引入了第三个字节,即故障类型字节(FTB),以报告更多关于问题的详细信息。用于定义 DTC 值的两个字节分别称为“高字节”和“中字节”,而“低字节”则代表 FTB。

  • 首字母由最高两位表示:00 代表 P,01 代表 C,10 代表 B,11 代表 U。
  • 第一个数字由高字节的第 5 至第 4 位表示。
  • 第二个数字由高字节的第 0 至第 3 位表示。
  • 第三个数字由中字节的第 7 至第 4 位表示。
  • 第四个数字由中字节的第 0 至第 3 位表示。

例如,DTC P0650 在 UDS 中的表示如下:

Byte | High Byte | Middle Byte | Low Byte | Bit index | 7 6 | 5 4 | 3 2 1 0 | 7 6 5 4 | 3 2 1 0 | 7 6 5 4 3 2 1 0 | Value | 0 0 | 0 0 | 0 1 1 0 | 0 1 0 1 | 0 0 0 0 | FTB |

在 UDS 负载中,这将显示为 06 50 <FTB>

最后,一个字节的最低位用于指示 DTC 的状态。该字节的每一位代表一个标志,其定义如下:

  • 第 0 位表示“测试失败”。
  • 第 1 位表示“本操作周期内测试失败”。
  • 第 2 位表示“待定 DTC”。
  • 第 3 位表示“已确认 DTC”。
  • 位 4 表示“自上次清除以来测试未完成”。
  • 位 5 表示“自上次清除以来测试失败”。
  • 位 6 表示“本操作周期内测试未完成”。
  • 位 7 表示“请求警告指示器”。

因此,ECU 总共会向您发送每个 DTC 四个字节。要请求读取 DTC,您需要提供一个子功能和一个 DTC 状态掩码。RAMN ECU 支持子功能 0x01,该功能返回与所提供掩码匹配的 DTC 数量;还支持子功能 0x02,该功能在同一帧中串联返回实际的 DTC。

例如,如果您想请求待处理的 DTC 数量,可以使用子功能 0x01,并设置参数 0x04(必须将“待处理 DTC”置位):

echo "19 01 04" | isotpsend -s 7e1 -d 7e9 can0

ECU 会以六个字节进行响应:

  • 第一个字节为 0x19 + 0x40 = 0x59,用于指示已接受请求。
  • 第二个字节重复子功能字节。
  • 第三个字节是“DTC 状态可用性掩码”——即 ECU 实际可以检查的状态标志位。
  • 第四个字节是“DTC 格式标识符”(例如,ISO 15031-6 DTC 格式为 0x00,ISO 14229-1 DTC 格式为 0x01)。
  • 最后两个字节表示 DTC 的数量。

出于演示目的,RAMN ECU 在复位时会确保其内存中至少包含一个 DTC。该 DTC 的标志位始终标记为待处理状态,并且 ECU 不允许您通过掩码进行过滤。

您可以使用以下命令询问 ECU B 内存中有多少个 DTC:

echo "19 01 FF" | isotpsend can0 -s 7e1 -d 7e9

您还可以使用以下命令要求 ECU B 向您发送所有 DTC:

echo "19 02 FF" | isotpsend can0 -s 7e1 -d 7e9

“59 01 04 00 00 00 01”的含义是:ECU 接受了您针对服务 0x19 和子功能 0x01(读取 DTC 数量)的请求,它仅支持 0x04 掩码(用于待处理 DTC),采用 DTC 格式 0x00,并且内存中存储了 0x0001 个 DTC。

“59 02 04 45 63 00 04”的含义是:ECU 接受了您针对服务 0x19 和子功能 0x02(读取 DTC)的请求,它支持 0x04 掩码,并且存在一个 DTC:0x4563,故障类型位为 0x00,状态为 0x04(待处理)。0x4563 以“01”开头,表示这是一个底盘域的 DTC(“C”),因此对应的 DTC 为“C0563”(出于演示目的,此处为任意设定)。

清除诊断信息 (0x14)

此服务可用于从 ECU 的内存中清除 DTC。它使用一个 3 字节的参数来指示要清除哪一组 DTC。与排放相关的系统 DTC 可以通过“00 00 00”来清除,而所有 DTC 则可通过“FF FF FF”来清除。其他可能的值则由制造商自定义。

例如,您可以使用以下方式清除 ECU B 内存中的所有 DTC:

echo "14 FF FF FF" | isotpsend can0 -s 7e1 -d 7e9

您可以通过在执行该命令前后分别读取 DTC 的数量,来验证 DTC 是否已被清除。ECU 在复位事件后会自动重新生成 DTC。

控制 DTC 设置(0x85)

此服务允许临时禁用 DTC,以防止 ECU 在诊断过程中添加新的 DTC。您可以使用子功能 0x01 来允许新 DTC 的生成,使用 0x02 来禁用它们。

启用 DTC:

echo "85 01" | isotpsend can0 -s 7e1 -d 7e9

禁用 DTC:

echo "85 02" | isotpsend can0 -s 7e1 -d 7e9

安全访问 (0x27)

某些服务可能要求您先解锁 ECU,然后才能使用。您会发现这些服务,因为它们会对您的请求回复错误代码 0x33(拒绝安全访问)。

安全访问服务可用于解锁 ECU。

安全访问可用于通过 ECU 实现简单的质询/响应身份验证。您首先需要从 ECU 请求一个“种子”。然后,您必须执行一些绝密算法,以从该种子计算出一个“密钥”,并将该“密钥”发送到 ECU 以解锁它。请注意,此处的密钥并不指加密密钥;而是指对质询的响应。

此服务有不同的安全级别。要请求级别 1 的种子,请使用以下命令:

echo "27 01" | isotpsend can0 -s 7e1 -d 7e9

您应该注意,ECU 会向您发送一个 4 字节的种子作为响应。该种子由 ECU 的真随机数生成器 (TRNG) 生成。您可以请求任意数量的种子。

要解锁 ECU,您需要计算种子值与 0x12345678 的异或值。这仅用于演示目的,并不是一个安全的身份验证机制。

在 Linux 中,您可以使用以下命令计算要发送到 ECU 的“密钥”(以 7D 70 9F 4D 为例):

printf "%08X " $((0x7D709F4D ^ 0x12345678)) | sed 's/../& /g'

您可以使用与请求相同的命令,但将安全级别加 1,以将您的答案(在本例中为 6F 44 C9 35)发送到 ECU:

echo "27 02 6F 44 C9 35" | isotpsend can0 -s 7e1 -d 7e9

如果 ECU 接受您的 UDS 请求(第一个字节为 0x67),则表示您提供了正确的“密钥”,ECU 现已解锁到级别 1。

在尝试使用安全访问时,您可能会遇到以下错误代码:

  • 0x24(请求序列错误):您在未先请求种子的情况下尝试使用密钥。
  • 0x35(密钥无效):您提供的密钥错误。
  • 0x36(尝试次数超限):您的失败尝试次数过多。
  • 0x37(所需时间延迟未到期):您需要等待更长时间后再尝试解锁 ECU(通常是在复位之后,以防止暴力破解)。

例程控制 (0x31)

例程控制服务可用于实现标准服务未涵盖的功能。例程通过一个两位字节的标识符进行识别。与 DID 类似,UDS 标准定义了许多标准例程标识符,但标识符 0x0200 至 0xDFFF 为 ECU 专用。

例程控制服务可配合三种子功能使用:

  • 0x01 开始一个例程。
  • 0x02 用于停止例程。
  • 0x03 用于请求例程的执行结果。

此服务使用以下参数:<子功能> <例程标识符> <可选例程参数>。RAMN ECU 提供了以下例程:

  • 例程 0x0200 可用于请求 ECU 停止发送周期性 CAN 消息。
  • 例程 0x0201 可用于擦除 EEPROM(其中保存有 DTC 和 VIN)。
  • 例程 0x0202 可用于将 EEPROM 复制到备用存储器库(通过 UDS 重新刷写 ECU 时)。
  • 例程 0x0203 可用于请求 ECU 回显您所传输的内容(用于负载测试)。
  • 例程 0x0204 可用于请求 ECU 回显请求的前 4 个字节(用于 PC 到 ECU 的链路测试)。
  • 例程 0x0205 可用于请求 ECU 传输指定大小的 UDS 有效载荷(用于 ECU 到 PC 的链路测试)。
  • 例程 0x0206 可用于计算 ECU 闪存的 CRC 校验值。
  • 例程 0x0207 可用于启用自动驾驶模式(需与 CARLA 配合使用)。
  • 例程 0x0208 可用于向 ECU 添加任意 DTC 故障码。
  • 例程 0x0209 可用于执行任意 ARM(Cortex M-33)Shell 代码。
  • 例程 0x0210 可用于重置 BOOT 选项字节(以修复固件损坏的 ECU)。
  • 例程 0x0211 可用于强制 ECU 切换内存 Bank(同样用于修复 ECU)。
  • 例程 0xFF00 可用于擦除备用固件。
  • 例程 0xFF01 可用于验证内存并切换内存 Bank。

由于这些例程可能会修改 ECU 闪存,因此请勿随意操作,除非您清楚自己在做什么。有关如何使用这些例程,请参阅 ramn_uds.c

例如,您可以要求 ECU B 停止发送周期性报文,方法如下:

echo "31 01 02 00" | isotpsend can0 -s 7e1 -d 7e9

并且你可以要求 ECU B 恢复发送周期性报文,方法是:

echo "31 02 02 00" | isotpsend can0 -s 7e1 -d 7e9

按地址读取内存 (0x23)

按地址读取内存服务可用于读取任意 ECU 地址。您自然需要提供两个参数:地址和要读取的字节数。

与按标识符读取数据服务所使用的 DID 不同,实际 ECU 地址的大小可能因微控制器架构而异。因此,您必须提供第三个参数,用于指定地址和内存字段的大小(地址长度格式标识符)。该参数为一个字节,其高 4 位表示“size”参数的大小,低 4 位表示“address”参数的大小。

按地址读取内存参数的格式为<格式标识符> <地址> <size>

例如,假设您想从地址 0x08000000(RAMN ECU 程序闪存的起始地址)读取 4 个字节。“4”可以容纳在一个字节中,因此您可以使用一个字节来提供要读取的大小。STM32 微控制器使用的地址为 32 位长(4 字节)。因此,您可以使用格式标识符 0x14(地址 4 字节,size 1 字节)。

你可以通过以下方式请求内存读取:

echo "23 14 08 00 00 00 04" | isotpsend can0 -s 7e1 -d 7e9

格式标识符指的是大小参数的尺寸(它并非您想要读取的字节数),这一点可能会让一些人感到困惑。如果您愿意,可以将大小参数(4)作为 2 字节或 4 字节的参数来提供。因此,以下命令与上述命令严格等效:

echo "23 24 08 00 00 00 00 04" | isotpsend can0 -s 7e1 -d 7e9 echo "23 34 08 00 00 00 00 00 04" | isotpsend can0 -s 7e1 -d 7e9 echo "23 44 08 00 00 00 00 00 00 04" | isotpsend can0 -s 7e1 -d 7e9

该服务会立即返回所读取的数据。例如,您可以使用以下方式读取 ECU B 程序闪存的前 256 字节(地址为 0x08000000):

echo "23 24 08 00 00 00 01 00" | isotpsend can0 -s 7e1 -d 7e9

请注意,在这种情况下,请求和响应均为分片的 ISO-TP 帧,因此您必须在另一个终端上启用 isotprecv。

同样,您也可以读取微控制器的 RAM(起始地址为 0x20000000):

echo "23 24 20 00 00 00 01 00" | isotpsend can0 -s 7e1 -d 7e9

如果你想了解 RAMN ECU 将变量存储在哪些地址上,必须编译固件,并查看构建过程生成的“.map”文件。

按地址写入内存 (0x3D)

按地址写入内存与按地址读取内存的工作方式相同,只是它额外需要一个参数来指定您希望写入到指定地址的数据。由于这允许覆盖 RAM(且仅限于 RAM),如果您不清楚自己在做什么,可能会导致 ECU 崩溃。

例如,您可以使用以下命令将“01 02 03 04”写入内存地址 0x20000000:

echo "3D 24 20 00 00 00 00 04 01 02 03 04" | isotpsend can0 -s 7e1 -d 7e9

此命令仅在您首先请求编程会话并通过安全访问(0x27)解锁 ECU 后才会被接受。您可以通过在操作前后使用按地址读取内存功能来验证内存是否已正确写入。变量地址取决于您所使用的固件的确切版本。在本示例中使用的版本中,0x20000000 对应于 CAN 适配器的“错误状态”变量,该变量可被覆盖而不会影响稳定性。如果您不清楚您的命令覆盖了哪些变量,则应重置 ECU。

链路控制 (0x87)

此服务可用于更改 CAN 总线的波特率,例如,在通过 UDS 重新刷写 ECU 时可提高速度。它需要两个步骤:

  • 一条命令用于验证 ECU 是否接受波特率变更。
  • 一条命令用于切换至新的波特率。

请注意,虽然真实的 ECU 在诊断会话结束后会自动恢复到原始波特率,但 RAMN ECU 则会保持当前波特率直至下次复位。标准实现采用功能寻址和正面响应抑制,以同时向所有 ECU 发送切换命令。然而,RAMN ECU 会在切换波特率前先关闭其 CAN 控制器并等待一秒钟,以更好地容忍更多的时序问题,因此您可以单独与每个 ECU 进行通信。

您可以使用此服务,它包含以下三种子功能之一:

  • 0x01 使用一个字节的波特率标识符来验证波特率变更(例如,0x12 对应 500000 bps)。
  • 0x02 使用三个字节的具体波特率值来验证波特率变更(例如,0x7A120 对应 500000 bps)。
  • 0x03 切换至新的波特率。

RAMN ECU 支持子功能 0x01 和 0x03。您必须为子功能 0x01 提供的参数是一个字节的标识符:

  • 0x10 表示 125000 bps。
  • 0x11 表示 250000 bps。
  • 0x12 表示 500000 bps。
  • 0x13 表示 1000000 bps。

要切换到新的波特率,请使用子功能 0x03,无需任何参数。如果您希望在无错误时让 ECU 不予响应,请改用 0x83。

在更改 ECU 的波特率时,您还需要同步更新 CAN 适配器的波特率。如果您使用的是 slcan 适配器,则需要重启 slcand,并使用 -s 选项(参见 CAN 波特率)。

对于 125000 bps 使用 -s4,250000 bps 使用 -s5,500000 bps 使用 -s6,1000000 bps 使用 -s8。

例如,可以使用以下命令将所有 ECU 的 RAMN 波特率逐一更新为 1000000 bps(假设您的接口为/dev/ttyACM0):

echo "87 01 13" | isotpsend can0 -s 7e1 -d 7e9 -b echo "87 01 13" | isotpsend can0 -s 7e2 -d 7ea -b echo "87 01 13" | isotpsend can0 -s 7e3 -d 7eb -b echo "87 03" | isotpsend can0 -s 7e1 -d 7e9 -b echo "87 03" | isotpsend can0 -s 7e2 -d 7ea -b echo "87 03" | isotpsend can0 -s 7e3 -d 7eb -b sleep 0.5 sudo killall -w slcand #turn off slcan interface sudo slcand -o -c -s8 /dev/ttyACM0 && sudo ip link set up can0

这还将重启您的 CAN 接口,因此您需要重新启动所有 CAN 命令。

你可以通过使用功能寻址,将命令同时发送至所有 ECU,从而简化通信流量,并利用正面响应抑制功能,要求它们在未发生错误时无需应答。

echo "87 81 13" | isotpsend can0 -s 7df -d 7e9 -b echo "87 83" | isotpsend can0 -s 7df -d 7e9 -b sleep 0.5 sudo killall -w slcand #turn off slcan interface sudo slcand -o -c -s8 /dev/ttyACM0 && sudo ip link set up can0

这样仅需两条 CAN 报文即可实现相同的波特率变更。

请求上传(0x35)

此服务可用于向 ECU 请求数据“上传”。请注意,在嵌入式系统中,“上传”通常指的是从 ECU 到计算机的数据传输(这意味着您正在将数据“下载”到您的计算机)。这可用于转储 RAMN ECU 的闪存。虽然使用按地址读取内存(0x23)服务也可以达到相同的效果,但该服务在可用时支持压缩和加密。

其使用方法如下:

  • 调用此服务以请求数据上传。
  • 根据需要多次调用传输数据(0x36)服务以接收数据。
  • 调用请求传输退出(0x37)服务以完成传输。

此服务包含多个参数:

  • 一个字节用于指定压缩和加密方法。RAMN 仅支持 0x00,表示两者均未使用。
  • 一个字节用于指定地址和大小字段的长度(类似于按地址读取内存 (0x23))。RAMN ECU 支持 0x44(4 字节大小,4 字节地址)。
  • 若干字节用于指定数据的地址。
  • 若干字节用于指定(未压缩)数据的长度。

例如,你可以通过以下方式请求从地址 0x08000000 转移 256(0x100)字节:

echo "35 00 44 08 00 00 00 00 00 01 00" | isotpsend can0 -s 7e1 -d 7e9 -b

ECU 应当回复:

  • 其地址字段和大小字段的长度。由于没有地址,此处的地址大小始终为 0,仅最高 4 位有意义。
  • 它将用于传输的最大尺寸(包括服务标识符和数据参数)。

传输数据 (0x36)

传输数据用于实现由另一服务启动的传输(例如,请求上传 (0x35) 或请求下载 (0x34))。每次调用传输数据服务对应于一个数据块的传输。您必须提供一个“块计数器”,第一个数据块的计数器从 0x01 开始。当计数器达到 0xFF 后会溢出,并从 0x00 重新开始。

例如,在请求上传 (0x35) 示例之后,您可以执行以下命令:

echo "36 01" | isotpsend can0 -s 7e1 -d 7e9 -b

ECU 将以块计数器的回显作为响应,随后发送待读取的数据。

如果您尝试读取或写入超出指定数据大小范围的数据,将收到错误代码 0x24,表示“请求序列错误”。

如果您正在写入数据,则必须在块计数器之后提供数据字节,而 ECU 仅会以块计数器的回显作为响应。

请求传输退出(0x37)

请求传输退出服务用于终止传输。如果您希望确认下载或启动另一项传输,则必须调用此服务。此服务可能包含可选参数,但 RAMN ECU 无需任何参数,因此只需简单地发送“37”即可:

echo "37" | isotpsend can0 -s 7e1 -d 7e9 -b

ECU 应当以“77”作为回应。

请求下载 (0x34)

请求下载的工作方式与请求上传 (0x35) 相同,但数据位于客户端的请求端,而非 ECU 的应答端。仅当 RAMN ECU 具有双内存区(微控制器参考号以 CET6 结尾)时才支持此功能。与按地址写入内存 (0x3D) 不同,此服务可将数据写入 ECU 的闪存,例如用于固件更新。

例如,您可以使用以下命令在地址 0x08000000 处启动 0x100 字节的下载:

echo "34 00 44 08 00 00 00 00 00 01 00" | isotpsend can0 -s 7e1 -d 7e9 -b

ECU 将指定后续“传输数据”调用中必须包含的数据大小,因此您必须根据 ECU 返回的值进行相应调整。

完成固件文件的“请求下载”传输后,并不会立即使 ECU 使用您上传的新固件。您还需要结合例程控制来验证新固件。对于 RAMN,这可以通过以下方式实现:

  • 例行控制 0x0202 用于请求 ECU 将其当前的 EEPROM 复制到备用存储器区。
  • 例行控制 0xFF01,用于请求 ECU 切换存储区(并使用新的固件)。

J1979 服务(OBD-II PID)

低于 0x10 的服务由 J1979 标准定义(参见 OBD-II PID)。RAMN 支持以下 J1979 服务:

  • 服务 0x01(请求当前诊断数据)
  • 服务 0x03(请求 DTC)
  • 服务 0x04(清除 DTC)
  • 服务 0x09(请求车辆信息)

请求当前诊断数据 (0x01)

此服务可用于读取车辆的当前数据。使用时需提供一个字节的参数,该参数表示您希望读取的数据(“PID”)。PID 0x00 用于指示 ECU 支持哪些 PID(从 0x01 到 0x20)。ECU 将以 4 个字节(0x20 位)进行响应,当对应 PID 被支持时,相应位将被置为 1(请参考此链接以获取示意图)。如果 PID 0x20 被支持,则它依次用于指示从 0x21 到 0x40 的哪些 PID 被支持,以此类推。

例如,您可以使用以下命令询问 ECU C 支持哪些 PID:

echo "01 00" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 20" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 40" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 60" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 80" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 A0" | isotpsend can0 -s 7e2 -d 7ea -b

请注意,isotpdump 无法解析这些命令,因为从技术上讲,它们并非 UDS 服务。

ECU 以 41 00 BE 1F A8 13 响应命令 01 00。与 UDS 类似,41 对应于服务 ID 加 0x40,表示这是一个肯定响应。第二个字节是对请求参数的回显。

BE 的二进制表示为 10111110,这意味着 ECU C 支持 PID 1、3、4、5、6、7,但不支持 PID 2 和 8。您可以通过尝试读取 PID 1、2 和 3 来验证这一观察结果。与 UDS 类似,否定响应由 0x7F 加上请求的服务 ID 和错误代码组成。

echo "01 01" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 02" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 03" | isotpsend can0 -s 7e2 -d 7ea -b

您可以在 OBD-II PIDs 的维基百科页面上找到每个 PID 的含义和格式。以下 PID 可能尤其值得关注:

  • 0x0C(发动机转速)。
  • 0x0D(车速)。
  • 0x1F(车辆启动后的运行时间)。
  • 0x1C(车辆使用的 OBD 标准)- RAMN 使用 10(JOBD)。
  • 0x49(加速踏板位置)。
  • 0xA6(里程表)。

请注意,RAMN 的 J1979 实现所返回的许多值均为占位符值,可能无法正确反映模拟器的状态。不过,PID 0x1F 和 0x49 应始终得到正确实现:

echo "01 1F" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 49" | isotpsend can0 -s 7e2 -d 7ea -b echo "01 1F" | isotpsend can0 -s 7e2 -d 7ea -b

在此情况下,ECU C 回复当前加速踏板位置为 0xFB(98%),我们可以观察到两次读取 PID 0x1F 命令之间间隔了 4 秒。

请求 DTC(0x03)

服务 0x03 用于请求 ECU 存储的 DTC。与 UDS 不同,该服务无需提供任何参数。相反,若要请求待处理 DTC 或永久性 DTC,应使用其他服务(分别为 0x07 和 0x0A)。需要注意的是,RAMN 会将服务 0x07 和 0x0A 视为服务 0x03 的别名,因此您也可以直接使用它们。

有关 DTC 的更多信息,请参阅读取 DTC 信息(0x19)。此服务每个 DTC 仅返回 2 个字节(没有状态掩码或 FTB)。您可以使用以下命令要求 ECU C 显示其存储的 DTC:

echo "03" | isotpsend can0 -s 7e2 -d 7ea -b

ECU C 的响应为 43 01 01 72。第一个字节 43 表示肯定回答,01 表示存在一个 DTC,其值为 0172。

清除 DTC(0x04)

此服务可用于清除 DTC,也可在不带参数的情况下使用。例如,您可以使用以下命令来读取 DTC、清除 DTC 并再次读取 DTC:

echo "03" | isotpsend can0 -s 7e2 -d 7ea -b echo "04" | isotpsend can0 -s 7e2 -d 7ea -b echo "03" | isotpsend can0 -s 7e2 -d 7ea -b

请求车辆信息 (0x09)

此服务与请求当前诊断数据 (0x01) 类似,用于读取车辆信息。它同样使用一个字节的参数来指定要读取的 PID,其中 PID 0x00 用于指示支持哪些 PID。PID 0x02 对应 VIN,PID 0x0A 对应 ECU 名称。您可以通过以下命令读取这些 PID:

echo "09 00" | isotpsend can0 -s 7e2 -d 7ea -b echo "09 02" | isotpsend can0 -s 7e2 -d 7ea -b echo "09 0A" | isotpsend can0 -s 7e2 -d 7ea -b

请注意,如果您尚未将 VIN 写入 ECU,则返回的答案将仅为零。