Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

2.22 文件传输(0x38)——病历归档

500KB日志文件——不是几个DID能解决的

你在一家4S店,处理一起保修索赔。发动机控制单元(ECU)记录了一份完整的故障日志文件——/log/fault_history_20250101.bin,大小500KB,记录了过去三个月所有传感器异常的时间戳、车速、发动机转速、环境温度。

你的诊断仪可以用0x22 ReadDataByIdentifier读取任何一个DID——但DID是一个2字节的ID,它指向一个信号或一组信号,不是文件。你可以用0x23 ReadMemoryByAddress直接从内存读——但你必须知道日志在Flash中的起始地址、偏移量、数据结构,而这在每一版ECU固件里都可能不一样。

你能用0x34-37上传序列——但这同样需要你声明一个内存地址和长度。如果你不知道日志文件存储在Flash的哪个物理地址,这条路也走不通。

一个现实的问题摆在面前:ECU的文件系统里有一个叫/log/fault_history_20250101.bin的文件,你怎么把它用UDS拿出来——通过文件名,而不是地址?

答案就是0x38 RequestFileTransfer。


为什么0x38必须存在——从地址到名字的抽象跃升

0x34的上传和下载工作得很好——前提是你知道数据在ECU内存空间里的物理地址。但现代ECU的内部存储已经远不止一块线性Flash了:它们有文件系统——FAT、JFFS2、或者车厂自研的嵌入式文件系统。这些文件系统管理着固件包、校准数据、日志文件、配置文件、密钥证书。

当ECU有了文件系统,“我在地址0x00048000有256字节数据“这种描述就变得力不从心了。你需要的是:“把/config/calibration_v2.json传给我。

0x38的设计哲学就是这一跃升:从内存地址寻址到文件路径寻址。它把ECU从“一块裸露的存储器“提升到了“一个有文件系统的计算节点“。这是UDS协议中最能体现汽车电子从控制器进化到计算平台的信号。

服务寻址维度最小的操作单元何时使用
0x22/0x2EDID(语义标签)一个信号或一组信号读发动机转速、写VIN码
0x3D绝对内存地址若干字节生产线上精确注入标定值
0x34-37内存地址+块传输若干块、有序列号校验刷写固件
0x38文件路径完整文件读日志、换配置文件、导出故障快照

0x38和0x34之间最重要的区别不是技术层面的——是语义层面的。当你发一条0x34 RequestUpload,你告诉ECU的是:“从地址X开始,给我Y字节。“当你发一条0x38 readFile,你告诉ECU的是:“把叫这个名字的文件给我。“ECU自己负责把文件名解析到存储位置——诊断仪不需要知道文件系统的物理布局。

这就像在电脑上通过双击文件名打开一个文档——你不关心它在硬盘的哪个扇区。


0x38支持的五种文件操作

0x38提供的不是“读“和“写“这种简单的二维操作——它是一套完整的文件管理指令集:

操作模式 (operationMode)含义医院类比
0x01 AddFile新建一个文件新建一份病历
0x02 DeleteFile删除一个文件销毁一份过期病历
0x03 ReplaceFile替换一个文件的内容更新病历内容——保留文件名,覆盖内容
0x04 ReadFile读取一个文件的全部或部分内容调出病历阅读
0x05 ReadDir列出目录下的文件列表查某科室所有病历清单

AddFile、DeleteFile、ReplaceFile 是写操作 —— 它们对文件系统做修改。AddFile创建一个新文件并写入初始数据。DeleteFile删除一个存在的文件。ReplaceFile用新数据覆盖已有文件——注意,它不是“先删再建“,而是原子性地替换内容。

ReadFile 是ECU→诊断仪的数据传输。它搭配一个lengthFormatIdentifier字段来指定读取的长度和偏移量——你可以读取整个文件,也可以读取文件的一部分。

ReadDir 是最特别的一个:它不传输文件内容,而是返回一个目录列表——就像你在终端里敲ls。诊断仪可以先用ReadDir发现ECU里有哪些文件,再选择一个用ReadFile读取。这在诊断场景中非常有用:诊断仪连接到一台陌生ECU,先列出所有日志文件,再选择相关日志读取。


请求与响应报文格式

请求(通用结构)

Byte 0: 0x38 (SID)
Byte 1: operationMode (1字节)
         0x01 AddFile
         0x02 DeleteFile
         0x03 ReplaceFile
         0x04 ReadFile
         0x05 ReadDir
Byte 2..3: filePathAndNameLength (2字节, 大端序)
           文件路径+文件名的总长度(字节数)
Byte 4..N: filePath[] (变长字符串)
           用ASCII编码的文件路径,如 "/log/fault_2025.bin

对于ReadFile(operationMode=0x04),附加字段

Byte N+1: dataFormatIdentifier (1字节)
          高4位 = compressionType (0x0=无压缩)
          低4位 = encryptingType (0x0=无加密)
Byte N+2: fileSizeParameterLength (1字节)
          高4位 = fileSizeUncompressed长度
          低4位 = fileSizeCompressed长度
Byte N+3..: fileSizeUncompressed[] (可选)
            fileSizeCompressed[] (可选)

这个设计让ReadFile和0x34 RequestDownload在语义上空前相似:你声明数据的格式(压缩/加密),以及要读的大小。区别在于:0x38用文件路径代替了0x34的内存地址。

正响应

Byte 0: 0x78 (SID+0x40, 0x38 | 0x40)
Byte 1: operationMode (回声)
Byte 2..3: lengthFormatIdentifier (2字节)
           针对当前操作返回的长度指示字段
Byte 4..N: dataRecord[] (变长, 取决于操作)

完整的AddFile交互示例

诊断仪 → ECU:   0x38 0x01 0x00 0x14
                 0x2F 0x63 0x6F 0x6E 0x66 0x69 0x67 0x2F
                 0x63 0x61 0x6C 0x69 0x62 0x2E 0x6A 0x73
                 0x6F 0x6E
                 0x7B 0x22 0x72 0x65 0x76 0x22 0x3A 0x32 0x7D

解读: SID=0x38
     operationMode=0x01 (AddFile)
     filePathAndNameLength=0x0014 (20字节)
     filePath="/config/calib.json"  (20字符 → 0x2F...0x6E)
     之后的数据 = 文件内容: {"rev":2}

ECU → 诊断仪:   0x78 0x01 0x00 0x04 0x00 0x00 0x00 0x09
解读: 正响应SID=0x78
      operationMode=0x01 (回声)
      文件已创建,返回成功信息

ReadDir交互示例

诊断仪 → ECU:   0x38 0x05 0x00 0x04
                 0x2F 0x6C 0x6F 0x67

解读: SID=0x38
     operationMode=0x05 (ReadDir)
     filePathAndNameLength=0x0004
     filePath="/log

ECU → 诊断仪:   0x78 0x05 [目录列表数据...]
               包含文件数量、每个文件的名称、大小、创建时间等属性

与0x34-37上传下载的关系——两套传输通道的共存哲学

这是初学者最容易混淆的地方:有了0x34-37,为什么还要0x38?它们不都是传输数据的吗?

答案是:它们解决的问题维度完全不同。

0x34-37是“存储层“的传输——你在对内存地址说话。你不需要ECU理解“文件“这个概念——你只需要它把数据从地址A搬到地址B,或者反过来。这适用于固件刷写场景——固件本身就是一块连续的数据,你不需要文件系统的任何语义。

0x38是“文件系统层“的传输——你在对文件名说话。ECU自己负责文件系统的索引、碎片整理、权限管理。你不需要知道数据在物理存储中的任何位置。

一个类比:0x34就像用dd if=/dev/sda1 bs=512 count=1000直接读磁盘扇区;0x38就像用cp /home/user/file.txt .按文件名拷贝文件。两者都能传输数据——但一个操作的是块设备,一个操作的是文件系统。

在实际应用中:

  • 固件刷写 → 优先用0x34-37——因为固件必须以确定的格式写入特定的Flash地址,需要精确控制。
  • 日志导出 → 优先用0x38——因为日志文件是ECU文件系统自然管理的对象,诊断仪不需要知道它的物理存储。
  • 配置文件读取 → 优先用0x38——因为/config/network.json比地址0x00408100有更好的自描述性和跨版本兼容性。

医院映射——从“看一项化验结果“到“调出完整病历档案

如果0x22 ReadDataByIdentifier是“医生看一眼化验单上的血糖值“——

那么0x38 ReadFile就是“档案室管理员把病人三年来全部住院病历用一个推车推过来“。

这不是一个数据量的区别——这是一个概念层次的区别。

0x22让你获取一个信号:发动机转速是多少?冷却液温度是多少?——这是即时快照

0x38让你获取一个文件:去年冬天所有的故障日志。今年每一个驾驶循环的NvM写入记录。所有传感器的全生命周期曲线。——这是历史档案

再延伸一步:

  • AddFile = 新建病历档案
  • DeleteFile = 销毁过期病历
  • ReplaceFile = 更新病历内容(原封面不变,内容换了)
  • ReadFile = 调阅病历
  • ReadDir = 查某个科室的所有病历夹

医院里的纸质病历和ECU里的日志文件在本质上是一样的:它们都是在长期运行中持续累积的结构化记录,需要在某个时刻被“调阅“——不是读一两个化验指标,而是把整摞文件搬到面前。


核心洞察:0x38的出现标志着ECU从“信号处理器“进化为“数据管理节点“。在UDS早期(2006年的第一版),ECU就是一块单片机——它采集信号、执行控制算法、输出执行器指令。它的数据就是一堆内存地址里的数字。到了今天,ECU运行着嵌入式操作系统,管理着文件系统,记录着TB级的历史数据——它的数据是有名字、有结构、有元信息的文件。0x38是UDS协议对“ECU = 一个带文件系统的计算机“这一现实的最直接承认。


本篇小结

  1. 0x38 RequestFileTransfer 提供了五种文件操作:AddFile(创建)、DeleteFile(删除)、ReplaceFile(替换)、ReadFile(读取)、ReadDir(列出目录)——覆盖了诊断仪对ECU文件系统的完整管理需求。
  2. 0x38和0x34-37的根本区别在于寻址维度:0x34用内存地址,0x38用文件路径——前者操作存储层,后者操作文件系统层。
  3. 文件路径使用ASCII编码,由filePathAndNameLength字段声明长度——这使得跨ECU平台的文件命名保持一致性。
  4. ReadFile与0x34 RequestDownload共享压缩/加密字段的设计——但用文件路径替代了内存地址,实现了语义上的“名到实“映射。
  5. ReadDir让诊断仪具备文件发现能力——在连接一台陌生ECU时,先列出文件目录、再选择读取,这是自动化诊断流程的基础设施。

【下集预告】:病历档案的保护不能只靠一把锁。0x27 SecurityAccess用的是Seed/Key——你和ECU共享同一个密钥算法,就像两个人都知道同一个密码。但现代汽车已经联网了——OTA更新、远程诊断、V2X通信——每一条连接都是攻击面。你需要更强的身份验证——从’对暗号’升级到’刷脸’。0x29 Authentication——基于证书的PKI互认证——我们进入安全访问的下一层。