Skip to content

本文档深入剖析 OTX 刷写框架中两种文件格式下载器的架构设计与实现差异——BinFileDownloader 处理原始二进制(.bin)文件,VbfFileDownloader 处理带结构化头部的 VBF(Vector Binary Format)文件。两者均位于 otx::flash 命名空间下,共同继承自 asam::mcd::MCDEventHandler,共享与 MCD3D 诊断栈的事件回调接口,但在刷写协议流程、数据分帧策略、签名验证机制和进度上报方式上存在本质差异。此外,本文档也将 MCDFileDownloader 作为对比基准——它是完全依赖 ASAM MCD3D 框架内建 MCDFlashJob 的替代实现路径,三者共同构成刷写文件下载的策略矩阵。

Sources: ECUFlasherImpl.cpp

架构定位与类关系

三个下载器均通过 asam::mcd::MCDEventHandler 接口与 MCD3D 诊断运行时对接,但各自的下载逻辑独立于继承关系之外。MCDFileDownloader 将全部刷写控制权委托给 MCD3D 框架(MCDFlashJob),自身仅负责参数设置与结果解析;BinFileDownloaderVbfFileDownloader 则绕过框架,通过 MCDCmdTrans 对象手动组装并发送 ISO 14229(UDS)诊断命令,实现对刷写流程的精细化控制。ECUFlasherImpl 将三者均声明为 friend class,允许它们直接访问内部字段(如 mDownloadedSizemFieldLock)以发布下载进度事件。

Sources: BinFileDownloader.hpp, VbfFileDownloader.hpp, MCDFileDownloader.hpp, ECUFlasherImpl.hpp

mermaid
classDiagram
    class MCDEventHandler {
        <<interface>>
        +onFirstEvent()
        +onLastEvent()
        +onPrimitiveProgressInfo()
        +onPrimitiveJobInfo()
        +onPrimitiveHasResult()
        ...
    }

    class MCDFileDownloader {
        -ECUFlasher *mFlasher
        -File *mEcuFile
        -ECU *mECU
        -int mErrorCode
        -string mMessage
        +downloadFile(ECU*, File*) bool
        +getFlasher() ECUFlasher*
        +setFlasher(ECUFlasher*)
    }

    class BinFileDownloader {
        -ECUFlasher *mFlasher
        -File *mEcuFile
        -ECU *mECU
        -int mErrorCode
        -string mMessage
        -MCDCmdTrans *cmdTrans
        +downloadFile(ECU*, File*) bool
        -transferFile(string, uint64_t, int32_t) bool
    }

    class VbfFileDownloader {
        -ECUFlasher *mFlasher
        -File *mEcuFile
        -ECU *mECU
        -int mErrorCode
        -string mMessage
        -MCDCmdTrans *cmdTrans
        +downloadFile(ECU*, File*) bool
        -transferFile() bool
    }

    class MCDCmdTrans {
        +EraseMemory(uint32_t, uint32_t) bool
        +RequestDownload(uint32_t, uint32_t, uint64_t) bool
        +RequestTransferExit() bool
        +SendFrame_Cmd36(uint8_t*, int, int, int) CBinary
        +VerifyAuthenticity(string, bool) bool
        +FinishProgram() bool
        +ActivateSbl(string) bool
        +SecurityAccess(string, int) bool
        +EnterProgramMode()
        +EnterSystem() bool
    }

    class ECUFlasherImpl {
        +downloadFile(ECU*, File*) bool
        -mDownloadedSize
        -mFieldLock
        +postFileDownloadProgress(...)
    }

    MCDEventHandler <|.. MCDFileDownloader
    MCDEventHandler <|.. BinFileDownloader
    MCDEventHandler <|.. VbfFileDownloader
    BinFileDownloader --> MCDCmdTrans : uses
    VbfFileDownloader --> MCDCmdTrans : uses
    MCDFileDownloader ..> ECUFlasherImpl : friend
    BinFileDownloader ..> ECUFlasherImpl : friend
    VbfFileDownloader ..> ECUFlasherImpl : friend

这种"平级兄弟类 + friend 访问"的设计使得每个下载器可以独立演进各自的刷写协议逻辑,同时保持对 ECUFlasherImpl 内部状态(如下载进度计数器)的直接同步能力。核心差异在于 MCDCmdTrans *cmdTrans 成员——MCDFileDownloader 没有此成员(因为它使用框架接管),而 Bin 和 Vbf 均持有一个 cmdTrans 指针来实现手动诊断通信。

Sources: MCDFileDownloader.hpp, BinFileDownloader.hpp, VbfFileDownloader.hpp

下载器选择工厂:ECUFlasherImpl 中的路由逻辑

三个下载器的选择由编译期宏 FLASHER_USE_MCD_METHOD 和运行时 ECU 名称共同决定。当前代码中该宏定义为 1,因此 MCDFileDownloader 路径被禁用,实际使用的是运行时名称匹配策略:

cpp
#define FLASHER_USE_MCD_METHOD 1

ECUFlasherImpl::downloadFile() 中,当 FLASHER_USE_MCD_METHOD > 0 时,通过 ECU 名称中的关键字判断文件格式:域控制器(TCAM、BGM、CDC、ACU)使用 BinFileDownloader 处理 .bin 原始二进制文件;其余所有 ECU 使用 VbfFileDownloader 处理 VBF 结构化文件。当宏定义为 0 时,回退到 MCDFileDownloader 的模拟进度路径(仅供调试)。

Sources: ECUFlasherImpl.cpp

条件选中的下载器适用场景
ECU 名称含 "TCAM" || "BGM" || "CDC" || "ACU"BinFileDownloader域控制器(.bin 原始二进制)
其他所有 ECUVbfFileDownloader标准 ECU(.vbf 结构化文件)
FLASHER_USE_MCD_METHOD == 0(编译期切换)MCDFileDownloader调试/框架原生模式

MCDFileDownloader:MCD3D 框架原生的刷写作业模式

MCDFileDownloader 的 downloadFile() 方法完全遵循 ASAM MCD3D 标准的刷写作业(MCDFlashJob)流程。它通过 MCDProjectProvider 获取共享的 MCD 对象(System、Project、Interface),创建逻辑连接(LogicalLink)并上线,然后基于 ECU 的数据库对象(MCDDbLocation)获取刷写作业(MCDFlashJob)定义。刷写参数——PIN 码(PA_PINCode)和文件类型标志(PA_FlashFileFlag)——通过 MCDRequestParameters 物理值接口注入,刷写数据文件路径通过 MCDDbFlashData::setActiveFileName() 绑定。

Sources: MCDFileDownloader.cpp

整个刷写过程由 job->executeSync() 同步执行,MCD3D 框架内部自动处理擦除、下载、校验等所有步骤。执行完毕后通过 MCDResult 提取 PA_JobStatuHEX(错误码)和 PA_AdditErrorInfor(附加错误信息)。这种方式的优势在于与 OTX 标准完全对齐、代码量少,但缺点是缺乏对中间过程的细粒度控制(进度事件完全依赖框架回调 onPrimitiveProgressInfo),且无法针对特定 ECU 定制刷写协议行为。

Sources: MCDFileDownloader.cpp

BinFileDownloader:手动 ISO 14229 协议的原始二进制下载器

downloadFile 主流程

BinFileDownloader 的 downloadFile() 实现了完整的 UDS 刷写序列,共分为五个阶段:

mermaid
flowchart TD
    A[开始 downloadFile] --> B{keyInfo 校验}
    B -->|为空| X1[返回 false]
    B -->|有效| C[创建 MCDCmdTrans]
    C --> D["阶段1: EraseMemory(0,0,fileSize)"]
    D -->|失败| X2[返回 false]
    D -->|成功| E["阶段2: RequestDownload(0,0,fileSize)"]
    E -->|失败| X3[返回 false]
    E -->|成功| F["阶段3: transferFile(filepath, fileSize, frameSize)"]
    F -->|失败| X4[返回 false]
    F -->|成功| G["阶段4: RequestTransferExit"]
    G -->|失败| X5[返回 false]
    G -->|成功| H["阶段5: VerifyAuthenticity(keyInfo, true)"]
    H -->|失败| X6[返回 false]
    H -->|成功| I{fileType == 3?}
    I -->|否| Y[返回 true]
    I -->|是 PBL| J["FinishProgram"]
    J --> K["ECU 复位 + 广播 \x11\x81"]
    K --> L["等待 10s + 重连车辆"]
    L --> M["EnterProgramMode + SecurityAccess"]
    M --> Y

阶段 1-2 的参数 (0, 0, fileSize) 表明 Bin 格式下擦除和下载请求使用全零的起始地址和长度标识符,实际由 ECU 内部自行解析。阶段 5 的签名验证使用 File 对象上的 keyInfo 字段(通过 ecuFile->getKeyInfo() 获取),第二个参数 isDomain=true 表示使用域签名模式。

Sources: BinFileDownloader.cpp

当文件类型为 3(PBL,Primary BootLoader)时,流程增加了 ECU 复位和重连逻辑:先调用 FinishProgram() 结束编程会话,然后通过 MCDCmdBoardcast 广播 \x11\x81(硬复位)命令,等待 10 秒后调用 otx_reconnect_vehicle_ex(50, 5000) 重新建立车辆通信,再等待 170 秒让 ECU 完全重启,最后重新进入编程模式并执行安全访问(支持两种安全访问模式:SecurityAccess_ECOS 和常规 SecurityAccess("FFFFFFFF", 2),根据 securityAccessMode 决定优先级)。

Sources: BinFileDownloader.cpp

transferFile:基于磁盘文件的帧传输

BinFileDownloader 的 transferFile() 从磁盘直接读取原始二进制文件,使用 fopen/fread 以二进制模式打开文件,单帧最大数据量取 nFrameSize 和 1 MiB 的较小值:

cpp
nFrameSize = MIN(1024 * 1024, nFrameSize);

每一帧的结构为:1 字节命令码(0x36,即 UDS TransferData)+ 1 字节帧计数器(nCTR,0x00-0xFF 循环)+ N 字节数据。通过 MCDCmdTrans::SendFrame_Cmd36() 发送后检查响应,若收到 0x7F(否定响应)则判定失败。最后一帧(nLastFrameSize > 0)独立发送,使用实际剩余数据量。

Sources: BinFileDownloader.cpp

进度上报机制

BinFileDownloader 的进度上报采用 ECU 级别累积 策略:每发送一帧后,将 nFrameSize(或最后一帧的 nLastFrameSize)累加到 mECU->getDownloadedSize()mEcuFile->increaseDownloadedSize(),然后按 ECU 总大小计算百分比 newProgress = 100 * downloadedSize / totalSize。进度事件仅在百分比变化时发布(oldProgress != newProgress),避免重复触发 UI 更新。

Sources: BinFileDownloader.cpp

VbfFileDownloader:基于 VBF 结构化头部的多块下载器

downloadFile 主流程

VbfFileDownloader 的 downloadFile() 流程与 Bin 有本质不同——它首先从 VBF 头部解析擦除信息和块结构,然后按块迭代刷写:

mermaid
flowchart TD
    A[开始 downloadFile] --> B[创建 MCDCmdTrans]
    B --> C["解析 VBF Header: getListOfErase()"]
    C --> D["遍历擦除列表<br/>每项格式: addr,size (hex)"]
    D --> E["EraseMemory(addr, size)<br/>精确地址擦除"]
    E --> F["阶段2: transferFile()<br/>逐块 RequestDownload + 0x36 帧"]
    F --> G{VBF 版本 == 2.6?}
    G -->|是| H{signatureMode?}
    H -->|0 自动| I["优先 prod 签名<br/>回退 dev 签名"]
    H -->|1 dev| J["VerifyAuthenticity(devSig, false)"]
    H -->|2 prod| K["VerifyAuthenticity(prodSig, false)"]
    G -->|否| L[跳过签名验证]
    I --> M{fileType == 1?}
    J --> M
    K --> M
    L --> M
    M -->|是 SBL| N["ActivateSbl(callAddress)"]
    M -->|否| O[返回 true]
    N --> O

与 Bin 的关键差异:

  • 擦除阶段:VBF 头部 erase 字段以逗号分隔 "addr,size" 字符串列表,每组精确指定擦除的起始地址和长度。Bin 则是全范围擦除 EraseMemory(0, 0, fileSize)
  • 下载阶段:VBF 按数据块(VBFBlock)组织,每个块有独立的起始地址(GetStartAddress())和长度(GetLength())。transferFile() 内部对每个块分别调用 RequestDownload(formatIdentifier, startAddress, length) → 发送 0x36 帧 → RequestTransferExit
  • 签名验证:VBF 支持三种签名模式(0=自动选择、1=仅开发签名、2=仅生产签名),从 VBF 头部读取 sw_signaturesw_signature_dev,签名验证使用 isDomain=false(非域签名模式)。仅 2.6 版本的 VBF 执行签名验证。
  • SBL 激活:文件类型为 1(SBL)时,从 VBF 头部 call 字段获取激活地址并调用 ActivateSbl()
  • 无 PBL 流程:VBF 模式下无需 FinishProgram 和 ECU 复位重连。

Sources: VbfFileDownloader.cpp

transferFile:基于 VBF 块的分段传输

VbfFileDownloader 的 transferFile() 从内存中的 VBFBlock 对象获取数据(由 VBFParserSmall 预先解析完成),而非从磁盘实时读取:

cpp
vector<VBFBlock> vecBlock = mEcuFile->parser.GetVBFBlocks();

每个 VBF 块包含三个核心属性:GetStartAddress()(起始地址)、GetLength()(数据长度)、GetBlockData()(二进制数据)。transferFile() 对每个块执行完整的"请求下载 → 数据传输 → 传输退出"子流程。帧结构与 Bin 相同:\x36 + CTR + data,但数据来源是内存中的 vector<unsigned char> 而非文件指针。

Sources: VbfFileDownloader.cpp

进度上报机制

VbfFileDownloader 的进度上报与 Bin 存在微妙差异。每帧后同样累加 nFrameSizemECU->setDownloadedSize()mEcuFile->increaseDownloadedSize(),但当 downloadedSize == totalSize 时强制发布 100% 事件。中间进度始终发布(不再做 oldProgress != newProgress 的去重判断),这由 #if false 预处理块中的注释代码可见——曾有过去重逻辑但当前被禁用,改为每次都发布事件。这种设计可能导致更多的 UI 刷新调用,但避免了进度倒退的视觉问题。

Sources: VbfFileDownloader.cpp

核心差异对比

维度MCDFileDownloaderBinFileDownloaderVbfFileDownloader
文件格式任意(框架自动识别).bin 原始二进制.vbf 结构化格式
诊断通信MCD3D FlashJob 框架MCDCmdTrans 手动 UDSMCDCmdTrans 手动 UDS
擦除策略框架内部控制EraseMemory(0, 0, fileSize) 全范围从 VBF 头部解析精确 (addr, size) 列表
数据分块框架内部控制单一连续数据流(fread)多个 VBFBlock(内存解析)
帧命令码框架内部控制0x36 + CTR + 数据0x36 + CTR + 数据
签名来源框架内部控制File::getKeyInfo()VBF Header: sw_signature / sw_signature_dev
签名模式框架内部控制isDomain=true(域签名)isDomain=false,支持 3 种模式切换
SBL 激活框架内部控制不支持ActivateSbl(call)(fileType==1)
PBL 复位流程框架内部控制FinishProgram + 复位 + 重连 + 安全访问不支持
进度去重框架回调 onPrimitiveProgressInfooldProgress != newProgress始终发布(无去重)
适用 ECU调试/备用路径TCAM, BGM, CDC, ACU其他所有 ECU

Sources: BinFileDownloader.cpp, VbfFileDownloader.cpp, MCDFileDownloader.cpp

MCDCmdTrans:两个手动下载器的共享诊断通信层

MCDCmdTrans 是 BinFileDownloader 和 VbfFileDownloader 共同依赖的低层诊断命令传输类。它在构造时持有 ECU 引用,并通过 MCDProjectProvider 获取共享的 MCD 对象。其关键方法构成了 ISO 14229 刷写协议的完整命令集:

方法UDS 服务说明
EraseMemory(addr, size)0x31(RoutineControl)擦除指定地址范围的内存
RequestDownload(fmtId, addr, len)0x34(RequestDownload)请求下载,协商帧大小
SendFrame_Cmd36(data, len)0x36(TransferData)发送单帧数据,自动重试
RequestTransferExit()0x37(RequestTransferExit)结束数据传输
VerifyAuthenticity(keyInfo, isDomain)0x31(RoutineControl)RSA 签名验证
FinishProgram()0x31(RoutineControl)完成编程(PBL 专用)
ActivateSbl(address)0x31(RoutineControl)激活 SBL(VBF 专用)
SecurityAccess(pin, level)0x27(SecurityAccess)Seed-to-Key 安全访问
EnterProgramMode()0x10 0x82进入编程会话
EnterSystem()0x22 0xF1 0x86 + 0x10 0x02检查并进入系统会话

Sources: MCDCmdTrans.hpp

SendFrame_Cmd36() 内置重试逻辑:最多尝试 nTryCount 次(默认 1 次),若收到 0x7F 否定响应则清空返回数据以表示失败。getFrameSize() 返回 nFrameSize(默认 0x8000 = 32768 字节),该值由 RequestDownload 与 ECU 协商后更新。

Sources: MCDCmdTrans.cpp

数据流与文件解析路径

两种下载器的数据来源路径截然不同,这决定了文件预处理阶段的差异:

mermaid
flowchart LR
    subgraph "Bin 路径"
        A1["磁盘 .bin 文件"] --> B1["fopen/fread<br/>实时读取"]
        B1 --> C1["0x36 帧组装"]
    end

    subgraph "VBF 路径"
        A2["磁盘 .vbf 文件"] --> B2["VBFParserSmall::ParseVBFFile"]
        B2 --> C2["VBFHeader 解析<br/>(erase, call, signature...)"]
        B2 --> D2["VBFBlock[] 解析<br/>(startAddr, length, data)"]
        C2 --> E2["downloadFile 流程控制"]
        D2 --> F2["transferFile 内存读取"]
    end

    C1 --> G["MCDCmdTrans::SendFrame_Cmd36"]
    F2 --> G
    G --> H["MCDLogicalLink<br/>DoIP 物理层"]

Bin 路径:文件在 transferFile() 执行时才被打开和读取,读取与发送交织进行,内存占用受 nFrameSize(最大 1 MiB)限制。VBF 路径:文件在刷写开始前即由 VBFParserSmall::ParseVBFFile() 完整解析到内存,头部元信息用于流程控制,所有块数据存储在 vector<VBFBlock> 中供逐块传输。

Sources: BinFileDownloader.cpp, VbfFileDownloader.cpp, VBFParserSmall.h

阅读建议

本文档位于刷写文件处理分类的核心位置。建议按以下路径继续深入: