2.16 上传下载(0x34/35/36/37)——手术四步曲
4MB固件,8字节帧——怎么拆
这是UDS里最复杂、最严谨的服务链——固件下载。一个ECU的新固件可能是2MB到16MB。经典CAN帧的数据区只有8字节(CAN FD可达64字节)。本书示例以经典CAN为例。你需要把一个大文件切成几千块,每一块编码上一个blockSequenceCounter以保证不丢、不重、不乱序——并且在最后验证完整性。
ISO 14229-1:2013用四条SID完成了这个任务:
- 0x34 RequestDownload —— 请求下载授权
- 0x36 TransferData —— 传输数据块
- 0x37 RequestTransferExit —— 终止传输并验证
- 0x35 RequestUpload —— 上传(数据方向相反)
第一步:请求下载(0x34)——手术授权
请求:
Byte 0: 0x34 (SID)
Byte 1: dataFormatIdentifier (1字节)
高4位=compressionType(0x0=无压缩, 0x1=...)
低4位=encryptingType(0x0=无加密, 0x1=...)
Byte 2: addressAndLengthFormatIdentifier (1字节)
高4位=memorySizeLength (内存长度占多少字节)
低4位=memoryAddressLength (内存地址占多少字节)
Byte 3..N: memoryAddress[] (内存首地址, 长度=低4位决定的字节数)
Byte N+1..M: memorySize[] (要下载的总长度, 长度=高4位决定的字节数)
正响应:
Byte 0: 0x74 (SID+0x40)
Byte 1: lengthFormatIdentifier (1字节)
高4位= 回显 maxNumberOfBlockLength 占多少字节(通常2)
低4位= 保留 (0x00)
Byte 2..N: maxNumberOfBlockLength (ECU允许的最大单块传输字节数, 大端序)
blockSequenceCounter的起点在正响应的瞬间被隐式重置为0。
第二步:传输数据(0x36)——手术进行
请求(下载方向):
Byte 0: 0x36 (SID)
Byte 1: blockSequenceCounter (1字节, 从0x01开始)
Byte 2..N: transferRequestParameterRecord[] (实际数据块)
正响应(下载):
Byte 0: 0x76 (SID+0x40)
Byte 1: blockSequenceCounter (回声)
Byte 2..N: transferResponseParameterRecord[] (下载可返回的响应参数记录)
blockSequenceCounter的规则:
- 第一条0x36的blockSequenceCounter必须是0x01
- 每一条后续0x36的blockSequenceCounter = 上一条+1
- blockSequenceCounter为0xFF时,下一个是0x00(回绕发生在同一传输会话内,ECU通过0x37收到的校验和验证整体完整性,因此0x00重复不会导致歧义)
- ECU在收到每一条时做校验:==当前预期的计数器?不等于→NRC 0x73(wrongBlockSequenceCounter)
第三步:传输终止(0x37)——手术结束
请求:
Byte 0: 0x37 (SID)
Byte 1..N: transferRequestParameterRecord[] (可选的, 例如校验和数据)
正响应:
Byte 0: 0x77 (SID+0x40)
Byte 1..N: transferResponseParameterRecord[]
ECU在这一步验证全部下载的数据完整性并关闭传输——此后不能再发0x36。
上传流程(0x35)——数据方向相反
上传把角色反转——ECU向诊断仪发送数据。流程相同:
0x35 RequestUpload → 请求上传, 声明地址/长度
0x36 TransferData → 每次传送: ECU发数据、诊断仪接收
0x37 RequestTransferExit → 上传完成
典型上传场景:刷写前备份当前固件(回滚用)、读取故障发生时的内存镜像做离线分析、产线读出某块存储区域的标定数据做质量抽检。
完整下载序列示例
诊断仪 → ECU: 0x34 0x00 0x44 0x00 0x04 0x00 0x00 0x00 0x00 0x04 0x00
解读: dataFormatIdentifier=0x00 (无压缩), addressFmtId=0x44 (4字节地址+4字节长度)
memoryAddress=0x00040000 (Flash起始地址)
memorySize=0x00000400 (1024字节要下载)
ECU → 诊断仪: 0x74 0x20 0x00 0xC8
解读: lengthFmtId=0x20 (2字节最大块长度) maxBlockLen=0x00C8=200字节
→ 每次传输≤200字节
诊断仪 → ECU: 0x36 0x01 [block1: 200字节固件数据]
ECU → 诊断仪: 0x76 0x01 [确认block 1 OK]
诊断仪 → ECU: 0x36 0x02 [block 2: 200字节]
ECU → 诊断仪: 0x76 0x02 [确认block 2 OK]
... 继续直到最后一个块 ...
诊断仪 → ECU: 0x36 0x06 [block 6: 24字节 (最后一块)]
ECU → 诊断仪: 0x76 0x06 [确认最后一块OK]
诊断仪 → ECU: 0x37
ECU → 诊断仪: 0x77 [确认传输终止, 固件完整]
为什么需要四步而不是一步
这是UDS里最漂亮的“分形约束“设计——每一步解决一个独立的约束:
- 0x34 = 协商:告诉ECU我要写哪个区域、总共多少。ECU告诉我每次发多大的块。
- 0x36 = 传输:实际搬运数据——分段、校验序号、累积进度。
- 0x37 = 关闭:校验完整性——我传完了,没问题,完成。
三个步骤是被CAN总线的8字节MTU和Flash存储的分块擦写粒度逼出来的——不是协议设计者的审美偏好。
本篇小结
- 下载(0x34→0x36→0x37)和上传(0x35→0x36→0x37)是UDS中数据操作的完整序列。
- blockSequenceCounter是传输可靠性的核心——保证不丢帧、不重帧、不乱序。
- addressAndLengthFormatIdentifier用半字节编码传达地址和长度字节数——实现可变宽度寻址。
- 下载序列的每一步都可以被NRC打断——如果blockSequenceCounter不连续→0x73(WBSC);如果ECU正忙→0x71(传输已暂停)。
【下集预告】:下载的通道已经建立——但有时候你不需要通过文件传输协议下载——你只需要直接写几个字节到特定的内存地址。0x3D WriteMemoryByAddress——极简直接的地址写入——在诊断生产中用于快速标定注入。